Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2026 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU General Public License as
7 : published by the Free Software Foundation; either version 3, or
8 : (at your option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful, but
11 : WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : GNU General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public
16 : License along with TALER; see the file COPYING. If not, see
17 : <http://www.gnu.org/licenses/>
18 : */
19 :
20 : /**
21 : * @file test_templating_api.c
22 : * @brief tests for the templating engine behind the TALER_TEMPLATING_*
23 : * API.
24 : *
25 : * TALER_TEMPLATING_fill() and TALER_TEMPLATING_fill2() are thin
26 : * passthroughs to mustach_jansson_mem() with Mustach_With_AllExtensions;
27 : * this test exercises exactly that code path (the bundled mustach
28 : * copylib) without dragging in the full libtaler* link stack. It pins
29 : * down the stable behaviour of the templating engine (interpolation,
30 : * escaping, sections, inverted sections, dotted access, iteration,
31 : * comments, scalar rendering, length handling, ...) so that updates of
32 : * the bundled mustach copylib do not silently introduce regressions.
33 : *
34 : * @author Christian Grothoff
35 : */
36 : #include "mustach-jansson.h"
37 : #include <gnunet/gnunet_util_lib.h>
38 : #include <string.h>
39 :
40 :
41 : /**
42 : * Global test result: 0 on success, 1 on failure.
43 : */
44 : static int global_ret;
45 :
46 :
47 : /**
48 : * Render @a tmpl with @a root, mirroring TALER_TEMPLATING_fill2()
49 : * (which calls mustach_jansson_mem with Mustach_With_AllExtensions),
50 : * and check that the result equals @a expected.
51 : *
52 : * @param name human readable name of the subtest
53 : * @param tmpl the mustach template
54 : * @param tmpl_len length of @a tmpl, or 0 to use strlen()
55 : * (matching the TALER_TEMPLATING_fill convention)
56 : * @param root JSON context (ownership stays with caller)
57 : * @param expected expected rendering
58 : */
59 : static void
60 27 : check_len (const char *name,
61 : const char *tmpl,
62 : size_t tmpl_len,
63 : json_t *root,
64 : const char *expected)
65 : {
66 : char *result;
67 : size_t result_size;
68 : int eno;
69 :
70 27 : result = NULL;
71 27 : result_size = 0;
72 27 : eno = mustach_jansson_mem (tmpl,
73 : tmpl_len,
74 : root,
75 : Mustach_With_AllExtensions,
76 : &result,
77 : &result_size);
78 27 : if (0 != eno)
79 : {
80 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
81 : "Subtest `%s' FAILED: mustach returned error %d\n",
82 : name,
83 : eno);
84 0 : global_ret = 1;
85 0 : return;
86 : }
87 27 : if ( (result_size != strlen (expected)) ||
88 27 : (0 != memcmp (result,
89 : expected,
90 : result_size)) )
91 : {
92 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
93 : "Subtest `%s' FAILED: expected `%s', got `%.*s'\n",
94 : name,
95 : expected,
96 : (int) result_size,
97 : (NULL == result) ? "" : result);
98 0 : global_ret = 1;
99 : }
100 27 : GNUNET_free (result);
101 : }
102 :
103 :
104 : /**
105 : * Render 0-terminated @a tmpl with @a root (mirrors
106 : * TALER_TEMPLATING_fill()) and check against @a expected.
107 : */
108 : static void
109 26 : check (const char *name,
110 : const char *tmpl,
111 : json_t *root,
112 : const char *expected)
113 : {
114 26 : check_len (name,
115 : tmpl,
116 : 0,
117 : root,
118 : expected);
119 26 : }
120 :
121 :
122 : /**
123 : * Test that the length argument is honored: trailing data beyond the
124 : * given length must be ignored (this is what TALER_TEMPLATING_fill2
125 : * relies on for non-0-terminated buffers).
126 : */
127 : static void
128 1 : check_fill2_length (void)
129 : {
130 : json_t *root;
131 :
132 1 : root = json_pack ("{s:s}",
133 : "x", "VALUE");
134 1 : GNUNET_assert (NULL != root);
135 : /* Only the first strlen("abc{{x}}def") bytes are part of the
136 : template; the trailing garbage must not be rendered. */
137 1 : check_len ("fill2-length",
138 : "abc{{x}}def_THIS_MUST_BE_IGNORED",
139 : strlen ("abc{{x}}def"),
140 : root,
141 : "abcVALUEdef");
142 1 : json_decref (root);
143 1 : }
144 :
145 :
146 : int
147 1 : main (int argc,
148 : char *const *argv)
149 : {
150 : (void) argc;
151 : (void) argv;
152 1 : GNUNET_log_setup ("test-templating-api",
153 : "WARNING",
154 : NULL);
155 :
156 : /* --- plain text, no tags --- */
157 : {
158 1 : json_t *root = json_object ();
159 :
160 1 : check ("literal",
161 : "hello world",
162 : root,
163 : "hello world");
164 1 : check ("empty-template",
165 : "",
166 : root,
167 : "");
168 1 : json_decref (root);
169 : }
170 :
171 : /* --- simple interpolation --- */
172 : {
173 1 : json_t *root = json_pack ("{s:s}",
174 : "a", "X");
175 :
176 1 : check ("interpolate",
177 : "a={{a}}",
178 : root,
179 : "a=X");
180 1 : check ("interpolate-spaces",
181 : "a={{ a }}",
182 : root,
183 : "a=X");
184 1 : check ("interpolate-missing",
185 : "a=[{{nope}}]",
186 : root,
187 : "a=[]");
188 1 : json_decref (root);
189 : }
190 :
191 : /* --- HTML escaping --- */
192 : {
193 1 : json_t *root = json_pack ("{s:s}",
194 : "h", "<b>&\"x");
195 :
196 1 : check ("escape-default",
197 : "{{h}}",
198 : root,
199 : "<b>&"x");
200 1 : check ("escape-triple",
201 : "{{{h}}}",
202 : root,
203 : "<b>&\"x");
204 1 : check ("escape-ampersand",
205 : "{{&h}}",
206 : root,
207 : "<b>&\"x");
208 1 : json_decref (root);
209 : }
210 :
211 : /* --- dotted / nested access --- */
212 : {
213 1 : json_t *root = json_pack ("{s:{s:s}}",
214 : "o",
215 : "x", "deep");
216 :
217 1 : check ("dotted",
218 : "{{o.x}}",
219 : root,
220 : "deep");
221 1 : check ("section-object",
222 : "{{#o}}{{x}}{{/o}}",
223 : root,
224 : "deep");
225 1 : json_decref (root);
226 : }
227 :
228 : /* --- sections over arrays --- */
229 : {
230 1 : json_t *root = json_pack ("{s:[s,s,s]}",
231 : "arr",
232 : "a", "b", "c");
233 :
234 1 : check ("section-array-scalars",
235 : "{{#arr}}[{{.}}]{{/arr}}",
236 : root,
237 : "[a][b][c]");
238 1 : json_decref (root);
239 : }
240 : {
241 1 : json_t *root = json_pack ("{s:[{s:s},{s:s}]}",
242 : "people",
243 : "name", "Alice",
244 : "name", "Bob");
245 :
246 1 : check ("section-array-objects",
247 : "{{#people}}{{name}};{{/people}}",
248 : root,
249 : "Alice;Bob;");
250 1 : json_decref (root);
251 : }
252 :
253 : /* --- empty array: section skipped, inverted shown --- */
254 : {
255 1 : json_t *root = json_pack ("{s:[]}",
256 : "empty");
257 :
258 1 : check ("section-empty-array",
259 : "{{#empty}}X{{/empty}}",
260 : root,
261 : "");
262 1 : check ("inverted-empty-array",
263 : "{{^empty}}none{{/empty}}",
264 : root,
265 : "none");
266 1 : json_decref (root);
267 : }
268 :
269 : /* --- inverted sections on missing / present keys --- */
270 : {
271 1 : json_t *root = json_pack ("{s:s}",
272 : "present", "v");
273 :
274 1 : check ("inverted-missing",
275 : "{{^missing}}fallback{{/missing}}",
276 : root,
277 : "fallback");
278 1 : check ("inverted-present",
279 : "{{^present}}fallback{{/present}}",
280 : root,
281 : "");
282 1 : json_decref (root);
283 : }
284 :
285 : /* --- boolean truthiness --- */
286 : {
287 1 : json_t *root = json_pack ("{s:b, s:b}",
288 : "t", 1,
289 : "f", 0);
290 :
291 1 : check ("section-true",
292 : "{{#t}}yes{{/t}}",
293 : root,
294 : "yes");
295 1 : check ("section-false",
296 : "{{#f}}yes{{/f}}",
297 : root,
298 : "");
299 1 : check ("inverted-false",
300 : "{{^f}}no{{/f}}",
301 : root,
302 : "no");
303 1 : json_decref (root);
304 : }
305 :
306 : /* --- null renders as empty, does not enter section --- */
307 : {
308 1 : json_t *root = json_pack ("{s:n}",
309 : "z");
310 :
311 1 : check ("null-interpolate",
312 : "[{{z}}]",
313 : root,
314 : "[]");
315 1 : check ("null-section",
316 : "{{#z}}X{{/z}}",
317 : root,
318 : "");
319 1 : check ("null-inverted",
320 : "{{^z}}empty{{/z}}",
321 : root,
322 : "empty");
323 1 : json_decref (root);
324 : }
325 :
326 : /* --- scalar rendering: integer and real --- */
327 : {
328 1 : json_t *root = json_pack ("{s:I, s:f}",
329 : "n", (json_int_t) 42,
330 : "r", 1.5);
331 :
332 1 : check ("integer",
333 : "{{n}}",
334 : root,
335 : "42");
336 1 : check ("real",
337 : "{{r}}",
338 : root,
339 : "1.5");
340 1 : json_decref (root);
341 : }
342 :
343 : /* --- comments are stripped --- */
344 : {
345 1 : json_t *root = json_object ();
346 :
347 1 : check ("comment",
348 : "x{{! this is a comment }}y",
349 : root,
350 : "xy");
351 1 : json_decref (root);
352 : }
353 :
354 : /* --- nested sections --- */
355 : {
356 1 : json_t *root = json_pack ("{s:{s:b, s:s}}",
357 : "outer",
358 : "inner", 1,
359 : "val", "Z");
360 :
361 1 : check ("nested-sections",
362 : "{{#outer}}{{#inner}}{{val}}{{/inner}}{{/outer}}",
363 : root,
364 : "Z");
365 1 : json_decref (root);
366 : }
367 :
368 : /* --- length-respecting rendering (TALER_TEMPLATING_fill2) --- */
369 1 : check_fill2_length ();
370 :
371 1 : if (0 == global_ret)
372 1 : fprintf (stderr,
373 : "All templating API tests passed.\n");
374 1 : return global_ret;
375 : }
376 :
377 :
378 : /* end of test_templating_api.c */
|