taler-typescript-core

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

commit 2eee6ed0b122d8443d75497bb52ce7b2c45666db
parent d5900403fd5f42fcf4fbb46a1076fa595ed3aa10
Author: Florian Dold <florian@dold.me>
Date:   Sun,  8 Mar 2026 12:30:02 +0100

harness: test for merchant deposits

Diffstat:
Mpackages/taler-harness/src/harness/environments.ts | 8++++++++
Mpackages/taler-harness/src/harness/harness.ts | 8+++++++-
Apackages/taler-harness/src/integrationtests/test-merchant-deposit-large.ts | 202+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 2++
4 files changed, 219 insertions(+), 1 deletion(-)

diff --git a/packages/taler-harness/src/harness/environments.ts b/packages/taler-harness/src/harness/environments.ts @@ -72,6 +72,7 @@ import { BankServiceHandle, BrowserService, DbInfo, + ExchangeConfig, ExchangeService, ExchangeServiceInterface, FakebankService, @@ -170,6 +171,8 @@ export interface EnvOptions { walletConfig?: PartialWalletRunConfig; + exchangeConfig?: Partial<ExchangeConfig>; + additionalExchangeConfig?(e: ExchangeService): void; additionalMerchantConfig?(m: MerchantService): void; additionalBankConfig?(b: BankService): void; @@ -562,6 +565,7 @@ export async function createSimpleTestkudosEnvironmentV3( currency: "TESTKUDOS", httpPort: 8081, database: db.connStr, + ...opts.exchangeConfig, }); const merchant = await MerchantService.create(t, { @@ -1009,6 +1013,7 @@ export async function makeTestPaymentV2( walletClient: WalletClient; order: TalerMerchantApi.Order; instance?: string; + refundDelay?: Duration; }, auth: WithAuthorization = {}, ): Promise<{ transactionId: TransactionIdStr; orderId: string }> { @@ -1023,6 +1028,9 @@ export async function makeTestPaymentV2( const orderResp = succeedOrThrow( await merchantClient.createOrder(merchantAdminAccessToken, { order: args.order, + refund_delay: args.refundDelay + ? Duration.toTalerProtocolDuration(args.refundDelay) + : undefined, }), ); diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts @@ -1168,6 +1168,7 @@ export interface ExchangeConfig { currency: string; hostname?: string; roundUnit?: string; + tinyAmount?: string; httpPort: number; database: string; allowExistingMasterPriv?: boolean; @@ -1339,6 +1340,11 @@ export class ExchangeService implements ExchangeServiceInterface { "currency_round_unit", e.roundUnit ?? `${e.currency}:0.01`, ); + config.setString( + "exchange", + "tiny_amount", + e.tinyAmount ?? `${e.currency}:0.01`, + ); // Set to a high value to not break existing test cases where the merchant // would cover all fees. config.setString("exchange", "STEFAN_ABS", `${e.currency}:1`); @@ -2031,7 +2037,7 @@ export class BrowserService { async start(): Promise<void> { const profileDir = path.join(this.globalState.testDir, "browser-profile"); fs.mkdirSync(profileDir); - + switch (this.type) { case "firefox": { this.driver = this.globalState.spawnService( diff --git a/packages/taler-harness/src/integrationtests/test-merchant-deposit-large.ts b/packages/taler-harness/src/integrationtests/test-merchant-deposit-large.ts @@ -0,0 +1,202 @@ +/* + This file is part of GNU Taler + (C) 2024 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/> + */ + +import { + AbsoluteTime, + ConfirmPayResultType, + Duration, + j2s, + PreparePayResultType, + succeedOrThrow, + TalerCoreBankHttpClient, + TalerMerchantApi, + TalerMerchantInstanceHttpClient, + TalerProtocolTimestamp, + UserAndToken, +} from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { CoinConfig } from "../harness/denomStructures.js"; +import { + createSimpleTestkudosEnvironmentV3, + withdrawViaBankV3, +} from "../harness/environments.js"; +import { GlobalTestState, waitMs } from "../harness/harness.js"; + +const coinCommon = { + cipher: "RSA" as const, + durationLegal: "3 years", + durationSpend: "2 years", + durationWithdraw: "7 days", + feeDeposit: "TESTKUDOS:0", + feeRefresh: "TESTKUDOS:0", + feeRefund: "TESTKUDOS:0", + feeWithdraw: "TESTKUDOS:0", + rsaKeySize: 1024, +}; + +const coinConfigList: CoinConfig[] = [ + { + ...coinCommon, + name: "n1", + value: "TESTKUDOS:1", + }, +]; + +/** + * Test deposit with a large number of coins. + * + * In particular, this checks that the wallet properly + * splits deposits into batches with <=64 coins per batch. + * + * Since we use an artificially large number of coins, this + * test is a bit slower than other tests. + */ +export async function runMerchantDepositLargeTest(t: GlobalTestState) { + // Set up test environment + const { + walletClient, + bankClient, + exchange, + bank, + exchangeBankAccount, + merchantAdminAccessToken, + merchant, + } = await createSimpleTestkudosEnvironmentV3(t, coinConfigList, { + forceLibeufin: true, + exchangeConfig: { + roundUnit: "TESTKUDOS:2", + tinyAmount: "TESTKUDOS:2", + }, + }); + + // Withdraw digital cash into the wallet. + const withdrawRes = await withdrawViaBankV3(t, { + walletClient, + bankClient, + exchange, + amount: "TESTKUDOS:200", + }); + + await withdrawRes.withdrawalFinishedCond; + + const order = { + summary: "Buy me!", + amount: "TESTKUDOS:100", + fulfillment_url: "taler://fulfillment-success/thx", + pay_deadline: AbsoluteTime.toProtocolTimestamp( + AbsoluteTime.addDuration( + AbsoluteTime.now(), + Duration.fromSpec({ seconds: 20 }), + ), + ), + wire_transfer_deadline: TalerProtocolTimestamp.now(), + } satisfies TalerMerchantApi.Order; + + const merchantClient = new TalerMerchantInstanceHttpClient( + merchant.makeInstanceBaseUrl(), + ); + + const orderResp = succeedOrThrow( + await merchantClient.createOrder(merchantAdminAccessToken, { + order: order, + refund_delay: Duration.toTalerProtocolDuration( + Duration.fromSpec({ seconds: 1 }), + ), + }), + ); + + let orderStatus = succeedOrThrow( + await merchantClient.getOrderDetails( + merchantAdminAccessToken, + orderResp.order_id, + ), + ); + + t.assertTrue(orderStatus.order_status === "unpaid"); + + // Make wallet pay for the order + + const preparePayResult = await walletClient.call( + WalletApiOperation.PreparePayForUri, + { + talerPayUri: orderStatus.taler_pay_uri, + }, + ); + + t.assertTrue( + preparePayResult.status === PreparePayResultType.PaymentPossible, + ); + + const diff = AbsoluteTime.difference( + AbsoluteTime.fromProtocolTimestamp( + preparePayResult.contractTerms.wire_transfer_deadline, + ), + AbsoluteTime.now(), + ); + + console.log(`wire transfer deadline in ${diff.d_ms}ms`); + + const r2 = await walletClient.call(WalletApiOperation.ConfirmPay, { + transactionId: preparePayResult.transactionId, + }); + + t.assertDeepEqual(r2.type, ConfirmPayResultType.Done); + + // Check if payment was successful. + + orderStatus = succeedOrThrow( + await merchantClient.getOrderDetails( + merchantAdminAccessToken, + orderResp.order_id, + ), + ); + + t.assertDeepEqual(orderStatus.order_status, "paid"); + + await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); + + const client = new TalerCoreBankHttpClient(bank.corebankApiBaseUrl); + const btok = succeedOrThrow( + await client.createAccessToken( + "exchange", + { + type: "basic", + password: "mypw-password", + }, + { + scope: "readwrite", + }, + ), + ).access_token; + const bankAuth: UserAndToken = { + token: btok, + username: "exchange", + }; + + while (1) { + const txns = succeedOrThrow(await client.getTransactions(bankAuth)); + console.log(`bank txns for exchange: ${j2s(txns)}`); + if (txns.transactions.length >= 2) { + break; + } + console.log("waiting for transaction..."); + await waitMs(1000); + await exchange.runAggregatorOnce(); + await exchange.runTransferOnce(); + } +} + +runMerchantDepositLargeTest.suites = ["merchant"]; diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -108,6 +108,7 @@ import { runLibeufinConversionTest } from "./test-libeufin-conversion.js"; import { runMerchantAcctselTest } from "./test-merchant-acctsel.js"; import { runMerchantBankBadWireTargetTest } from "./test-merchant-bank-bad-wire-target.js"; import { runMerchantCategoriesTest } from "./test-merchant-categories.js"; +import { runMerchantDepositLargeTest } from "./test-merchant-deposit-large.js"; import { runMerchantExchangeConfusionTest } from "./test-merchant-exchange-confusion.js"; import { runMerchantInstancesDeleteTest } from "./test-merchant-instances-delete.js"; import { runMerchantInstancesUrlsTest } from "./test-merchant-instances-urls.js"; @@ -429,6 +430,7 @@ const allTests: TestMainFunction[] = [ runMerchantKycAuthMultiTest, runTopsMerchantTosTest, runWalletWithdrawalRedenominateTest, + runMerchantDepositLargeTest, ]; export interface TestRunSpec {