taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit acb99a89afb91ba4e88f78bbf7533d63a9cb2dd2
parent ea4d0e3eb96997c9e036c4cc3d7e7edec3fbec39
Author: Florian Dold <dold@taler.net>
Date:   Mon, 20 Jul 2026 20:43:16 +0200

util: test payto normalization and IBAN validation

Diffstat:
Mpackages/taler-util/src/iban.test.ts | 9+++++++++
Mpackages/taler-util/src/payto.test.ts | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 68 insertions(+), 0 deletions(-)

diff --git a/packages/taler-util/src/iban.test.ts b/packages/taler-util/src/iban.test.ts @@ -59,3 +59,12 @@ test("validateIban accepts lowercase and multi-space IBANs", () => { assert.equal(validateIban(iban.toLowerCase()).type, "valid"); assert.equal(validateIban("NL71 RABO 9996 6667 78").type, "valid"); }); + +test("IBAN check digits must be numeric", (t) => { + // ISO 13616 fixes positions 3-4 as two numeric check digits. appendDigit + // expands A-Z into two digits, so alphabetic check digits could otherwise + // still satisfy the mod-97-10 test. + const r = parseIban("DEAA000000000000000008"); + assert.strictEqual(r.tag, "error"); +}); + diff --git a/packages/taler-util/src/payto.test.ts b/packages/taler-util/src/payto.test.ts @@ -239,3 +239,62 @@ test("taler-reserve payto with an unparseable reservePub does not throw when ign assert.strictEqual(lenient.value.reservePub.length, 32); assert.ok(lenient.value.reservePub.every((b) => b === 0)); }); + +test("payto parsing rejects Object.prototype names as target type", (t) => { + // supportedTargets is a plain object literal, so a lookup for an inherited + // key used to be truthy, fall through the switch and hit assertUnreachable. + for (const t of ["constructor", "toString", "valueOf", "__proto__"]) { + const r = Paytos.fromString(`payto://${t}/foo`); + assert.strictEqual(r.tag, "error", `payto://${t}/foo should be an error`); + } +}); + +test("payto void normalizes without duplicating the target type", (t) => { + const r = Paytos.fromString("payto://void/12345"); + assert.strictEqual(r.tag, "ok"); + assert.strictEqual( + Paytos.toNormalizedString(Result.unpack(r)), + "payto://void/12345", + ); +}); + +test("payto keeps the target type for unsupported schemes", (t) => { + const r = Paytos.fromString("payto://unknown-thing/foo", { + allowUnsupported: true, + }); + assert.strictEqual(r.tag, "ok"); + // Serializing used to interpolate an undefined targetType, collapsing every + // unsupported target type onto the literal string "undefined". + assert.strictEqual( + Paytos.toFullString(Result.unpack(r)), + "payto://unknown-thing/foo", + ); +}); + +test("payto iban normalization is canonical", (t) => { + // Separators and case must not survive into the normalized form, otherwise + // the same account yields two different H_PAYTO values. + const dashed = Paytos.fromString("payto://iban/de75-5121-0800-1245-1261-99"); + const plain = Paytos.fromString("payto://iban/DE75512108001245126199"); + assert.strictEqual(dashed.tag, "ok"); + assert.strictEqual(plain.tag, "ok"); + assert.strictEqual( + Paytos.toNormalizedString(Result.unpack(dashed)), + Paytos.toNormalizedString(Result.unpack(plain)), + ); +}); + +test("payto query is not truncated at a second question mark", (t) => { + // RFC 3986 section 3.4 permits "?" inside the query component. + const r = Paytos.fromString( + "payto://iban/DE75512108001245126199?receiver-name=Ann&msg=a?b", + ); + assert.strictEqual(r.tag, "ok"); + assert.strictEqual(Result.unpack(r).params?.["msg"], "a?b"); +}); + +test("addPaytoQueryParams rejects a non-payto input", (t) => { + assert.throws(() => + addPaytoQueryParams("https://evil.com/x", { message: "hi" }), + ); +});