module dtm2 import os import time const test_root_name = 'dtm2_test' fn dtm2_test_root() string { return os.join_path(os.vtmp_dir(), '${test_root_name}_${os.getpid()}') } fn dtm2_outside_template_path() string { return os.join_path(os.vtmp_dir(), '${test_root_name}_${os.getpid()}_outside.html') } fn testsuite_begin() { root := dtm2_test_root() os.rmdir_all(root) or {} os.mkdir_all(os.join_path(root, 'partials'))! outside_path := dtm2_outside_template_path() os.rm(outside_path) or {} os.write_file(outside_path, '

@title outside

')! os.write_file(os.join_path(root, 'page.html'), '
@title @body @missing
')! os.write_file(os.join_path(root, 'invalid.css'), 'body { color: red; }')! os.write_file(os.join_path(root, 'raw.txt'), 'Raw: @body')! os.write_file(os.join_path(root, 'with_include.html'), '
@include "partials/nav"
@title
')! os.write_file(os.join_path(root, 'partials', 'nav.html'), '')! os.write_file(os.join_path(root, 'include_outside_relative.html'), '@include "../${os.base(outside_path)}"')! os.write_file(os.join_path(root, 'include_outside_absolute.html'), '@include "${outside_path}"')! os.write_file(os.join_path(root, 'reload.html'), '

@title

')! os.write_file(os.join_path(root, 'pinned.html'), '@title')! os.write_file(os.join_path(root, 'path_alias.html'), '
@title
')! os.write_file(os.join_path(root, 'prefix.html'), '

@unknown

')! os.write_file(os.join_path(root, 'recursive.html'), '@include "recursive"')! os.write_file(os.join_path(root, 'compress.html'), '
\n @title\n
')! os.write_file(os.join_path(root, 'empty.html'), '')! os.write_file(os.join_path(root, 'feed.xml'), '@title')! os.write_file(os.join_path(root, 'custom.page'), '
@title
')! os.write_file(os.join_path(root, 'custom.note'), 'Note: @body')! os.write_file(os.join_path(root, 'override.html'), '
\n @title\n
')! os.write_file(os.join_path(root, 'custom.view'), '
@title
')! os.write_file(os.join_path(root, 'custom.mail'), 'Subject: @title')! os.write_file(os.join_path(root, 'auto.fragment'), '')! os.write_file(os.join_path(root, 'auto.message'), 'Auto: @title')! os.write_file(os.join_path(root, 'bad.path'), '

@title

')! os.write_file(os.join_path(root, 'extensions.json'), '{"html":[".view"],"text":["mail"]}')! os.write_file(os.join_path(root, default_extension_config_filename), '{"html":[".fragment"],"text":[".message"]}')! os.write_file(os.join_path(root, 'invalid_extensions.json'), '{"html":["../bad"],"text":["bad/value",".message"]}')! } fn testsuite_end() { os.rmdir_all(dtm2_test_root()) or {} os.rm(dtm2_outside_template_path()) or {} } fn new_test_manager(compress_html bool) &Manager { return initialize( template_dir: dtm2_test_root() compress_html: compress_html ) } fn test_render_placeholders_escape_and_preserve_missing() { mut manager := new_test_manager(false) placeholders := { 'title': 'Hello' 'body': '' } rendered := manager.expand('page.html', placeholders: &placeholders) assert rendered == '
Hello <script>alert(1)</script> @missing
\n' assert manager.compiled_template_count() == 1 } fn test_include_and_compiled_template_registry() { mut manager := new_test_manager(true) placeholders := { 'title': 'Home' } rendered := manager.expand('with_include.html', placeholders: &placeholders) assert rendered == '
Home
' assert manager.compiled_template_count() == 1 _ = manager.expand('with_include.html', placeholders: &placeholders) assert manager.compiled_template_count() == 1 } fn test_include_html_suffix_keeps_only_allowed_tags() { mut manager := new_test_manager(false) placeholders := { 'body_#includehtml': 'safe' 'title': 'HTML' } rendered := manager.expand('page.html', placeholders: &placeholders) assert rendered.contains('safe') assert rendered.contains('<script>bad</script>') } fn test_text_mode_always_escapes_html() { mut manager := new_test_manager(false) placeholders := { 'body_#includehtml': 'text' } rendered := manager.expand('raw.txt', placeholders: &placeholders) assert rendered == 'Raw: <span>text</span>\n' } fn test_reload_modified_template_without_render_cache() { mut manager := new_test_manager(false) placeholders := { 'title': 'One' } first := manager.expand('reload.html', placeholders: &placeholders) assert first == '

One

\n' os.write_file(os.join_path(dtm2_test_root(), 'reload.html'), '@title')! second := manager.expand('reload.html', placeholders: &placeholders) assert second == 'One\n' assert manager.compiled_template_count() == 1 } fn test_reload_same_second_same_size_template_content_change() { path := os.join_path(dtm2_test_root(), 'same_size.html') fixed_time := i64(2_306_102_495) os.write_file(path, '

@title A

')! os.utime(path, fixed_time, fixed_time)! mut manager := new_test_manager(false) placeholders := { 'title': 'One' } first := manager.expand('same_size.html', placeholders: &placeholders) assert first == '

One A

\n' os.write_file(path, '@title B')! os.utime(path, fixed_time, fixed_time)! second := manager.expand('same_size.html', placeholders: &placeholders) assert second == 'One B\n' assert manager.compiled_template_count() == 1 } fn test_reload_same_second_same_size_include_content_change() { parent_path := os.join_path(dtm2_test_root(), 'same_size_include_parent.html') include_path := os.join_path(dtm2_test_root(), 'partials', 'same_size_include.html') fixed_time := i64(2_306_102_496) os.write_file(parent_path, '
@include "partials/same_size_include"
')! os.write_file(include_path, '

@title A

')! os.utime(include_path, fixed_time, fixed_time)! mut manager := new_test_manager(false) placeholders := { 'title': 'One' } first := manager.expand('same_size_include_parent.html', placeholders: &placeholders) assert first == '

One A

\n' os.write_file(include_path, '@title B')! os.utime(include_path, fixed_time, fixed_time)! second := manager.expand('same_size_include_parent.html', placeholders: &placeholders) assert second == '
One B
\n' assert manager.compiled_template_count() == 1 } fn test_reload_modified_include_without_render_cache() { mut manager := new_test_manager(true) placeholders := { 'title': 'Home' } first := manager.expand('with_include.html', placeholders: &placeholders) assert first == '
Home
' time.sleep(1100 * time.millisecond) os.write_file(os.join_path(dtm2_test_root(), 'partials', 'nav.html'), '')! second := manager.expand('with_include.html', placeholders: &placeholders) assert second == '
Home
' assert manager.compiled_template_count() == 1 } fn test_reload_disabled_keeps_cached_template_tree() { mut manager := initialize( template_dir: dtm2_test_root() compress_html: false reload_modified_templates: false ) placeholders := { 'title': 'Pinned' } first := manager.expand('pinned.html', placeholders: &placeholders) assert first == 'Pinned\n' os.write_file(os.join_path(dtm2_test_root(), 'pinned.html'), '@title')! second := manager.expand('pinned.html', placeholders: &placeholders) assert second == 'Pinned\n' assert manager.compiled_template_count() == 1 } fn test_relative_and_absolute_paths_share_compiled_template() { mut manager := new_test_manager(false) placeholders := { 'title': 'Alias' } relative := manager.expand('path_alias.html', placeholders: &placeholders) absolute := manager.expand(os.join_path(dtm2_test_root(), 'path_alias.html'), placeholders: &placeholders ) assert relative == '
Alias
\n' assert absolute == relative assert manager.compiled_template_count() == 1 } fn test_template_path_cannot_escape_template_dir() { mut manager := new_test_manager(false) placeholders := { 'title': 'Escape' } relative_escape := manager.expand('../${os.base(dtm2_outside_template_path())}', placeholders: &placeholders ) absolute_escape := manager.expand(dtm2_outside_template_path(), placeholders: &placeholders) assert relative_escape == internal_server_error assert absolute_escape == internal_server_error assert manager.compiled_template_count() == 0 } fn test_cached_template_path_revalidates_symlink_escape() { $if windows { return } path := os.join_path(dtm2_test_root(), 'symlink_swap.html') os.write_file(path, '

@title safe

')! mut manager := new_test_manager(false) placeholders := { 'title': 'Link' } first := manager.expand('symlink_swap.html', placeholders: &placeholders) assert first == '

Link safe

\n' os.rm(path)! os.symlink(dtm2_outside_template_path(), path) or { return } escaped := manager.expand('symlink_swap.html', placeholders: &placeholders) assert escaped == internal_server_error } fn test_cached_include_dependency_revalidates_symlink_escape() { $if windows { return } parent_path := os.join_path(dtm2_test_root(), 'include_symlink_swap_parent.html') include_path := os.join_path(dtm2_test_root(), 'partials', 'symlink_swap.html') os.write_file(parent_path, '
@include "partials/symlink_swap"
')! os.write_file(include_path, '

@title safe

')! mut manager := new_test_manager(false) placeholders := { 'title': 'Link' } first := manager.expand('include_symlink_swap_parent.html', placeholders: &placeholders) assert first == '

Link safe

\n' os.rm(include_path)! os.symlink(dtm2_outside_template_path(), include_path) or { return } escaped := manager.expand('include_symlink_swap_parent.html', placeholders: &placeholders) assert escaped == internal_server_error } fn test_reload_disabled_does_not_reopen_cached_symlink_swap() { $if windows { return } path := os.join_path(dtm2_test_root(), 'pinned_symlink_swap.html') os.write_file(path, '

@title safe

')! mut manager := initialize( template_dir: dtm2_test_root() compress_html: false reload_modified_templates: false ) placeholders := { 'title': 'Pinned' } first := manager.expand('pinned_symlink_swap.html', placeholders: &placeholders) assert first == '

Pinned safe

\n' os.rm(path)! os.symlink(dtm2_outside_template_path(), path) or { return } second := manager.expand('pinned_symlink_swap.html', placeholders: &placeholders) assert second == first assert manager.compiled_template_count() == 1 } fn test_include_path_cannot_escape_template_dir() { mut manager := new_test_manager(false) placeholders := { 'title': 'Escape' } relative_include := manager.expand('include_outside_relative.html', placeholders: &placeholders) absolute_include := manager.expand('include_outside_absolute.html', placeholders: &placeholders) assert relative_include == internal_server_error assert absolute_include == internal_server_error assert manager.compiled_template_count() == 0 } fn test_custom_missing_placeholder_prefix() { mut manager := new_test_manager(false) placeholders := map[string]string{} rendered := manager.expand('prefix.html', placeholders: &placeholders missing_placeholder_prefix: '?' ) assert rendered == '

?unknown

\n' } fn test_recursive_include_returns_internal_server_error() { mut manager := new_test_manager(false) placeholders := map[string]string{} rendered := manager.expand('recursive.html', placeholders: &placeholders) assert rendered == internal_server_error assert manager.compiled_template_count() == 0 } fn test_html_compression_is_deterministic() { mut manager := new_test_manager(true) placeholders := { 'title': 'Compact' } rendered := manager.expand('compress.html', placeholders: &placeholders) assert rendered == '
Compact
' } fn test_empty_template_renders_empty() { mut manager := new_test_manager(false) placeholders := map[string]string{} rendered := manager.expand('empty.html', placeholders: &placeholders) assert rendered == '' } fn test_xml_template_uses_html_rendering_mode() { mut manager := new_test_manager(false) placeholders := { 'title': '' } rendered := manager.expand('feed.xml', placeholders: &placeholders) assert rendered == '<escaped>\n' } fn test_custom_template_extension_map() { mut manager := initialize( template_dir: dtm2_test_root() template_extensions: { '.page': TemplateType.html 'note': TemplateType.text } ) html_placeholders := { 'title_#includehtml': 'custom' } text_placeholders := { 'body_#includehtml': 'custom' } html := manager.expand('custom.page', placeholders: &html_placeholders) text := manager.expand('custom.note', placeholders: &text_placeholders) assert html.contains('custom<script>blocked</script>') assert text == 'Note: <span>custom</span>\n' } fn test_custom_template_extension_map_can_override_builtin_mapping() { mut manager := initialize( template_dir: dtm2_test_root() compress_html: true template_extensions: { '.html': TemplateType.text } ) placeholders := { 'title': 'Text' } rendered := manager.expand('override.html', placeholders: &placeholders) assert rendered == '
\n Text\n
\n' } fn test_json_extension_config_file() { mut manager := initialize( template_dir: dtm2_test_root() extension_config_file: os.join_path(dtm2_test_root(), 'extensions.json') ) placeholders := { 'title': '' } html := manager.expand('custom.view', placeholders: &placeholders) text := manager.expand('custom.mail', placeholders: &placeholders) assert html == '
<Config>
' assert text == 'Subject: <Config>\n' } fn test_default_json_extension_config_file_is_loaded_from_template_dir() { mut manager := initialize( template_dir: dtm2_test_root() compress_html: false ) placeholders := { 'title': '' } html := manager.expand('auto.fragment', placeholders: &placeholders) text := manager.expand('auto.message', placeholders: &placeholders) assert html == '\n' assert text == 'Auto: <Auto>\n' } fn test_invalid_json_extension_entries_are_ignored() { mut manager := initialize( template_dir: dtm2_test_root() extension_config_file: os.join_path(dtm2_test_root(), 'invalid_extensions.json') compress_html: false ) placeholders := { 'title': '' } ignored := manager.expand('bad.path', placeholders: &placeholders) loaded := manager.expand('auto.message', placeholders: &placeholders) assert ignored == internal_server_error assert loaded == 'Auto: <Safe>\n' } fn test_missing_template_returns_internal_server_error() { mut manager := new_test_manager(false) placeholders := map[string]string{} rendered := manager.expand('missing.html', placeholders: &placeholders) assert rendered == internal_server_error assert manager.compiled_template_count() == 0 } fn test_invalid_template_extension_returns_internal_server_error() { mut manager := new_test_manager(false) placeholders := map[string]string{} rendered := manager.expand('invalid.css', placeholders: &placeholders) assert rendered == internal_server_error assert manager.compiled_template_count() == 0 }