v2 / vlib / x / templating / dtm / dynamic_template_manager_behavior_test.v
333 lines · 270 sloc · 11.14 KB · 3eff1b83cf719199d0ff6f63524da63c9294ffd5
Raw
1// vtest retry: 3
2module dtm
3
4import os
5import time
6
7const behavior_temp_dtm_dir = 'dynamic_template_manager_behavior_test'
8const behavior_temp_cache_dir = 'vcache_dtm'
9const behavior_temp_templates_dir = 'templates'
10const behavior_vtmp_dir = os.vtmp_dir()
11
12fn behavior_test_root_dir() string {
13 return os.join_path(behavior_vtmp_dir, '${behavior_temp_dtm_dir}_${os.getpid()}')
14}
15
16fn testsuite_begin() {
17 temp_folder := behavior_test_root_dir()
18 os.rmdir_all(temp_folder) or {}
19 os.mkdir_all(os.join_path(temp_folder, behavior_temp_cache_dir))!
20 os.mkdir_all(os.join_path(temp_folder, behavior_temp_templates_dir))!
21
22 templates_path := os.join_path(temp_folder, behavior_temp_templates_dir)
23 os.write_file(os.join_path(templates_path, 'page.html'), [
24 '<!doctype html>',
25 '<title>@title</title>',
26 '<p>@body</p>',
27 '<p>@count</p>',
28 ].join('\n'))!
29 os.write_file(os.join_path(templates_path, 'page.txt'), [
30 'Title: @title',
31 'Body: @body',
32 ].join('\n'))!
33 os.write_file(os.join_path(templates_path, 'layout.html'), [
34 '<header>Start</header>',
35 "@include 'partial'",
36 '<footer>End</footer>',
37 ].join('\n'))!
38 os.write_file(os.join_path(templates_path, 'partial.html'), '<section>@title</section>')!
39 os.write_file(os.join_path(templates_path, 'cache.html'), '<main>@title</main>')!
40 os.write_file(os.join_path(templates_path, 'cache_file_update.html'), '<main>@title</main>')!
41 os.write_file(os.join_path(templates_path, 'no_store.html'), '<main>@title</main>')!
42 os.write_file(os.join_path(templates_path, 'forever.html'), '<main>@title</main>')!
43 os.write_file(os.join_path(templates_path, 'disk_cache.html'), '<main>@title</main>')!
44 os.write_file(os.join_path(templates_path, 'include_html.html'), '<article>@content</article>')!
45 os.write_file(os.join_path(templates_path, 'include_text.txt'), 'Text: @content')!
46 os.write_file(os.join_path(templates_path, 'invalid.css'), 'body { color: red; }')!
47}
48
49fn testsuite_end() {
50 os.rmdir_all(behavior_test_root_dir()) or {}
51}
52
53fn new_behavior_dtm(active_cache_server bool, compress_html bool, max_size_data_in_mem int) &DynamicTemplateManager {
54 temp_folder := behavior_test_root_dir()
55 return initialize(
56 active_cache_server: active_cache_server
57 compress_html: compress_html
58 max_size_data_in_mem: max_size_data_in_mem
59 test_cache_dir: os.join_path(temp_folder, behavior_temp_cache_dir)
60 test_template_dir: os.join_path(temp_folder, behavior_temp_templates_dir)
61 )
62}
63
64fn behavior_template_path(file_name string) string {
65 return os.join_path(behavior_test_root_dir(), behavior_temp_templates_dir, file_name)
66}
67
68fn rendered_cache_count(dtmi &DynamicTemplateManager) int {
69 rlock dtmi.template_caches {
70 return dtmi.template_caches.len
71 }
72}
73
74fn test_expand_escapes_html_placeholders_by_default() {
75 mut dtmi := new_behavior_dtm(false, false, max_size_data_in_memory)
76 placeholders := {
77 'title': DtmMultiTypeMap('Hello <V>')
78 'body': DtmMultiTypeMap('<script>alert(1)</script>')
79 'count': DtmMultiTypeMap(7)
80 }
81
82 rendered := dtmi.expand(behavior_template_path('page.html'), placeholders: &placeholders)
83
84 assert rendered.contains('<title>Hello <V></title>')
85 assert rendered.contains('<p><script>alert(1)</script></p>')
86 assert rendered.contains('<p>7</p>')
87}
88
89fn test_expand_does_not_double_escape_placeholder_values_containing_dollar() {
90 mut dtmi := new_behavior_dtm(false, false, max_size_data_in_memory)
91 placeholders := {
92 'title': DtmMultiTypeMap('Cost $5 <V>')
93 'body': DtmMultiTypeMap('Body')
94 'count': DtmMultiTypeMap(1)
95 }
96
97 rendered := dtmi.expand(behavior_template_path('page.html'), placeholders: &placeholders)
98
99 assert rendered.contains('<title>Cost $5 <V></title>')
100}
101
102fn test_expand_escapes_html_entities_and_quotes() {
103 mut dtmi := new_behavior_dtm(false, false, max_size_data_in_memory)
104 placeholders := {
105 'title': DtmMultiTypeMap('A & B "quoted" \'single\'')
106 'body': DtmMultiTypeMap('Body')
107 'count': DtmMultiTypeMap(1)
108 }
109
110 rendered := dtmi.expand(behavior_template_path('page.html'), placeholders: &placeholders)
111
112 assert rendered.contains('<title>A & B "quoted" 'single'</title>')
113}
114
115fn test_expand_escapes_text_templates() {
116 mut dtmi := new_behavior_dtm(false, false, max_size_data_in_memory)
117 placeholders := {
118 'title': DtmMultiTypeMap('Plain <Title>')
119 'body': DtmMultiTypeMap('<strong>raw text</strong>')
120 }
121
122 rendered := dtmi.expand(behavior_template_path('page.txt'), placeholders: &placeholders)
123
124 assert rendered.contains('Title: Plain <Title>')
125 assert rendered.contains('Body: <strong>raw text</strong>')
126}
127
128fn test_expand_compresses_html_without_regex_failure() {
129 mut dtmi := new_behavior_dtm(false, true, max_size_data_in_memory)
130 placeholders := {
131 'title': DtmMultiTypeMap('Compressed')
132 'body': DtmMultiTypeMap('Body')
133 'count': DtmMultiTypeMap(1)
134 }
135
136 rendered := dtmi.expand(behavior_template_path('page.html'), placeholders: &placeholders)
137
138 assert rendered != internat_server_error
139 assert !rendered.contains('\n')
140 assert rendered.contains('<!doctype html><title>Compressed</title><p>Body</p><p>1</p>')
141}
142
143fn test_expand_with_empty_placeholders_keeps_template_renderable() {
144 mut dtmi := new_behavior_dtm(false, false, max_size_data_in_memory)
145 placeholders := map[string]DtmMultiTypeMap{}
146
147 rendered := dtmi.expand(behavior_template_path('page.html'), placeholders: &placeholders)
148
149 assert rendered.contains('<title>$title</title>')
150 assert rendered.contains('<p>$body</p>')
151 assert rendered.contains('<p>$count</p>')
152}
153
154fn test_expand_resolves_relative_includes() {
155 mut dtmi := new_behavior_dtm(false, false, max_size_data_in_memory)
156 placeholders := {
157 'title': DtmMultiTypeMap('Included <Title>')
158 }
159
160 rendered := dtmi.expand(behavior_template_path('layout.html'), placeholders: &placeholders)
161
162 assert rendered.contains('<header>Start</header>')
163 assert rendered.contains('<section>Included <Title></section>')
164 assert rendered.contains('<footer>End</footer>')
165}
166
167fn test_includehtml_preserves_allowed_tags_and_escapes_unlisted_tags() {
168 mut dtmi := new_behavior_dtm(false, false, max_size_data_in_memory)
169 placeholders := {
170 'content_#includehtml': DtmMultiTypeMap('<span>allowed</span><script>blocked()</script>')
171 }
172
173 rendered :=
174 dtmi.expand(behavior_template_path('include_html.html'), placeholders: &placeholders)
175
176 assert rendered.contains('<article><span>allowed</span><script>blocked()</script></article>')
177}
178
179fn test_includehtml_is_still_escaped_in_text_templates() {
180 mut dtmi := new_behavior_dtm(false, false, max_size_data_in_memory)
181 placeholders := {
182 'content_#includehtml': DtmMultiTypeMap('<span>text</span>')
183 }
184
185 rendered := dtmi.expand(behavior_template_path('include_text.txt'), placeholders: &placeholders)
186
187 assert rendered.contains('Text: <span>text</span>')
188}
189
190fn test_expand_returns_internal_error_for_missing_template() {
191 mut dtmi := new_behavior_dtm(false, false, max_size_data_in_memory)
192 placeholders := map[string]DtmMultiTypeMap{}
193
194 rendered := dtmi.expand(behavior_template_path('missing.html'), placeholders: &placeholders)
195
196 assert rendered == internat_server_error
197}
198
199fn test_expand_returns_internal_error_for_invalid_template_extension() {
200 mut dtmi := new_behavior_dtm(false, false, max_size_data_in_memory)
201 placeholders := map[string]DtmMultiTypeMap{}
202
203 rendered := dtmi.expand(behavior_template_path('invalid.css'), placeholders: &placeholders)
204
205 assert rendered == internat_server_error
206}
207
208fn test_expand_without_cache_renders_fresh_placeholder_content() {
209 mut dtmi := new_behavior_dtm(false, false, max_size_data_in_memory)
210 first_placeholders := {
211 'title': DtmMultiTypeMap('first')
212 }
213 second_placeholders := {
214 'title': DtmMultiTypeMap('second')
215 }
216
217 first := dtmi.expand(behavior_template_path('cache.html'), placeholders: &first_placeholders)
218 second := dtmi.expand(behavior_template_path('cache.html'), placeholders: &second_placeholders)
219
220 assert first.contains('<main>first</main>')
221 assert second.contains('<main>second</main>')
222}
223
224fn test_expand_with_cache_updates_when_placeholder_content_changes() {
225 mut dtmi := new_behavior_dtm(true, false, max_size_data_in_memory)
226 defer {
227 dtmi.disable_cache()
228 }
229 first_placeholders := {
230 'title': DtmMultiTypeMap('cached first')
231 }
232 second_placeholders := {
233 'title': DtmMultiTypeMap('cached second')
234 }
235
236 first := dtmi.expand(behavior_template_path('cache.html'), placeholders: &first_placeholders)
237 second := dtmi.expand(behavior_template_path('cache.html'), placeholders: &first_placeholders)
238 third := dtmi.expand(behavior_template_path('cache.html'), placeholders: &second_placeholders)
239
240 assert first.contains('<main>cached first</main>')
241 assert second == first
242 assert third.contains('<main>cached second</main>')
243}
244
245fn test_expand_with_legacy_cache_enabled_uses_new_parsed_template_engine() {
246 mut dtmi := new_behavior_dtm(true, false, max_size_data_in_memory)
247 defer {
248 dtmi.disable_cache()
249 }
250 placeholders := {
251 'title': DtmMultiTypeMap('stable cached value')
252 }
253
254 first := dtmi.expand(behavior_template_path('cache.html'), placeholders: &placeholders)
255 second := dtmi.expand(behavior_template_path('cache.html'), placeholders: &placeholders)
256
257 assert first == second
258 assert rendered_cache_count(dtmi) == 0
259 assert dtmi.render_engine.compiled_template_count() == 1
260}
261
262fn test_expand_with_cache_updates_when_template_file_changes() {
263 mut dtmi := new_behavior_dtm(true, false, max_size_data_in_memory)
264 defer {
265 dtmi.disable_cache()
266 }
267 template_path := behavior_template_path('cache_file_update.html')
268 placeholders := {
269 'title': DtmMultiTypeMap('file driven')
270 }
271
272 first := dtmi.expand(template_path, placeholders: &placeholders)
273 time.sleep(1100 * time.millisecond)
274 os.write_file(template_path, '<section>@title</section>')!
275 second := dtmi.expand(template_path, placeholders: &placeholders)
276
277 assert first.contains('<main>file driven</main>')
278 assert second.contains('<section>file driven</section>')
279}
280
281fn test_cache_delay_minus_one_skips_rendered_cache_storage() {
282 mut dtmi := new_behavior_dtm(true, false, max_size_data_in_memory)
283 defer {
284 dtmi.disable_cache()
285 }
286 placeholders := {
287 'title': DtmMultiTypeMap('no store')
288 }
289
290 rendered := dtmi.expand(behavior_template_path('no_store.html'),
291 placeholders: &placeholders
292 cache_delay_expiration: -1
293 )
294
295 assert rendered.contains('<main>no store</main>')
296 assert rendered_cache_count(dtmi) == 0
297}
298
299fn test_cache_delay_zero_is_accepted_without_rendered_cache_storage() {
300 mut dtmi := new_behavior_dtm(true, false, max_size_data_in_memory)
301 defer {
302 dtmi.disable_cache()
303 }
304 placeholders := {
305 'title': DtmMultiTypeMap('forever')
306 }
307
308 rendered := dtmi.expand(behavior_template_path('forever.html'),
309 placeholders: &placeholders
310 cache_delay_expiration: 0
311 )
312
313 assert rendered.contains('<main>forever</main>')
314 assert rendered_cache_count(dtmi) == 0
315}
316
317fn test_memory_limit_zero_does_not_create_rendered_disk_cache() {
318 mut dtmi := new_behavior_dtm(true, false, 0)
319 defer {
320 dtmi.disable_cache()
321 }
322 placeholders := {
323 'title': DtmMultiTypeMap('disk mode')
324 }
325
326 rendered := dtmi.expand(behavior_template_path('disk_cache.html'), placeholders: &placeholders)
327
328 assert rendered.contains('<main>disk mode</main>')
329 assert rendered_cache_count(dtmi) == 0
330 for file_name in os.ls(dtmi.template_cache_folder)! {
331 assert !file_name.ends_with('.cache')
332 }
333}
334