-- 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: %', 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$
{{ for item in items }}

{{ item.name }}

{{ include item.template }}{{ end }}
$hemar8$, $expected8$TEXT: "
" SECTION: iterator="item", collection="items" TEXT: "

" INTERPOLATE: "item.name" TEXT: "

" INCLUDE: "item.template" TEXT: "
"$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 }}

{{ item.title }}

{{ item.description }}

{{ include item.footer }} {{ end }}$hemar12$, $expected12$SECTION: iterator="item", collection="items" TEXT: "

" INTERPOLATE: "item.title" TEXT: "

" INTERPOLATE: "item.description" TEXT: "

" 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$
text before
{{ include inner_template }} {{ name }} {{ for item in array }} some text: {{ name2 }} {{ item.name }} {{ end }}
code insertion:
{{ exec context + '{"name3": "zalupa"}'; IF context->condition THEN RAISE INFO 'some log'; RETURN 'some text'; END RETURN 'some other text'; }} $template15$, $expected15$Template parsed successfully. Structure: TEXT: "
text before
" INCLUDE: "inner_template" TEXT: " " INTERPOLATE: "name" TEXT: " " SECTION: iterator="item", collection="array" TEXT: " some text: " INTERPOLATE: "name2" TEXT: " " INTERPOLATE: "item.name" TEXT: " " TEXT: "
code insertion:
" EXECUTE: "context + '{"name3": "zalupa"}'; IF context->condition THEN RAISE INFO 'some log'; RETURN 'some text'; END RETURN 'some other text';" TEXT: " "$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 }}', 'SECTION: iterator="a", collection="items" TEXT: " " SECTION: iterator="b", collection="a.items" TEXT: " " SECTION: iterator="c", collection="b.items" TEXT: " " INTERPOLATE: "c.name" 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$
{{ page_title }}
{{ include content_template }} {{ exec SELECT get_footer_text() AS footer_text; }}
$template23$, $expected23$Template parsed successfully. Structure: TEXT: "
" INTERPOLATE: "page_title" TEXT: "
" INCLUDE: "content_template" TEXT: " " EXECUTE: "SELECT get_footer_text() AS footer_text;" TEXT: "
"$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$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$
{{ for item in items }} {{ item.name }} {{ end }}
$template31$, $expected31$TEXT: "
" SECTION: iterator="item", collection="items" TEXT: " " INTERPOLATE: "item.name" TEXT: " " TEXT: "
"$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$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$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$ {{ page.title }} {{ include meta_tags }}
{{ include header }}
{{ for section in page.sections }}

{{ section.title }}

{{ for item in section.items }}
{{ item.content }} {{ include item.template }} {{ exec -- Get dynamic content for this item SELECT get_dynamic_content( '{{ item.id }}', '{{ section.id }}', (SELECT context->'user'->'preferences') ); }}
{{ end }}
{{ end }}
{{ include footer }}
$template40$, $expected40$TEXT: " " INTERPOLATE: "page.title" TEXT: " " INCLUDE: "meta_tags" TEXT: "
" INCLUDE: "header" TEXT: "
" 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; -- 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 $$;