1102 lines
34 KiB
PL/PgSQL
Executable File
1102 lines
34 KiB
PL/PgSQL
Executable File
-- Test file for hemar template parser
|
||
-- Run with: psql -f test_template_parser.sql
|
||
|
||
-- Load extension if not already loaded
|
||
-- CREATE EXTENSION IF NOT EXISTS hemar;
|
||
|
||
-- SAFETY(yukkop): !!! If you fix identation, you will ruin the tests.
|
||
|
||
-- Create test function to validate template parsing
|
||
CREATE OR REPLACE FUNCTION test_template_parse(template_text text, expected_structure text) RETURNS boolean AS $$
|
||
DECLARE
|
||
parsed_result text;
|
||
passed boolean;
|
||
BEGIN
|
||
BEGIN
|
||
parsed_result := hemar.parse(template_text);
|
||
|
||
IF parsed_result IS NULL THEN
|
||
RAISE WARNING 'Parser returned NULL for template: %', template_text;
|
||
RETURN false;
|
||
END IF;
|
||
|
||
passed := position(expected_structure in parsed_result) > 0;
|
||
|
||
IF NOT passed THEN
|
||
RAISE WARNING 'Template parsing test failed!';
|
||
RAISE WARNING 'Template: %', template_text;
|
||
-- RAISE WARNING 'Expected to find: %', pg_temp.test_regexp_replace(expected_structure);
|
||
-- RAISE WARNING 'Actual result: %', pg_temp.test_regexp_replace(parsed_result);
|
||
RAISE WARNING 'Expected to find: %', expected_structure;
|
||
RAISE WARNING 'Actual result: %', parsed_result;
|
||
END IF;
|
||
|
||
RETURN passed;
|
||
EXCEPTION WHEN OTHERS THEN
|
||
RAISE WARNING 'Exception during parsing: % (state: %)', SQLERRM, SQLSTATE;
|
||
RAISE WARNING 'Template: %', template_text;
|
||
RETURN false;
|
||
END;
|
||
END;
|
||
$$ LANGUAGE plpgsql;
|
||
|
||
-- Run the tests
|
||
DO $$
|
||
DECLARE
|
||
total_tests integer := 0;
|
||
passed_tests integer := 0;
|
||
result boolean;
|
||
BEGIN
|
||
PERFORM pg_sleep(2);
|
||
RAISE NOTICE 'Starting template parser tests...';
|
||
|
||
-- Test 1: Simple interpolation
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$hemar1${{ simple_var }}$hemar1$,
|
||
$expected1$INTERPOLATE: "simple_var"$expected1$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Simple interpolation - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Simple interpolation - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 2: Interpolation with surrounding text
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$hemar2$Hello, {{ name }}!$hemar2$,
|
||
$expected2$TEXT: "Hello, "
|
||
INTERPOLATE: "name"
|
||
TEXT: "!"$expected2$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Interpolation with surrounding text - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Interpolation with surrounding text - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 3: Simple section (for loop)
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$hemar3${{ for item in items }}{{ item }}{{ end }}$hemar3$,
|
||
$expected3$SECTION: iterator="item", collection="items"$expected3$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Simple section (for loop) - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Simple section (for loop) - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 4: Section with nested interpolation
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$hemar4${{ for item in items }}Name: {{ item.name }}{{ end }}$hemar4$,
|
||
$expected4$SECTION: iterator="item", collection="items"
|
||
TEXT: "Name: "
|
||
INTERPOLATE: "item.name"$expected4$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Section with nested interpolation - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Section with nested interpolation - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 5: Nested sections
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$hemar5${{ for item in items }}{{ for subitem in item.subitems }}{{ subitem }}{{ end }}{{ end }}$hemar5$,
|
||
$expected5$SECTION: iterator="item", collection="items"
|
||
SECTION: iterator="subitem", collection="item.subitems"
|
||
INTERPOLATE: "subitem"$expected5$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Nested sections - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Nested sections - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 6: Include tag
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$hemar6${{ include template_name }}$hemar6$,
|
||
$expected6$INCLUDE: "template_name"$expected6$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Include tag - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Include tag - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 7: Execute tag
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$hemar7${{ exec RETURN my_function(arg1, arg2) }}$hemar7$,
|
||
$expected7$EXECUTE: "RETURN my_function(arg1, arg2)"$expected7$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Execute tag - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Execute tag - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 8: Complex mixed template
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$hemar8$<div>{{ for item in items }}<p>{{ item.name }}</p>{{ include item.template }}{{ end }}</div>$hemar8$,
|
||
$expected8$TEXT: "<div>"
|
||
SECTION: iterator="item", collection="items"
|
||
TEXT: "<p>"
|
||
INTERPOLATE: "item.name"
|
||
TEXT: "</p>"
|
||
INCLUDE: "item.template"
|
||
TEXT: "</div>"$expected8$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Complex mixed template - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Complex mixed template - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 9: Execute tag with complex SQL
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
'{{ exec SELECT 123 AS number; }}',
|
||
'EXECUTE: "SELECT 123 AS number;"'
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Execute tag with complex SQL - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Execute tag with complex SQL - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 10: Whitespace handling
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$hemar10${{ spaced_var }}$hemar10$,
|
||
$expected10$INTERPOLATE: "spaced_var"$expected10$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Whitespace handling - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Whitespace handling - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 11: Multiple consecutive tags
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$hemar11${{ var1 }}{{ var2 }}{{ var3 }}$hemar11$,
|
||
$expected11$INTERPOLATE: "var1"
|
||
INTERPOLATE: "var2"
|
||
INTERPOLATE: "var3"$expected11$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Multiple consecutive tags - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Multiple consecutive tags - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 12: Section with multiple nested elements
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$hemar12${{ for item in items }}
|
||
<h2>{{ item.title }}</h2>
|
||
<p>{{ item.description }}</p>
|
||
{{ include item.footer }}
|
||
{{ end }}$hemar12$,
|
||
$expected12$Template parsed successfully. Structure:
|
||
SECTION: iterator="item", collection="items"
|
||
TEXT: " <h2>"
|
||
INTERPOLATE: "item.title"
|
||
TEXT: "</h2>
|
||
<p>"
|
||
INTERPOLATE: "item.description"
|
||
TEXT: "</p>
|
||
"
|
||
INCLUDE: "item.footer"
|
||
TEXT: "
|
||
"
|
||
$expected12$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Section with multiple nested elements - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Section with multiple nested elements - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 13: Empty template
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
'',
|
||
'TEXT: ""'
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Empty template - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Empty template - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 14: Just text, no tags
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$hemar14$Just plain text, no tags here.$hemar14$,
|
||
$expected14$TEXT: "Just plain text, no tags here."$expected14$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Just text, no tags - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Just text, no tags - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 15: Complex example from documentation
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template15$<div>text before<div>
|
||
|
||
{{ include inner_template }}
|
||
|
||
{{ name }}
|
||
|
||
{{ for item in array }}
|
||
some text: {{ name2 }}
|
||
{{ item.name }}
|
||
{{ end }}
|
||
|
||
<div>code insertion:</div>
|
||
{{ exec
|
||
context + '{"name3": "zalupa"}';
|
||
|
||
IF context->condition THEN
|
||
RAISE INFO 'some log';
|
||
|
||
RETURN 'some text';
|
||
END
|
||
RETURN 'some other text';
|
||
}}
|
||
|
||
<div id="footer">...</div>$template15$,
|
||
$expected15$Template parsed successfully. Structure:
|
||
TEXT: "<div>text before<div>
|
||
|
||
"
|
||
INCLUDE: "inner_template"
|
||
TEXT: "
|
||
|
||
"
|
||
INTERPOLATE: "name"
|
||
TEXT: "
|
||
|
||
"
|
||
SECTION: iterator="item", collection="array"
|
||
TEXT: " some text: "
|
||
INTERPOLATE: "name2"
|
||
TEXT: "
|
||
"
|
||
INTERPOLATE: "item.name"
|
||
TEXT: "
|
||
"
|
||
TEXT: "
|
||
|
||
<div>code insertion:</div>
|
||
"
|
||
EXECUTE: "context + '{"name3": "zalupa"}';
|
||
|
||
IF context->condition THEN
|
||
RAISE INFO 'some log';
|
||
|
||
RETURN 'some text';
|
||
END
|
||
RETURN 'some other text';"
|
||
TEXT: "
|
||
|
||
<div id="footer">...</div>"$expected15$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Complex example from documentation - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Complex example from documentation - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 16: Multiple nested sections
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
'{{ for a in items }}
|
||
{{ for b in a.items }}
|
||
{{ for c in b.items }}
|
||
{{ c.name }}
|
||
{{ end }}
|
||
{{ end }}
|
||
{{ end }}',
|
||
'Template parsed successfully. Structure:
|
||
SECTION: iterator="a", collection="items"
|
||
TEXT: " "
|
||
SECTION: iterator="b", collection="a.items"
|
||
TEXT: " "
|
||
SECTION: iterator="c", collection="b.items"
|
||
TEXT: " "
|
||
INTERPOLATE: "c.name"
|
||
TEXT: "
|
||
"
|
||
TEXT: "
|
||
"
|
||
TEXT: "
|
||
"
|
||
'
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Multiple nested sections - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Multiple nested sections - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 17: Interpolation with special characters
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
'{{ special@field }}',
|
||
'INTERPOLATE: "special@field"'
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Interpolation with special characters - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Interpolation with special characters - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 18: Section with complex iterator and collection names
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
'{{ for complex_item.with.dots in complex_collection[0].items }}{{ end }}',
|
||
'SECTION: iterator="complex_item.with.dots", collection="complex_collection[0].items"'
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Section with complex iterator and collection names - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Section with complex iterator and collection names - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 19: Include with complex path
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
'{{ include templates[0].nested.path }}',
|
||
'INCLUDE: "templates[0].nested.path"'
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Include with complex path - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Include with complex path - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 20: Execute with complex SQL and quotes
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template20$
|
||
{{ exec SELECT 'text with "double" quotes' AS result; }}
|
||
$template20$,
|
||
$expected20$EXECUTE: "SELECT 'text with "double" quotes' AS result;"$expected20$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Execute with complex SQL and quotes - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Execute with complex SQL and quotes - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 21: Execute tag with braces inside SQL code
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template21${{ exec
|
||
-- SQL with curly braces in string literals and comments
|
||
/* Comment with {{ braces }} inside */
|
||
SELECT
|
||
'{{ This is inside a string literal }}' AS braced_text,
|
||
$str$String with {{ and }} inside$str$ AS dollar_quoted,
|
||
regexp_replace('test', 'e(.)t', 'a$1z') AS regex_with_curly;
|
||
}}$template21$,
|
||
$expected21$EXECUTE: "-- SQL with curly braces in string literals and comments
|
||
/* Comment with {{ braces }} inside */
|
||
SELECT
|
||
'{{ This is inside a string literal }}' AS braced_text,
|
||
$str$String with {{ and }} inside$str$ AS dollar_quoted,
|
||
regexp_replace('test', 'e(.)t', 'a$1z') AS regex_with_curly;"$expected21$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Execute tag with braces inside SQL code - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Execute tag with braces inside SQL code - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 22: Deeply nested sections with mixed content
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template22${{ for x in outer }}
|
||
Level 1: {{ x.name }}
|
||
{{ for y in x.items }}
|
||
Level 2: {{ y.title }}
|
||
{{ for z in y.subitems }}
|
||
Level 3: {{ z.label }} - {{ z.value }}
|
||
{{ for detail in z.details }}
|
||
Details: {{ detail }}
|
||
{{ end }}
|
||
{{ end }}
|
||
{{ end }}
|
||
{{ end }}$template22$,
|
||
$expected22$Template parsed successfully. Structure:
|
||
SECTION: iterator="x", collection="outer"
|
||
TEXT: " Level 1: "
|
||
INTERPOLATE: "x.name"
|
||
TEXT: "
|
||
"
|
||
SECTION: iterator="y", collection="x.items"
|
||
TEXT: " Level 2: "
|
||
INTERPOLATE: "y.title"
|
||
TEXT: "
|
||
"
|
||
SECTION: iterator="z", collection="y.subitems"
|
||
TEXT: " Level 3: "
|
||
INTERPOLATE: "z.label"
|
||
TEXT: " - "
|
||
INTERPOLATE: "z.value"
|
||
TEXT: "
|
||
"
|
||
SECTION: iterator="detail", collection="z.details"
|
||
TEXT: " Details: "
|
||
INTERPOLATE: "detail"
|
||
TEXT: "
|
||
"
|
||
TEXT: "
|
||
"
|
||
TEXT: "
|
||
"
|
||
TEXT: "
|
||
"
|
||
$expected22$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Deeply nested sections with mixed content - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Deeply nested sections with mixed content - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 23: Multiple tag types mixed with HTML
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template23$<div class="container">
|
||
<header>{{ page_title }}</header>
|
||
<nav>
|
||
{{ for item in menu_items }}
|
||
<a href="{{ item.url }}">{{ item.label }}</a>
|
||
{{ end }}
|
||
</nav>
|
||
<main>
|
||
{{ include content_template }}
|
||
{{ exec SELECT get_footer_text() AS footer_text; }}
|
||
</main>
|
||
</div>$template23$,
|
||
$expected23$Template parsed successfully. Structure:
|
||
TEXT: "<div class="container">
|
||
<header>"
|
||
INTERPOLATE: "page_title"
|
||
TEXT: "</header>
|
||
<nav>
|
||
"
|
||
SECTION: iterator="item", collection="menu_items"
|
||
TEXT: " <a href=""
|
||
INTERPOLATE: "item.url"
|
||
TEXT: "">"
|
||
INTERPOLATE: "item.label"
|
||
TEXT: "</a>
|
||
"
|
||
TEXT: "
|
||
</nav>
|
||
<main>
|
||
"
|
||
INCLUDE: "content_template"
|
||
TEXT: "
|
||
"
|
||
EXECUTE: "SELECT get_footer_text() AS footer_text;"
|
||
TEXT: "
|
||
</main>
|
||
</div>"
|
||
$expected23$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Multiple tag types mixed with HTML - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Multiple tag types mixed with HTML - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 24: Section with complex iterator paths
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template24${{ for item.nested[0].value in collection[5].items[2].values }}
|
||
{{ item.nested[0].value }}
|
||
{{ end }}$template24$,
|
||
$expected24$SECTION: iterator="item.nested[0].value", collection="collection[5].items[2].values"$expected24$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Section with complex iterator paths - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Section with complex iterator paths - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 25: Interpolation with Unicode characters
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template25${{ unicode_var_ẞαж한글💻🌍 }}$template25$,
|
||
$expected25$INTERPOLATE: "unicode_var_ẞαж한글💻🌍"$expected25$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Interpolation with Unicode characters - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Interpolation with Unicode characters - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 26: Multiple consecutive sections
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template26${{ for a in list_a }}{{ a }}{{ end }}{{ for b in list_b }}{{ b }}{{ end }}{{ for c in list_c }}{{ c }}{{ end }}$template26$,
|
||
$expected26$SECTION: iterator="a", collection="list_a"
|
||
INTERPOLATE: "a"
|
||
SECTION: iterator="b", collection="list_b"
|
||
INTERPOLATE: "b"
|
||
SECTION: iterator="c", collection="list_c"
|
||
INTERPOLATE: "c"$expected26$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Multiple consecutive sections - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Multiple consecutive sections - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 27: Includes with variable paths
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template27${{ include user.preferences.theme_template }}
|
||
{{ include system.templates[user.template_index] }}$template27$,
|
||
$expected27$INCLUDE: "user.preferences.theme_template"
|
||
TEXT: "
|
||
"
|
||
INCLUDE: "system.templates[user.template_index]"$expected27$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Includes with variable paths - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Includes with variable paths - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 28: Extremely long interpolation key
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template28${{ very_long_variable_name_with_many_parts.that_continues_for_a_while.with_multiple_segments.and_keeps_going.for_quite_some_time.until_it_becomes_quite_verbose }}$template28$,
|
||
$expected28$INTERPOLATE: "very_long_variable_name_with_many_parts.that_continues_for_a_while.with_multiple_segments.and_keeps_going.for_quite_some_time.until_it_becomes_quite_verbose"$expected28$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Extremely long interpolation key - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Extremely long interpolation key - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 29: Tags with extra whitespace
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template29${{ for item in items }}
|
||
{{ item.name }}
|
||
{{ end }}$template29$,
|
||
$expected29$Template parsed successfully. Structure:
|
||
SECTION: iterator="item", collection="items"
|
||
TEXT: " "
|
||
INTERPOLATE: "item.name"
|
||
TEXT: "
|
||
"
|
||
$expected29$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Tags with extra whitespace - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Tags with extra whitespace - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 30: Execute with PL/pgSQL code blocks
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template30${{ exec
|
||
DECLARE
|
||
temp_var text;
|
||
counter int := 0;
|
||
BEGIN
|
||
FOR i IN 1..10 LOOP
|
||
counter := counter + i;
|
||
END LOOP;
|
||
|
||
IF counter > 50 THEN
|
||
temp_var := 'High';
|
||
ELSE
|
||
temp_var := 'Low';
|
||
END IF;
|
||
|
||
RETURN json_build_object('result', temp_var, 'count', counter);
|
||
END;
|
||
}}$template30$,
|
||
$expected30$EXECUTE: "DECLARE
|
||
temp_var text;
|
||
counter int := 0;
|
||
BEGIN
|
||
FOR i IN 1..10 LOOP
|
||
counter := counter + i;
|
||
END LOOP;
|
||
|
||
IF counter > 50 THEN
|
||
temp_var := 'High';
|
||
ELSE
|
||
temp_var := 'Low';
|
||
END IF;
|
||
|
||
RETURN json_build_object('result', temp_var, 'count', counter);
|
||
END;"$expected30$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Execute with PL/pgSQL code blocks - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Execute with PL/pgSQL code blocks - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 31: Template with mixed indentation and newlines
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template31$<div>
|
||
{{ for item in items }}
|
||
<span>{{ item.name }}</span>
|
||
{{ end }}
|
||
</div>$template31$,
|
||
$expected31$Template parsed successfully. Structure:
|
||
TEXT: "<div>
|
||
"
|
||
SECTION: iterator="item", collection="items"
|
||
TEXT: " <span>"
|
||
INTERPOLATE: "item.name"
|
||
TEXT: "</span>
|
||
"
|
||
TEXT: "
|
||
</div>"$expected31$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Template with mixed indentation and newlines - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Template with mixed indentation and newlines - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 32: Execute with window functions and complex SQL
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template32${{ exec
|
||
WITH recursive cte AS (
|
||
SELECT id, parent_id, name, 1 AS level
|
||
FROM categories
|
||
WHERE parent_id IS NULL
|
||
UNION ALL
|
||
SELECT c.id, c.parent_id, c.name, cte.level + 1
|
||
FROM categories c
|
||
JOIN cte ON c.parent_id = cte.id
|
||
)
|
||
SELECT
|
||
id,
|
||
repeat(' ', level - 1) || name AS indented_name,
|
||
row_number() OVER (PARTITION BY level ORDER BY name) AS row_num
|
||
FROM cte
|
||
ORDER BY level, name;
|
||
}}$template32$,
|
||
$expected32$EXECUTE: "WITH recursive cte AS (
|
||
SELECT id, parent_id, name, 1 AS level
|
||
FROM categories
|
||
WHERE parent_id IS NULL
|
||
UNION ALL
|
||
SELECT c.id, c.parent_id, c.name, cte.level + 1
|
||
FROM categories c
|
||
JOIN cte ON c.parent_id = cte.id
|
||
)
|
||
SELECT
|
||
id,
|
||
repeat(' ', level - 1) || name AS indented_name,
|
||
row_number() OVER (PARTITION BY level ORDER BY name) AS row_num
|
||
FROM cte
|
||
ORDER BY level, name;"$expected32$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Execute with window functions and complex SQL - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Execute with window functions and complex SQL - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 33: Recursive template includes
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template33${{ include base_template }}
|
||
{{ include dynamic_templates[index] }}
|
||
{{ for template_name in available_templates }}
|
||
{{ include template_name }}
|
||
{{ end }}$template33$,
|
||
$expected33$Template parsed successfully. Structure:
|
||
INCLUDE: "base_template"
|
||
TEXT: "
|
||
"
|
||
INCLUDE: "dynamic_templates[index]"
|
||
TEXT: "
|
||
"
|
||
SECTION: iterator="template_name", collection="available_templates"
|
||
TEXT: " "
|
||
INCLUDE: "template_name"
|
||
TEXT: "
|
||
"
|
||
$expected33$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Recursive template includes - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Recursive template includes - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 34: Complex JSON manipulation in execute
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template34${{ exec
|
||
WITH input_data AS (
|
||
SELECT '{"users": [
|
||
{"id": 1, "name": "Alice", "roles": ["admin", "editor"]},
|
||
{"id": 2, "name": "Bob", "roles": ["viewer"]},
|
||
{"id": 3, "name": "Charlie", "roles": ["editor", "contributor"]}
|
||
]}'::jsonb AS data
|
||
)
|
||
SELECT
|
||
jsonb_agg(
|
||
jsonb_build_object(
|
||
'name', user_data->>'name',
|
||
'roles', user_data->'roles',
|
||
'is_admin', user_data->'roles' ? 'admin'
|
||
)
|
||
) AS result
|
||
FROM input_data,
|
||
jsonb_array_elements(data->'users') AS user_data;
|
||
}}$template34$,
|
||
$expected34$EXECUTE: "WITH input_data AS (
|
||
SELECT '{"users": [
|
||
{"id": 1, "name": "Alice", "roles": ["admin", "editor"]},
|
||
{"id": 2, "name": "Bob", "roles": ["viewer"]},
|
||
{"id": 3, "name": "Charlie", "roles": ["editor", "contributor"]}
|
||
]}'::jsonb AS data
|
||
)
|
||
SELECT
|
||
jsonb_agg(
|
||
jsonb_build_object(
|
||
'name', user_data->>'name',
|
||
'roles', user_data->'roles',
|
||
'is_admin', user_data->'roles' ? 'admin'
|
||
)
|
||
) AS result
|
||
FROM input_data,
|
||
jsonb_array_elements(data->'users') AS user_data;"$expected34$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Complex JSON manipulation in execute - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Complex JSON manipulation in execute - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 35: Tags at start/end without whitespace
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template35${{ var1 }}Text{{ var2 }}$template35$,
|
||
$expected35$INTERPOLATE: "var1"
|
||
TEXT: "Text"
|
||
INTERPOLATE: "var2"$expected35$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Tags at start/end without whitespace - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Tags at start/end without whitespace - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 36: Template with special characters in text
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template36$Special chars: <>!@#$%^&*()_+`-=[]{}|;':",./<>?\nAnd {{ variable }} insertion$template36$,
|
||
$expected36$TEXT: "Special chars: <>!@#$%^&*()_+`-=[]{}|;':",./<>?\nAnd "
|
||
INTERPOLATE: "variable"
|
||
TEXT: " insertion"$expected36$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Template with special characters in text - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Template with special characters in text - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 37: Complex execute with error handling
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template37${{ exec
|
||
BEGIN
|
||
RETURN process_data(input_json);
|
||
EXCEPTION
|
||
WHEN no_data_found THEN
|
||
RETURN jsonb_build_object('error', 'No data found', 'code', 404);
|
||
WHEN unique_violation THEN
|
||
RETURN jsonb_build_object('error', 'Duplicate entry', 'code', 409);
|
||
WHEN OTHERS THEN
|
||
RETURN jsonb_build_object(
|
||
'error', SQLERRM,
|
||
'code', SQLSTATE,
|
||
'severity', 'CRITICAL'
|
||
);
|
||
END;
|
||
}}$template37$,
|
||
$expected37$EXECUTE: "BEGIN
|
||
RETURN process_data(input_json);
|
||
EXCEPTION
|
||
WHEN no_data_found THEN
|
||
RETURN jsonb_build_object('error', 'No data found', 'code', 404);
|
||
WHEN unique_violation THEN
|
||
RETURN jsonb_build_object('error', 'Duplicate entry', 'code', 409);
|
||
WHEN OTHERS THEN
|
||
RETURN jsonb_build_object(
|
||
'error', SQLERRM,
|
||
'code', SQLSTATE,
|
||
'severity', 'CRITICAL'
|
||
);
|
||
END;"$expected37$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Complex execute with error handling - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Complex execute with error handling - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 38: Mixed nested sections and interpolations
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template38${{ for user in users }}
|
||
{{ user.name }}'s permissions:
|
||
{{ for permission in user.permissions }}
|
||
- {{ permission.name }}: {{ permission.status }}
|
||
{{ for scope in permission.scopes }}
|
||
* {{ scope.area }}: {{ scope.level }}
|
||
{{ end }}
|
||
{{ end }}
|
||
{{ end }}$template38$,
|
||
$expected38$Template parsed successfully. Structure:
|
||
SECTION: iterator="user", collection="users"
|
||
TEXT: " "
|
||
INTERPOLATE: "user.name"
|
||
TEXT: "'s permissions:
|
||
"
|
||
SECTION: iterator="permission", collection="user.permissions"
|
||
TEXT: " - "
|
||
INTERPOLATE: "permission.name"
|
||
TEXT: ": "
|
||
INTERPOLATE: "permission.status"
|
||
TEXT: "
|
||
"
|
||
SECTION: iterator="scope", collection="permission.scopes"
|
||
TEXT: " * "
|
||
INTERPOLATE: "scope.area"
|
||
TEXT: ": "
|
||
INTERPOLATE: "scope.level"
|
||
TEXT: "
|
||
"
|
||
TEXT: "
|
||
"
|
||
TEXT: "
|
||
"
|
||
$expected38$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Mixed nested sections and interpolations - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Mixed nested sections and interpolations - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 39: Execute with dynamic SQL generation
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template39${{ exec
|
||
DECLARE
|
||
column_names text[] := ARRAY['id', 'name', 'created_at'];
|
||
table_name text := 'users';
|
||
conditions text[] := ARRAY['is_active = true', 'created_at > now() - interval ''1 month'''];
|
||
order_clause text := 'last_login DESC';
|
||
query text;
|
||
BEGIN
|
||
query := 'SELECT ' || array_to_string(column_names, ', ') ||
|
||
' FROM ' || table_name;
|
||
|
||
IF array_length(conditions, 1) > 0 THEN
|
||
query := query || ' WHERE ' || array_to_string(conditions, ' AND ');
|
||
END IF;
|
||
|
||
IF order_clause IS NOT NULL THEN
|
||
query := query || ' ORDER BY ' || order_clause;
|
||
END IF;
|
||
|
||
EXECUTE query;
|
||
RETURN query;
|
||
END;
|
||
}}$template39$,
|
||
$expected39$EXECUTE: "DECLARE
|
||
column_names text[] := ARRAY['id', 'name', 'created_at'];
|
||
table_name text := 'users';
|
||
conditions text[] := ARRAY['is_active = true', 'created_at > now() - interval ''1 month'''];
|
||
order_clause text := 'last_login DESC';
|
||
query text;
|
||
BEGIN
|
||
query := 'SELECT ' || array_to_string(column_names, ', ') ||
|
||
' FROM ' || table_name;
|
||
|
||
IF array_length(conditions, 1) > 0 THEN
|
||
query := query || ' WHERE ' || array_to_string(conditions, ' AND ');
|
||
END IF;
|
||
|
||
IF order_clause IS NOT NULL THEN
|
||
query := query || ' ORDER BY ' || order_clause;
|
||
END IF;
|
||
|
||
EXECUTE query;
|
||
RETURN query;
|
||
END;"$expected39$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Execute with dynamic SQL generation - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Execute with dynamic SQL generation - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 40: Complex nested structure with all tag types
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$template40$<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>{{ page.title }}</title>
|
||
{{ include meta_tags }}
|
||
</head>
|
||
<body>
|
||
<header>{{ include header }}</header>
|
||
<main>
|
||
{{ for section in page.sections }}
|
||
<section id="{{ section.id }}">
|
||
<h2>{{ section.title }}</h2>
|
||
{{ for item in section.items }}
|
||
<div class="item {{ item.status }}">
|
||
{{ item.content }}
|
||
{{ include item.template }}
|
||
{{ exec
|
||
-- Get dynamic content for this item
|
||
SELECT get_dynamic_content(
|
||
'{{ item.id }}',
|
||
'{{ section.id }}',
|
||
(SELECT context->'user'->'preferences')
|
||
);
|
||
}}
|
||
</div>
|
||
{{ end }}
|
||
</section>
|
||
{{ end }}
|
||
</main>
|
||
<footer>{{ include footer }}</footer>
|
||
</body>
|
||
</html>$template40$,
|
||
$expected40$TEXT: "<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>"
|
||
INTERPOLATE: "page.title"
|
||
TEXT: "</title>
|
||
"
|
||
INCLUDE: "meta_tags"
|
||
TEXT: "
|
||
</head>
|
||
<body>
|
||
<header>"
|
||
INCLUDE: "header"
|
||
TEXT: "</header>
|
||
<main>
|
||
"
|
||
SECTION: iterator="section", collection="page.sections"$expected40$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Complex nested structure with all tag types - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Complex nested structure with all tag types - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Test 41: Subsequent object interpolation
|
||
total_tests := total_tests + 1;
|
||
result := test_template_parse(
|
||
$hemar$User: {{ user.profile.name }}, Age: {{ user.profile.age }}$hemar$,
|
||
$zalupa$Template parsed successfully. Structure:
|
||
TEXT: "User: "
|
||
INTERPOLATE: "user.profile.name"
|
||
TEXT: ", Age: "
|
||
INTERPOLATE: "user.profile.age"$zalupa$
|
||
);
|
||
IF result THEN
|
||
passed_tests := passed_tests + 1;
|
||
RAISE NOTICE 'Test %: Subsequent object interpolation - PASSED', total_tests;
|
||
ELSE
|
||
RAISE WARNING 'Test %: Subsequent object interpolation - FAILED', total_tests;
|
||
END IF;
|
||
|
||
-- Print summary
|
||
IF passed_tests = total_tests THEN
|
||
RAISE NOTICE '------------------------------------';
|
||
RAISE NOTICE 'SUMMARY: % of % template parser tests passed (100%%)',
|
||
passed_tests, total_tests;
|
||
RAISE NOTICE '------------------------------------';
|
||
ELSE
|
||
RAISE WARNING '------------------------------------';
|
||
RAISE WARNING 'SUMMARY: % of % template parser 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 % template parser tests did not pass', (total_tests - passed_tests), total_tests;
|
||
END IF;
|
||
END $$; |