taler-typescript-core

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

commit c34007726aac7814dd1fe1bf8978adfc785e6fa5
parent 467ab67cc90e3c3dbd2e3c4d9e686865e1f747ee
Author: Florian Dold <dold@taler.net>
Date:   Tue, 21 Jul 2026 10:57:35 +0200

util: test that the time codecs reject malformed numbers

Diffstat:
Mpackages/taler-util/src/time.test.ts | 45++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 44 insertions(+), 1 deletion(-)

diff --git a/packages/taler-util/src/time.test.ts b/packages/taler-util/src/time.test.ts @@ -16,7 +16,14 @@ import { test } from "node:test"; import assert from "node:assert"; -import { Duration } from "./time.js"; +import { + codecForAbsoluteTime, + codecForDuration, + codecForDurationMs, + codecForPreciseTimestamp, + codecForTimestamp, + Duration, +} from "./time.js"; test("duration parsing", (t) => { const d1 = Duration.fromPrettyString("1h"); @@ -48,3 +55,39 @@ test("duration parsing rejects a trailing number with no unit", (t) => { assert.throws(() => Duration.fromPrettyString("")); assert.throws(() => Duration.fromPrettyString(" ")); }); + +test("time codecs reject non-integral and negative values", (t) => { + // These codecs sit at the boundary with untrusted exchange and merchant + // JSON. A NaN timestamp is especially bad: it serializes back to null and + // compares as smaller than everything, so an expiry check silently inverts. + const cases: Array<[{ decode(x: any): unknown }, object]> = [ + [codecForTimestamp, { t_s: NaN }], + [codecForTimestamp, { t_s: Infinity }], + [codecForTimestamp, { t_s: 1.5 }], + [codecForTimestamp, { t_s: -1 }], + [codecForPreciseTimestamp, { t_s: NaN }], + [codecForPreciseTimestamp, { t_s: 1, off_us: NaN }], + [codecForDuration, { d_us: NaN }], + [codecForDuration, { d_us: -1 }], + [codecForDurationMs, { d_ms: NaN }], + [codecForDurationMs, { d_ms: -1 }], + [codecForAbsoluteTime, { t_ms: NaN }], + ]; + for (const [codec, value] of cases) { + assert.throws(() => codec.decode(value), JSON.stringify(value)); + } +}); + +test("time codecs accept the documented values", (t) => { + assert.deepStrictEqual(codecForTimestamp.decode({ t_s: 0 }), { t_s: 0 }); + assert.deepStrictEqual(codecForTimestamp.decode({ t_s: "never" }), { + t_s: "never", + }); + assert.deepStrictEqual(codecForDuration.decode({ d_us: "forever" }), { + d_us: "forever", + }); + assert.deepStrictEqual( + codecForPreciseTimestamp.decode({ t_s: 3, off_us: 4 }), + { t_s: 3, off_us: 4 }, + ); +});