LCOV - code coverage report
Current view: top level - templating - test_templating_api.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 93.8 % 80 75
Test Date: 2026-09-09 15:11:34 Functions: 100.0 % 4 4

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

Generated by: LCOV version 2.0-1