refactor: hemar reboot

This commit is contained in:
2025-09-28 04:44:46 +00:00
parent 5dd03a947f
commit 38bf9151eb
27 changed files with 374 additions and 38 deletions

View File

@@ -0,0 +1,38 @@
BEGIN;
CREATE OR REPLACE FUNCTION pg_temp.diff(string1 text, string2 text) RETURNS TABLE("index" int, char1 text, char2 text) AS $$
BEGIN
RETURN QUERY WITH
s1 AS (SELECT string1 AS str),
s2 AS (SELECT string2 AS str)
SELECT i,
substring(s1.str FROM i FOR 1) AS char1,
substring(s2.str FROM i FOR 1) AS char2
FROM s1, s2,
generate_series(1, GREATEST(length(s1.str), length(s2.str))) AS i
WHERE substring(s1.str FROM i FOR 1) IS DISTINCT FROM substring(s2.str FROM i FOR 1);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION pg_temp.test_regexp_replace(string text) RETURNS text AS $$
BEGIN
RETURN regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(string, E'\t', '\\t', 'g'),
E'\n', '\\n', 'g'),
E'\r', '\\r', 'g'),
' ', '[S]', 'g'),
'\s', '\\s', 'g');
END;
$$ LANGUAGE plpgsql;
\ir test_jsonb_path.sql
--\ir test_template_parser.sql
\ir test_render_exec.sql
\ir test_render_interpolate.sql
\ir test_render_section.sql
\ir test_render_include.sql
--\ir test_render_all.sql
ROLLBACK;

View File

@@ -0,0 +1,516 @@
-- Test file for hemar.jsonb_get_by_path function
-- Run with: psql -f test_jsonb_path.sql
-- Load extension if not already loaded
-- CREATE EXTENSION IF NOT EXISTS hemar;
-- Create sample test data
DO $$
DECLARE
test_json jsonb;
result jsonb;
passed boolean;
total_tests integer := 0;
passed_tests integer := 0;
current_path text;
BEGIN
test_json := jsonb_build_object(
'name', 'John Doe',
'age', 30,
'is_active', true,
'tags', jsonb_build_array('developer', 'postgresql', 'jsonb'),
'address', jsonb_build_object(
'street', '123 Main St',
'city', 'New York',
'zip', '10001'
),
'contacts', jsonb_build_array(
jsonb_build_object(
'type', 'email',
'value', 'john@example.com'
),
jsonb_build_object(
'type', 'phone',
'value', '555-1234',
'verified', true
)
),
'skills', jsonb_build_array(
jsonb_build_array('PostgreSQL', 5),
jsonb_build_array('Python', 4),
jsonb_build_array('JavaScript', 3)
)
);
-- Test basic field access
total_tests := total_tests + 1;
current_path := 'name';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"John Doe"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Simple field access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Simple field access (path="%"): % | PASSED: % (expected: "John Doe")',
total_tests, current_path, result, passed;
END IF;
total_tests := total_tests + 1;
current_path := 'age';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '30'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Numeric field access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Numeric field access (path="%"): % | PASSED: % (expected: 30)',
total_tests, current_path, result, passed;
END IF;
total_tests := total_tests + 1;
current_path := 'is_active';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = 'true'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Boolean field access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Boolean field access (path="%"): % | PASSED: % (expected: true)',
total_tests, current_path, result, passed;
END IF;
-- Test nested field access
total_tests := total_tests + 1;
current_path := 'address.city';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"New York"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Nested object field access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Nested object field access (path="%"): % | PASSED: % (expected: "New York")',
total_tests, current_path, result, passed;
END IF;
-- Test array access
total_tests := total_tests + 1;
current_path := 'tags[1]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"postgresql"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Simple array access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Simple array access (path="%"): % | PASSED: % (expected: "postgresql")',
total_tests, current_path, result, passed;
END IF;
total_tests := total_tests + 1;
current_path := 'contacts[0].type';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"email"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Object in array access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Object in array access (path="%"): % | PASSED: % (expected: "email")',
total_tests, current_path, result, passed;
END IF;
total_tests := total_tests + 1;
current_path := 'skills[1][0]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"Python"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Nested array access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Nested array access (path="%"): % | PASSED: % (expected: "Python")',
total_tests, current_path, result, passed;
END IF;
total_tests := total_tests + 1;
current_path := 'contacts[1].value';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"555-1234"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Complex path with multiple array indices (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Complex path with multiple array indices (path="%"): % | PASSED: % (expected: "555-1234")',
total_tests, current_path, result, passed;
END IF;
-- Test object and array returns
total_tests := total_tests + 1;
current_path := 'address';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := jsonb_typeof(result) = 'object';
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Path to object (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Path to object (path="%"): % | PASSED: % (expected type: object, got: %)',
total_tests, current_path, result, passed, jsonb_typeof(result);
END IF;
total_tests := total_tests + 1;
current_path := 'contacts';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := jsonb_typeof(result) = 'array';
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Path to array (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Path to array (path="%"): % | PASSED: % (expected type: array, got: %)',
total_tests, current_path, result, passed, jsonb_typeof(result);
END IF;
-- Test error cases
total_tests := total_tests + 1;
current_path := 'unknown_field';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result IS NULL;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Non-existent field (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Non-existent field (path="%"): % | PASSED: % (expected: NULL)',
total_tests, current_path, result, passed;
END IF;
total_tests := total_tests + 1;
current_path := 'address.country';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result IS NULL;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Non-existent nested field (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Non-existent nested field (path="%"): % | PASSED: % (expected: NULL)',
total_tests, current_path, result, passed;
END IF;
total_tests := total_tests + 1;
current_path := 'tags[10]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result IS NULL;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Array index out of bounds (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Array index out of bounds (path="%"): % | PASSED: % (expected: NULL)',
total_tests, current_path, result, passed;
END IF;
-- Test edge cases
total_tests := total_tests + 1;
current_path := '';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result IS NULL;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Empty path (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Empty path (path="%"): % | PASSED: % (expected: NULL)',
total_tests, current_path, result, passed;
END IF;
total_tests := total_tests + 1;
current_path := 'skills[0][1]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '5'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Multiple array indices (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Multiple array indices (path="%"): % | PASSED: % (expected: 5)',
total_tests, current_path, result, passed;
END IF;
-- Additional complex test cases
-- Test 16: Deep nested object access
total_tests := total_tests + 1;
test_json := jsonb_build_object(
'level1', jsonb_build_object(
'level2', jsonb_build_object(
'level3', jsonb_build_object(
'level4', jsonb_build_object(
'value', 'deep nested value'
)
)
)
)
);
current_path := 'level1.level2.level3.level4.value';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"deep nested value"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Deep nested object access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Deep nested object access (path="%"): % | PASSED: % (expected: "deep nested value")',
total_tests, current_path, result, passed;
END IF;
-- Test 17: Deep nested array access
total_tests := total_tests + 1;
test_json := jsonb_build_array(
jsonb_build_array(
jsonb_build_array(
jsonb_build_array(
'nested array value'
)
)
)
);
current_path := '[0][0][0][0]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"nested array value"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Deep nested array access (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Deep nested array access (path="%"): % | PASSED: % (expected: "nested array value")',
total_tests, current_path, result, passed;
END IF;
-- Test 18: Complex mixed nesting (object -> array -> object -> array)
total_tests := total_tests + 1;
test_json := jsonb_build_object(
'users', jsonb_build_array(
jsonb_build_object(
'name', 'Alice',
'permissions', jsonb_build_array('read', 'write', 'admin')
),
jsonb_build_object(
'name', 'Bob',
'permissions', jsonb_build_array('read', 'write')
)
)
);
current_path := 'users[1].permissions[0]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"read"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Complex mixed nesting (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Complex mixed nesting (path="%"): % | PASSED: % (expected: "read")',
total_tests, current_path, result, passed;
END IF;
-- Test 19: Array with mixed types
total_tests := total_tests + 1;
test_json := jsonb_build_array(
'string',
42,
true,
jsonb_build_object('key', 'value'),
jsonb_build_array(1, 2, 3)
);
current_path := '[3].key';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"value"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Array with mixed types (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Array with mixed types (path="%"): % | PASSED: % (expected: "value")',
total_tests, current_path, result, passed;
END IF;
-- Test 20: Path with array at the end
total_tests := total_tests + 1;
test_json := jsonb_build_object(
'data', jsonb_build_object(
'items', jsonb_build_array(10, 20, 30, 40)
)
);
current_path := 'data.items[2]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '30'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Path with array at the end (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Path with array at the end (path="%"): % | PASSED: % (expected: 30)',
total_tests, current_path, result, passed;
END IF;
-- Test 21: Numeric field names
total_tests := total_tests + 1;
test_json := jsonb_build_object(
'123', 'numeric key',
'456', jsonb_build_object(
'789', 'nested numeric key'
)
);
current_path := '456.789';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"nested numeric key"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Numeric field names (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Numeric field names (path="%"): % | PASSED: % (expected: "nested numeric key")',
total_tests, current_path, result, passed;
END IF;
-- Test 22: Special characters in field names
total_tests := total_tests + 1;
test_json := jsonb_build_object(
'special@field', 'special value',
'nested', jsonb_build_object(
'field-with-hyphens', 'hyphenated value'
)
);
current_path := 'nested.field-with-hyphens';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"hyphenated value"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Special characters in field names (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Special characters in field names (path="%"): % | PASSED: % (expected: "hyphenated value")',
total_tests, current_path, result, passed;
END IF;
-- Test 23: Array of arrays of arrays
total_tests := total_tests + 1;
test_json := jsonb_build_array(
jsonb_build_array(
jsonb_build_array(1, 2),
jsonb_build_array(3, 4)
),
jsonb_build_array(
jsonb_build_array(5, 6),
jsonb_build_array(7, 8)
)
);
current_path := '[1][0][1]';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '6'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Array of arrays of arrays (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Array of arrays of arrays (path="%"): % | PASSED: % (expected: 6)',
total_tests, current_path, result, passed;
END IF;
-- Test 24: Complex path with multiple array indices and object fields
total_tests := total_tests + 1;
test_json := jsonb_build_object(
'companies', jsonb_build_array(
jsonb_build_object(
'name', 'Company A',
'departments', jsonb_build_array(
jsonb_build_object(
'name', 'Engineering',
'teams', jsonb_build_array(
jsonb_build_object(
'name', 'Backend',
'members', jsonb_build_array(
jsonb_build_object('name', 'John', 'role', 'Developer'),
jsonb_build_object('name', 'Jane', 'role', 'Lead')
)
)
)
)
)
)
)
);
current_path := 'companies[0].departments[0].teams[0].members[1].role';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := result = '"Lead"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Very complex path (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Very complex path (path="%"): % | PASSED: % (expected: "Lead")',
total_tests, current_path, result, passed;
END IF;
-- Test 25: Empty array and object edge cases
total_tests := total_tests + 1;
test_json := jsonb_build_object(
'emptyArray', jsonb_build_array(),
'emptyObject', jsonb_build_object(),
'arrayWithEmptyObject', jsonb_build_array(jsonb_build_object()),
'objectWithEmptyArray', jsonb_build_object('empty', jsonb_build_array())
);
current_path := 'objectWithEmptyArray.empty';
result := hemar.jsonb_get_by_path(test_json, current_path);
passed := jsonb_typeof(result) = 'array' AND jsonb_array_length(result) = 0;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Empty array/object edge cases (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Empty array/object edge cases (path="%"): % | PASSED: % (expected: empty array)',
total_tests, current_path, result, passed;
END IF;
-- Test nested object path parsing
total_tests := total_tests + 1;
result := hemar.jsonb_get_by_path(
'{"user": {"profile": {"name": "John", "age": 30}}}'::jsonb,
'user.profile.name'
);
passed := result = '"John"'::jsonb;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Nested object path parsing (path="%"): % | PASSED: %',
total_tests, current_path, result, passed;
ELSE
RAISE WARNING 'Test %: Nested object path parsing (path="%"): % | PASSED: % (expected: "John")',
total_tests, current_path, result, passed;
END IF;
-- Print summary
IF passed_tests = total_tests THEN
RAISE NOTICE '------------------------------------';
RAISE NOTICE 'SUMMARY: % of % jsonb_get_by_path tests passed (100%%)',
passed_tests, total_tests;
RAISE NOTICE '------------------------------------';
ELSE
RAISE WARNING '------------------------------------';
RAISE WARNING 'SUMMARY: % of % jsonb_get_by_path 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 % jsonb_get_by_path tests did not pass', (total_tests - passed_tests), total_tests;
END IF;
END $$;

View File

@@ -0,0 +1,261 @@
-- Test all template tags together
CREATE OR REPLACE FUNCTION pg_temp.diff(string1 text, string2 text) RETURNS TABLE("index" int, char1 text, char2 text) AS $$
BEGIN
RETURN QUERY WITH
s1 AS (SELECT string1 AS str),
s2 AS (SELECT string2 AS str)
SELECT i,
substring(s1.str FROM i FOR 1) AS char1,
substring(s2.str FROM i FOR 1) AS char2
FROM s1, s2,
generate_series(1, GREATEST(length(s1.str), length(s2.str))) AS i
WHERE substring(s1.str FROM i FOR 1) IS DISTINCT FROM substring(s2.str FROM i FOR 1);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION pg_temp.test_regexp_replace(string text) RETURNS text AS $$
BEGIN
RETURN regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(
regexp_replace(string, E'\t', '[TAB]', 'g'),
E'\n', '[LF]', 'g'),
E'\r', '[CR]', 'g'),
' ', '[SPACE]', 'g'),
'\s', '[WHITESPACE]', 'g');
END;
$$ LANGUAGE plpgsql;
DO $$
DECLARE
total_tests INT := 0;
passed_tests INT := 0;
test_result TEXT;
expected TEXT;
passed BOOLEAN;
item INT;
c1 TEXT;
c2 TEXT;
BEGIN
-- Test 1: Template with execute tag using context from section
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{
"items": [
{"id": 1, "value": 100},
{"id": 2, "value": 200},
{"id": 3, "value": 300}
]
}'::jsonb,
$template$Items:
{{ for item in items }}
Item {{ item.id }}: {{ exec RETURN (context->'item'->>'value')::int * 2; }}
{{ end }}$template$
);
expected:='Items:
Item 1: 200
Item 2: 400
Item 3: 600
';
passed := test_result = expected;
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;
ELSE
RAISE WARNING 'Test %: Template with execute tag using context from section: FAILED. Expected "%", got "%"',
total_tests, pg_temp.test_regexp_replace(expected), pg_temp.test_regexp_replace(test_result);
FOR item, c1, c2 IN
SELECT * FROM pg_temp.diff(expected, test_result)
LOOP
RAISE NOTICE ' % | % | %', item, c1, c2;
END LOOP;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test % failed: Error: %', total_tests, SQLERRM;
END;
-- Test 2: Complex template with all tag types
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{
"page": {
"title": "My Page",
"sections": [
{
"id": "section1",
"title": "Section 1",
"items": [
{
"id": "item1",
"status": "active",
"content": "Item 1 Content",
"template": "item_template"
},
{
"id": "item2",
"status": "inactive",
"content": "Item 2 Content",
"template": "item_template"
}
]
}
]
},
"include": {
"meta_tags": {
"content": "<meta name=\"description\" content=\"Test Page\">"
},
"header": {
"template": "Welcome to {{ page.title }}!",
"context": {
"page": {
"title": "My Page"
}
}
},
"item_template": {
"template": "Status: {{ status }}, Content: {{ content }}"
},
"footer": {
"content": "<footer>Copyright 2024</footer>"
}
}
}'::jsonb,
$template$<!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 }}">
{{ include item.template }}
{{ exec
DECLARE
v_status TEXT;
BEGIN
v_status := context->'item'->>'status';
RETURN CASE
WHEN v_status = 'active' THEN ' (Active Item)'
ELSE ' (Inactive Item)'
END;
END;
}}
</div>
{{ end }}
</section>
{{ end }}
</main>
<footer>{{ include footer }}</footer>
</body>
</html>$template$
);
expected := '<!DOCTYPE html>
<html>
<head>
<title>My Page</title>
<meta name="description" content="Test Page">
</head>
<body>
<header>Welcome to My Page!</header>
<main>
<section id="section1">
<h2>Section 1</h2>
<div class="item active">
Status: active, Content: Item 1 Content (Active Item)
</div>
<div class="item inactive">
Status: inactive, Content: Item 2 Content (Inactive Item)
</div>
</section>
</main>
<footer><footer>Copyright 2024</footer></footer>
</body>
</html>';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Complex template with all tag types: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Complex template with all tag types: FAILED. Expected "%", got "%"',
total_tests, pg_temp.test_regexp_replace(expected), pg_temp.test_regexp_replace(test_result);
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test % failed: Error: %', total_tests, SQLERRM;
END;
-- Test 3: Template with nested includes and shared context
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{
"user": {
"name": "John",
"role": "admin"
},
"include": {
"user_info": {
"template": "User: {{ user.name }} ({{ user.role }})"
},
"permissions": {
"template": "{{ include user_info }} - Permissions: {{ for perm in user.permissions }}{{ perm }} {{ end }}",
"context": {
"user": {
"name": "John",
"role": "admin",
"permissions": ["read", "write", "delete"]
}
}
}
}
}'::jsonb,
$template${{ include permissions }}$template$
);
expected := 'User: John (admin) - Permissions: read write delete ';
passed := test_result = expected;
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;
ELSE
RAISE WARNING 'Test %: Template with nested includes and shared context: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test % failed: Error: %', total_tests, SQLERRM;
END;
-- Print summary
IF passed_tests = total_tests THEN
RAISE NOTICE '------------------------------------';
RAISE NOTICE 'SUMMARY: % of % combined template tests passed (100%%)',
passed_tests, total_tests;
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 $$;

View File

@@ -0,0 +1,343 @@
-- Test the render function with execute tags
CREATE EXTENSION IF NOT EXISTS hemar;
DO $$
DECLARE
total_tests INT := 0;
passed_tests INT := 0;
test_result TEXT;
expected TEXT;
passed BOOLEAN;
BEGIN
-- Test 1: Simple execute tag that sets a variable
total_tests := total_tests + 1;
test_result := hemar.render(
'{"name": "John", "age": 30}'::jsonb,
'Hello {{ exec PERFORM 1; }}'
);
expected := 'Hello ';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Simple execute tag: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Simple execute tag: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 2: Execute tag with context access
total_tests := total_tests + 1;
DROP TABLE IF EXISTS test_output;
CREATE TEMP TABLE test_output (value TEXT);
test_result := hemar.render(
'{"name": "John", "age": 30}'::jsonb,
$expected$Hello {{ exec INSERT INTO test_output VALUES (context->'name'); }}$expected$
);
SELECT value INTO expected FROM test_output;
passed := expected = '"John"';
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with context access: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with context access: FAILED. Expected "John", got "%"',
total_tests, expected;
END IF;
-- Test 3: Execute tag with quotes and complex SQL
total_tests := total_tests + 1;
DROP TABLE IF EXISTS test_output;
CREATE TEMP TABLE test_output (value TEXT);
test_result := hemar.render(
'{"items": [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]}'::jsonb,
$expected$Items: {{ exec
INSERT INTO test_output
SELECT jsonb_array_elements(context->'items')->>'name';
}}$expected$
);
SELECT string_agg(value, ', ' ORDER BY value) INTO expected FROM test_output;
passed := expected = 'Item 1, Item 2';
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with complex SQL: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with complex SQL: FAILED. Expected "Item 1, Item 2", got "%"',
total_tests, expected;
END IF;
-- Test 4: Execute tag with output capture
total_tests := total_tests + 1;
test_result := hemar.render(
'{"name": "John", "age": 30}'::jsonb,
$expected$Hello {{ exec RETURN context->>'name'; }}$expected$
);
expected := 'Hello John';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with output capture: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with output capture: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 5: Execute tag with complex output
total_tests := total_tests + 1;
test_result := hemar.render(
'{"items": [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]}'::jsonb,
$expected$Items: {{ exec
RETURN (SELECT string_agg(value, ', ')
FROM (
SELECT jsonb_array_elements(context->'items')->>'name' as value
) t);
}}$expected$
);
expected := 'Items: Item 1, Item 2';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with complex output: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with complex output: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 6: Execute tag with multiple statements
total_tests := total_tests + 1;
test_result := hemar.render(
'{"name": "John", "age": 30}'::jsonb,
$expected$Hello {{ exec
DECLARE
v_name TEXT;
BEGIN
v_name := context->>'name';
RETURN v_name;
END;
}}$expected$
);
expected := 'Hello John';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with multiple statements: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with multiple statements: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 7: Execute tag with array operations
total_tests := total_tests + 1;
test_result := hemar.render(
'{"numbers": [1, 2, 3, 4, 5]}'::jsonb,
$expected$Sum: {{ exec
RETURN (SELECT sum(value::int)
FROM jsonb_array_elements_text(context->'numbers') as value);
}}$expected$
);
expected := 'Sum: 15';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with array operations: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with array operations: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 8: Execute tag with nested JSON operations
total_tests := total_tests + 1;
test_result := hemar.render(
'{"user": {"profile": {"settings": {"theme": "dark", "notifications": true}}}}'::jsonb,
$expected$Settings: {{ exec
RETURN context->'user'->'profile'->'settings'->>'theme';
}}$expected$
);
expected := 'Settings: dark';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with nested JSON operations: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with nested JSON operations: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 9: Execute tag with conditional logic
total_tests := total_tests + 1;
test_result := hemar.render(
'{"age": 25, "country": "US"}'::jsonb,
$expected$Status: {{ exec
DECLARE
v_status TEXT;
BEGIN
IF (context->>'age')::int >= 21 AND context->>'country' = 'US' THEN
v_status := 'Adult in US';
ELSE
v_status := 'Other';
END IF;
RETURN v_status;
END;
}}$expected$
);
expected := 'Status: Adult in US';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with conditional logic: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with conditional logic: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 10: Execute tag with string manipulation
total_tests := total_tests + 1;
test_result := hemar.render(
'{"text": "hello world"}'::jsonb,
$expected$Text: {{ exec
RETURN upper(context->>'text');
}}$expected$
);
expected := 'Text: HELLO WORLD';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with string manipulation: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with string manipulation: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 11: Execute tag with date operations
total_tests := total_tests + 1;
test_result := hemar.render(
'{"date": "2024-03-15"}'::jsonb,
$expected$Date: {{ exec
RETURN to_char((context->>'date')::date, 'Month DD, YYYY');
}}$expected$
);
expected := 'Date: March 15, 2024';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with date operations: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with date operations: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 12: Execute tag with aggregation
total_tests := total_tests + 1;
test_result := hemar.render(
'{"scores": [85, 92, 78, 95, 88]}'::jsonb,
$expected$Stats: {{ exec
RETURN (SELECT format('Avg: %s, Max: %s',
round(avg(value::float)::numeric, 1),
max(value::int))
FROM jsonb_array_elements_text(context->'scores') as value);
}}$expected$
);
expected := 'Stats: Avg: 87.6, Max: 95';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with aggregation: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with aggregation: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 13: Execute tag with error handling
total_tests := total_tests + 1;
test_result := hemar.render(
'{"value": "not_a_number"}'::jsonb,
$expected$Result: {{ exec
BEGIN
RETURN (context->>'value')::int::text;
EXCEPTION WHEN OTHERS THEN
RETURN 'Error: Invalid number';
END;
}}$expected$
);
expected := 'Result: Error: Invalid number';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with error handling: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with error handling: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 14: Execute tag with complex JSON transformation
total_tests := total_tests + 1;
test_result := hemar.render(
'{"users": [{"name": "Alice", "roles": ["admin", "user"]}, {"name": "Bob", "roles": ["user"]}]}'::jsonb,
$expected$Users: {{ exec
RETURN (SELECT string_agg(
format('%s (%s)',
user_data->>'name',
(SELECT string_agg(role, ', ')
FROM jsonb_array_elements_text(user_data->'roles') as role)
),
'; '
)
FROM jsonb_array_elements(context->'users') as user_data);
}}$expected$
);
expected := 'Users: Alice (admin, user); Bob (user)';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with complex JSON transformation: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with complex JSON transformation: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 15: Execute tag with empty/null handling
total_tests := total_tests + 1;
test_result := hemar.render(
'{"name": null, "items": []}'::jsonb,
$expected$Result: {{ exec
DECLARE
v_name TEXT;
v_count INT;
BEGIN
v_name := COALESCE(context->>'name', 'Unknown');
v_count := jsonb_array_length(context->'items');
RETURN format('Name: %s, Items: %s', v_name, v_count);
END;
}}$expected$
);
expected := 'Result: Name: Unknown, Items: 0';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Execute tag with empty/null handling: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Execute tag with empty/null handling: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Print summary
IF passed_tests = total_tests THEN
RAISE NOTICE '------------------------------------';
RAISE NOTICE 'SUMMARY: % of % render exec tests passed (100%%)',
passed_tests, total_tests;
RAISE NOTICE '------------------------------------';
ELSE
RAISE WARNING '------------------------------------';
RAISE WARNING 'SUMMARY: % of % render exec 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 % render exec tests did not pass', (total_tests - passed_tests), total_tests;
END IF;
END $$;

View File

@@ -0,0 +1,235 @@
-- Test include tag functionality
DO $$
DECLARE
result text;
total_tests integer := 0;
passed_tests integer := 0;
BEGIN
-- Test 1: Plain text inclusion
total_tests := total_tests + 1;
BEGIN
result := hemar.render(
'{
"include": {
"inner_template": {
"content": "<p>Hello World</p>"
}
}
}'::jsonb,
$hemar${{ include inner_template }}$hemar$
);
IF result = '<p>Hello World</p>' THEN
RAISE NOTICE 'Test %: Plain text inclusion works correctly', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: failed, Expected "<p>Hello World</p>", got "%"', total_tests, result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Plain text inclusion: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 2: Template with separate context
total_tests := total_tests + 1;
result := hemar.render(
'{
"include": {
"inner_template": {
"template": "Hello {{ name }}!",
"context": {
"name": "John"
}
}
}
}'::jsonb,
$hemar${{ include inner_template }}$hemar$
);
IF result = 'Hello John!' THEN
RAISE NOTICE 'Test %: Template with separate context works correctly', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: failed, Expected "Hello John!", got "%"', total_tests, result;
END IF;
-- Test 3: Template with shared context
total_tests := total_tests + 1;
result := hemar.render(
'{
"name": "John",
"include": {
"inner_template": {
"template": "Hello {{ name }}!"
}
}
}'::jsonb,
$hemar${{ include inner_template }}$hemar$
);
IF result = 'Hello John!' THEN
RAISE NOTICE 'Test % passed: Template with shared context works correctly', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test % failed: Expected "Hello John!", got "%"', total_tests, result;
END IF;
-- Test 4: Nested includes
total_tests := total_tests + 1;
result := hemar.render(
'{
"include": {
"outer_template": {
"template": "Outer: {{ include inner_template }}",
"context": {
"include": {
"inner_template": {
"template": "Inner: {{ name }}",
"context": {
"name": "John"
}
}
}
}
}
}
}'::jsonb,
$hemar${{ include outer_template }}$hemar$
);
IF result = 'Outer: Inner: John' THEN
RAISE NOTICE 'Test % passed: Nested includes work correctly', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test % failed: Expected "Outer: Inner: John", got "%"', total_tests, result;
END IF;
-- Test 5: Complex template with multiple includes
total_tests := total_tests + 1;
result := hemar.render(
'{
"include": {
"header": {
"content": "<header>Welcome</header>"
},
"content": {
"template": "Hello {{ user.name }}!",
"context": {
"user": {
"name": "John"
}
}
},
"footer": {
"template": "Copyright {{ year }}",
"context": {
"year": "2024"
}
}
}
}'::jsonb,
$hemar$Header: {{ include header }}
Content: {{ include content }}
Footer: {{ include footer }}$hemar$
);
IF result = 'Header: <header>Welcome</header>
Content: Hello John!
Footer: Copyright 2024' THEN
RAISE NOTICE 'Test % passed: Complex template with multiple includes works correctly', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test % failed: Expected , got "%"', total_tests, result;
END IF;
-- Test 6: Error handling - missing include data
total_tests := total_tests + 1;
BEGIN
result := hemar.render(
'{{ include missing_template }}',
'{}'::jsonb
);
RAISE WARNING 'Test % failed: Should have raised an error for missing include data', total_tests;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Test % passed: Error handling for missing include data works correctly', total_tests;
passed_tests := passed_tests + 1;
END;
-- Test 7: Error handling - invalid include data
total_tests := total_tests + 1;
BEGIN
result := hemar.render(
'{
"include": {
"invalid_template": "not an object"
}
}'::jsonb,
'{{ include invalid_template }}'
);
IF result = '' THEN
RAISE NOTICE 'Test % passed: Error handling for invalid 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 invalid include data', total_tests;
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
RAISE NOTICE '------------------------------------';
RAISE NOTICE 'SUMMARY: % of % template include tests passed (100%%)',
passed_tests, total_tests;
RAISE NOTICE '------------------------------------';
ELSE
RAISE WARNING '------------------------------------';
RAISE WARNING 'SUMMARY: % of % template include 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 include tests did not pass', (total_tests - passed_tests), total_tests;
END IF;
END $$;

View File

@@ -0,0 +1,190 @@
-- Test the render function with interpolation tags
CREATE EXTENSION IF NOT EXISTS hemar;
DO $$
DECLARE
total_tests INT := 0;
passed_tests INT := 0;
test_result TEXT;
expected TEXT;
passed BOOLEAN;
BEGIN
-- Test 1: Simple string interpolation
total_tests := total_tests + 1;
test_result := hemar.render(
'{"name": "John"}'::jsonb,
'Hello {{ name }}!'
);
expected := 'Hello John!';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Simple string interpolation: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Simple string interpolation: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 2: Numeric interpolation
total_tests := total_tests + 1;
test_result := hemar.render(
'{"age": 30, "price": 19.99}'::jsonb,
'Age: {{ age }}, Price: {{ price }}'
);
expected := 'Age: 30, Price: 19.99';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Numeric interpolation: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Numeric interpolation: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 3: Boolean interpolation
total_tests := total_tests + 1;
test_result := hemar.render(
'{"is_active": true, "is_deleted": false}'::jsonb,
'Status: {{ is_active }}, Deleted: {{ is_deleted }}'
);
expected := 'Status: true, Deleted: false';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Boolean interpolation: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Boolean interpolation: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 4: Null value interpolation
total_tests := total_tests + 1;
test_result := hemar.render(
'{"name": null}'::jsonb,
'Name: {{ name }}'
);
expected := 'Name: ';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Null value interpolation: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Null value interpolation: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 5: Nested object interpolation
total_tests := total_tests + 1;
test_result := hemar.render(
'{"user": {"profile": {"name": "John", "age": 30}}}'::jsonb,
'User: {{ user.profile.name }}, Age: {{ user.profile.age }}'
);
expected := 'User: John, Age: 30';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Nested object interpolation: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Nested object interpolation: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 6: Array interpolation
total_tests := total_tests + 1;
test_result := hemar.render(
'{"numbers": [1, 2, 3], "names": ["John", "Jane"]}'::jsonb,
'Numbers: {{ numbers }}, Names: {{ names }}'
);
expected := 'Numbers: [1, 2, 3], Names: ["John", "Jane"]';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Array interpolation: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Array interpolation: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 7: Array index interpolation
total_tests := total_tests + 1;
test_result := hemar.render(
'{"items": [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]}'::jsonb,
'First item: {{ items[0].name }}, Second item: {{ items[1].name }}'
);
expected := 'First item: Item 1, Second item: Item 2';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Array index interpolation: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Array index interpolation: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 8: Complex nested structure interpolation
total_tests := total_tests + 1;
test_result := hemar.render(
'{"company": {"name": "Tech Corp", "employees": [{"name": "John", "role": "Developer"}, {"name": "Jane", "role": "Manager"}]}}'::jsonb,
'Company: {{ company.name }}, First employee: {{ company.employees[0].name }} ({{ company.employees[0].role }})'
);
expected := 'Company: Tech Corp, First employee: John (Developer)';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Complex nested structure interpolation: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Complex nested structure interpolation: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 9: Multiple interpolations in text
total_tests := total_tests + 1;
test_result := hemar.render(
'{"greeting": "Hello", "name": "John", "punctuation": "!"}'::jsonb,
'{{ greeting }} {{ name }}{{ punctuation }} How are you {{ name }}?'
);
expected := 'Hello John! How are you John?';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Multiple interpolations in text: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Multiple interpolations in text: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Test 10: Invalid path handling
total_tests := total_tests + 1;
test_result := hemar.render(
'{"name": "John"}'::jsonb,
'Name: {{ name }}, Age: {{ age }}, Address: {{ address.street }}'
);
expected := 'Name: John, Age: , Address: ';
passed := test_result = expected;
passed_tests := passed_tests + (CASE WHEN passed THEN 1 ELSE 0 END);
IF passed THEN
RAISE NOTICE 'Test %: Invalid path handling: PASSED', total_tests;
ELSE
RAISE WARNING 'Test %: Invalid path handling: FAILED. Expected "%", got "%"',
total_tests, expected, test_result;
END IF;
-- Print summary
IF passed_tests = total_tests THEN
RAISE NOTICE '------------------------------------';
RAISE NOTICE 'SUMMARY: % of % interpolation render tests passed (100%%)',
passed_tests, total_tests;
RAISE NOTICE '------------------------------------';
ELSE
RAISE WARNING '------------------------------------';
RAISE WARNING 'SUMMARY: % of % interpolation render 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 % interpolation render tests did not pass', (total_tests - passed_tests), total_tests;
END IF;
END $$;

View File

@@ -0,0 +1,401 @@
-- Test section rendering
DO $$
DECLARE
test_result text;
expected text;
total_tests integer := 0;
passed_tests integer := 0;
BEGIN
-- Test 1: String iteration
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"text": "Hello"}'::jsonb,
'{{for char in text}}{{char}}{{end}}'
);
expected := 'Hello';
IF test_result = expected THEN
RAISE NOTICE 'Test %: String iteration: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: String iteration: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: String iteration: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 2: Array iteration
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"numbers": [1, 2, 3]}'::jsonb,
'{{for num in numbers}}{{num}}{{end}}'
);
expected := '123';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Array iteration: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Array iteration: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Array iteration: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 3: Object iteration
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"user": {"name": "John", "age": 30}}'::jsonb,
'{{for item in user}}{{item.key}}: {{item.value}}{{end}}'
);
expected := 'age: 30name: John';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Object iteration: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Object iteration: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Object iteration: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 4: Boolean condition (true)
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"show": true}'::jsonb,
'{{for show in show}}Content{{end}}'
);
expected := 'Content';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Boolean condition (true): PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Boolean condition (true): FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Boolean condition (true): FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 5: Boolean condition (false)
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"show": false}'::jsonb,
'{{for show in show}}Content{{end}}'
);
expected := '';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Boolean condition (false): PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Boolean condition (false): FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Boolean condition (false): FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 6: Nested sections
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"items": [{"name": "Item 1", "tags": ["tag1", "tag2"]}, {"name": "Item 2", "tags": ["tag3"]}]}'::jsonb,
'{{for item in items}}{{item.name}}: {{for tag in item.tags}}{{tag}} {{end}}{{end}}'
);
expected := 'Item 1: tag1 tag2 Item 2: tag3 ';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Nested sections: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Nested sections: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Nested sections: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 7: Section with context
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"items": ["a", "b"], "prefix": "Item: "}'::jsonb,
'{{for item in items}}{{prefix}}{{item}}{{end}}'
);
expected := 'Item: aItem: b';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Section with context: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Section with context: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Section with context: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 8: Empty array
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"items": []}'::jsonb,
'{{for item in items}}{{item}}{{end}}'
);
expected := '';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Empty array: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Empty array: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Empty array: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 9: Empty object
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"user": {}}'::jsonb,
'{{for key in user}}{{key}}{{end}}'
);
expected := '';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Empty object: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Empty object: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Empty object: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 10: Invalid collection type (number)
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"number": 42}'::jsonb,
'{{for item in number}}{{item}}{{end}}'
);
expected := '';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Invalid collection type: PASSED (error raised as expected)', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Invalid collection type: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Invalid collection type: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 11: Section whitespaces
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"array": [1, 2, 3]}'::jsonb,
'{{for item in array}}item{{end}}'
);
expected := 'itemitemitem';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Section whitespaces: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Section whitespaces: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Section whitespaces: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 12: Section whitespaces 2
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"array": [1, 2, 3]}'::jsonb,
'{{for item in array}}
item
{{end}}'
);
expected := ' item
item
item
';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Section whitespaces 2: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Section whitespaces 2: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Section whitespaces 2: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 13: Section whitespaces 3
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"array": [1, 2, 3]}'::jsonb,
'{{for item in array}} item
{{end}}'
);
expected := ' item
item
item
';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Section whitespaces 3: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Section whitespaces 3: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Section whitespaces 3: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 14: Section whitespaces 4
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"array": [1, 2, 3]}'::jsonb,
'{{for item in array}}
item {{end}}'
);
expected := ' item item item ';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Section whitespaces 4: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Section whitespaces 4: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Section whitespaces 4: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 15: Section whitespaces 5
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"array": [1, 2, 3]}'::jsonb,
'{{for item in array}}
item
{{end}}
'
);
expected := ' item
item
item
';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Section whitespaces 5: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Section whitespaces 5: FAILED. Expected "%", got "%"', total_tests, pg_temp.test_regexp_replace(expected), pg_temp.test_regexp_replace(test_result);
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Section whitespaces 5: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 16: Tabs
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"array": [1, 2, 3]}'::jsonb,
'
identation1
{{for item in array}}
identation2
{{end}}
identation1
'
);
expected := '
identation1
identation2
identation2
identation2
identation1
';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Tabs: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Tabs: FAILED. Expected "%", got "%"', total_tests, pg_temp.test_regexp_replace(expected), pg_temp.test_regexp_replace(test_result);
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Tabs: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 17: Tabs 2
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"array": [1, 2, 3]}'::jsonb,
'
identation1
{{for item in array}}
identation2
{{end}}
identation1
'
);
expected := '
identation1
identation2
identation2
identation2
identation1
';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Tabs: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Tabs: FAILED. Expected "%", got "%"', total_tests, pg_temp.test_regexp_replace(expected), pg_temp.test_regexp_replace(test_result);
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Tabs: FAILED with error: %', total_tests, SQLERRM;
END;
-- Test 18: Context
total_tests := total_tests + 1;
BEGIN
test_result := hemar.render(
'{"value": 12, "array": [1, 2, 3]}'::jsonb,
'
{{for item in array}}
{{exec RETURN context::TEXT}}
{{end}}
'
);
expected := '
';
IF test_result = expected THEN
RAISE NOTICE 'Test %: Context: PASSED', total_tests;
passed_tests := passed_tests + 1;
ELSE
RAISE WARNING 'Test %: Context: FAILED. Expected "%", got "%"', total_tests, expected, test_result;
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'Test %: Context: FAILED with error: %', total_tests, SQLERRM;
END;
-- Print summary
IF passed_tests = total_tests THEN
RAISE NOTICE '------------------------------------';
RAISE NOTICE 'SUMMARY: % of % template section tests passed (100%%)',
passed_tests, total_tests;
RAISE NOTICE '------------------------------------';
ELSE
RAISE WARNING '------------------------------------';
RAISE WARNING 'SUMMARY: % of % template section 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 section tests did not pass', (total_tests - passed_tests), total_tests;
END IF;
END $$;

File diff suppressed because it is too large Load Diff