commit f80bfee758403381bc0b8549406fe32c105f8101
parent 23df01115277378669f2c550389b72281998a732
Author: Florian Dold <florian@dold.me>
Date: Wed, 17 Jul 2024 01:58:33 +0200
harness: withdrawal idempotency test
Diffstat:
3 files changed, 127 insertions(+), 0 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-idempotent.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-idempotent.ts
@@ -0,0 +1,121 @@
+/*
+ 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, j2s, TalerError } from "@gnu-taler/taler-util";
+import {
+ CryptoDispatcher,
+ SynchronousCryptoWorkerFactoryPlain,
+} from "@gnu-taler/taler-wallet-core";
+import {
+ checkReserve,
+ downloadExchangeInfo,
+ findDenomOrThrow,
+ topupReserveWithBank,
+ withdrawCoin,
+} from "@gnu-taler/taler-wallet-core/dbless";
+import { GlobalTestState, harnessHttpLib } from "../harness/harness.js";
+import { createSimpleTestkudosEnvironmentV2 } from "../harness/helpers.js";
+
+/**
+ * Run test for basic, bank-integrated withdrawal and payment.
+ */
+export async function runWithdrawalIdempotentTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { bank, exchange } = await createSimpleTestkudosEnvironmentV2(t);
+
+ const http = harnessHttpLib;
+ const cryptiDisp = new CryptoDispatcher(
+ new SynchronousCryptoWorkerFactoryPlain(),
+ );
+ const cryptoApi = cryptiDisp.cryptoApi;
+
+ try {
+ // Withdraw digital cash into the wallet.
+
+ const exchangeInfo = await downloadExchangeInfo(exchange.baseUrl, http);
+
+ const reserveKeyPair = await cryptoApi.createEddsaKeypair({});
+
+ let reserveUrl = new URL(
+ `reserves/${reserveKeyPair.pub}`,
+ exchange.baseUrl,
+ );
+ reserveUrl.searchParams.set("timeout_ms", "30000");
+ const longpollReq = http.fetch(reserveUrl.href, {
+ method: "GET",
+ });
+
+ await topupReserveWithBank({
+ amount: "TESTKUDOS:10" as AmountString,
+ http,
+ reservePub: reserveKeyPair.pub,
+ corebankApiBaseUrl: bank.corebankApiBaseUrl,
+ exchangeInfo,
+ });
+
+ console.log("waiting for longpoll request");
+ const resp = await longpollReq;
+ console.log(`got response, status ${resp.status}`);
+
+ console.log(exchangeInfo);
+
+ await checkReserve(http, exchange.baseUrl, reserveKeyPair.pub);
+ const denomselAllowLate = false;
+
+ const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8" as AmountString, {
+ denomselAllowLate,
+ });
+
+ const coin = await withdrawCoin({
+ http,
+ cryptoApi,
+ reserveKeyPair: {
+ reservePriv: reserveKeyPair.priv,
+ reservePub: reserveKeyPair.pub,
+ },
+ denom: d1,
+ exchangeBaseUrl: exchange.baseUrl,
+ });
+
+ {
+ // Do it again for idempotency!
+ const coin2 = await withdrawCoin({
+ http,
+ cryptoApi,
+ reserveKeyPair: {
+ reservePriv: reserveKeyPair.priv,
+ reservePub: reserveKeyPair.pub,
+ },
+ denom: d1,
+ exchangeBaseUrl: exchange.baseUrl,
+ });
+ }
+ } catch (e) {
+ if (e instanceof TalerError) {
+ console.log(e);
+ console.log(j2s(e.errorDetail));
+ } else {
+ console.log(e);
+ }
+ throw e;
+ }
+}
+
+runWithdrawalIdempotentTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -125,6 +125,7 @@ import { runWithdrawalFeesTest } from "./test-withdrawal-fees.js";
import { runWithdrawalFlexTest } from "./test-withdrawal-flex.js";
import { runWithdrawalHandoverTest } from "./test-withdrawal-handover.js";
import { runWithdrawalHugeTest } from "./test-withdrawal-huge.js";
+import { runWithdrawalIdempotentTest } from "./test-withdrawal-idempotent.js";
import { runWithdrawalManualTest } from "./test-withdrawal-manual.js";
/**
@@ -242,6 +243,7 @@ const allTests: TestMainFunction[] = [
runExchangeMasterPubChangeTest,
runMerchantCategoriesTest,
runWithdrawalExternalTest,
+ runWithdrawalIdempotentTest,
];
export interface TestRunSpec {
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
@@ -1392,6 +1392,10 @@ async function processPlanchetExchangeBatchRequest(
withdrawalGroup.exchangeBaseUrl,
).href;
+ // if (logger.shouldLogTrace()) {
+ // logger.trace(`batch-withdraw request: ${j2s(batchReq)}`);
+ // }
+
try {
const resp = await wex.http.fetch(reqUrl, {
method: "POST",