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'),
'@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'), '')!
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'), '')!
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 == '\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 == ''
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
}