/*
  This file is part of TALER
  Copyright (C) 2026 Taler Systems SA

  TALER is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as
  published by the Free Software Foundation; either version 3, or
  (at your option) any later version.

  TALER is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public
  License along with TALER; see the file COPYING.  If not, see
  <http://www.gnu.org/licenses/>
*/

/**
 * @file test_templating_api.c
 * @brief tests for the templating engine behind the TALER_TEMPLATING_*
 *        API.
 *
 * TALER_TEMPLATING_fill() and TALER_TEMPLATING_fill2() are thin
 * passthroughs to mustach_jansson_mem() with Mustach_With_AllExtensions;
 * this test exercises exactly that code path (the bundled mustach
 * copylib) without dragging in the full libtaler* link stack.  It pins
 * down the stable behaviour of the templating engine (interpolation,
 * escaping, sections, inverted sections, dotted access, iteration,
 * comments, scalar rendering, length handling, ...) so that updates of
 * the bundled mustach copylib do not silently introduce regressions.
 *
 * @author Christian Grothoff
 */
#include "mustach-jansson.h"
#include <gnunet/gnunet_util_lib.h>
#include <string.h>


/**
 * Global test result: 0 on success, 1 on failure.
 */
static int global_ret;


/**
 * Render @a tmpl with @a root, mirroring TALER_TEMPLATING_fill2()
 * (which calls mustach_jansson_mem with Mustach_With_AllExtensions),
 * and check that the result equals @a expected.
 *
 * @param name human readable name of the subtest
 * @param tmpl the mustach template
 * @param tmpl_len length of @a tmpl, or 0 to use strlen()
 *        (matching the TALER_TEMPLATING_fill convention)
 * @param root JSON context (ownership stays with caller)
 * @param expected expected rendering
 */
static void
check_len (const char *name,
           const char *tmpl,
           size_t tmpl_len,
           json_t *root,
           const char *expected)
{
  char *result;
  size_t result_size;
  int eno;

  result = NULL;
  result_size = 0;
  eno = mustach_jansson_mem (tmpl,
                             tmpl_len,
                             root,
                             Mustach_With_AllExtensions,
                             &result,
                             &result_size);
  if (0 != eno)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Subtest `%s' FAILED: mustach returned error %d\n",
                name,
                eno);
    global_ret = 1;
    return;
  }
  if ( (result_size != strlen (expected)) ||
       (0 != memcmp (result,
                     expected,
                     result_size)) )
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Subtest `%s' FAILED: expected `%s', got `%.*s'\n",
                name,
                expected,
                (int) result_size,
                (NULL == result) ? "" : result);
    global_ret = 1;
  }
  GNUNET_free (result);
}


/**
 * Render 0-terminated @a tmpl with @a root (mirrors
 * TALER_TEMPLATING_fill()) and check against @a expected.
 */
static void
check (const char *name,
       const char *tmpl,
       json_t *root,
       const char *expected)
{
  check_len (name,
             tmpl,
             0,
             root,
             expected);
}


/**
 * Test that the length argument is honored: trailing data beyond the
 * given length must be ignored (this is what TALER_TEMPLATING_fill2
 * relies on for non-0-terminated buffers).
 */
static void
check_fill2_length (void)
{
  json_t *root;

  root = json_pack ("{s:s}",
                    "x", "VALUE");
  GNUNET_assert (NULL != root);
  /* Only the first strlen("abc{{x}}def") bytes are part of the
     template; the trailing garbage must not be rendered. */
  check_len ("fill2-length",
             "abc{{x}}def_THIS_MUST_BE_IGNORED",
             strlen ("abc{{x}}def"),
             root,
             "abcVALUEdef");
  json_decref (root);
}


int
main (int argc,
      char *const *argv)
{
  (void) argc;
  (void) argv;
  GNUNET_log_setup ("test-templating-api",
                    "WARNING",
                    NULL);

  /* --- plain text, no tags --- */
  {
    json_t *root = json_object ();

    check ("literal",
           "hello world",
           root,
           "hello world");
    check ("empty-template",
           "",
           root,
           "");
    json_decref (root);
  }

  /* --- simple interpolation --- */
  {
    json_t *root = json_pack ("{s:s}",
                              "a", "X");

    check ("interpolate",
           "a={{a}}",
           root,
           "a=X");
    check ("interpolate-spaces",
           "a={{ a }}",
           root,
           "a=X");
    check ("interpolate-missing",
           "a=[{{nope}}]",
           root,
           "a=[]");
    json_decref (root);
  }

  /* --- HTML escaping --- */
  {
    json_t *root = json_pack ("{s:s}",
                              "h", "<b>&\"x");

    check ("escape-default",
           "{{h}}",
           root,
           "&lt;b&gt;&amp;&quot;x");
    check ("escape-triple",
           "{{{h}}}",
           root,
           "<b>&\"x");
    check ("escape-ampersand",
           "{{&h}}",
           root,
           "<b>&\"x");
    json_decref (root);
  }

  /* --- dotted / nested access --- */
  {
    json_t *root = json_pack ("{s:{s:s}}",
                              "o",
                              "x", "deep");

    check ("dotted",
           "{{o.x}}",
           root,
           "deep");
    check ("section-object",
           "{{#o}}{{x}}{{/o}}",
           root,
           "deep");
    json_decref (root);
  }

  /* --- sections over arrays --- */
  {
    json_t *root = json_pack ("{s:[s,s,s]}",
                              "arr",
                              "a", "b", "c");

    check ("section-array-scalars",
           "{{#arr}}[{{.}}]{{/arr}}",
           root,
           "[a][b][c]");
    json_decref (root);
  }
  {
    json_t *root = json_pack ("{s:[{s:s},{s:s}]}",
                              "people",
                              "name", "Alice",
                              "name", "Bob");

    check ("section-array-objects",
           "{{#people}}{{name}};{{/people}}",
           root,
           "Alice;Bob;");
    json_decref (root);
  }

  /* --- empty array: section skipped, inverted shown --- */
  {
    json_t *root = json_pack ("{s:[]}",
                              "empty");

    check ("section-empty-array",
           "{{#empty}}X{{/empty}}",
           root,
           "");
    check ("inverted-empty-array",
           "{{^empty}}none{{/empty}}",
           root,
           "none");
    json_decref (root);
  }

  /* --- inverted sections on missing / present keys --- */
  {
    json_t *root = json_pack ("{s:s}",
                              "present", "v");

    check ("inverted-missing",
           "{{^missing}}fallback{{/missing}}",
           root,
           "fallback");
    check ("inverted-present",
           "{{^present}}fallback{{/present}}",
           root,
           "");
    json_decref (root);
  }

  /* --- boolean truthiness --- */
  {
    json_t *root = json_pack ("{s:b, s:b}",
                              "t", 1,
                              "f", 0);

    check ("section-true",
           "{{#t}}yes{{/t}}",
           root,
           "yes");
    check ("section-false",
           "{{#f}}yes{{/f}}",
           root,
           "");
    check ("inverted-false",
           "{{^f}}no{{/f}}",
           root,
           "no");
    json_decref (root);
  }

  /* --- null renders as empty, does not enter section --- */
  {
    json_t *root = json_pack ("{s:n}",
                              "z");

    check ("null-interpolate",
           "[{{z}}]",
           root,
           "[]");
    check ("null-section",
           "{{#z}}X{{/z}}",
           root,
           "");
    check ("null-inverted",
           "{{^z}}empty{{/z}}",
           root,
           "empty");
    json_decref (root);
  }

  /* --- scalar rendering: integer and real --- */
  {
    json_t *root = json_pack ("{s:I, s:f}",
                              "n", (json_int_t) 42,
                              "r", 1.5);

    check ("integer",
           "{{n}}",
           root,
           "42");
    check ("real",
           "{{r}}",
           root,
           "1.5");
    json_decref (root);
  }

  /* --- comments are stripped --- */
  {
    json_t *root = json_object ();

    check ("comment",
           "x{{! this is a comment }}y",
           root,
           "xy");
    json_decref (root);
  }

  /* --- nested sections --- */
  {
    json_t *root = json_pack ("{s:{s:b, s:s}}",
                              "outer",
                              "inner", 1,
                              "val", "Z");

    check ("nested-sections",
           "{{#outer}}{{#inner}}{{val}}{{/inner}}{{/outer}}",
           root,
           "Z");
    json_decref (root);
  }

  /* --- length-respecting rendering (TALER_TEMPLATING_fill2) --- */
  check_fill2_length ();

  if (0 == global_ret)
    fprintf (stderr,
             "All templating API tests passed.\n");
  return global_ret;
}


/* end of test_templating_api.c */
