taler-typescript-core

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

commit 397c8a6d8244119444a31912aa6c588918393736
parent eca6e416309a2494aef690c292e438cbd70513d6
Author: Florian Dold <florian@dold.me>
Date:   Wed,  8 Jan 2025 19:58:18 +0100

wallet-core: fix handling of internal withdrawal transactions

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-wallet-balance.ts | 5++---
Mpackages/taler-harness/src/integrationtests/test-wallettesting.ts | 51+++++++++------------------------------------------
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 25+++++++++++++++++++++++--
Mpackages/taler-wallet-core/src/withdraw.ts | 72++++++++++++++++++++++++++++++++++++++++++++----------------------------
4 files changed, 78 insertions(+), 75 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-wallet-balance.ts b/packages/taler-harness/src/integrationtests/test-wallet-balance.ts @@ -140,7 +140,7 @@ async function createMyEnvironment( exchange2.addOfferedCoins(defaultCoinConfig); - await exchange.enableAccount("payto://void/foo"); + await exchange.enableAccount("payto://void/foo?receiver-name=Foo"); await exchange2.start(); @@ -152,8 +152,7 @@ async function createMyEnvironment( await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - //paytoUris: [getTestHarnessPaytoForLabel("merchant-default")], - paytoUris: [`payto://void/merchant-default`], + paytoUris: [`payto://void/merchant-default?receiver-name=Merchant`], defaultWireTransferDelay: Duration.toTalerProtocolDuration( Duration.fromSpec({ minutes: 1 }), ), diff --git a/packages/taler-harness/src/integrationtests/test-wallettesting.ts b/packages/taler-harness/src/integrationtests/test-wallettesting.ts @@ -24,56 +24,19 @@ */ import { AmountString, Amounts, CoinStatus } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; -import { - SimpleTestEnvironmentNg3, - createSimpleTestkudosEnvironmentV3, - createWalletDaemonWithClient, -} from "../harness/environments.js"; +import { createSimpleTestkudosEnvironmentV3 } from "../harness/environments.js"; import { GlobalTestState, setupDb } from "../harness/harness.js"; const merchantAuthToken = "secret-token:sandbox"; /** - * Run a test case with a simple TESTKUDOS Taler environment, consisting - * of one exchange, one bank and one merchant. - */ -export async function createMyEnvironment( - t: GlobalTestState, - coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")), -): Promise<SimpleTestEnvironmentNg3> { - const db = await setupDb(t); - - const { bankClient, bank, exchange, merchant, exchangeBankAccount } = - await createSimpleTestkudosEnvironmentV3(t, coinConfig, {}); - - console.log("setup done!"); - - const { walletClient, walletService } = await createWalletDaemonWithClient( - t, - { - name: "w1", - }, - ); - - return { - commonDb: db, - exchange, - merchant, - walletClient, - walletService, - bank, - bankClient, - exchangeBankAccount, - }; -} - -/** * Run test for basic, bank-integrated withdrawal. */ export async function runWallettestingTest(t: GlobalTestState) { - const { walletClient, bankClient, exchange, merchant } = - await createMyEnvironment(t); + const db = await setupDb(t); + + const { bankClient, walletClient, exchange, merchant } = + await createSimpleTestkudosEnvironmentV3(t, undefined, {}); await walletClient.call(WalletApiOperation.RunIntegrationTest, { amountToSpend: "TESTKUDOS:5" as AmountString, @@ -183,12 +146,16 @@ export async function runWallettestingTest(t: GlobalTestState) { }); await walletClient.call(WalletApiOperation.ClearDb, {}); + t.logStep("cleared-before-integration-test"); await walletClient.call(WalletApiOperation.RunIntegrationTestV2, { corebankApiBaseUrl: bankClient.baseUrl, exchangeBaseUrl: exchange.baseUrl, merchantAuthToken: merchantAuthToken, merchantBaseUrl: merchant.makeInstanceBaseUrl(), }); + t.logStep("after-integration-test"); + + console.log("end of test"); } runWallettestingTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -412,6 +412,8 @@ export async function runTests(spec: TestRunSpec) { suites = new Set(spec.suiteSpec.split(",").map((x) => x.trim())); } + const filteredTests: TestMainFunction[] = []; + for (const [n, testCase] of allTests.entries()) { const testName = getTestName(testCase); if (spec.includePattern && !minimatch(testName, spec.includePattern)) { @@ -429,7 +431,15 @@ export async function runTests(spec: TestRunSpec) { continue; } } + filteredTests.push(testCase); + } + + console.log(`selected ${filteredTests.length} tests`); + let numFailed = 0; + + for (const [n, testCase] of filteredTests.entries()) { + const testName = getTestName(testCase); if (spec.dryRun) { console.log(`dry run: would run test ${testName}`); continue; @@ -476,10 +486,17 @@ export async function runTests(spec: TestRunSpec) { ? Math.max(testCase.timeoutMs, defaultTimeout) : defaultTimeout; + let progressText = `${n + 1} of ${filteredTests.length}`; + if (numFailed > 0) { + progressText = progressText + `, failed ${numFailed}`; + } + if (spec.noTimeout) { - console.log(`running ${testName}, no timeout`); + console.log(`running ${testName} (${progressText}), no timeout`); } else { - console.log(`running ${testName} with timeout ${testTimeoutMs}ms`); + console.log( + `running ${testName} (${progressText}) with timeout ${testTimeoutMs}ms`, + ); } const token = spec.noTimeout @@ -546,6 +563,10 @@ export async function runTests(spec: TestRunSpec) { } } + if (result.status === "fail") { + numFailed++; + } + harnessLogStream.close(); const stepsFile = `${testDir}/steps.txt`; diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts @@ -272,8 +272,11 @@ function buildTransactionForManualWithdraw( ort: OperationRetryRecord | undefined, kycDetails: TxKycDetails | undefined, ): TransactionWithdrawal { - if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankManual) - throw Error(""); + if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankManual) { + throw Error( + `unexpected withdrawal type (got ${wg.wgInfo.withdrawalType}, expected ${WithdrawalRecordType.BankManual}`, + ); + } const plainPaytoUris = exchangeDetails?.wireInfo?.accounts.map((x) => x.payto_uri) ?? []; @@ -390,29 +393,33 @@ export class WithdrawTransactionContext implements TransactionContext { } const ort = await tx.operationRetries.get(this.taskId); - if ( - withdrawalGroupRecord.wgInfo.withdrawalType === - WithdrawalRecordType.BankIntegrated - ) { - return buildTransactionForBankIntegratedWithdraw( - withdrawalGroupRecord, - scopes, - ort, - kycDetails, - ); - } - if (!exchangeDetails) { - logger.warn( - `transaction ${this.transactionId} is a manual withdrawal, but no exchange wire details found`, - ); + + switch (withdrawalGroupRecord.wgInfo.withdrawalType) { + case WithdrawalRecordType.BankIntegrated: + return buildTransactionForBankIntegratedWithdraw( + withdrawalGroupRecord, + scopes, + ort, + kycDetails, + ); + case WithdrawalRecordType.BankManual: + if (!exchangeDetails) { + logger.warn( + `transaction ${this.transactionId} is a manual withdrawal, but no exchange wire details found`, + ); + } + return buildTransactionForManualWithdraw( + withdrawalGroupRecord, + exchangeDetails, + scopes, + ort, + kycDetails, + ); } - return buildTransactionForManualWithdraw( - withdrawalGroupRecord, - exchangeDetails, - scopes, - ort, - kycDetails, + logger.warn( + `not returning withdrawal transaction details, withdrawal type is ${withdrawalGroupRecord.wgInfo.withdrawalType}`, ); + return undefined; } /** @@ -428,15 +435,24 @@ export class WithdrawTransactionContext implements TransactionContext { return; } - if ( - // !wgRecord.instructedAmount || - // !wgRecord.denomsSel || - !wgRecord.exchangeBaseUrl - ) { + if (!wgRecord.exchangeBaseUrl) { // withdrawal group is in preparation, nothing to update return; } + switch (wgRecord.wgInfo.withdrawalType) { + case WithdrawalRecordType.BankManual: + case WithdrawalRecordType.BankIntegrated: + break; + case WithdrawalRecordType.PeerPullCredit: + case WithdrawalRecordType.PeerPushCredit: + case WithdrawalRecordType.Recoup: + // These withdrawal transactions are internal/hidden. + return; + default: + assertUnreachable(wgRecord.wgInfo); + } + let currency: string | undefined; if (wgRecord.rawWithdrawalAmount) { currency = Amounts.currencyOf(wgRecord.rawWithdrawalAmount);