fix: hemar: include object does not exists
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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 $$;
|
||||||
@@ -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%%)',
|
||||||
|
|||||||
Reference in New Issue
Block a user