commit 8c17cedf9312c3d4ba6882336285b176eda51bef
parent 8dd16c9618adbd00bd95ead627a8336cd6e0ef81
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 16 Nov 2025 16:37:56 +0100
prepare typst test
Diffstat:
6 files changed, 415 insertions(+), 6 deletions(-)
diff --git a/src/mhd/.gitignore b/src/mhd/.gitignore
@@ -0,0 +1 @@
+test_typst
diff --git a/src/mhd/Makefile.am b/src/mhd/Makefile.am
@@ -56,3 +56,24 @@ libtalermhd2_la_LIBADD = \
-ljansson \
-lz \
$(XLIB)
+
+
+EXTRA_DIST = \
+ test_typst.conf \
+ test_typst_1.typ
+
+check_PROGRAMS = \
+ test_typst
+
+# TESTS = $(check_PROGRAMS)
+
+test_typst_SOURCES = \
+ test_typst.c
+test_typst_LDADD = \
+ libtalermhd.la \
+ -ltalerutil \
+ -lcurl \
+ -lmicrohttpd \
+ -lgnunetjson \
+ -ljansson \
+ -lgnunetutil
diff --git a/src/mhd/mhd_typst.c b/src/mhd/mhd_typst.c
@@ -130,6 +130,8 @@ static struct TypstContext *tc_tail;
static void
cleanup_tc (struct TypstContext *tc)
{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Cleaning up TypstContext\n");
GNUNET_CONTAINER_DLL_remove (tc_head,
tc_tail,
tc);
@@ -451,8 +453,8 @@ setup_stage (struct TypstStage *stage,
tmpdir,
i);
if (0 !=
- symlink (input, /* target */
- template_fn))
+ symlink (template_fn, /* target */
+ input))
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
"symlink",
@@ -550,6 +552,7 @@ TALER_MHD_typst (
cleanup_tc (tc);
return NULL;
}
+ tc->p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
/* setup typst stages */
{
@@ -558,12 +561,12 @@ TALER_MHD_typst (
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_filename (cfg,
cfg_section_name,
- "TYPIST_TEMPLATES",
+ "TYPST_TEMPLATES",
&template_path))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
cfg_section_name,
- "TYPIST_TEMPLATES");
+ "TYPST_TEMPLATES");
cleanup_tc (tc);
return NULL;
}
@@ -590,8 +593,6 @@ TALER_MHD_typst (
complete_response (tc);
}
- tc->p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
-
/* return response */
{
struct MHD_Response *resp;
diff --git a/src/mhd/test_typst.c b/src/mhd/test_typst.c
@@ -0,0 +1,183 @@
+/*
+ This file is part of TALER
+ (C) 2025 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 mhd/test_typst.c
+ * @brief Tests for Typst-MHD logic
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "taler/platform.h"
+#include "taler/taler_util.h"
+#include "taler/taler_mhd_lib.h"
+#include <curl/curl.h>
+
+static int global_ret;
+
+static struct MHD_Daemon *mhd;
+
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+
+static MHD_RESULT
+handler_cb (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ struct MHD_Response *resp;
+ json_t *forms[] = {
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("test",
+ "value")),
+ NULL,
+ };
+ struct TALER_MHD_TypstDocument docs[] = {
+ {
+ .form_name = "test_typst_1.typ",
+ .data = forms[0],
+ }
+ };
+
+ resp = TALER_MHD_typst (cfg,
+ "test-typst",
+ 1,
+ docs);
+
+ for (unsigned int i = 0; NULL != forms[i]; i++)
+ json_decref (forms[i]);
+ return MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ resp);
+}
+
+
+static void
+do_shutdown (void *cls)
+{
+ if (NULL != mhd)
+ {
+ MHD_stop_daemon (mhd);
+ mhd = NULL;
+ }
+}
+
+
+static void
+run_client ()
+{
+ struct CURL *eh;
+ FILE *f;
+
+ f = fopen ("test_typst.pdf",
+ "w+");
+ if (NULL == f)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "fopen",
+ "test_typst.pdf");
+ global_ret = 1;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ eh = curl_easy_init ();
+ GNUNET_assert (NULL != eh);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ "http://localhost:9967/"));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_WRITEDATA,
+ f));
+ if (CURLE_OK !=
+ curl_easy_perform (eh))
+ {
+ fprintf (stderr,
+ "Curl download failed!\n");
+ global_ret = 1;
+ }
+ curl_easy_cleanup (eh);
+ GNUNET_SCHEDULER_shutdown ();
+}
+
+
+/**
+ * Main function that will be run.
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *c)
+{
+ cfg = c;
+ mhd = MHD_start_daemon (MHD_USE_AUTO_INTERNAL_THREAD | MHD_USE_DEBUG,
+ 9967,
+ NULL, NULL,
+ &handler_cb, NULL,
+ NULL);
+ if (NULL == mhd)
+ {
+ GNUNET_break (0);
+ global_ret = 1;
+ return;
+ }
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ run_client ();
+}
+
+
+int
+main (int argc,
+ const char *const argv[])
+{
+ char *const argvx[] = {
+ (char*) "test_typst",
+ (char*) "-c", (char*) "test_typst.conf",
+ (char*) "-L", (char*) "WARNING",
+ NULL
+ };
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_OPTION_END
+ };
+
+ (void) argc;
+ (void) argv;
+ GNUNET_log_setup ("test-typst",
+ "WARNING",
+ NULL);
+ if (GNUNET_OK !=
+ GNUNET_PROGRAM_run (TALER_EXCHANGE_project_data (),
+ 5, argvx,
+ "test_typst",
+ "Test typst",
+ options,
+ &run,
+ NULL))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ return global_ret;
+}
+
+
+/* end of test_typst.c */
diff --git a/src/mhd/test_typst.conf b/src/mhd/test_typst.conf
@@ -0,0 +1,2 @@
+[test-typst]
+TYPST_TEMPLATES = ${PWD}/
diff --git a/src/mhd/test_typst_1.typ b/src/mhd/test_typst_1.typ
@@ -0,0 +1,200 @@
+
+#let form(data) = {
+ set page(
+ paper: "a4",
+ margin: (left: 2cm, right: 2cm, top: 2cm, bottom: 2.5cm),
+ footer: context [
+ #grid(
+ columns: (1fr, 1fr),
+ align: (left, right),
+ text(size: 8pt)[
+ VQF doc. Nr. 902.9#linebreak()
+ Version of 1 December 2015
+ ],
+ text(size: 8pt)[
+ Page #here().page() of #counter(page).final().first()
+ ]
+ )
+ ]
+ )
+
+ set text(font: "Liberation Sans", size: 10pt)
+ set par(justify: false, leading: 0.65em)
+
+ // Helper function to get value or empty string
+ let get(key, default: "") = {
+ data.at(key, default: default)
+ }
+
+ // Helper function for checkbox
+ let checkbox(checked) = {
+ box(
+ width: 3mm,
+ height: 3mm,
+ stroke: 0.5pt + black,
+ inset: 0.3mm,
+ if checked == true or checked == "true" {
+ place(center + horizon, text(size: 8pt, sym.checkmark))
+ }
+ )
+ }
+
+ // Header
+ align(center, text(size: 11pt, weight: "bold")[CONFIDENTIAL])
+
+ v(0.5em)
+
+ grid(
+ columns: (50%, 50%),
+ gutter: 1em,
+ image("vss_vqf_verein.png", width: 80%),
+ align(right)[
+ #table(
+ columns: (1fr, 1fr),
+ stroke: 0.5pt + black,
+ inset: 5pt,
+ align: (left, left),
+ [VQF member no.], [AMLA File No.],
+ [#get("VQF_MEMBER_NUMBER")], [#get("FILE_NUMBER")]
+ )
+ ]
+ )
+
+ v(1em)
+
+ align(left, text(size: 14pt, weight: "bold")[Declaration of identity of the beneficial owner (A)])
+
+ v(-1em)
+ line(length:100%)
+
+ v(1em)
+
+ // Section 1: Contracting Partner
+ text(size: 11pt, weight: "bold")[Contracting partner:]
+
+ v(0.5em)
+
+ table(
+ columns: (1fr),
+ stroke: 0.5pt + black,
+ inset: 5pt,
+ [#get("IDENTITY_CONTRACTING_PARTNER")]
+ )
+
+ v(1em)
+
+ text()[The contracting partner hereby declares that the person(s) listed below is/are the beneficial owner(s) of the assets involved in the business relationship. If the contracting partner is also the sole beneficial owner of the assets, the contracting partner's detail must be set out below]
+
+ v(1em)
+
+ // Section 2: Beneficial Owners
+ let owners = get("IDENTITY_LIST", default: ())
+ let has_owners = type(owners) == array and owners.len() > 0
+
+ if has_owners {
+ for owner in owners {
+ let get_owner(key) = {
+ owner.at(key, default: "")
+ }
+
+ block(breakable: false)[
+ #v(0.5em)
+ #table(
+ columns: (35%, 65%),
+ stroke: 0.5pt + black,
+ inset: 5pt,
+ [Fullname:], [#get_owner("FULL_NAME")],
+ [Date of birth:], [#get_owner("DATE_OF_BIRTH")],
+ [Nationality:], [#get_owner("NATIONALITY")],
+ [Actual address of domicile:], [#get_owner("DOMICILE_ADDRESS")]
+ )
+ #v(0.5em)
+ ]
+ }
+ } else {
+ block(breakable: false)[
+ #v(0.5em)
+ #table(
+ columns: (35%, 65%),
+ stroke: 0.5pt + black,
+ inset: 5pt,
+ [Surname(s):], [],
+ [First name(s):], [],
+ [Date(s) of birth:], [],
+ [Nationality:], [],
+ [Actual address of domicile:], []
+ )
+ #v(0.5em)
+ ]
+ }
+
+ v(1em)
+
+ text()[The contracting partner hereby undertakes to inform automatically of any changes to the information contained herein.]
+
+ v(1.5em)
+
+ // Signature Section
+ let submitted_by = get("SUBMITTED_BY")
+
+ if submitted_by == "CUSTOMER" {
+ table(
+ columns: (40%, 10%, 50%),
+ stroke: 0.5pt + black,
+ inset: 5pt,
+ [Date:],
+ [],
+ [Signature(s):],
+ [#get("SIGN_DATE")],
+ [],
+ [#get("SIGNATURE")]
+ )
+
+ v(1em)
+
+ text(size: 9pt, style: "italic")[
+ It is a criminal offence to deliberately provide false information on this form (article 251 of the Swiss Criminal Code, document forgery)
+ ]
+ } else if submitted_by == "AML_OFFICER" {
+ text(weight: "bold")[Signed declaration by the customer]
+
+ v(0.5em)
+
+ text(size: 9pt)[The attachment contains the customer's signature on the beneficial owner declaration.]
+
+ v(0.5em)
+
+ table(
+ columns: (1fr),
+ stroke: 0.5pt + black,
+ inset: 5pt,
+ [Signed Document:],
+ [#if (get("ATTACHMENT_SIGNED_DOCUMENT") != ""){
+ [Document attached]
+ } else {
+ [No document]
+ }
+ ]
+ )
+ } else {
+ text(weight: "bold")[Invalid submitter (#submitted_by)]
+ }
+}
+
+// Example usage:
+#form((
+ "VQF_MEMBER_NUMBER": "12345",
+ "FILE_NUMBER": "42",
+ "IDENTITY_CONTRACTING_PARTNER": "Example Company AG\nBahnhofstrasse 1\n8001 Zurich\nSwitzerland",
+ "IDENTITY_LIST": (
+ (
+ "FULL_NAME": "John Doe",
+ "DATE_OF_BIRTH": "01.01.1980",
+ "NATIONALITY": "CH",
+ "DOMICILE_ADDRESS": "Teststrasse 123\n8001 Zurich"
+ ),
+ ),
+ "SUBMITTED_BY": "CUSTOMER",
+ "SIGNATURE": "John Doe",
+ "SIGN_DATE": "10.11.2025",
+))
+\ No newline at end of file