From 749f525f83f962d5852642a05f8de824f51f848c Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sat, 15 Aug 2026 00:54:05 +0200 Subject: [PATCH] tests and eurc --- .forgejo/workflows/deploy.yaml | 63 +- deploy/README.md | 172 +- deploy/catcrafts-deploy.service | 20 +- deploy/catcrafts-server.service | 6 +- project.cpp | 160 +- .../Catcrafts.Server-Coingate.cpp | 286 --- .../implementations/Catcrafts.Server-Eurc.cpp | 673 +++++++ .../implementations/Catcrafts.Server-Http.cpp | 25 +- .../Catcrafts.Server-Mollie.cpp | 17 +- .../Catcrafts.Server-Shipping.cpp | 4 +- server/implementations/main.cpp | 1749 +---------------- server/interfaces/Catcrafts.Server.cppm | 114 +- .../interfaces/Catcrafts.Shared-Content.cppm | 14 +- shared/interfaces/Catcrafts.Shared-Form.cppm | 6 +- shared/interfaces/Catcrafts.Shared-Html.cppm | 4 + shared/interfaces/Catcrafts.Shared-Model.cppm | 21 + shared/interfaces/Catcrafts.Shared-Money.cppm | 6 +- shared/interfaces/Catcrafts.Shared-Views.cppm | 97 +- styles/styles.css | 13 + tests/ShouldBootWasmAtDepth/main.cpp | 70 + tests/ShouldBuildInvoices/main.cpp | 149 ++ tests/ShouldBuildMediaLadders/main.cpp | 137 ++ tests/ShouldComputeMoney/main.cpp | 167 ++ tests/ShouldEmitStructuredData/main.cpp | 175 ++ tests/ShouldEscapeHtml/main.cpp | 107 + tests/ShouldGuardRequestProvenance/main.cpp | 89 + tests/ShouldMintOrderTokens/main.cpp | 49 + tests/ShouldParseEurcChains/main.cpp | 93 + tests/ShouldParseJson/main.cpp | 116 ++ tests/ShouldParseMolliePayments/main.cpp | 82 + tests/ShouldParseSendcloudRates/main.cpp | 102 + tests/ShouldProcessCheckout/main.cpp | 566 ++++++ tests/ShouldPublishFinancials/main.cpp | 273 +++ tests/ShouldRenderMarkdown/main.cpp | 182 ++ tests/ShouldSellTheShopFront/main.cpp | 76 + tests/ShouldServeFinancialsLive/main.cpp | 183 ++ tests/ShouldServePostPages/main.cpp | 245 +++ tests/ShouldServePosts/main.cpp | 173 ++ tests/ShouldServeRoutes/main.cpp | 102 + tests/ShouldShipContent/main.cpp | 219 +++ tests/ShouldStayScriptFree/main.cpp | 151 ++ tests/ShouldValidateForms/main.cpp | 236 +++ tests/harness/Catcrafts.E2eHarness.cppm | 338 ++++ tools/e2e.sh | 1382 ------------- 44 files changed, 5380 insertions(+), 3532 deletions(-) delete mode 100644 server/implementations/Catcrafts.Server-Coingate.cpp create mode 100644 server/implementations/Catcrafts.Server-Eurc.cpp create mode 100644 tests/ShouldBootWasmAtDepth/main.cpp create mode 100644 tests/ShouldBuildInvoices/main.cpp create mode 100644 tests/ShouldBuildMediaLadders/main.cpp create mode 100644 tests/ShouldComputeMoney/main.cpp create mode 100644 tests/ShouldEmitStructuredData/main.cpp create mode 100644 tests/ShouldEscapeHtml/main.cpp create mode 100644 tests/ShouldGuardRequestProvenance/main.cpp create mode 100644 tests/ShouldMintOrderTokens/main.cpp create mode 100644 tests/ShouldParseEurcChains/main.cpp create mode 100644 tests/ShouldParseJson/main.cpp create mode 100644 tests/ShouldParseMolliePayments/main.cpp create mode 100644 tests/ShouldParseSendcloudRates/main.cpp create mode 100644 tests/ShouldProcessCheckout/main.cpp create mode 100644 tests/ShouldPublishFinancials/main.cpp create mode 100644 tests/ShouldRenderMarkdown/main.cpp create mode 100644 tests/ShouldSellTheShopFront/main.cpp create mode 100644 tests/ShouldServeFinancialsLive/main.cpp create mode 100644 tests/ShouldServePostPages/main.cpp create mode 100644 tests/ShouldServePosts/main.cpp create mode 100644 tests/ShouldServeRoutes/main.cpp create mode 100644 tests/ShouldShipContent/main.cpp create mode 100644 tests/ShouldStayScriptFree/main.cpp create mode 100644 tests/ShouldValidateForms/main.cpp create mode 100644 tests/harness/Catcrafts.E2eHarness.cppm delete mode 100755 tools/e2e.sh diff --git a/.forgejo/workflows/deploy.yaml b/.forgejo/workflows/deploy.yaml index a0cc41d..ad686ff 100644 --- a/.forgejo/workflows/deploy.yaml +++ b/.forgejo/workflows/deploy.yaml @@ -31,9 +31,10 @@ jobs: # the pixel dimensions of each mirrored file, which become the # width/height attributes that stop the posts page reflowing as 5 MB # recordings arrive. ffmpeg transcodes each still image into the AVIF - # and PNG renditions the page serves it between. tools/e2e.sh asserts - # both are present — so without this package the deploy fails at the - # e2e gate rather than shipping a janky page. + # and PNG renditions the page serves it between. The + # ShouldServePostPages suite asserts both are present — so without + # this package the deploy fails at the test gate rather than + # shipping a janky page. pacman -Syu --noconfirm --needed \ nodejs \ clang lld libc++ \ @@ -121,24 +122,37 @@ jobs: dir=media fi tools/fetch-media.sh "$dir" - # Handed to the e2e step so it checks the files where they actually - # are. It used to assume ./media and reported every referenced file - # as missing here, which reads as a broken site and means a wrong path. + # Handed to the test step (E2E_MEDIA_DIR) so ShouldServePostPages + # checks the files where they actually are. It used to assume + # ./media and reported every referenced file as missing here, which + # reads as a broken site and means a wrong path. echo "dir=$dir" >> "$GITHUB_OUTPUT" - name: Build and test the backend id: srv # The server product builds Catcrafts.Shared for the host, which is the # only way to actually RUN the code that generates every byte of markup - # the site emits. --selftest is a gate: if escaping or the JSON reader - # regress, the deploy stops here rather than shipping broken pages. + # the site emits. `crafter-build test` is the gate: if escaping or the + # JSON reader regress, the deploy stops here rather than shipping + # broken pages. One suite per tests//main.cpp — the unit suites + # replaced the binary's --selftest flag, the Should{ServeRoutes,…} + # black-box suites replaced tools/e2e.sh: each spawns the freshly + # built server on its own scratch port with the fake payment rails and + # exercises it over real HTTP. ShouldBootWasmAtDepth self-skips here — + # no wasm bundle exists yet — and re-runs after the wasm build below. + # + # E2E_MEDIA_DIR points ShouldServePostPages at the mirror mount, where + # the media files actually are. # # Same refuse-to-guess rule as the wasm bundle below: a variant # directory embeds a config hash, so more than one match means the tree # is ambiguous and picking the first would deploy an arbitrary build. + env: + E2E_MEDIA_DIR: ${{ steps.media.outputs.dir }} run: | set -eux - crafter-build -- --product=server + crafter-build --product=server + crafter-build test --product=server matches=$(find bin -maxdepth 1 -type d -name 'Catcrafts.Server-*' | sort) count=$(printf '%s\n' "$matches" | grep -c . || true) if [ "$count" -ne 1 ]; then @@ -147,7 +161,6 @@ jobs: exit 1 fi echo "srv=$matches" >> "$GITHUB_OUTPUT" - "$matches/catcrafts-server" --selftest "$matches/catcrafts-server" --routes - name: Generate sitemap and Atom feed @@ -204,26 +217,18 @@ jobs: DIST: ${{ steps.out.outputs.dist }} run: tools/fix-bundle-depth.sh "$DIST" - - name: End-to-end HTTP tests - # Starts the freshly built server on a scratch port and exercises it over - # real HTTP: status codes, redirects, headers, form submission, and the - # no-JavaScript guarantee. --selftest covers the pure functions; only a - # real request can show that /nope is a 404 rather than a soft 404, that - # /projects contains its content with no "), - "<script>alert(1)</script>", "escape: script tag"); - // Non-ASCII passes through untouched — the output is UTF-8, and - // entity-encoding it would just bloat the page. - CheckEq(Escape("café ✓ 日本"), "café ✓ 日本", "escape: utf-8 passthrough"); - CheckEq(Escape(""), "", "escape: empty"); - - // ── Num ─────────────────────────────────────────────────────────── - CheckEq(Num(0), "0", "num: zero"); - CheckEq(Num(-42), "-42", "num: negative"); - CheckEq(Num(9007199254740993LL), "9007199254740993", "num: beyond double precision"); - - // ── Attr ────────────────────────────────────────────────────────── - CheckEq(Attr("class", "card"), " class=\"card\"", "attr: basic"); - CheckEq(Attr("data-x", "a\"b"), " data-x=\"a"b\"", "attr: value escaped"); - CheckEq(Attr("class", ""), "", "attr: empty value omits attribute"); - // An invalid name is a programming error, not user data. Emitting - // nothing is safer than emitting mangled markup. - CheckEq(Attr("on error", "x"), "", "attr: invalid name rejected"); - CheckEq(Attr("x>"), " href=\"#\"", "url: data: neutralised"); - // Browsers strip control characters before resolving the scheme, so a - // naive prefix check would pass this straight through. - CheckEq(Url("href", "java\tscript:alert(1)"), " href=\"#\"", "url: embedded tab"); - CheckEq(Url("href", " javascript:alert(1)"), " href=\"#\"", "url: leading space"); - CheckEq(Url("href", "//evil.example/x"), " href=\"#\"", "url: protocol-relative blocked"); - CheckEq(Url("href", "vbscript:x"), " href=\"#\"", "url: vbscript neutralised"); - - // ── Format ──────────────────────────────────────────────────────── - // The compile-time half of this guarantee (raw std::string rejected) is - // verified by the build itself — see the negative test in the notes. - CheckEq(Format("

{}

", Escape("aa<b", "format: escapes flow through"); - CheckEq(Format("{}", Url("href", "/x"), Escape("go")), - "go", "format: attr + text"); - CheckEq(Format("{}{}", Num(1), Num(2)), "12", "format: multiple args"); - CheckEq(Format("literal"), "literal", "format: no args"); - CheckEq(Format("{{literal braces}}"), "{literal braces}", "format: brace escaping"); - - // ── Join / concat ───────────────────────────────────────────────── - const std::array parts{ Escape("a"), Escape("b"), Escape("c") }; - CheckEq(Join(parts, Raw(", ")), "a, b, c", "join: separator"); - CheckEq(Join(std::span{}), "", "join: empty"); - CheckEq(Escape("a") + Escape("<"), "a<", "operator+: escapes preserved"); -} - -// The format ladder. One /