exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

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            "&lt;b&gt;&amp;&quot;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 */