commit 03b7cfb3310b0e29e26c0d760ab60e3a14c32098
parent 21b6b42e20192fd8a8a7c75c2d7fc394ef8ee4ee
Author: Florian Dold <dold@taler.net>
Date: Thu, 3 Sep 2026 15:42:47 +0200
wallet-core: test availability rebuild, token domains, refund totals
Diffstat:
3 files changed, 76 insertions(+), 0 deletions(-)
diff --git a/packages/taler-wallet-core/src/coinAvailability.test.ts b/packages/taler-wallet-core/src/coinAvailability.test.ts
@@ -274,3 +274,49 @@ test("source finalization does not make spent coins available again", async () =
assert.strictEqual(availability.visibleCoinCount, 1);
assert.strictEqual(availabilityWrites, 1);
});
+
+test("coin availability rebuild tolerates coins without a denomination", async () => {
+ const availability = makeAvailability();
+ const orphan = makeCoin("orphan", CoinStatus.Fresh, 1);
+ orphan.denomPubHash = "gone-denom-pub-hash";
+ const coins = [makeCoin("fresh-visible", CoinStatus.Fresh, 1), orphan];
+ const created: WalletCoinAvailability[] = [];
+ const tx = {
+ async getCoinAvailabilities() {
+ return [availability];
+ },
+ async listAllCoins() {
+ return coins;
+ },
+ async getDenomination() {
+ return undefined;
+ },
+ async listAllRefreshGroups() {
+ return [
+ {
+ refreshGroupId: "refresh-group",
+ oldCoinPubs: ["purged-old-coin"],
+ statusPerCoin: [RefreshCoinStatus.Pending],
+ },
+ ];
+ },
+ async listAllRefreshSessions() {
+ return [
+ {
+ refreshGroupId: "refresh-group",
+ coinIndex: 0,
+ newDenoms: [{ denomPubHash: availability.denomPubHash, count: 3 }],
+ },
+ ];
+ },
+ async upsertCoinAvailability(av: WalletCoinAvailability) {
+ if (av !== availability) created.push(av);
+ },
+ } as unknown as WalletDbTransaction;
+
+ await recomputeCoinAvailability(tx);
+
+ assert.strictEqual(availability.freshCoinCount, 1);
+ assert.strictEqual(availability.visibleCoinCount, 1);
+ assert.deepStrictEqual(created, []);
+});
diff --git a/packages/taler-wallet-core/src/pay-merchant.test.ts b/packages/taler-wallet-core/src/pay-merchant.test.ts
@@ -962,3 +962,21 @@ test("merchant repair recovers the contribution of a coin the merchant rejected"
{ coinPub: "coin-three", amount: "TESTKUDOS:3" },
]);
});
+
+test("refund totals leave out refunds that did not happen", () => {
+ const done = refundGroup("done", "TESTKUDOS:3", "TESTKUDOS:2.5");
+ const pending = refundGroup("pending", "TESTKUDOS:1", "TESTKUDOS:0.5");
+ pending.status = RefundGroupStatus.Pending;
+ const failed = refundGroup("failed", "TESTKUDOS:7", "TESTKUDOS:0");
+ failed.status = RefundGroupStatus.Failed;
+ const aborted = refundGroup("aborted", "TESTKUDOS:5", "TESTKUDOS:0");
+ aborted.status = RefundGroupStatus.Aborted;
+
+ assert.deepStrictEqual(
+ getRefundTotals([done, pending, failed, aborted], "TESTKUDOS"),
+ {
+ raw: "TESTKUDOS:4",
+ effective: "TESTKUDOS:3",
+ },
+ );
+});
diff --git a/packages/taler-wallet-core/src/tokenSelection.test.ts b/packages/taler-wallet-core/src/tokenSelection.test.ts
@@ -249,3 +249,15 @@ test("payment repair retains its tokens and excludes other reservations", () =>
assert.strictEqual(result.details.tokensAvailable, 2);
}
});
+
+test("an unparseable issuer domain does not match instead of failing", (t) => {
+ const issuer = "https://backend.test.taler.net/";
+
+ assert.strictEqual(
+ verifyTokenMerchant("https://shop.example/", issuer, {
+ class: MerchantContractTokenKind.Discount,
+ expected_domains: ["localhost", "shop."],
+ }),
+ TokenMerchantVerificationResult.Unexpected,
+ );
+});