fix: hemar: include object does not exists

This commit is contained in:
2025-05-18 20:18:54 +00:00
parent 1b1deaf721
commit ef314495ba
3 changed files with 274 additions and 220 deletions

View File

@@ -1547,9 +1547,13 @@ render_template(TemplateNode *node, Jsonb *define, StringInfo result, MemoryCont
/* Get the include data from the context */ /* Get the include data from the context */
JsonbValue *include_data = jsonb_get_by_path_internal(define, include_path, context); JsonbValue *include_data = jsonb_get_by_path_internal(define, include_path, context);
elog(DEBUG1, "Include data: %s", JsonbToCString(NULL, &JsonbValueToJsonb(include_data)->root, VARSIZE_ANY_EXHDR(JsonbValueToJsonb(include_data)))); if (!include_data)
{
elog(WARNING, "Include data not found");
break;
}
if (include_data != NULL && include_data->type == jbvBinary) if (include_data->type == jbvBinary)
{ {
JsonbIterator *it = JsonbIteratorInit((JsonbContainer *)include_data->val.binary.data); JsonbIterator *it = JsonbIteratorInit((JsonbContainer *)include_data->val.binary.data);
JsonbIteratorToken token; JsonbIteratorToken token;

View File

@@ -1,219 +1,231 @@
-- Test all template tags together -- Test all template tags together
DO $$ DO $$
DECLARE DECLARE
total_tests INT := 0; total_tests INT := 0;
passed_tests INT := 0; passed_tests INT := 0;
test_result TEXT; test_result TEXT;
expected TEXT; expected TEXT;
passed BOOLEAN; passed BOOLEAN;
BEGIN BEGIN
-- Test 1: Complex template with all tag types -- Test 1: Complex template with all tag types
total_tests := total_tests + 1; total_tests := total_tests + 1;
test_result := hemar.render( BEGIN
'{ test_result := hemar.render(
"page": { '{
"title": "My Page", "page": {
"sections": [ "title": "My Page",
{ "sections": [
"id": "section1", {
"title": "Section 1", "id": "section1",
"items": [ "title": "Section 1",
{ "items": [
"id": "item1", {
"status": "active", "id": "item1",
"content": "Item 1 Content", "status": "active",
"template": "item_template" "content": "Item 1 Content",
}, "template": "item_template"
{ },
"id": "item2", {
"status": "inactive", "id": "item2",
"content": "Item 2 Content", "status": "inactive",
"template": "item_template" "content": "Item 2 Content",
} "template": "item_template"
] }
} ]
] }
}, ]
"include": { },
"meta_tags": { "include": {
"content": "<meta name=\"description\" content=\"Test Page\">" "meta_tags": {
}, "content": "<meta name=\"description\" content=\"Test Page\">"
"header": { },
"template": "Welcome to {{ page.title }}!", "header": {
"context": { "template": "Welcome to {{ page.title }}!",
"page": { "context": {
"title": "My Page" "page": {
} "title": "My Page"
} }
}, }
"item_template": { },
"template": "Status: {{ status }}, Content: {{ content }}" "item_template": {
}, "template": "Status: {{ status }}, Content: {{ content }}"
"footer": { },
"content": "<footer>Copyright 2024</footer>" "footer": {
} "content": "<footer>Copyright 2024</footer>"
} }
}'::jsonb, }
$template$<!DOCTYPE html> }'::jsonb,
<html> $template$<!DOCTYPE html>
<head> <html>
<title>{{ page.title }}</title> <head>
{{ include meta_tags }} <title>{{ page.title }}</title>
</head> {{ include meta_tags }}
<body> </head>
<header>{{ include header }}</header> <body>
<main> <header>{{ include header }}</header>
{{ for section in page.sections }} <main>
<section id="{{ section.id }}"> {{ for section in page.sections }}
<h2>{{ section.title }}</h2> <section id="{{ section.id }}">
{{ for item in section.items }} <h2>{{ section.title }}</h2>
<div class="item {{ item.status }}"> {{ for item in section.items }}
{{ include item.template }} <div class="item {{ item.status }}">
{{ exec {{ include item.template }}
DECLARE {{ exec
v_status TEXT; DECLARE
BEGIN v_status TEXT;
v_status := context->'item'->>'status'; BEGIN
RETURN CASE v_status := context->'item'->>'status';
WHEN v_status = 'active' THEN ' (Active Item)' RETURN CASE
ELSE ' (Inactive Item)' WHEN v_status = 'active' THEN ' (Active Item)'
END; ELSE ' (Inactive Item)'
END; END;
}} END;
</div> }}
{{ end }} </div>
</section> {{ end }}
{{ end }} </section>
</main> {{ end }}
<footer>{{ include footer }}</footer> </main>
</body> <footer>{{ include footer }}</footer>
</html>$template$ </body>
); </html>$template$
);
expected := '<!DOCTYPE html>
<html> expected := '<!DOCTYPE html>
<head> <html>
<title>My Page</title> <head>
<meta name="description" content="Test Page"> <title>My Page</title>
</head> <meta name="description" content="Test Page">
<body> </head>
<header>Welcome to My Page!</header> <body>
<main> <header>Welcome to My Page!</header>
<section id="section1"> <main>
<h2>Section 1</h2> <section id="section1">
<div class="item active"> <h2>Section 1</h2>
Status: active, Content: Item 1 Content (Active Item) <div class="item active">
</div> Status: active, Content: Item 1 Content (Active Item)
<div class="item inactive"> </div>
Status: inactive, Content: Item 2 Content (Inactive Item) <div class="item inactive">
</div> Status: inactive, Content: Item 2 Content (Inactive Item)
</section> </div>
</main> </section>
<footer><footer>Copyright 2024</footer></footer> </main>
</body> <footer><footer>Copyright 2024</footer></footer>
</html>'; </body>
</html>';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); passed := test_result = expected;
IF passed THEN passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
RAISE NOTICE 'Test %: Complex template with all tag types: PASSED', total_tests; IF passed THEN
ELSE RAISE NOTICE 'Test %: Complex template with all tag types: PASSED', total_tests;
RAISE WARNING 'Test %: Complex template with all tag types: FAILED. Expected "%", got "%"', ELSE
total_tests, expected, test_result; RAISE WARNING 'Test %: Complex template with all tag types: FAILED. Expected "%", got "%"',
END IF; total_tests, expected, test_result;
END IF;
-- Test 2: Template with nested includes and shared context EXCEPTION WHEN OTHERS THEN
total_tests := total_tests + 1; RAISE WARNING 'Test % failed: Error: %', total_tests, SQLERRM;
test_result := hemar.render( END;
'{
"user": { -- Test 2: Template with nested includes and shared context
"name": "John", total_tests := total_tests + 1;
"role": "admin" BEGIN
}, test_result := hemar.render(
"include": { '{
"user_info": { "user": {
"template": "User: {{ user.name }} ({{ user.role }})" "name": "John",
}, "role": "admin"
"permissions": { },
"template": "{{ include user_info }} - Permissions: {{ for perm in user.permissions }}{{ perm }} {{ end }}", "include": {
"context": { "user_info": {
"user": { "template": "User: {{ user.name }} ({{ user.role }})"
"name": "John", },
"role": "admin", "permissions": {
"permissions": ["read", "write", "delete"] "template": "{{ include user_info }} - Permissions: {{ for perm in user.permissions }}{{ perm }} {{ end }}",
} "context": {
} "user": {
} "name": "John",
} "role": "admin",
}'::jsonb, "permissions": ["read", "write", "delete"]
$template${{ include permissions }}$template$ }
); }
}
expected := 'User: John (admin) - Permissions: read write delete '; }
}'::jsonb,
passed := test_result = expected; $template${{ include permissions }}$template$
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); );
IF passed THEN
RAISE NOTICE 'Test %: Template with nested includes and shared context: PASSED', total_tests; expected := 'User: John (admin) - Permissions: read write delete ';
ELSE
RAISE WARNING 'Test %: Template with nested includes and shared context: FAILED. Expected "%", got "%"', passed := test_result = expected;
total_tests, expected, test_result; passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
END IF; IF passed THEN
RAISE NOTICE 'Test %: Template with nested includes and shared context: PASSED', total_tests;
-- Test 3: Template with execute tag using context from section ELSE
total_tests := total_tests + 1; RAISE WARNING 'Test %: Template with nested includes and shared context: FAILED. Expected "%", got "%"',
test_result := hemar.render( total_tests, expected, test_result;
'{ END IF;
"items": [ EXCEPTION WHEN OTHERS THEN
{"id": 1, "value": 100}, RAISE WARNING 'Test % failed: Error: %', total_tests, SQLERRM;
{"id": 2, "value": 200}, END;
{"id": 3, "value": 300}
] -- Test 3: Template with execute tag using context from section
}'::jsonb, total_tests := total_tests + 1;
$template$Items: BEGIN
{{ for item in items }} test_result := hemar.render(
Item {{ item.id }}: {{ exec '{
DECLARE "items": [
v_value INT; {"id": 1, "value": 100},
BEGIN {"id": 2, "value": 200},
v_value := (context->>'value')::int; {"id": 3, "value": 300}
RETURN v_value * 2; ]
END; }'::jsonb,
}} $template$Items:
{{ end }}$template$ {{ for item in items }}
); Item {{ item.id }}: {{ exec
DECLARE
expected := 'Items: v_value INT;
Item 1: 200 BEGIN
Item 2: 400 v_value := (context->>'value')::int;
Item 3: 600 RETURN v_value * 2;
'; END;
}}
passed := test_result = expected; {{ end }}$template$
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END); );
IF passed THEN
RAISE NOTICE 'Test %: Template with execute tag using context from section: PASSED', total_tests; expected := 'Items:
ELSE Item 1: 200
RAISE WARNING 'Test %: Template with execute tag using context from section: FAILED. Expected "%", got "%"', Item 2: 400
total_tests, expected, test_result; Item 3: 600
END IF; ';
-- Print summary passed := test_result = expected;
IF passed_tests = total_tests THEN passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
RAISE NOTICE '------------------------------------'; IF passed THEN
RAISE NOTICE 'SUMMARY: % of % combined template tests passed (100%%)', RAISE NOTICE 'Test %: Template with execute tag using context from section: PASSED', total_tests;
passed_tests, total_tests; ELSE
RAISE NOTICE '------------------------------------'; RAISE WARNING 'Test %: Template with execute tag using context from section: FAILED. Expected "%", got "%"',
ELSE total_tests, expected, test_result;
RAISE WARNING '------------------------------------'; END IF;
RAISE WARNING 'SUMMARY: % of % combined template tests passed (%)', EXCEPTION WHEN OTHERS THEN
passed_tests, RAISE WARNING 'Test % failed: Error: %', total_tests, SQLERRM;
total_tests, END;
round((passed_tests::numeric / total_tests::numeric) * 100, 2) || '%';
RAISE WARNING '------------------------------------'; -- Print summary
END IF; IF passed_tests = total_tests THEN
RAISE NOTICE '------------------------------------';
IF passed_tests != total_tests THEN RAISE NOTICE 'SUMMARY: % of % combined template tests passed (100%%)',
RAISE EXCEPTION 'Tests failed: % of % combined template tests did not pass', (total_tests - passed_tests), total_tests; passed_tests, total_tests;
END IF; RAISE NOTICE '------------------------------------';
ELSE
RAISE WARNING '------------------------------------';
RAISE WARNING 'SUMMARY: % of % combined template tests passed (%)',
passed_tests,
total_tests,
round((passed_tests::numeric / total_tests::numeric) * 100, 2) || '%';
RAISE WARNING '------------------------------------';
END IF;
IF passed_tests != total_tests THEN
RAISE EXCEPTION 'Tests failed: % of % combined template tests did not pass', (total_tests - passed_tests), total_tests;
END IF;
END $$; END $$;

View File

@@ -177,6 +177,44 @@ BEGIN
RAISE WARNING 'Test % failed: Should have raised an error for invalid include data', total_tests; RAISE WARNING 'Test % failed: Should have raised an error for invalid include data', total_tests;
END; END;
-- Test 8: Error handling - unexisting include object
total_tests := total_tests + 1;
BEGIN
result := hemar.render(
'{}'::jsonb,
'{{ include invalid_template }}'
);
IF result = '' THEN
RAISE NOTICE 'Test % passed: Error handling for unexisting include object works correctly', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test % failed: Expected "", got "%"', total_tests, result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test % failed: Should have raised an error for unexisting include object', total_tests;
END;
-- Test 9: Error handling - unexisting include data
total_tests := total_tests + 1;
BEGIN
result := hemar.render(
'{
"include": { }
}'::jsonb,
'{{ include invalid_template }}'
);
IF result = '' THEN
RAISE NOTICE 'Test % passed: Error handling for unexisting include data works correctly', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test % failed: Expected "", got "%"', total_tests, result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test % failed: Should have raised an error for unexisting include data', total_tests;
END;
IF passed_tests = total_tests THEN IF passed_tests = total_tests THEN
RAISE NOTICE '------------------------------------'; RAISE NOTICE '------------------------------------';
RAISE NOTICE 'SUMMARY: % of % template include tests passed (100%%)', RAISE NOTICE 'SUMMARY: % of % template include tests passed (100%%)',