commit 9bff5113ba72a2d2c3032c73246686c13905e7eb
parent 257911ee08b5876d1daf8033ee5f699d99abfca2
Author: Florian Dold <dold@taler.net>
Date: Tue, 21 Jul 2026 01:43:39 +0200
util: test taler URI stringify/parse round-trips
Diffstat:
1 file changed, 67 insertions(+), 0 deletions(-)
diff --git a/packages/taler-util/src/taleruri-roundtrip.test.ts b/packages/taler-util/src/taleruri-roundtrip.test.ts
@@ -0,0 +1,67 @@
+/*
+ This file is part of GNU Taler
+ (C) 2026 Taler Systems S.A.
+
+ GNU 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.
+
+ GNU 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
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { test } from "node:test";
+import assert from "node:assert";
+import { TalerUris, TalerUriAction } from "./taleruri.js";
+import { Result } from "./result.js";
+
+const payUri = (orderId: string, sessionId = "sess") => ({
+ type: TalerUriAction.Pay as const,
+ merchantBaseUrl: "https://m.example.com/" as any,
+ orderId,
+ sessionId,
+});
+
+test("pay URI: a component with structural characters round-trips", (t) => {
+ for (const orderId of ["a/b", "a?c=evil", "a#b", "a&b", "plain-01", "a b"]) {
+ const s = TalerUris.stringify(payUri(orderId));
+ const r = TalerUris.parse(s);
+ assert.strictEqual(r.tag, "ok", `parse failed for orderId ${orderId}: ${s}`);
+ const v = Result.unpack(r) as any;
+ assert.strictEqual(v.orderId, orderId, `orderId not preserved: ${s}`);
+ // the merchant host must survive intact
+ assert.strictEqual(v.merchantBaseUrl, "https://m.example.com/", s);
+ }
+});
+
+test("pay URI: a dot-segment component cannot be represented", (t) => {
+ // "." and ".." are resolved away by the URL parser, even percent-encoded,
+ // so they cannot be a path segment and must be rejected rather than
+ // silently erasing the host.
+ assert.throws(() => TalerUris.stringify(payUri("..")));
+ assert.throws(() => TalerUris.stringify(payUri(".")));
+});
+
+test("withdrawal-transfer-result URI round-trips", (t) => {
+ const parsed = TalerUris.parse(
+ "taler://withdrawal-transfer-result?ref=abc&status=success",
+ );
+ assert.strictEqual(parsed.tag, "ok");
+ const v = Result.unpack(parsed) as any;
+ assert.strictEqual(v.ref, "abc");
+ assert.strictEqual(v.status, "success");
+
+ const s = TalerUris.stringify(v);
+ const reparsed = TalerUris.parse(s);
+ assert.strictEqual(reparsed.tag, "ok", `not re-parseable: ${s}`);
+ assert.deepStrictEqual(Result.unpack(reparsed), v);
+});
+
+test("withdrawal-transfer-result URI requires a ref", (t) => {
+ const r = TalerUris.parse("taler://withdrawal-transfer-result?status=success");
+ assert.strictEqual(r.tag, "error");
+});