test_templating_api.c (9463B)
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 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 result = NULL; 71 result_size = 0; 72 eno = mustach_jansson_mem (tmpl, 73 tmpl_len, 74 root, 75 Mustach_With_AllExtensions, 76 &result, 77 &result_size); 78 if (0 != eno) 79 { 80 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 81 "Subtest `%s' FAILED: mustach returned error %d\n", 82 name, 83 eno); 84 global_ret = 1; 85 return; 86 } 87 if ( (result_size != strlen (expected)) || 88 (0 != memcmp (result, 89 expected, 90 result_size)) ) 91 { 92 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 global_ret = 1; 99 } 100 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 check (const char *name, 110 const char *tmpl, 111 json_t *root, 112 const char *expected) 113 { 114 check_len (name, 115 tmpl, 116 0, 117 root, 118 expected); 119 } 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 check_fill2_length (void) 129 { 130 json_t *root; 131 132 root = json_pack ("{s:s}", 133 "x", "VALUE"); 134 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 check_len ("fill2-length", 138 "abc{{x}}def_THIS_MUST_BE_IGNORED", 139 strlen ("abc{{x}}def"), 140 root, 141 "abcVALUEdef"); 142 json_decref (root); 143 } 144 145 146 int 147 main (int argc, 148 char *const *argv) 149 { 150 (void) argc; 151 (void) argv; 152 GNUNET_log_setup ("test-templating-api", 153 "WARNING", 154 NULL); 155 156 /* --- plain text, no tags --- */ 157 { 158 json_t *root = json_object (); 159 160 check ("literal", 161 "hello world", 162 root, 163 "hello world"); 164 check ("empty-template", 165 "", 166 root, 167 ""); 168 json_decref (root); 169 } 170 171 /* --- simple interpolation --- */ 172 { 173 json_t *root = json_pack ("{s:s}", 174 "a", "X"); 175 176 check ("interpolate", 177 "a={{a}}", 178 root, 179 "a=X"); 180 check ("interpolate-spaces", 181 "a={{ a }}", 182 root, 183 "a=X"); 184 check ("interpolate-missing", 185 "a=[{{nope}}]", 186 root, 187 "a=[]"); 188 json_decref (root); 189 } 190 191 /* --- HTML escaping --- */ 192 { 193 json_t *root = json_pack ("{s:s}", 194 "h", "<b>&\"x"); 195 196 check ("escape-default", 197 "{{h}}", 198 root, 199 "<b>&"x"); 200 check ("escape-triple", 201 "{{{h}}}", 202 root, 203 "<b>&\"x"); 204 check ("escape-ampersand", 205 "{{&h}}", 206 root, 207 "<b>&\"x"); 208 json_decref (root); 209 } 210 211 /* --- dotted / nested access --- */ 212 { 213 json_t *root = json_pack ("{s:{s:s}}", 214 "o", 215 "x", "deep"); 216 217 check ("dotted", 218 "{{o.x}}", 219 root, 220 "deep"); 221 check ("section-object", 222 "{{#o}}{{x}}{{/o}}", 223 root, 224 "deep"); 225 json_decref (root); 226 } 227 228 /* --- sections over arrays --- */ 229 { 230 json_t *root = json_pack ("{s:[s,s,s]}", 231 "arr", 232 "a", "b", "c"); 233 234 check ("section-array-scalars", 235 "{{#arr}}[{{.}}]{{/arr}}", 236 root, 237 "[a][b][c]"); 238 json_decref (root); 239 } 240 { 241 json_t *root = json_pack ("{s:[{s:s},{s:s}]}", 242 "people", 243 "name", "Alice", 244 "name", "Bob"); 245 246 check ("section-array-objects", 247 "{{#people}}{{name}};{{/people}}", 248 root, 249 "Alice;Bob;"); 250 json_decref (root); 251 } 252 253 /* --- empty array: section skipped, inverted shown --- */ 254 { 255 json_t *root = json_pack ("{s:[]}", 256 "empty"); 257 258 check ("section-empty-array", 259 "{{#empty}}X{{/empty}}", 260 root, 261 ""); 262 check ("inverted-empty-array", 263 "{{^empty}}none{{/empty}}", 264 root, 265 "none"); 266 json_decref (root); 267 } 268 269 /* --- inverted sections on missing / present keys --- */ 270 { 271 json_t *root = json_pack ("{s:s}", 272 "present", "v"); 273 274 check ("inverted-missing", 275 "{{^missing}}fallback{{/missing}}", 276 root, 277 "fallback"); 278 check ("inverted-present", 279 "{{^present}}fallback{{/present}}", 280 root, 281 ""); 282 json_decref (root); 283 } 284 285 /* --- boolean truthiness --- */ 286 { 287 json_t *root = json_pack ("{s:b, s:b}", 288 "t", 1, 289 "f", 0); 290 291 check ("section-true", 292 "{{#t}}yes{{/t}}", 293 root, 294 "yes"); 295 check ("section-false", 296 "{{#f}}yes{{/f}}", 297 root, 298 ""); 299 check ("inverted-false", 300 "{{^f}}no{{/f}}", 301 root, 302 "no"); 303 json_decref (root); 304 } 305 306 /* --- null renders as empty, does not enter section --- */ 307 { 308 json_t *root = json_pack ("{s:n}", 309 "z"); 310 311 check ("null-interpolate", 312 "[{{z}}]", 313 root, 314 "[]"); 315 check ("null-section", 316 "{{#z}}X{{/z}}", 317 root, 318 ""); 319 check ("null-inverted", 320 "{{^z}}empty{{/z}}", 321 root, 322 "empty"); 323 json_decref (root); 324 } 325 326 /* --- scalar rendering: integer and real --- */ 327 { 328 json_t *root = json_pack ("{s:I, s:f}", 329 "n", (json_int_t) 42, 330 "r", 1.5); 331 332 check ("integer", 333 "{{n}}", 334 root, 335 "42"); 336 check ("real", 337 "{{r}}", 338 root, 339 "1.5"); 340 json_decref (root); 341 } 342 343 /* --- comments are stripped --- */ 344 { 345 json_t *root = json_object (); 346 347 check ("comment", 348 "x{{! this is a comment }}y", 349 root, 350 "xy"); 351 json_decref (root); 352 } 353 354 /* --- nested sections --- */ 355 { 356 json_t *root = json_pack ("{s:{s:b, s:s}}", 357 "outer", 358 "inner", 1, 359 "val", "Z"); 360 361 check ("nested-sections", 362 "{{#outer}}{{#inner}}{{val}}{{/inner}}{{/outer}}", 363 root, 364 "Z"); 365 json_decref (root); 366 } 367 368 /* --- length-respecting rendering (TALER_TEMPLATING_fill2) --- */ 369 check_fill2_length (); 370 371 if (0 == global_ret) 372 fprintf (stderr, 373 "All templating API tests passed.\n"); 374 return global_ret; 375 } 376 377 378 /* end of test_templating_api.c */