commit d2e770e0ff271cc0477fa317b5572d726a51d6cb
parent 4cf0b13414deb1579453bd72a4c785a045c74c1d
Author: Florian Dold <dold@taler.net>
Date: Wed, 22 Jul 2026 17:10:16 +0200
harness: drive a withdrawal over a failing network
Uses the start-tc dev experiment to fail and delay a share of the requests
once the withdrawal is running as a background task, so that the scheduler has
to work through repeated errors, backoffs and the wakeups that follow them.
Diffstat:
2 files changed, 95 insertions(+), 0 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-bad-network.ts b/packages/taler-harness/src/integrationtests/test-wallet-bad-network.ts
@@ -0,0 +1,93 @@
+/*
+ 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/>
+ */
+
+/**
+ * Imports.
+ */
+import {
+ TalerCorebankApiClient,
+ TransactionMajorState,
+ TransactionType,
+} from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import {
+ createSimpleTestkudosEnvironmentV3,
+ createWalletDaemonWithClient,
+ withdrawViaBankV3,
+} from "../harness/environments.js";
+import { GlobalTestState } from "../harness/harness.js";
+
+/**
+ * Drive a withdrawal while a good share of the HTTP traffic fails, so that the
+ * task scheduler has to work through repeated errors, backoffs and the wakeups
+ * that follow each stored result.
+ *
+ * The scheduler is otherwise only exercised on paths where everything answers
+ * first time, which is the half of it that cannot go wrong.
+ */
+export async function runWalletBadNetworkTest(t: GlobalTestState) {
+ const { bankClient, exchange } = await createSimpleTestkudosEnvironmentV3(t);
+
+ const { walletClient } = await createWalletDaemonWithClient(t, {
+ name: "w-bad-net",
+ config: {
+ testing: {
+ devModeActive: true,
+ },
+ },
+ });
+
+ // Set the withdrawal up over a working network: these are foreground
+ // requests from the test itself, and nothing retries those.
+ const withdrawRes = await withdrawViaBankV3(t, {
+ walletClient,
+ bankClient: bankClient as TalerCorebankApiClient,
+ exchange,
+ amount: "TESTKUDOS:20",
+ });
+
+ // From here on the withdrawal runs as a background task, so every failure
+ // below has to be absorbed by the scheduler. The delay makes each handler
+ // run long enough for the results of other requests to land while one is
+ // still in flight.
+ await walletClient.call(WalletApiOperation.ApplyDevExperiment, {
+ devExperimentUri:
+ "taler://dev-experiment/start-tc?fake_500=0.3&delay_resp=100",
+ });
+
+ // The wallet has to get here on its own, by retrying.
+ await withdrawRes.withdrawalFinishedCond;
+
+ await walletClient.call(WalletApiOperation.ApplyDevExperiment, {
+ devExperimentUri: "taler://dev-experiment/stop-tc",
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
+
+ const txs = await walletClient.call(WalletApiOperation.GetTransactionsV2, {});
+ const withdrawal = txs.transactions.find(
+ (x) => x.type === TransactionType.Withdrawal,
+ );
+ t.assertTrue(!!withdrawal);
+ t.assertDeepEqual(withdrawal.txState.major, TransactionMajorState.Done);
+
+ // Compared against what the withdrawal itself says rather than a constant,
+ // so that a change in fees does not look like a scheduler regression.
+ const bal = await walletClient.call(WalletApiOperation.GetBalances, {});
+ t.assertAmountEquals(bal.balances[0].available, withdrawal.amountEffective);
+}
+
+runWalletBadNetworkTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -192,6 +192,7 @@ import { runWalletDblessTest } from "./test-wallet-dbless.js";
import { runWalletDd48Test } from "./test-wallet-dd48.js";
import { runWalletDenomExpireTest } from "./test-wallet-denom-expire.js";
import { runWalletDepositLargeTest } from "./test-wallet-deposit-large.js";
+import { runWalletBadNetworkTest } from "./test-wallet-bad-network.js";
import { runWalletDevExperimentsTest } from "./test-wallet-dev-experiments.js";
import { runWalletDevexpFakeprotoverTest } from "./test-wallet-devexp-fakeprotover.js";
import { runWalletExchangeFeaturesTest } from "./test-wallet-exchange-features.js";
@@ -321,6 +322,7 @@ const allTests: TestMainFunction[] = [
runWalletBalanceNotificationsTest,
runExchangeManagementTest,
runWalletObservabilityTest,
+ runWalletBadNetworkTest,
runWalletDevExperimentsTest,
runWalletBalanceZeroTest,
runWalletInsufficientBalanceTest,