commit d9e4d8ec33936c27c6f101760b29de6ecfd1075e
parent c66a050c17f817b853ab4818f9487ce95608a005
Author: Sebastian <sebasjm@gmail.com>
Date: Mon, 21 Apr 2025 12:39:40 -0300
test for #9695
Diffstat:
2 files changed, 297 insertions(+), 0 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-deposit-merge.ts b/packages/taler-harness/src/integrationtests/test-deposit-merge.ts
@@ -0,0 +1,295 @@
+/*
+ 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 {
+ AmountString,
+ Duration,
+ NotificationType,
+ TransactionIdStr,
+ TransactionMajorState,
+ TransactionMinorState,
+ j2s,
+} from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import {
+ GlobalTestState,
+ getTestHarnessPaytoForLabel,
+} from "../harness/harness.js";
+import {
+ createSimpleTestkudosEnvironmentV3,
+ withdrawViaBankV3,
+} from "../harness/environments.js";
+
+/**
+ * Run test for basic, bank-integrated withdrawal and payment.
+ */
+export async function runDepositMergeTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, bankClient, exchange } =
+ await createSimpleTestkudosEnvironmentV3(t);
+
+ // Withdraw digital cash into the wallet.
+
+ const withdrawalResult = await withdrawViaBankV3(t, {
+ walletClient,
+ bankClient,
+ exchange,
+ amount: "TESTKUDOS:50",
+ });
+
+ await withdrawalResult.withdrawalFinishedCond;
+
+ const depositPaytoUri = getTestHarnessPaytoForLabel("foo");
+ const bal = await walletClient.call(WalletApiOperation.GetBalances, {});
+ t.assertAmountEquals(bal.balances[0].available, "TESTKUDOS:49.01");
+
+ /**
+ * first deposit
+ */
+ let d1Id: TransactionIdStr;
+ let d1Track: Promise<boolean>;
+ let d1Done: Promise<boolean>;
+ {
+ const { transactionId: depositTxId } = await walletClient.client.call(
+ WalletApiOperation.GenerateDepositGroupTxId,
+ {},
+ );
+
+ d1Track = walletClient.waitForNotificationCond(
+ (n) =>
+ n.type == NotificationType.TransactionStateTransition &&
+ n.transactionId == depositTxId &&
+ n.newTxState.major == TransactionMajorState.Finalizing &&
+ n.newTxState.minor == TransactionMinorState.Track,
+ );
+
+ d1Done = walletClient.waitForNotificationCond(
+ (n) =>
+ n.type == NotificationType.TransactionStateTransition &&
+ n.transactionId == depositTxId &&
+ n.newTxState.major == TransactionMajorState.Done,
+ );
+
+ const depositGroupResult = await walletClient.client.call(
+ WalletApiOperation.CreateDepositGroup,
+ {
+ amount: "TESTKUDOS:3" as AmountString,
+ depositPaytoUri,
+ transactionId: depositTxId,
+ },
+ );
+
+ t.assertDeepEqual(depositGroupResult.transactionId, depositTxId);
+ d1Id = depositGroupResult.transactionId;
+ }
+
+ await d1Track;
+ await exchange.stop();
+ // @ts-ignore duration is not forever
+ exchange.setTimetravel(Duration.fromSpec({ minutes: 1 }).d_ms);
+ await exchange.start();
+ await exchange.pingUntilAvailable();
+ // total time: 1 minute
+
+ /**
+ * second deposit after 1 minute
+ */
+ let d2Id: TransactionIdStr;
+ let d2Track: Promise<boolean>;
+ let d2Done: Promise<boolean>;
+ {
+ const { transactionId: depositTxId } = await walletClient.client.call(
+ WalletApiOperation.GenerateDepositGroupTxId,
+ {},
+ );
+
+ d2Track = walletClient.waitForNotificationCond(
+ (n) =>
+ n.type == NotificationType.TransactionStateTransition &&
+ n.transactionId == depositTxId &&
+ n.newTxState.major == TransactionMajorState.Finalizing &&
+ n.newTxState.minor == TransactionMinorState.Track,
+ );
+
+ d2Done = walletClient.waitForNotificationCond(
+ (n) =>
+ n.type == NotificationType.TransactionStateTransition &&
+ n.transactionId == depositTxId &&
+ n.newTxState.major == TransactionMajorState.Done,
+ );
+
+ const depositGroupResult = await walletClient.client.call(
+ WalletApiOperation.CreateDepositGroup,
+ {
+ amount: "TESTKUDOS:3" as AmountString,
+ depositPaytoUri,
+ transactionId: depositTxId,
+ },
+ );
+
+ t.assertDeepEqual(depositGroupResult.transactionId, depositTxId);
+ d2Id = depositGroupResult.transactionId;
+ }
+
+ await d2Track;
+ await exchange.stop();
+ // @ts-ignore duration is not forever
+ exchange.setTimetravel(Duration.fromSpec({ minutes: 2 }).d_ms);
+ await exchange.start();
+ await exchange.pingUntilAvailable();
+ // total time: 2 minute
+
+ /**
+ * third deposit after 2 minute
+ */
+ let d3Id: TransactionIdStr;
+ let d3Track: Promise<boolean>;
+ let d3Done: Promise<boolean>;
+ {
+ const { transactionId: depositTxId } = await walletClient.client.call(
+ WalletApiOperation.GenerateDepositGroupTxId,
+ {},
+ );
+
+ d3Track = walletClient.waitForNotificationCond(
+ (n) =>
+ n.type == NotificationType.TransactionStateTransition &&
+ n.transactionId == depositTxId &&
+ n.newTxState.major == TransactionMajorState.Finalizing &&
+ n.newTxState.minor == TransactionMinorState.Track,
+ );
+
+ d3Done = walletClient.waitForNotificationCond(
+ (n) =>
+ n.type == NotificationType.TransactionStateTransition &&
+ n.transactionId == depositTxId &&
+ n.newTxState.major == TransactionMajorState.Done,
+ );
+
+ const depositGroupResult = await walletClient.client.call(
+ WalletApiOperation.CreateDepositGroup,
+ {
+ amount: "TESTKUDOS:3" as AmountString,
+ depositPaytoUri,
+ transactionId: depositTxId,
+ },
+ );
+
+ t.assertDeepEqual(depositGroupResult.transactionId, depositTxId);
+ d3Id = depositGroupResult.transactionId;
+ }
+
+ await d3Track;
+ await exchange.stop();
+ // @ts-ignore duration is not forever
+ exchange.setTimetravel(Duration.fromSpec({ minutes: 3 }).d_ms);
+ await exchange.start();
+ await exchange.pingUntilAvailable();
+ // total time: 3 minute
+
+ /**
+ * otherwise we get ECONNRESET in tx state because of the exchange restart
+ */
+ await walletClient.call(WalletApiOperation.RetryTransaction, {
+ transactionId: d1Id,
+ });
+ await walletClient.call(WalletApiOperation.RetryTransaction, {
+ transactionId: d2Id,
+ });
+ await walletClient.call(WalletApiOperation.RetryTransaction, {
+ transactionId: d3Id,
+ });
+
+ /**
+ * check deposit tx after 3 minute, all pending
+ */
+ {
+ const d1Details = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ { transactionId: d1Id },
+ );
+
+ const d2Details = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ { transactionId: d2Id },
+ );
+
+ const d3Details = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ { transactionId: d3Id },
+ );
+ console.log(j2s({ d3Details, d2Details, d1Details }));
+
+ t.assertTrue(d1Details.txState.major === TransactionMajorState.Finalizing);
+ t.assertTrue(d1Details.txState.minor === TransactionMinorState.Track);
+ t.assertTrue(d2Details.txState.major === TransactionMajorState.Finalizing);
+ t.assertTrue(d2Details.txState.minor === TransactionMinorState.Track);
+ t.assertTrue(d3Details.txState.major === TransactionMajorState.Finalizing);
+ t.assertTrue(d3Details.txState.minor === TransactionMinorState.Track);
+ }
+
+ ///////////////////////////////////////////
+
+ await exchange.stop();
+ // @ts-ignore duration is not forever
+ exchange.setTimetravel(Duration.fromSpec({ minutes: 6 }).d_ms);
+ await exchange.start();
+ await exchange.pingUntilAvailable();
+ // total time: 6 minute
+
+ await walletClient.call(WalletApiOperation.RetryTransaction, {
+ transactionId: d1Id,
+ });
+ await walletClient.call(WalletApiOperation.RetryTransaction, {
+ transactionId: d2Id,
+ });
+ await walletClient.call(WalletApiOperation.RetryTransaction, {
+ transactionId: d3Id,
+ });
+
+ await d1Done;
+ /**
+ * check deposit tx after 6 minute, first one should already be wired since default
+ * wire deadline is 5 minutes
+ *
+ * other deposit should already be completed since it should use the same wire transfer
+ *
+ */
+ {
+ const d1Details = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ { transactionId: d1Id },
+ );
+ const d2Details = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ { transactionId: d2Id },
+ );
+ const d3Details = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ { transactionId: d3Id },
+ );
+ console.log(j2s({ d3Details, d2Details, d1Details }));
+ t.assertTrue(d1Details.txState.major === TransactionMajorState.Done);
+ t.assertTrue(d2Details.txState.major === TransactionMajorState.Done);
+ t.assertTrue(d3Details.txState.major === TransactionMajorState.Done);
+ }
+}
+
+runDepositMergeTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -42,6 +42,7 @@ import { runDenomLostTest } from "./test-denom-lost.js";
import { runDenomUnofferedTest } from "./test-denom-unoffered.js";
import { runDepositFaultTest } from "./test-deposit-fault.js";
import { runDepositTest } from "./test-deposit.js";
+import { runDepositMergeTest } from "./test-deposit-merge.js";
import { runExchangeDepositTest } from "./test-exchange-deposit.js";
import { runExchangeManagementFaultTest } from "./test-exchange-management-fault.js";
import { runExchangeManagementTest } from "./test-exchange-management.js";
@@ -183,6 +184,7 @@ const allTests: TestMainFunction[] = [
runClauseSchnorrTest,
runDenomUnofferedTest,
runDepositTest,
+ runDepositMergeTest,
runSimplePaymentTest,
runExchangeManagementFaultTest,
runExchangeTimetravelTest,