taler-typescript-core

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

commit a001795148d9c535160cb98380b3e752ab2f7998
parent 4b3d3bcbf389fbdc5d92f027940be54b84e8395e
Author: Iván Ávalos <avalos@disroot.org>
Date:   Wed, 23 Apr 2025 18:56:14 +0200

harness: add share idempotency test

Diffstat:
Apackages/taler-harness/src/integrationtests/test-payment-share-idempotency.ts | 201+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 2++
2 files changed, 203 insertions(+), 0 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-payment-share-idempotency.ts b/packages/taler-harness/src/integrationtests/test-payment-share-idempotency.ts @@ -0,0 +1,201 @@ +/* + 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, + ConfirmPayResultType, + PreparePayResultType, + succeedOrThrow, + TalerMerchantInstanceHttpClient, + TransactionMajorState, + TransactionMinorState, +} 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"; + +/** + * Run test for basic, bank-integrated withdrawal and payment. + */ +export async function runPaymentShareIdempotencyTest(t: GlobalTestState) { + // Set up test environment + const { + walletClient: firstWallet, + bankClient, + exchange, + merchant, + } = await createSimpleTestkudosEnvironmentV3(t); + + const merchantClient = new TalerMerchantInstanceHttpClient( + merchant.makeInstanceBaseUrl(), + ); + + // Withdraw digital cash into the wallet. + await withdrawViaBankV3(t, { + walletClient: firstWallet, + bankClient, + exchange, + amount: "TESTKUDOS:20", + }); + await firstWallet.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); + + const { walletClient: secondWallet } = await createWalletDaemonWithClient(t, { + name: "wallet2", + }); + + await withdrawViaBankV3(t, { + walletClient: secondWallet, + bankClient, + exchange, + amount: "TESTKUDOS:20", + }); + await secondWallet.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); + + { + const first = await firstWallet.call(WalletApiOperation.GetBalances, {}); + const second = await secondWallet.call(WalletApiOperation.GetBalances, {}); + t.assertAmountEquals(first.balances[0].available, "TESTKUDOS:19.53"); + t.assertAmountEquals(second.balances[0].available, "TESTKUDOS:19.53"); + } + + t.logStep("setup-done"); + + // create two orders to pay + async function createOrder(amount: string) { + const order = { + summary: "Buy me!", + amount: amount as AmountString, + fulfillment_url: "taler://fulfillment-success/thx", + }; + + const args = { order }; + + const orderResp = succeedOrThrow( + await merchantClient.createOrder(undefined, { + order: args.order, + }), + ); + + const orderStatus = succeedOrThrow( + await merchantClient.getOrderDetails(undefined, orderResp.order_id), + ); + + t.assertTrue(orderStatus.order_status === "unpaid"); + return { id: orderResp.order_id, uri: orderStatus.taler_pay_uri }; + } + + t.logStep("orders-created"); + + /** + * Case 1: + * - Claim with first wallet and pay in the second wallet. + * - First wallet should be notified. + */ + { + const order = await createOrder("TESTKUDOS:5"); + // Claim the order with the first wallet + const claimFirstWallet = await firstWallet.call( + WalletApiOperation.PreparePayForUri, + { talerPayUri: order.uri }, + ); + + t.assertTrue( + claimFirstWallet.status === PreparePayResultType.PaymentPossible, + ); + + t.logStep("w1-payment-possible"); + + // share order from the first wallet + const { privatePayUri } = await firstWallet.call( + WalletApiOperation.SharePayment, + { + merchantBaseUrl: merchant.makeInstanceBaseUrl(), + orderId: order.id, + }, + ); + + t.logStep("w1-payment-shared"); + + // claim from the second wallet + const claimSecondWallet = await secondWallet.call( + WalletApiOperation.PreparePayForUri, + { talerPayUri: privatePayUri }, + ); + + t.assertTrue( + claimSecondWallet.status === PreparePayResultType.PaymentPossible, + ); + + t.logStep("w2-claimed"); + + // pay from the second wallet + const r2 = await secondWallet.call(WalletApiOperation.ConfirmPay, { + transactionId: claimSecondWallet.transactionId, + }); + + t.assertTrue(r2.type === ConfirmPayResultType.Done); + + t.logStep("w2-confirmed"); + + // Wait for refresh to settle before we do checks + await secondWallet.call( + WalletApiOperation.TestingWaitTransactionsFinal, + {}, + ); + + t.logStep("w2-refresh-settled"); + + { + const first = await firstWallet.call(WalletApiOperation.GetBalances, {}); + const second = await secondWallet.call( + WalletApiOperation.GetBalances, + {}, + ); + t.assertAmountEquals(first.balances[0].available, "TESTKUDOS:19.53"); + t.assertAmountEquals(second.balances[0].available, "TESTKUDOS:14.23"); + } + + t.logStep("wait-for-payment"); + + // claim from the second wallet (again) + const claimSecondWalletAgain = await secondWallet.call( + WalletApiOperation.PreparePayForUri, + { talerPayUri: privatePayUri }, + ); + + t.assertTrue( + claimSecondWalletAgain.status === PreparePayResultType.AlreadyConfirmed, + ); + + await secondWallet.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: claimSecondWalletAgain.transactionId, + txState: { + major: TransactionMajorState.Done, + }, + }); + } + + t.logStep("second-case-done"); +} + +runPaymentShareIdempotencyTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -162,6 +162,7 @@ import { runWithdrawalPrepareTest } from "./test-withdrawal-prepare.js"; import { runKycMerchantDepositRewriteTest } from "./test-kyc-merchant-deposit-rewrite.js"; import { runKycMerchantDepositTest } from "./test-kyc-merchant-deposit.js"; import { runWalletTokensTest } from "./test-wallet-tokens.js"; +import { runPaymentShareIdempotencyTest } from "./test-payment-share-idempotency.js"; /** * Test runner. @@ -207,6 +208,7 @@ const allTests: TestMainFunction[] = [ runPaymentMultipleTest, runPaymentTest, runPaymentShareTest, + runPaymentShareIdempotencyTest, runPaymentTemplateTest, runPaymentAbortTest, runPaymentTransientTest,