commit f0b8b496221d1a2536424886d1a1310fa2a35052
parent fb87b6b5ef97d0e461b232b1ae6c0ca968fdc216
Author: Florian Dold <florian@dold.me>
Date: Tue, 2 Jun 2026 01:51:39 +0200
harness: test for overspending with prospective coin selection
Diffstat:
2 files changed, 128 insertions(+), 0 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-balance-prospective.ts b/packages/taler-harness/src/integrationtests/test-balance-prospective.ts
@@ -0,0 +1,126 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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/>
+ */
+
+/**
+ * Imports.
+ */
+import {
+ TransactionMajorState,
+ TransactionMinorState,
+} from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+ createSimpleTestkudosEnvironmentV3,
+ withdrawViaBankV4,
+} from "../harness/environments.js";
+import { GlobalTestState } from "../harness/harness.js";
+
+/**
+ * Test handling of the prospective coin selection in balances,
+ * i.e. coin selections where the funds we're using are
+ * still waiting to be refreshed.
+ */
+export async function runBalanceProspectiveTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const {
+ bankClient,
+ walletClient,
+ exchange,
+ merchant,
+ merchantAdminAccessToken,
+ bank,
+ } = await createSimpleTestkudosEnvironmentV3(
+ t,
+ defaultCoinConfig.map((x) => x("TESTKUDOS")),
+ {
+ walletConfig: {
+ testing: {
+ devModeActive: true,
+ },
+ },
+ },
+ );
+
+ // Withdraw exactly one coin
+ await withdrawViaBankV4(t, {
+ walletClient,
+ exchange,
+ amount: "TESTKUDOS:10.01",
+ bank,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
+
+ const coins = await walletClient.call(WalletApiOperation.DumpCoins, {});
+ t.assertDeepEqual(coins.coins.length, 1);
+
+ await walletClient.call(WalletApiOperation.ApplyDevExperiment, {
+ devExperimentUri: "taler://dev-experiment/start-block-refresh",
+ });
+
+ const p1 = await walletClient.call(WalletApiOperation.InitiatePeerPushDebit, {
+ partialContractTerms: {
+ amount: "TESTKUDOS:4",
+ summary: "test",
+ },
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: p1.transactionId,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.Ready,
+ },
+ });
+
+ {
+ // This should succeed based on a prospective balance
+ const p2 = await walletClient.call(
+ WalletApiOperation.InitiatePeerPushDebit,
+ {
+ partialContractTerms: {
+ amount: "TESTKUDOS:4",
+ summary: "test",
+ },
+ },
+ );
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: p2.transactionId,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.CreatePurse,
+ },
+ requireError: true,
+ });
+ }
+
+ {
+ // No more prospective balance, this should fail
+ await t.assertThrowsTalerErrorAsync(async () => {
+ await walletClient.call(WalletApiOperation.InitiatePeerPushDebit, {
+ partialContractTerms: {
+ amount: "TESTKUDOS:4",
+ summary: "test",
+ },
+ });
+ });
+ }
+}
+
+runBalanceProspectiveTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -38,6 +38,7 @@ import { runAgeRestrictionsDepositTest } from "./test-age-restrictions-deposit.j
import { runAgeRestrictionsMerchantTest } from "./test-age-restrictions-merchant.js";
import { runAgeRestrictionsMixedMerchantTest } from "./test-age-restrictions-mixed-merchant.js";
import { runAgeRestrictionsPeerTest } from "./test-age-restrictions-peer.js";
+import { runBalanceProspectiveTest } from "./test-balance-prospective.js";
import { runBankApiTest } from "./test-bank-api.js";
import { runBankWopTest } from "./test-bank-wop.js";
import { runClaimLoopTest } from "./test-claim-loop.js";
@@ -442,6 +443,7 @@ const allTests: TestMainFunction[] = [
runWithdrawalShortenTest,
runMerchantRefundFeesTest,
runMerchantTokenfamiliesTest,
+ runBalanceProspectiveTest,
];
export interface TestRunSpec {