taler-typescript-core

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

commit ef37c7b7569bf0fab38926d10ceeabe812a5fb02
parent 1fb7d167bcce30e59d782e16151d63190f4fcb51
Author: Florian Dold <dold@taler.net>
Date:   Wed, 22 Jul 2026 13:34:27 +0200

harness: check aborting a push debit whose purse was already merged

Suspends the sender so it cannot observe the merge itself, lets the recipient
accept, and then aborts.  The exchange answers the purse deletion with a
conflict in that ordering.

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-peer-push.ts | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 52 insertions(+), 0 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-peer-push.ts b/packages/taler-harness/src/integrationtests/test-peer-push.ts @@ -403,6 +403,58 @@ export async function runPeerPushTest(t: GlobalTestState) { }); }); + await t.runSpanAsync("P2P push abort after the purse was merged", async () => { + const tx = await initPeerPushDebit("abort after merge"); + + const prepare4 = await wallet4.call( + WalletApiOperation.PreparePeerPushCredit, + { + talerUri: tx.talerUri!, + }, + ); + + // Suspend the sender so that it does not observe the merge on its own. + // The abort below is then the first request that learns about it, which is + // the ordering that leaves the exchange answering the purse deletion with + // a conflict. + await wallet1.call(WalletApiOperation.SuspendTransaction, { + transactionId: tx.transactionId, + }); + await wallet1.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: tx.transactionId, + txState: { + major: TransactionMajorState.Suspended, + minor: TransactionMinorState.Ready, + }, + }); + + await wallet4.call(WalletApiOperation.ConfirmPeerPushCredit, { + transactionId: prepare4.transactionId, + }); + await wallet4.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: prepare4.transactionId, + txState: { + major: TransactionMajorState.Done, + }, + }); + + // The recipient has the money, so the payment cannot be taken back. + await wallet1.call(WalletApiOperation.AbortTransaction, { + transactionId: tx.transactionId, + }); + await wallet1.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: tx.transactionId, + txState: { + major: TransactionMajorState.Done, + }, + }); + + const finalTx = await wallet1.call(WalletApiOperation.GetTransactionById, { + transactionId: tx.transactionId, + }); + t.assertDeepEqual(finalTx.abortReason, undefined); + }); + await t.runSpanAsync("P2P push abort before create purse", async () => { // Make sure the reserve can't be created. // This will test the case where the transaction is aborted