taler-typescript-core

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

commit 1ef89f3a7b718fa433af2095a62a271206a6bbaa
parent ad7166000230515fcd1cd1ff996ef85d1dad5e6e
Author: Florian Dold <dold@taler.net>
Date:   Fri, 21 Aug 2026 09:30:24 +0200

taler-harness: consolidate integration tests

We have lots of integration tests by now. Merging very related tests
makes our test suite runs faster, since start-up time is significant.

Diffstat:
Dpackages/taler-harness/src/integrationtests/test-exchange-base-url-completion.ts | 104-------------------------------------------------------------------------------
Mpackages/taler-harness/src/integrationtests/test-exchange-management.ts | 66+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Dpackages/taler-harness/src/integrationtests/test-kyc-deposit-deposit-kyctransfer.ts | 210-------------------------------------------------------------------------------
Mpackages/taler-harness/src/integrationtests/test-kyc-deposit-deposit.ts | 110++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Dpackages/taler-harness/src/integrationtests/test-kyc-merchant-activate-bank-account.ts | 185-------------------------------------------------------------------------------
Mpackages/taler-harness/src/integrationtests/test-kyc-merchant-deposit.ts | 9+++++++++
Dpackages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts | 155-------------------------------------------------------------------------------
Dpackages/taler-harness/src/integrationtests/test-merchant-instances-urls.ts | 192-------------------------------------------------------------------------------
Mpackages/taler-harness/src/integrationtests/test-merchant-instances.ts | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dpackages/taler-harness/src/integrationtests/test-merchant-self-provision-forgot-password.ts | 165-------------------------------------------------------------------------------
Mpackages/taler-harness/src/integrationtests/test-merchant-self-provision-inactive-account-permissions.ts | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mpackages/taler-harness/src/integrationtests/test-paivana-repurchase.ts | 3+++
Dpackages/taler-harness/src/integrationtests/test-paivana.ts | 109-------------------------------------------------------------------------------
Dpackages/taler-harness/src/integrationtests/test-payment-claim-already-claimed.ts | 186-------------------------------------------------------------------------------
Mpackages/taler-harness/src/integrationtests/test-payment-claim.ts | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Dpackages/taler-harness/src/integrationtests/test-payment-idempotency.ts | 166-------------------------------------------------------------------------------
Dpackages/taler-harness/src/integrationtests/test-payment-zero.ts | 66------------------------------------------------------------------
Mpackages/taler-harness/src/integrationtests/test-payment.ts | 123++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Dpackages/taler-harness/src/integrationtests/test-repurchase-v1.ts | 328-------------------------------------------------------------------------------
Mpackages/taler-harness/src/integrationtests/test-repurchase.ts | 363+++++++++++++++++++++++++++++++++++++------------------------------------------
Dpackages/taler-harness/src/integrationtests/test-tops-aml-legi.ts | 91-------------------------------------------------------------------------------
Mpackages/taler-harness/src/integrationtests/test-tops-aml-measures.ts | 9+++++++++
Dpackages/taler-harness/src/integrationtests/test-tops-challenger-twice.ts | 61-------------------------------------------------------------
Mpackages/taler-harness/src/integrationtests/test-tops-merchant-tos.ts | 14++++++++++++++
Dpackages/taler-harness/src/integrationtests/test-wallet-balance-notifications.ts | 122-------------------------------------------------------------------------------
Dpackages/taler-harness/src/integrationtests/test-wallet-cli-termination.ts | 47-----------------------------------------------
Dpackages/taler-harness/src/integrationtests/test-wallet-exchange-migration-existing.ts | 124-------------------------------------------------------------------------------
Mpackages/taler-harness/src/integrationtests/test-wallet-exchange-migration.ts | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mpackages/taler-harness/src/integrationtests/test-wallet-notifications.ts | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dpackages/taler-harness/src/integrationtests/test-wallet-observability.ts | 154-------------------------------------------------------------------------------
Mpackages/taler-harness/src/integrationtests/test-wallet-tokens-discount.ts | 4+++-
Mpackages/taler-harness/src/integrationtests/test-wallet-tokens.ts | 4+++-
Mpackages/taler-harness/src/integrationtests/test-withdrawal-fakebank.ts | 15+++++++++------
Mpackages/taler-harness/src/integrationtests/test-withdrawal-handover.ts | 8++++++++
Dpackages/taler-harness/src/integrationtests/test-withdrawal-prepare.ts | 78------------------------------------------------------------------------------
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 36------------------------------------
36 files changed, 887 insertions(+), 2843 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-exchange-base-url-completion.ts b/packages/taler-harness/src/integrationtests/test-exchange-base-url-completion.ts @@ -1,104 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2026 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 { j2s } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { createSimpleTestkudosEnvironmentV3 } from "../harness/environments.js"; -import { GlobalTestState } from "../harness/harness.js"; - -/** - * Check that completing an exchange base URL actually talks to the exchange, - * instead of just tidying up the string it was given. - */ -export async function runExchangeBaseUrlCompletionTest( - t: GlobalTestState, -): Promise<void> { - const { walletClient, exchange, bankClient } = - await createSimpleTestkudosEnvironmentV3(t); - - // A URL that a real exchange answers on is completed as-is. - { - const res = await walletClient.call( - WalletApiOperation.CompleteExchangeBaseUrl, - { url: exchange.baseUrl }, - ); - t.assertDeepEqual(res.status, "ok"); - if (res.status !== "ok") throw Error("unreachable"); - t.assertDeepEqual(res.completion, exchange.baseUrl); - } - - // The same URL without its trailing slash names the same exchange. - { - const res = await walletClient.call( - WalletApiOperation.CompleteExchangeBaseUrl, - { url: exchange.baseUrl.replace(/\/$/, "") }, - ); - t.assertDeepEqual(res.status, "ok"); - if (res.status !== "ok") throw Error("unreachable"); - t.assertDeepEqual(res.completion, exchange.baseUrl); - } - - // Something is listening, but it is not an exchange. - { - const res = await walletClient.call( - WalletApiOperation.CompleteExchangeBaseUrl, - { url: bankClient.baseUrl }, - ); - console.log(`completing the bank URL gave ${j2s(res)}`); - t.assertDeepEqual(res.status, "bad-exchange"); - } - - // Nothing is listening on that port. - { - const res = await walletClient.call( - WalletApiOperation.CompleteExchangeBaseUrl, - { url: "http://localhost:9/" }, - ); - console.log(`completing an unused port gave ${j2s(res)}`); - t.assertDeepEqual(res.status, "bad-network"); - } - - // Rejected before any request is made. - { - const res = await walletClient.call( - WalletApiOperation.CompleteExchangeBaseUrl, - { url: "ftp://exchange.example.com/" }, - ); - t.assertDeepEqual(res.status, "bad-syntax"); - } - - // A host the wallet already knows is offered when completion fails, so that - // a typo does not leave the user with nothing. - { - await walletClient.call(WalletApiOperation.UpdateExchangeEntry, { - exchangeBaseUrl: exchange.baseUrl, - force: true, - }); - const exchangeHost = new URL(exchange.baseUrl).hostname; - const res = await walletClient.call( - WalletApiOperation.CompleteExchangeBaseUrl, - { url: `http://${exchangeHost}x:9/` }, - ); - console.log(`completing a typo gave ${j2s(res)}`); - if (res.status === "ok") throw Error("expected completion to fail"); - t.assertTrue((res.suggestions ?? []).includes(exchange.baseUrl)); - } -} - -runExchangeBaseUrlCompletionTest.suites = ["wallet", "exchange"]; diff --git a/packages/taler-harness/src/integrationtests/test-exchange-management.ts b/packages/taler-harness/src/integrationtests/test-exchange-management.ts @@ -23,14 +23,14 @@ import { createSimpleTestkudosEnvironmentV3 } from "../harness/environments.js"; import { GlobalTestState } from "../harness/harness.js"; /** - * Test if the wallet handles outdated exchange versions correctly. + * Test exchange entry lifecycle and validated base URL completion. */ export async function runExchangeManagementTest( t: GlobalTestState, ): Promise<void> { // Set up test environment - const { walletClient, exchange } = + const { walletClient, exchange, bankClient } = await createSimpleTestkudosEnvironmentV3(t); // Since the default exchanges can change, we start the wallet in tests @@ -101,7 +101,7 @@ export async function runExchangeManagementTest( { const err = await t.assertThrowsTalerErrorAsync(async () => { - const res = await walletClient.call(WalletApiOperation.AddExchange, { + await walletClient.call(WalletApiOperation.AddExchange, { uri: `${url.hostname}:${url.port}`, allowCompletion: true, }); @@ -114,6 +114,66 @@ export async function runExchangeManagementTest( "https://localhost:8081/config", ); } + + await t.runSpanAsync("exchange-base-url-completion", async () => { + { + const res = await walletClient.call( + WalletApiOperation.CompleteExchangeBaseUrl, + { url: exchange.baseUrl }, + ); + t.assertDeepEqual(res.status, "ok"); + if (res.status !== "ok") throw Error("unreachable"); + t.assertDeepEqual(res.completion, exchange.baseUrl); + } + + { + const res = await walletClient.call( + WalletApiOperation.CompleteExchangeBaseUrl, + { url: exchange.baseUrl.replace(/\/$/, "") }, + ); + t.assertDeepEqual(res.status, "ok"); + if (res.status !== "ok") throw Error("unreachable"); + t.assertDeepEqual(res.completion, exchange.baseUrl); + } + + { + const res = await walletClient.call( + WalletApiOperation.CompleteExchangeBaseUrl, + { url: bankClient.baseUrl }, + ); + console.log(`completing the bank URL gave ${j2s(res)}`); + t.assertDeepEqual(res.status, "bad-exchange"); + } + + { + const res = await walletClient.call( + WalletApiOperation.CompleteExchangeBaseUrl, + { url: "http://localhost:9/" }, + ); + console.log(`completing an unused port gave ${j2s(res)}`); + t.assertDeepEqual(res.status, "bad-network"); + } + + { + const res = await walletClient.call( + WalletApiOperation.CompleteExchangeBaseUrl, + { url: "ftp://exchange.example.com/" }, + ); + t.assertDeepEqual(res.status, "bad-syntax"); + } + + await walletClient.call(WalletApiOperation.UpdateExchangeEntry, { + exchangeBaseUrl: exchange.baseUrl, + force: true, + }); + const res = await walletClient.call( + WalletApiOperation.CompleteExchangeBaseUrl, + { url: `http://${url.hostname}x:9/` }, + ); + console.log(`completing a typo gave ${j2s(res)}`); + if (res.status === "ok") throw Error("expected completion to fail"); + t.assertTrue((res.suggestions ?? []).includes(exchange.baseUrl)); + }); } runExchangeManagementTest.suites = ["wallet", "exchange"]; diff --git a/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit-kyctransfer.ts b/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit-kyctransfer.ts @@ -1,210 +0,0 @@ -/* - 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 { - Configuration, - Logger, - Paytos, - Result, - succeedOrThrow, - TransactionMajorState, - TransactionMinorState, - TransactionType, -} from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { - configureCommonKyc, - createKycTestkudosEnvironmentFull, - postAmlDecisionNoRules, - withdrawViaBankV3, -} from "../harness/environments.js"; -import { - getTestHarnessPaytoForLabel, - GlobalTestState, -} from "../harness/harness.js"; - -const logger = new Logger("test-kyc-deposit-deposit.ts"); - -function adjustExchangeConfig(config: Configuration) { - configureCommonKyc(config); - - config.setString("KYC-RULE-R1", "operation_type", "deposit"); - config.setString("KYC-RULE-R1", "enabled", "yes"); - config.setString("KYC-RULE-R1", "exposed", "yes"); - config.setString("KYC-RULE-R1", "is_and_combinator", "yes"); - config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5"); - config.setString("KYC-RULE-R1", "timeframe", "1d"); - config.setString("KYC-RULE-R1", "next_measures", "M1"); - - config.setString("KYC-MEASURE-M1", "check_name", "C1"); - config.setString("KYC-MEASURE-M1", "context", "{}"); - config.setString("KYC-MEASURE-M1", "program", "NONE"); - - config.setString("KYC-CHECK-C1", "type", "INFO"); - config.setString("KYC-CHECK-C1", "description", "my check!"); - config.setString("KYC-CHECK-C1", "fallback", "FREEZE"); -} - -export async function runKycDepositDepositKyctransferTest(t: GlobalTestState) { - // Set up test environment - - const { - walletClient, - bankClient, - exchange, - amlKeypair, - bank, - wireGatewayApi, - } = await createKycTestkudosEnvironmentFull(t, { - adjustExchangeConfig, - }); - - // Withdraw digital cash into the wallet. - - const wres = await withdrawViaBankV3(t, { - bankClient, - amount: "TESTKUDOS:50", - exchange: exchange, - walletClient: walletClient, - }); - - await wres.withdrawalFinishedCond; - - const depositPaytoUri = getTestHarnessPaytoForLabel("deposit-test"); - - await bankClient.registerAccountExtended({ - name: "deposit-test", - password: "deposit-password", - username: "deposit-test", - is_taler_exchange: false, - payto_uri: depositPaytoUri, - }); - - const depositResp = await walletClient.call( - WalletApiOperation.CreateDepositGroup, - { - amount: "TESTKUDOS:10", - depositPaytoUri, - }, - ); - - console.log("waiting for kyc-required"); - - await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: depositResp.transactionId, - txState: { - major: TransactionMajorState.Pending, - minor: TransactionMinorState.KycAuthRequired, - }, - }); - - t.logStep("kyc-auth-requested"); - - { - const txDetails = await walletClient.call( - WalletApiOperation.GetTransactionById, - { - transactionId: depositResp.transactionId, - }, - ); - - t.assertDeepEqual(txDetails.type, TransactionType.Deposit); - const kycTx = txDetails.kycAuthTransferInfo; - t.assertTrue(!!kycTx); - - { - const kycAuthCreditPayto = kycTx.creditPaytoUris[0]; - t.assertTrue(!!kycAuthCreditPayto); - const p = Result.orUndefined(Paytos.fromString(kycAuthCreditPayto)); - t.assertTrue(!!p); - t.assertAmountEquals(p.params["amount"], "TESTKUDOS:0.01"); - } - - logger.info(`account pub: ${kycTx.accountPub}`); - - succeedOrThrow( - await wireGatewayApi.addKycAuth({ - auth: bank.getAdminAuth(), - body: { - amount: "TESTKUDOS:0.1", - debit_account: depositPaytoUri, - account_pub: kycTx.accountPub, - }, - }), - ); - } - - await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: depositResp.transactionId, - txState: { - major: TransactionMajorState.Pending, - minor: TransactionMinorState.KycRequired, - }, - }); - - const txDetails = await walletClient.call( - WalletApiOperation.GetTransactionById, - { - transactionId: depositResp.transactionId, - }, - ); - - const kycPaytoHash = txDetails.kycPaytoHash; - - t.assertTrue(!!kycPaytoHash); - - await postAmlDecisionNoRules(t, { - amlPriv: amlKeypair.priv, - amlPub: amlKeypair.pub, - exchangeBaseUrl: exchange.baseUrl, - paytoHash: kycPaytoHash, - }); - - logger.info(`made decision to have no rules on ${kycPaytoHash}`); - - await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: depositResp.transactionId, - txState: [ - { - major: TransactionMajorState.Done, - }, - { - major: TransactionMajorState.Finalizing, - minor: TransactionMinorState.Track, - }, - ], - }); - - await exchange.runAggregatorOnceWithTimetravel({ - timetravelMicroseconds: 1000 * 1000 * 60 * 60, - }); - - await exchange.runTransferOnceWithTimetravel({ - timetravelMicroseconds: 1000 * 1000 * 60 * 60, - }); - - await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: depositResp.transactionId, - txState: { - major: TransactionMajorState.Done, - }, - }); -} - -runKycDepositDepositKyctransferTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit.ts b/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit.ts @@ -20,8 +20,12 @@ import { Configuration, Logger, + Paytos, + Result, + succeedOrThrow, TransactionMajorState, TransactionMinorState, + TransactionType, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { @@ -30,7 +34,10 @@ import { postAmlDecisionNoRules, withdrawViaBankV3, } from "../harness/environments.js"; -import { GlobalTestState } from "../harness/harness.js"; +import { + getTestHarnessPaytoForLabel, + GlobalTestState, +} from "../harness/harness.js"; const logger = new Logger("test-kyc-deposit-deposit.ts"); @@ -57,8 +64,14 @@ function adjustExchangeConfig(config: Configuration) { export async function runKycDepositDepositTest(t: GlobalTestState) { // Set up test environment - const { walletClient, bankClient, exchange, amlKeypair } = - await createKycTestkudosEnvironmentFull(t, { adjustExchangeConfig }); + const { + walletClient, + bankClient, + exchange, + amlKeypair, + bank, + wireGatewayApi, + } = await createKycTestkudosEnvironmentFull(t, { adjustExchangeConfig }); // Withdraw digital cash into the wallet. @@ -136,6 +149,97 @@ export async function runKycDepositDepositTest(t: GlobalTestState) { major: TransactionMajorState.Done, }, }); + + await t.runSpanAsync("deposit-with-kyc-auth-transfer", async () => { + const depositPaytoUri = getTestHarnessPaytoForLabel("deposit-test"); + await bankClient.registerAccountExtended({ + name: "deposit-test", + password: "deposit-password", + username: "deposit-test", + is_taler_exchange: false, + payto_uri: depositPaytoUri, + }); + + const foreignDeposit = await walletClient.call( + WalletApiOperation.CreateDepositGroup, + { + amount: "TESTKUDOS:10", + depositPaytoUri, + }, + ); + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: foreignDeposit.transactionId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.KycAuthRequired, + }, + }); + + const authDetails = await walletClient.call( + WalletApiOperation.GetTransactionById, + { transactionId: foreignDeposit.transactionId }, + ); + t.assertDeepEqual(authDetails.type, TransactionType.Deposit); + const kycAuth = authDetails.kycAuthTransferInfo; + t.assertTrue(kycAuth != null); + const creditPayto = kycAuth.creditPaytoUris[0]; + const parsedCreditPayto = Result.orUndefined( + Paytos.fromString(creditPayto), + ); + t.assertTrue(parsedCreditPayto != null); + t.assertAmountEquals(parsedCreditPayto.params["amount"], "TESTKUDOS:0.01"); + + succeedOrThrow( + await wireGatewayApi.addKycAuth({ + auth: bank.getAdminAuth(), + body: { + amount: "TESTKUDOS:0.1", + debit_account: depositPaytoUri, + account_pub: kycAuth.accountPub, + }, + }), + ); + + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: foreignDeposit.transactionId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.KycRequired, + }, + }); + const kycDetails = await walletClient.call( + WalletApiOperation.GetTransactionById, + { transactionId: foreignDeposit.transactionId }, + ); + t.assertTrue(kycDetails.kycPaytoHash != null); + await postAmlDecisionNoRules(t, { + amlPriv: amlKeypair.priv, + amlPub: amlKeypair.pub, + exchangeBaseUrl: exchange.baseUrl, + paytoHash: kycDetails.kycPaytoHash, + }); + + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: foreignDeposit.transactionId, + txState: [ + { major: TransactionMajorState.Done }, + { + major: TransactionMajorState.Finalizing, + minor: TransactionMinorState.Track, + }, + ], + }); + await exchange.runAggregatorOnceWithTimetravel({ + timetravelMicroseconds: 1000 * 1000 * 60 * 60, + }); + await exchange.runTransferOnceWithTimetravel({ + timetravelMicroseconds: 1000 * 1000 * 60 * 60, + }); + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: foreignDeposit.transactionId, + txState: { major: TransactionMajorState.Done }, + }); + }); } runKycDepositDepositTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-kyc-merchant-activate-bank-account.ts b/packages/taler-harness/src/integrationtests/test-kyc-merchant-activate-bank-account.ts @@ -1,185 +0,0 @@ -/* - 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/> - */ - -/** - * Imports. - */ -import { - codecForAccountKycRedirects, - codecForQueryInstancesResponse, - j2s, - Logger, - MerchantAccountKycRedirectsResponse, - MerchantAccountKycStatus, - Paytos, - Result, - succeedOrThrow, -} from "@gnu-taler/taler-util"; -import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; -import { createKycTestkudosEnvironmentFull } from "../harness/environments.js"; -import { - delayMs, - GlobalTestState, - harnessHttpLib, -} from "../harness/harness.js"; - -const logger = new Logger(`test-kyc-merchant-activate-bank-account.ts`); - -export async function runKycMerchantActivateBankAccountTest( - t: GlobalTestState, -) { - // Set up test environment - const { - merchant, - bankClient, - bank, - wireGatewayApi, - merchantAdminAccessToken, - } = await createKycTestkudosEnvironmentFull(t, { - adjustExchangeConfig(config) { - config.setString("exchange", "enable_kyc", "yes"); - - // no kyc deposit requirement, the exchange - // just need the merchant to prove the that it is the - // legal owner of the account making a simple - // wire transfer - }, - }); - - let accountPub: string; - const headers = { - Authorization: `Bearer ${merchantAdminAccessToken}`, - }; - - { - const instanceUrl = new URL("private", merchant.makeInstanceBaseUrl()); - const resp = await harnessHttpLib.fetch(instanceUrl.href, { headers }); - const parsedResp = await readSuccessResponseJsonOrThrow( - resp, - codecForQueryInstancesResponse(), - ); - accountPub = parsedResp.merchant_pub; - } - - let kycRespOne: MerchantAccountKycRedirectsResponse | undefined = undefined; - - while (1) { - const kycStatusUrl = new URL("private/kyc", merchant.makeInstanceBaseUrl()) - .href; - logger.info(`requesting GET ${kycStatusUrl}`); - const resp = await harnessHttpLib.fetch(kycStatusUrl, { headers }); - if (resp.status === 200) { - kycRespOne = await readSuccessResponseJsonOrThrow( - resp, - codecForAccountKycRedirects(), - ); - if ( - kycRespOne.kyc_data[0].status == - MerchantAccountKycStatus.EXCHANGE_UNREACHABLE - ) { - logger.info(`merchant claims exchange is not reachable (yet)`); - } else { - break; - } - } - // Wait 500ms - await delayMs(500); - } - - t.assertTrue(!!kycRespOne); - - logger.info(`mechant kyc status: ${j2s(kycRespOne)}`); - - // the exchange replies with 404 - // meaning that the bank account is not yet known by the exchange - t.assertDeepEqual(kycRespOne.kyc_data[0].exchange_http_status, 404); - - // prove that we own the account by sending some money from - // the merchant account - bankClient.setAuth({ - username: "merchant-default", - password: "merchant-default", - }); - - await bankClient.registerAccountExtended({ - name: "merchant-default", - password: "merchant-default", - username: "merchant-default", - payto_uri: kycRespOne.kyc_data[0].payto_uri, //this bank user needs to have the same payto that the exchange is asking from - }); - - const kycauthPayto = kycRespOne.kyc_data[0].payto_kycauths![0]; - logger.info(`kycauth payto: ${kycauthPayto}`); - const p = Result.orUndefined(Paytos.fromString(kycauthPayto)); - - const msgAccountPub = p?.params["message"]; - t.assertTrue(!!accountPub); - - // FIXME: This is kinda brittle, would be better to pick out - // what looks like a public key from the message. - t.assertDeepEqual(`KYC:${accountPub}`, msgAccountPub); - - succeedOrThrow( - await wireGatewayApi.addKycAuth({ - auth: bank.getAdminAuth(), - body: { - amount: "TESTKUDOS:0.1", - debit_account: kycRespOne.kyc_data[0].payto_uri, - account_pub: accountPub, - }, - }), - ); - - let kycRespTwo: MerchantAccountKycRedirectsResponse | undefined = undefined; - - // Loop requesting the KYC status. - // The merchant currently doesn't support long-polling for this. - while (true) { - const kycStatusLongpollUrl = new URL( - "private/kyc", - merchant.makeInstanceBaseUrl(), - ); - kycStatusLongpollUrl.searchParams.set("lpt", "1"); - const resp = await harnessHttpLib.fetch(kycStatusLongpollUrl.href, { - headers, - }); - t.assertDeepEqual(resp.status, 200); - const parsedResp = await readSuccessResponseJsonOrThrow( - resp, - codecForAccountKycRedirects(), - ); - logger.info(`kyc resp 2: ${j2s(parsedResp)}`); - if ( - parsedResp.kyc_data[0].status !== - MerchantAccountKycStatus.EXCHANGE_UNREACHABLE - ) { - if (parsedResp.kyc_data[0].payto_kycauths == null) { - kycRespTwo = parsedResp; - break; - } - } else { - logger.info(`kyc status is exchange-unreachable`); - } - // Wait 500ms - await delayMs(500); - } - - t.assertTrue(!!kycRespTwo); - - t.assertDeepEqual(kycRespTwo.kyc_data[0].exchange_http_status, 200); -} - -runKycMerchantActivateBankAccountTest.suites = ["wallet", "merchant", "kyc"]; diff --git a/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit.ts b/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit.ts @@ -28,6 +28,7 @@ import { Logger, MerchantAccountKycRedirectsResponse, MerchantAccountKycStatus, + Result, succeedOrThrow, TalerMerchantApi, } from "@gnu-taler/taler-util"; @@ -145,6 +146,13 @@ export async function runKycMerchantDepositTest(t: GlobalTestState) { t.assertTrue(resp.status !== 200); }); + const kycauthPayto = kycRespOne.kyc_data[0].payto_kycauths?.[0]; + t.assertTrue(kycauthPayto != null); + const parsedKycauthPayto = Result.orUndefined( + Paytos.fromString(kycauthPayto), + ); + t.assertDeepEqual(parsedKycauthPayto?.params["message"], `KYC:${accountPub}`); + await bankClient.registerAccountExtended({ name: "merchant-default", password: "merchant-default", @@ -190,6 +198,7 @@ export async function runKycMerchantDepositTest(t: GlobalTestState) { } t.assertTrue(!!kycRespTwo); + t.assertDeepEqual(kycRespTwo.kyc_data[0].exchange_http_status, 200); await postAmlDecisionNoRules(t, { amlPriv: amlKeypair.priv, diff --git a/packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts b/packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts @@ -1,155 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 { - AccessToken, - HttpStatusCode, - MerchantAuthMethod, - TalerMerchantManagementHttpClient, - URL, -} from "@gnu-taler/taler-util"; -import { - ExchangeService, - GlobalTestState, - MERCHANT_DEFAULT_LOGIN_SCOPE, - MerchantService, - getTestHarnessPaytoForLabel, - harnessHttpLib, - setupDb, -} from "../harness/harness.js"; - -/** - * Test instance deletion and authentication for it - */ -export async function runMerchantInstancesDeleteTest(t: GlobalTestState) { - // Set up test environment - - const db = await setupDb(t); - - const exchange = ExchangeService.create(t, { - name: "testexchange-1", - currency: "TESTKUDOS", - httpPort: 8081, - database: db.connStr, - }); - - const merchant = await MerchantService.create(t, { - name: "testmerchant-1", - httpPort: 8083, - database: db.connStr, - }); - - // We add the exchange to the config, but note that the exchange won't be started. - merchant.addExchange(exchange); - - await merchant.start(); - await merchant.pingUntilAvailable(); - - // Base URL for the default instance. - const baseUrl = merchant.makeInstanceBaseUrl(); - - { - const r = await harnessHttpLib.fetch(new URL("config", baseUrl).href); - const data = await r.json(); - console.log(data); - t.assertDeepEqual(data.currency, "TESTKUDOS"); - } - - // Instances should initially be empty - { - const r = await harnessHttpLib.fetch( - new URL("management/instances", baseUrl).href, - ); - const data = await r.json(); - t.assertDeepEqual(data.instances, []); - } - - // Add an instance, no auth! - const { accessToken: adminAccessToken } = - await merchant.addInstanceWithWireAccount({ - id: "admin", - name: "Default Instance", - paytoUris: [getTestHarnessPaytoForLabel("merchant-default")], - auth: { - method: MerchantAuthMethod.TOKEN, - password: "admin-pwd", - }, - }); - - // Add an instance, no auth! - await merchant.addInstanceWithWireAccount( - { - id: "myinst", - name: "Second Instance", - paytoUris: [getTestHarnessPaytoForLabel("merchant-default")], - auth: { - method: MerchantAuthMethod.TOKEN, - password: "myinst-pwd", - }, - }, - { adminAccessToken }, - ); - - const merchantApi = new TalerMerchantManagementHttpClient( - merchant.makeInstanceBaseUrl(), - ); - - const adminPwd = await merchantApi.createAccessToken( - "admin", - "admin-pwd", - MERCHANT_DEFAULT_LOGIN_SCOPE, - ); - t.assertTrue(adminPwd.type === "ok"); - - // Check that deleting an instance checks the auth - // of the default instance. - { - const res = await merchantApi.deleteInstance( - "undefined" as AccessToken, - "myinst", - ); - t.assertTrue(res.type === "fail"); - t.assertTrue(res.case === HttpStatusCode.Unauthorized); - } - - // The administrator can soft-delete the instance. Management listings - // retain it for compliance, marked as deleted, while direct lookup treats - // it as unavailable. - { - const res = await merchantApi.deleteInstance(adminAccessToken, "myinst"); - t.assertTrue(res.type === "ok"); - - const listed = await merchantApi.listInstances(adminAccessToken); - t.assertTrue(listed.type === "ok"); - const listedById = new Map( - listed.body.instances.map((instance) => [instance.id, instance]), - ); - t.assertDeepEqual(listedById.get("admin")?.deleted, false); - t.assertDeepEqual(listedById.get("myinst")?.deleted, true); - - const deleted = await merchantApi.getInstanceDetails( - adminAccessToken, - "myinst", - ); - t.assertTrue(deleted.type === "fail"); - t.assertDeepEqual(deleted.response.status, HttpStatusCode.NotFound); - } -} - -runMerchantInstancesDeleteTest.suites = ["merchant"]; diff --git a/packages/taler-harness/src/integrationtests/test-merchant-instances-urls.ts b/packages/taler-harness/src/integrationtests/test-merchant-instances-urls.ts @@ -1,192 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 { - AccessToken, - Duration, - LoginTokenScope, - MerchantAuthMethod, - succeedOrThrow, - TalerMerchantManagementHttpClient, -} from "@gnu-taler/taler-util"; -import { - ExchangeService, - GlobalTestState, - harnessHttpLib, - MerchantService, - setupDb, -} from "../harness/harness.js"; - -/** - * Do basic checks on instance management and authentication. - */ -export async function runMerchantInstancesUrlsTest(t: GlobalTestState) { - const db = await setupDb(t); - - const exchange = ExchangeService.create(t, { - name: "testexchange-1", - currency: "TESTKUDOS", - httpPort: 8081, - database: db.connStr, - }); - - const merchant = await MerchantService.create(t, { - name: "testmerchant-1", - httpPort: 8083, - database: db.connStr, - }); - - merchant.addExchange(exchange); - - await merchant.start(); - await merchant.pingUntilAvailable(); - - const adminPw = "i-am-admin" as AccessToken; - const clientForAdminInst = new TalerMerchantManagementHttpClient( - merchant.makeInstanceBaseUrl(), - ); - - const clientForMyInst = new TalerMerchantManagementHttpClient( - merchant.makeInstanceBaseUrl("myinst"), - ); - - succeedOrThrow( - await clientForAdminInst.createInstance(adminPw, { - id: "admin", - address: {}, - use_stefan: true, - default_pay_delay: Duration.toTalerProtocolDuration( - Duration.fromSpec({ seconds: 60 }), - ), - default_wire_transfer_delay: Duration.toTalerProtocolDuration( - Duration.fromSpec({ seconds: 60 }), - ), - jurisdiction: {}, - name: "My Admin Instance", - auth: { - method: MerchantAuthMethod.TOKEN, - password: adminPw, - }, - }), - ); - - const { access_token: adminToken } = succeedOrThrow( - await clientForAdminInst.createAccessToken("admin", adminPw, { - scope: LoginTokenScope.All, - }), - ); - - succeedOrThrow( - await clientForAdminInst.createInstance(adminToken, { - id: "myinst", - address: {}, - default_pay_delay: Duration.toTalerProtocolDuration( - Duration.fromSpec({ seconds: 60 }), - ), - use_stefan: true, - default_wire_transfer_delay: Duration.toTalerProtocolDuration( - Duration.fromSpec({ seconds: 60 }), - ), - jurisdiction: {}, - name: "My Second Instance", - auth: { - method: MerchantAuthMethod.TOKEN, - password: "i-am-myinst", - }, - }), - ); - - const { access_token: myInstToken } = succeedOrThrow( - await clientForMyInst.createAccessToken("myinst", "i-am-myinst", { - scope: LoginTokenScope.All, - }), - ); - - async function check(url: string, token: string, expectedStatus: number) { - const resp = await harnessHttpLib.fetch(url, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - console.log( - `checking ${url}, expected ${expectedStatus}, got ${resp.status}`, - ); - t.assertDeepEqual(resp.status, expectedStatus); - } - - const adminInstBaseUrl = merchant.makeInstanceBaseUrl(); - - await check( - `${adminInstBaseUrl}private/instances/admin/instances/admin/config`, - adminToken, - 404, - ); - - // Instance management is only available when accessing the admin instance - // directly. - await check( - `${adminInstBaseUrl}instances/admin/private/instances`, - "foo", - 404, - ); - - // Non-admin instances don't allow instance management. - await check(`${adminInstBaseUrl}instances/foo/private/instances`, "foo", 404); - await check( - `${adminInstBaseUrl}instances/myinst/private/instances`, - "foo", - 404, - ); - - await check(`${adminInstBaseUrl}config`, "foo", 200); - await check(`${adminInstBaseUrl}instances/admin/config`, "foo", 200); - await check(`${adminInstBaseUrl}instances/myinst/config`, "foo", 200); - await check(`${adminInstBaseUrl}instances/foo/config`, "foo", 404); - await check( - `${adminInstBaseUrl}instances/admin/instances/config`, - "foo", - 404, - ); - - await check( - `${adminInstBaseUrl}private/instances/myinst/config`, - adminToken, - 404, - ); - - await check( - `${adminInstBaseUrl}instances/myinst/private/orders`, - adminToken, - 401, - ); - - await check( - `${adminInstBaseUrl}instances/myinst/private/orders`, - myInstToken, - 200, - ); - - await check( - `${adminInstBaseUrl}private/instances/myinst/orders`, - adminToken, - 404, - ); -} - -runMerchantInstancesUrlsTest.suites = ["merchant"]; diff --git a/packages/taler-harness/src/integrationtests/test-merchant-instances.ts b/packages/taler-harness/src/integrationtests/test-merchant-instances.ts @@ -184,6 +184,76 @@ export async function runMerchantInstancesTest(t: GlobalTestState) { t.assertDeepEqual((fullDetails.auth as any).token, undefined); } + await t.runSpanAsync("instance-url-routing", async () => { + async function check( + requestUrl: string, + token: string, + expectedStatus: number, + ) { + const resp = await harnessHttpLib.fetch(requestUrl, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + console.log( + `checking ${requestUrl}, expected ${expectedStatus}, got ${resp.status}`, + ); + t.assertDeepEqual(resp.status, expectedStatus); + } + + const adminInstBaseUrl = merchant.makeInstanceBaseUrl(); + + await check( + `${adminInstBaseUrl}private/instances/admin/instances/admin/config`, + auth, + 404, + ); + await check( + `${adminInstBaseUrl}instances/admin/private/instances`, + "foo", + 404, + ); + await check( + `${adminInstBaseUrl}instances/foo/private/instances`, + "foo", + 404, + ); + await check( + `${adminInstBaseUrl}instances/myinst/private/instances`, + "foo", + 404, + ); + await check(`${adminInstBaseUrl}config`, "foo", 200); + await check(`${adminInstBaseUrl}instances/admin/config`, "foo", 200); + await check(`${adminInstBaseUrl}instances/myinst/config`, "foo", 200); + await check(`${adminInstBaseUrl}instances/foo/config`, "foo", 404); + await check( + `${adminInstBaseUrl}instances/admin/instances/config`, + "foo", + 404, + ); + await check( + `${adminInstBaseUrl}private/instances/myinst/config`, + auth, + 404, + ); + await check( + `${adminInstBaseUrl}instances/myinst/private/orders`, + auth, + 401, + ); + await check( + `${adminInstBaseUrl}instances/myinst/private/orders`, + myInstAccessToken, + 200, + ); + await check( + `${adminInstBaseUrl}private/instances/myinst/orders`, + auth, + 404, + ); + }); + // Check that deleting an instance checks the auth // of the default instance. { @@ -194,6 +264,28 @@ export async function runMerchantInstancesTest(t: GlobalTestState) { t.assertTrue(res.type === "fail"); t.assertTrue(res.case === HttpStatusCode.Unauthorized); } + + await t.runSpanAsync("instance-soft-delete", async () => { + const unauthenticated = await merchantClient.deleteInstance( + "undefined" as AccessToken, + "myinst", + ); + t.assertTrue(unauthenticated.type === "fail"); + t.assertTrue(unauthenticated.case === HttpStatusCode.Unauthorized); + + succeedOrThrow(await merchantClient.deleteInstance(auth, "myinst")); + + const listed = succeedOrThrow(await merchantClient.listInstances(auth)); + const listedById = new Map( + listed.instances.map((instance) => [instance.id, instance]), + ); + t.assertDeepEqual(listedById.get("admin")?.deleted, false); + t.assertDeepEqual(listedById.get("myinst")?.deleted, true); + + const deleted = await merchantClient.getInstanceDetails(auth, "myinst"); + t.assertTrue(deleted.type === "fail"); + t.assertDeepEqual(deleted.response.status, HttpStatusCode.NotFound); + }); } runMerchantInstancesTest.suites = ["merchant"]; diff --git a/packages/taler-harness/src/integrationtests/test-merchant-self-provision-forgot-password.ts b/packages/taler-harness/src/integrationtests/test-merchant-self-provision-forgot-password.ts @@ -1,165 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 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 { - alternativeOrThrow, - Duration, - HttpStatusCode, - LoginTokenScope, - MerchantAuthMethod, - succeedOrThrow, - TalerMerchantInstanceHttpClient, - TalerMerchantManagementHttpClient, -} from "@gnu-taler/taler-util"; -import { createSimpleTestkudosEnvironmentV3 } from "../harness/environments.js"; -import { GlobalTestState } from "../harness/harness.js"; -import { - configureTestMerchantMfa, - makeMfaConfigEmailOnly, - solveMFA, -} from "../harness/tan-helper.js"; - -/** - * A password reset requires MFA, invalidates the old password and produces a - * usable login token for the new password. - */ -export async function runMerchantSelfProvisionForgotPasswordTest( - t: GlobalTestState, -) { - // Set up test environment - - const instanceInfo = { - id: "self-instance", - name: "My instance", - auth: { - method: MerchantAuthMethod.TOKEN, - password: "123", - }, - default_pay_delay: Duration.toTalerProtocolDuration( - Duration.fromSpec({ days: 1 }), - ), - default_wire_transfer_delay: Duration.toTalerProtocolDuration( - Duration.fromSpec({ days: 1 }), - ), - jurisdiction: {}, - address: {}, - email: "some@taler.net", - use_stefan: false, - }; - - const mfaConfig = makeMfaConfigEmailOnly(t, instanceInfo.email); - - const { merchant, merchantAdminAccessToken } = - await createSimpleTestkudosEnvironmentV3(t, undefined, { - additionalMerchantConfig(m) { - m.modifyConfig(async (cfg) => { - cfg.setString("merchant", "ENABLE_SELF_PROVISIONING", "yes"); - configureTestMerchantMfa(cfg, mfaConfig); - }); - }, - }); - - const merchantClient = new TalerMerchantManagementHttpClient( - merchant.makeInstanceBaseUrl(), - ); - - { - const r = succeedOrThrow( - await merchantClient.listInstances(merchantAdminAccessToken), - ); - t.assertDeepEqual(r.instances.length, 2); - } - - const tryCreation = alternativeOrThrow( - await merchantClient.createInstanceSelfProvision(instanceInfo), - HttpStatusCode.Accepted, - ); - - await solveMFA(t, merchantClient, tryCreation, mfaConfig); - - succeedOrThrow( - await merchantClient.createInstanceSelfProvision(instanceInfo, { - challengeIds: tryCreation.challenges.map((c) => c.challenge_id), - }), - ); - - const instanceApi = new TalerMerchantInstanceHttpClient( - merchantClient.getSubInstanceAPI(instanceInfo.id), - merchantClient.httpLib, - ); - - const newPassword = { - method: MerchantAuthMethod.TOKEN, - password: "xxx", - }; - - const mfa = alternativeOrThrow( - await instanceApi.forgotPasswordSelfProvision(newPassword), - HttpStatusCode.Accepted, - ); - - await solveMFA(t, instanceApi, mfa, mfaConfig); - - succeedOrThrow( - await instanceApi.forgotPasswordSelfProvision(newPassword, { - challengeIds: mfa.challenges.map((c) => c.challenge_id), - }), - ); - - const oldPasswordLogin = await instanceApi.createAccessToken( - instanceInfo.id, - instanceInfo.auth.password, - { scope: LoginTokenScope.All }, - ); - t.assertTrue( - oldPasswordLogin.type === "fail", - "old password still worked after password reset", - ); - - const mfa2 = alternativeOrThrow( - await instanceApi.createAccessToken(instanceInfo.id, newPassword.password, { - scope: LoginTokenScope.All, - }), - HttpStatusCode.Accepted, - ); - - await solveMFA(t, instanceApi, mfa2, mfaConfig); - - const tk = succeedOrThrow( - await instanceApi.createAccessToken( - instanceInfo.id, - newPassword.password, - { - scope: LoginTokenScope.All, - }, - { - challengeIds: mfa2.challenges.map((c) => c.challenge_id), - }, - ), - ); - const instanceDetails = succeedOrThrow( - await instanceApi.getCurrentInstanceDetails(tk.access_token), - ); - t.assertDeepEqual(instanceDetails.email, instanceInfo.email); -} - -runMerchantSelfProvisionForgotPasswordTest.suites = [ - "merchant", - "self-provision", -]; diff --git a/packages/taler-harness/src/integrationtests/test-merchant-self-provision-inactive-account-permissions.ts b/packages/taler-harness/src/integrationtests/test-merchant-self-provision-inactive-account-permissions.ts @@ -37,8 +37,8 @@ import { import { GlobalTestState } from "../harness/harness.js"; /** - * Test that a pending self-provisioned instance can update its email, but - * cannot log in (and therefore cannot start KYC) before account activation. + * Test activation-time permissions and password recovery for a + * self-provisioned instance. */ export async function runMerchantSelfProvisionInactiveAccountPermissionsTest( t: GlobalTestState, @@ -200,6 +200,59 @@ export async function runMerchantSelfProvisionInactiveAccountPermissionsTest( // check that the instance has the new email t.assertDeepEqual(det.email, instanceInfo.email); t.assertDeepEqual(det.email_validated, true); + + await t.runSpanAsync("forgot-password-after-activation", async () => { + const newPassword = { + method: MerchantAuthMethod.TOKEN, + password: "xxx", + }; + + const resetChallenge = alternativeOrThrow( + await instanceApi.forgotPasswordSelfProvision(newPassword), + HttpStatusCode.Accepted, + ); + await solveMFA(t, instanceApi, resetChallenge, mfaConfig); + succeedOrThrow( + await instanceApi.forgotPasswordSelfProvision(newPassword, { + challengeIds: resetChallenge.challenges.map((c) => c.challenge_id), + }), + ); + + const oldPasswordLogin = await instanceApi.createAccessToken( + instanceInfo.id, + instanceInfo.auth.password, + { scope: LoginTokenScope.All }, + ); + t.assertTrue( + oldPasswordLogin.type === "fail", + "old password still worked after password reset", + ); + + const newLoginChallenge = alternativeOrThrow( + await instanceApi.createAccessToken( + instanceInfo.id, + newPassword.password, + { scope: LoginTokenScope.All }, + ), + HttpStatusCode.Accepted, + ); + await solveMFA(t, instanceApi, newLoginChallenge, mfaConfig); + + const newLogin = succeedOrThrow( + await instanceApi.createAccessToken( + instanceInfo.id, + newPassword.password, + { scope: LoginTokenScope.All }, + { + challengeIds: newLoginChallenge.challenges.map((c) => c.challenge_id), + }, + ), + ); + const instanceDetails = succeedOrThrow( + await instanceApi.getCurrentInstanceDetails(newLogin.access_token), + ); + t.assertDeepEqual(instanceDetails.email, instanceInfo.email); + }); } runMerchantSelfProvisionInactiveAccountPermissionsTest.suites = [ diff --git a/packages/taler-harness/src/integrationtests/test-paivana-repurchase.ts b/packages/taler-harness/src/integrationtests/test-paivana-repurchase.ts @@ -86,6 +86,7 @@ export async function runPaivanaRepurchaseTest(t: GlobalTestState) { WalletApiOperation.GetTransactionById, { transactionId: templateStatus.transactionId, + includeContractTerms: true, }, ); t.assertDeepEqual(txDet.type, TransactionType.Payment); @@ -121,6 +122,8 @@ export async function runPaivanaRepurchaseTest(t: GlobalTestState) { }, }, ); + t.assertTrue(txDet.contractTerms != null); + logger.info("2) order paid", txDet.contractTerms.fulfillment_url); } const cookieResult = await walletClient.call( diff --git a/packages/taler-harness/src/integrationtests/test-paivana.ts b/packages/taler-harness/src/integrationtests/test-paivana.ts @@ -1,109 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020-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/> - */ - -/** - * Imports. - */ -import { - Logger, - TransactionMajorState, - TransactionType, -} from "@gnu-taler/taler-util"; -import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { - createSimpleTestkudosEnvironmentV3, - withdrawViaBankV3, -} from "../harness/environments.js"; -import { GlobalTestState } from "../harness/harness.js"; - -const harnessHttpLib = createPlatformHttpLib({ - enableThrottling: false, -}); -export const logger = new Logger("test-paivana.ts"); - -export async function runPaivanaTest(t: GlobalTestState) { - // Set up test environment - - const { walletClient, bankClient, exchange, paivana } = - await createSimpleTestkudosEnvironmentV3(t, undefined, { - paivanaWebsite: ".*.html", // block all html pages - }); - - const withdrawalRes = await withdrawViaBankV3(t, { - walletClient, - bankClient, - exchange, - amount: "TESTKUDOS:20", - }); - - await withdrawalRes.withdrawalFinishedCond; - - const website = `${paivana.baseUrl}index.html`; - - logger.info("1) access denied, preparing Paivana payment"); - - { - const templateStatus = await walletClient.call( - WalletApiOperation.PreparePayForPaivana, - { url: website }, - ); - await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: templateStatus.transactionId, - txState: { - major: TransactionMajorState.Dialog, - minor: "*", - }, - }); - await walletClient.call(WalletApiOperation.ConfirmPay, { - transactionId: templateStatus.transactionId, - choiceIndex: 0, - }); - - await walletClient.call( - WalletApiOperation.TestingWaitTransactionsFinal, - {}, - ); - const paymentTx = await walletClient.call( - WalletApiOperation.GetTransactionById, - { - transactionId: templateStatus.transactionId, - includeContractTerms: true, - }, - ); - t.assertDeepEqual(paymentTx.txState.major, TransactionMajorState.Done); - t.assertTrue(paymentTx.type === TransactionType.Payment); - t.assertTrue(paymentTx.contractTerms != null); - logger.info("2) order paid", paymentTx.contractTerms.fulfillment_url); - - const cookieResult = await walletClient.call( - WalletApiOperation.GetPaivanaCookie, - { - transactionId: templateStatus.transactionId, - paivana: templateStatus.paivana, - }, - ); - t.assertTrue(cookieResult.cookie.startsWith("Paivana-Cookie=")); - - const protectedResponse = await harnessHttpLib.fetch(website, { - headers: { Cookie: cookieResult.cookie }, - }); - t.assertTrue(protectedResponse.status === 200); - t.assertTrue((await protectedResponse.bytes()).byteLength > 0); - } -} - -runPaivanaTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-payment-claim-already-claimed.ts b/packages/taler-harness/src/integrationtests/test-payment-claim-already-claimed.ts @@ -1,186 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2026 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 { - NotificationType, - TalerErrorCode, - TalerMerchantApi, - TalerMerchantInstanceHttpClient, - TransactionIdStr, - TransactionMajorState, - TransactionMinorState, - TransactionStateTransitionNotification, - succeedOrThrow, -} from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { - createSimpleTestkudosEnvironmentV3, - createWalletDaemonWithClient, -} from "../harness/environments.js"; -import { GlobalTestState, WalletClient } from "../harness/harness.js"; - -/** - * Scanning a payment URI for an order that another wallet has already claimed - * must report the conflict and must not leave a transaction behind. - * - * The user never saw a contract for such a scan, so there is nothing to show - * them and nothing for them to act on. The claim runs in the background, so - * the error is reported in the state transition that removes the transaction. - */ -export async function runPaymentClaimAlreadyClaimedTest(t: GlobalTestState) { - const { walletClient, merchant, merchantAdminAccessToken } = - await createSimpleTestkudosEnvironmentV3(t); - - const merchantClient = new TalerMerchantInstanceHttpClient( - merchant.makeInstanceBaseUrl(), - ); - - const orderResp = succeedOrThrow( - await merchantClient.createOrder(merchantAdminAccessToken, { - order: { - summary: "Buy me!", - amount: "TESTKUDOS:5", - fulfillment_url: "taler://fulfillment-success/thx", - } satisfies TalerMerchantApi.Order, - }), - ); - const orderStatus = succeedOrThrow( - await merchantClient.getOrderDetails( - merchantAdminAccessToken, - orderResp.order_id, - ), - ); - t.assertTrue(orderStatus.order_status === "unpaid"); - const talerPayUri = orderStatus.taler_pay_uri; - - // The first wallet claims the order, so that the merchant answers any - // further claim with a conflict. - const firstPrep = await walletClient.call( - WalletApiOperation.PreparePayForUriV2, - { talerPayUri }, - ); - await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: firstPrep.transactionId, - txState: { - major: TransactionMajorState.Dialog, - minor: TransactionMinorState.Proposed, - }, - }); - - let w2 = await createWalletDaemonWithClient(t, { - name: "w2", - persistent: true, - }); - - async function restartWallet(): Promise<void> { - w2.walletClient.remoteWallet?.close(); - await w2.walletService.stop(); - w2 = await createWalletDaemonWithClient(t, { - name: "w2", - persistent: true, - }); - } - - async function hasTransaction( - walletClient: WalletClient, - transactionId: TransactionIdStr, - ): Promise<boolean> { - try { - await walletClient.call(WalletApiOperation.GetTransactionById, { - transactionId, - }); - return true; - } catch (e) { - return false; - } - } - - /** - * Wait for a claim to be given up on because the order is already claimed. - * - * Must be set up before the scan that triggers it: the waiter only sees - * notifications that arrive after the condition is registered, and the - * claim fails on the task scheduler's schedule. - */ - function claimDiscardedCond( - walletClient: WalletClient, - ): Promise<TransactionStateTransitionNotification> { - return walletClient.waitForNotificationCond((n) => - n.type === NotificationType.TransactionStateTransition && - n.newTxState.major === TransactionMajorState.Deleted && - n.errorInfo?.code === TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED - ? n - : false, - ); - } - - // The second wallet scans the URI of the order it cannot have. - - await t.runSpanAsync("scan reports the conflict", async () => { - const discarded = claimDiscardedCond(w2.walletClient); - const scan = await w2.walletClient.call( - WalletApiOperation.PreparePayForUriV2, - { talerPayUri }, - ); - - const notif = await discarded; - t.assertDeepEqual(notif.transactionId, scan.transactionId); - t.assertDeepEqual( - notif.oldTxState.minor, - TransactionMinorState.ClaimProposal, - ); - - // Nothing is left behind, neither as a hidden record nor in the list. - t.assertTrue(!(await hasTransaction(w2.walletClient, scan.transactionId))); - const txs = await w2.walletClient.call( - WalletApiOperation.GetTransactionsV2, - { includeAll: true }, - ); - t.assertTrue( - !txs.transactions.some((x) => x.transactionId === scan.transactionId), - ); - - await restartWallet(); - t.assertTrue(!(await hasTransaction(w2.walletClient, scan.transactionId))); - - // Scanning again claims anew instead of handing back the gone - // transaction, and gives up on that claim the same way. - const discardedAgain = claimDiscardedCond(w2.walletClient); - const rescan = await w2.walletClient.call( - WalletApiOperation.PreparePayForUriV2, - { talerPayUri }, - ); - t.assertTrue(rescan.transactionId !== scan.transactionId); - const notifAgain = await discardedAgain; - t.assertDeepEqual(notifAgain.transactionId, rescan.transactionId); - t.assertTrue( - !(await hasTransaction(w2.walletClient, rescan.transactionId)), - ); - }); - - // The wallet that does hold the claim keeps its offer. - await t.runSpanAsync("claim holder is unaffected", async () => { - const tx = await walletClient.call(WalletApiOperation.GetTransactionById, { - transactionId: firstPrep.transactionId, - }); - t.assertDeepEqual(tx.txState.major, TransactionMajorState.Dialog); - }); -} - -runPaymentClaimAlreadyClaimedTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-payment-claim.ts b/packages/taler-harness/src/integrationtests/test-payment-claim.ts @@ -22,6 +22,7 @@ import { succeedOrThrow, TalerErrorCode, TalerMerchantInstanceHttpClient, + TransactionIdStr, TransactionMajorState, TransactionMinorState, TransactionStateTransitionNotification, @@ -32,7 +33,7 @@ import { createWalletDaemonWithClient, withdrawViaBankV3, } from "../harness/environments.js"; -import { GlobalTestState } from "../harness/harness.js"; +import { GlobalTestState, WalletClient } from "../harness/harness.js"; /** * Run test where a wallet tries to claim an already claimed order. @@ -52,11 +53,34 @@ export async function runPaymentClaimTest(t: GlobalTestState) { merchant.makeInstanceBaseUrl(), ); - const w2 = await createWalletDaemonWithClient(t, { + let w2 = await createWalletDaemonWithClient(t, { name: "w2", persistent: true, }); + async function restartWallet(): Promise<void> { + w2.walletClient.remoteWallet?.close(); + await w2.walletService.stop(); + w2 = await createWalletDaemonWithClient(t, { + name: "w2", + persistent: true, + }); + } + + async function hasTransaction( + walletClient: WalletClient, + transactionId: TransactionIdStr, + ): Promise<boolean> { + try { + await walletClient.call(WalletApiOperation.GetTransactionById, { + transactionId, + }); + return true; + } catch { + return false; + } + } + // Withdraw digital cash into the wallet. const wres = await withdrawViaBankV3(t, { @@ -114,8 +138,10 @@ export async function runPaymentClaimTest(t: GlobalTestState) { * Must be set up before the scan that triggers it: the waiter only sees * notifications that arrive after the condition is registered. */ - function claimDiscardedCond(): Promise<TransactionStateTransitionNotification> { - return w2.walletClient.waitForNotificationCond((n) => + function claimDiscardedCond( + walletClient: WalletClient, + ): Promise<TransactionStateTransitionNotification> { + return walletClient.waitForNotificationCond((n) => n.type === NotificationType.TransactionStateTransition && n.newTxState.major === TransactionMajorState.Deleted && n.errorInfo?.code === TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED @@ -124,18 +150,54 @@ export async function runPaymentClaimTest(t: GlobalTestState) { ); } - // The second wallet can't claim the order anymore. The claim is given up on - // and its transaction removed, with the reason in the transition. - const claimOneDiscarded = claimDiscardedCond(); - const claimOne = await w2.walletClient.call( - WalletApiOperation.PreparePayForUriV2, - { - talerPayUri, - }, - ); + await t.runSpanAsync("claim-conflict-before-payment", async () => { + const claimOneDiscarded = claimDiscardedCond(w2.walletClient); + const claimOne = await w2.walletClient.call( + WalletApiOperation.PreparePayForUriV2, + { talerPayUri }, + ); - const claimOneNotif = await claimOneDiscarded; - t.assertDeepEqual(claimOneNotif.transactionId, claimOne.transactionId); + const claimOneNotif = await claimOneDiscarded; + t.assertDeepEqual(claimOneNotif.transactionId, claimOne.transactionId); + t.assertDeepEqual( + claimOneNotif.oldTxState.minor, + TransactionMinorState.ClaimProposal, + ); + t.assertTrue( + !(await hasTransaction(w2.walletClient, claimOne.transactionId)), + ); + + const txs = await w2.walletClient.call( + WalletApiOperation.GetTransactionsV2, + { includeAll: true }, + ); + t.assertTrue( + !txs.transactions.some((x) => x.transactionId === claimOne.transactionId), + ); + + await restartWallet(); + t.assertTrue( + !(await hasTransaction(w2.walletClient, claimOne.transactionId)), + ); + + const claimAgainDiscarded = claimDiscardedCond(w2.walletClient); + const claimAgain = await w2.walletClient.call( + WalletApiOperation.PreparePayForUriV2, + { talerPayUri }, + ); + t.assertTrue(claimAgain.transactionId !== claimOne.transactionId); + const claimAgainNotif = await claimAgainDiscarded; + t.assertDeepEqual(claimAgainNotif.transactionId, claimAgain.transactionId); + t.assertTrue( + !(await hasTransaction(w2.walletClient, claimAgain.transactionId)), + ); + + const holderTx = await walletClient.call( + WalletApiOperation.GetTransactionById, + { transactionId: preparePayResult.transactionId }, + ); + t.assertDeepEqual(holderTx.txState.major, TransactionMajorState.Dialog); + }); await walletClient.call(WalletApiOperation.ConfirmPay, { transactionId: preparePayResult.transactionId, @@ -155,7 +217,7 @@ export async function runPaymentClaimTest(t: GlobalTestState) { await w2.walletClient.call(WalletApiOperation.ClearDb, {}); - const claimTwoDiscarded = claimDiscardedCond(); + const claimTwoDiscarded = claimDiscardedCond(w2.walletClient); const claimTwo = await w2.walletClient.call( WalletApiOperation.PreparePayForUriV2, { diff --git a/packages/taler-harness/src/integrationtests/test-payment-idempotency.ts b/packages/taler-harness/src/integrationtests/test-payment-idempotency.ts @@ -1,166 +0,0 @@ -/* - 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 { - succeedOrThrow, - TalerMerchantInstanceHttpClient, - TransactionMajorState, - TransactionMinorState, -} from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { - createSimpleTestkudosEnvironmentV3, - withdrawViaBankV3, -} from "../harness/environments.js"; -import { GlobalTestState } from "../harness/harness.js"; - -/** - * Test the wallet-core payment API, especially that repeated operations - * return the expected result. - */ -export async function runPaymentIdempotencyTest(t: GlobalTestState) { - // Set up test environment - - const { - walletClient, - bankClient, - exchange, - merchant, - merchantAdminAccessToken, - } = await createSimpleTestkudosEnvironmentV3(t); - - // Withdraw digital cash into the wallet. - - const wres = await withdrawViaBankV3(t, { - walletClient, - bankClient, - exchange, - amount: "TESTKUDOS:20", - }); - - await wres.withdrawalFinishedCond; - - const merchantClient = new TalerMerchantInstanceHttpClient( - merchant.makeInstanceBaseUrl(), - ); - - // Set up order. - - const orderResp = succeedOrThrow( - await merchantClient.createOrder(merchantAdminAccessToken, { - order: { - summary: "Buy me!", - amount: "TESTKUDOS:5", - fulfillment_url: "taler://fulfillment-success/thx", - }, - }), - ); - - let orderStatus = succeedOrThrow( - await merchantClient.getOrderDetails( - merchantAdminAccessToken, - orderResp.order_id, - ), - ); - - t.assertTrue(orderStatus.order_status === "unpaid"); - - const talerPayUri = orderStatus.taler_pay_uri; - - // Make wallet pay for the order - - const preparePayResult = await walletClient.call( - WalletApiOperation.PreparePayForUriV2, - { - talerPayUri: orderStatus.taler_pay_uri, - }, - ); - - const preparePayResultRep = await walletClient.call( - WalletApiOperation.PreparePayForUriV2, - { - talerPayUri: orderStatus.taler_pay_uri, - }, - ); - - // Preparing the payment twice must return the same transaction. - t.assertDeepEqual( - preparePayResultRep.transactionId, - preparePayResult.transactionId, - ); - - await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: preparePayResult.transactionId, - txState: { - major: TransactionMajorState.Dialog, - minor: TransactionMinorState.Proposed, - }, - }); - - const proposalTransactionId = preparePayResult.transactionId; - - const confirmPayResult = await walletClient.call( - WalletApiOperation.ConfirmPay, - { - transactionId: proposalTransactionId, - choiceIndex: 0, - }, - ); - - console.log("confirm pay result", confirmPayResult); - - await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); - - // Check if payment was successful. - - orderStatus = succeedOrThrow( - await merchantClient.getOrderDetails( - merchantAdminAccessToken, - orderResp.order_id, - ), - ); - - t.assertTrue(orderStatus.order_status === "paid"); - - const preparePayResultAfter = await walletClient.call( - WalletApiOperation.PreparePayForUriV2, - { - talerPayUri, - }, - ); - - console.log("result after:", preparePayResultAfter); - - // Preparing an already paid order returns the existing, done transaction. - t.assertDeepEqual( - preparePayResultAfter.transactionId, - preparePayResult.transactionId, - ); - - await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: preparePayResultAfter.transactionId, - txState: { - major: TransactionMajorState.Done, - }, - }); - - await t.shutdown(); -} - -runPaymentIdempotencyTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-payment-zero.ts b/packages/taler-harness/src/integrationtests/test-payment-zero.ts @@ -1,66 +0,0 @@ -/* - 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 { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState } from "../harness/harness.js"; -import { - createSimpleTestkudosEnvironmentV3, - makeTestPaymentV2, -} from "../harness/environments.js"; -import { TransactionMajorState, TransactionType } from "@gnu-taler/taler-util"; - -/** - * Run test for a payment for a "free" order with - * an amount of zero. - */ -export async function runPaymentZeroTest(t: GlobalTestState) { - // Set up test environment - - const { walletClient, merchant, merchantAdminAccessToken } = - await createSimpleTestkudosEnvironmentV3(t); - - const before = await walletClient.call(WalletApiOperation.GetTransactions, {}); - t.assertDeepEqual(before.transactions.length, 0); - - await makeTestPaymentV2(t, { - walletClient, - merchant, - merchantAdminAccessToken, - order: { - summary: "I am free!", - amount: "TESTKUDOS:0", - fulfillment_url: "taler://fulfillment-success/thx", - }, - }); - - await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); - - const transactions = await walletClient.call( - WalletApiOperation.GetTransactions, - {}, - ); - - t.assertDeepEqual(transactions.transactions.length, 1); - const payment = transactions.transactions[0]; - t.assertDeepEqual(payment.type, TransactionType.Payment); - t.assertDeepEqual(payment.txState.major, TransactionMajorState.Done); - t.assertAmountEquals(payment.amountRaw, "TESTKUDOS:0"); -} - -runPaymentZeroTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-payment.ts b/packages/taler-harness/src/integrationtests/test-payment.ts @@ -17,7 +17,15 @@ /** * Imports. */ -import { TalerMerchantApi, j2s } from "@gnu-taler/taler-util"; +import { + j2s, + succeedOrThrow, + TalerMerchantApi, + TalerMerchantInstanceHttpClient, + TransactionMajorState, + TransactionMinorState, + TransactionType, +} from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { createSimpleTestkudosEnvironmentV3, @@ -40,6 +48,39 @@ export async function runPaymentTest(t: GlobalTestState) { merchantAdminAccessToken, } = await createSimpleTestkudosEnvironmentV3(t); + await t.runSpanAsync("zero-value-payment", async () => { + const before = await walletClient.call( + WalletApiOperation.GetTransactions, + {}, + ); + t.assertDeepEqual(before.transactions.length, 0); + + await makeTestPaymentV2(t, { + walletClient, + merchant, + merchantAdminAccessToken, + order: { + summary: "I am free!", + amount: "TESTKUDOS:0", + fulfillment_url: "taler://fulfillment-success/thx", + }, + }); + await walletClient.call( + WalletApiOperation.TestingWaitTransactionsFinal, + {}, + ); + + const after = await walletClient.call( + WalletApiOperation.GetTransactions, + {}, + ); + t.assertDeepEqual(after.transactions.length, 1); + const payment = after.transactions[0]; + t.assertDeepEqual(payment.type, TransactionType.Payment); + t.assertDeepEqual(payment.txState.major, TransactionMajorState.Done); + t.assertAmountEquals(payment.amountRaw, "TESTKUDOS:0"); + }); + // Withdraw digital cash into the wallet. t.assertTrue(bankClient !== undefined); @@ -52,19 +93,75 @@ export async function runPaymentTest(t: GlobalTestState) { await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); - const order = { - summary: "Buy me!", - amount: "TESTKUDOS:5", - fulfillment_url: "taler://fulfillment-success/thx", - } satisfies TalerMerchantApi.Order; - - await makeTestPaymentV2(t, { - walletClient, - merchant, - order, - merchantAdminAccessToken, + await t.runSpanAsync("idempotent-payment", async () => { + const merchantClient = new TalerMerchantInstanceHttpClient( + merchant.makeInstanceBaseUrl(), + ); + const orderResp = succeedOrThrow( + await merchantClient.createOrder(merchantAdminAccessToken, { + order: { + summary: "Buy me!", + amount: "TESTKUDOS:5", + fulfillment_url: "taler://fulfillment-success/thx", + }, + }), + ); + let orderStatus = succeedOrThrow( + await merchantClient.getOrderDetails( + merchantAdminAccessToken, + orderResp.order_id, + ), + ); + t.assertDeepEqual(orderStatus.order_status, "unpaid"); + const talerPayUri = orderStatus.taler_pay_uri; + + const firstPrepare = await walletClient.call( + WalletApiOperation.PreparePayForUriV2, + { talerPayUri }, + ); + const repeatedPrepare = await walletClient.call( + WalletApiOperation.PreparePayForUriV2, + { talerPayUri }, + ); + t.assertDeepEqual( + repeatedPrepare.transactionId, + firstPrepare.transactionId, + ); + + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: firstPrepare.transactionId, + txState: { + major: TransactionMajorState.Dialog, + minor: TransactionMinorState.Proposed, + }, + }); + await walletClient.call(WalletApiOperation.ConfirmPay, { + transactionId: firstPrepare.transactionId, + choiceIndex: 0, + }); + await walletClient.call( + WalletApiOperation.TestingWaitTransactionsFinal, + {}, + ); + + orderStatus = succeedOrThrow( + await merchantClient.getOrderDetails( + merchantAdminAccessToken, + orderResp.order_id, + ), + ); + t.assertDeepEqual(orderStatus.order_status, "paid"); + + const afterPayment = await walletClient.call( + WalletApiOperation.PreparePayForUriV2, + { talerPayUri }, + ); + t.assertDeepEqual(afterPayment.transactionId, firstPrepare.transactionId); + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: afterPayment.transactionId, + txState: { major: TransactionMajorState.Done }, + }); }); - await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); // Test JSON normalization of contract terms: Does the wallet // agree with the merchant? diff --git a/packages/taler-harness/src/integrationtests/test-repurchase-v1.ts b/packages/taler-harness/src/integrationtests/test-repurchase-v1.ts @@ -1,328 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2023 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 { - ConfirmPayResultType, - j2s, - succeedOrThrow, - TalerMerchantInstanceHttpClient, - TransactionMajorState, - TransactionMinorState, - TransactionType, -} from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { - createSimpleTestkudosEnvironmentV3, - withdrawViaBankV3, -} from "../harness/environments.js"; -import { GlobalTestState, harnessHttpLib } from "../harness/harness.js"; - -/** - * Repurchase detection with v1 contracts. - */ -export async function runRepurchaseV1Test(t: GlobalTestState) { - // Set up test environment - - const { - walletClient, - exchange, - merchant, - merchantAdminAccessToken, - bankClient, - } = await createSimpleTestkudosEnvironmentV3(t); - - // Withdraw digital cash into the wallet. - - await withdrawViaBankV3(t, { - walletClient, - bankClient, - exchange, - amount: "TESTKUDOS:20", - }); - - await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); - - const merchantClient = new TalerMerchantInstanceHttpClient( - merchant.makeInstanceBaseUrl(), - ); - - const orderOneResp = succeedOrThrow( - await merchantClient.createOrder(merchantAdminAccessToken, { - order: { - version: 1, - summary: "Buy me", - fulfillment_url: "https://example.com/test", - choices: [ - { - amount: "TESTKUDOS:5", - description: "foo", - }, - { - amount: "TESTKUDOS:2", - description: "bla", - }, - ], - }, - }), - ); - - let orderOneStatus = succeedOrThrow( - await merchantClient.getOrderDetails( - merchantAdminAccessToken, - orderOneResp.order_id, - { - sessionId: "session1", - }, - ), - ); - - t.assertTrue(orderOneStatus.order_status === "unpaid"); - - const preparePayOneResult = await walletClient.call( - WalletApiOperation.PreparePayForUriV2, - { - talerPayUri: orderOneStatus.taler_pay_uri, - }, - ); - - await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: preparePayOneResult.transactionId, - txState: { - major: TransactionMajorState.Dialog, - minor: "*", - }, - }); - - const confirmPayResp = await walletClient.call( - WalletApiOperation.ConfirmPay, - { - transactionId: preparePayOneResult.transactionId, - choiceIndex: 0, - }, - ); - - t.assertTrue(confirmPayResp.type === ConfirmPayResultType.Done); - - { - const resp = succeedOrThrow( - await merchantClient.getOrderDetails( - merchantAdminAccessToken, - orderOneResp.order_id, - { - sessionId: "session1", - }, - ), - ); - console.log(`status orderId one, session session1`, resp); - } - - const orderTwoResp = succeedOrThrow( - await merchantClient.createOrder(merchantAdminAccessToken, { - order: { - version: 1, - summary: "Buy me", - fulfillment_url: "https://example.com/test", - choices: [ - { - amount: "TESTKUDOS:5", - description: "foo", - }, - { - amount: "TESTKUDOS:2", - description: "bla", - }, - ], - }, - }), - ); - - let orderTwoStatus = succeedOrThrow( - await merchantClient.getOrderDetails( - merchantAdminAccessToken, - orderTwoResp.order_id, - { - sessionId: "session2", - }, - ), - ); - - t.assertTrue(orderTwoStatus.order_status === "unpaid"); - - const orderLongpollUrl = new URL( - `orders/${orderTwoResp.order_id}`, - merchant.makeInstanceBaseUrl(), - ); - if (orderTwoResp.token) { - orderLongpollUrl.searchParams.set("token", orderTwoResp.token); - } - orderLongpollUrl.searchParams.set("timeout_ms", "60000"); - orderLongpollUrl.searchParams.set("session_id", "session2"); - - const longpollPromise = harnessHttpLib.fetch(orderLongpollUrl.href); - - const preparePayTwoResult = await walletClient.call( - WalletApiOperation.PreparePayForUriV2, - { - talerPayUri: orderTwoStatus.taler_pay_uri, - }, - ); - - // Repurchase should be detected - await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: preparePayTwoResult.transactionId, - txState: { - major: TransactionMajorState.Failed, - minor: TransactionMinorState.Repurchase, - }, - }); - - t.logStep("start-wait-longpoll-promise"); - await longpollPromise; - t.logStep("done-wait-longpoll-promise"); - - { - const resp1 = succeedOrThrow( - await merchantClient.getOrderDetails( - merchantAdminAccessToken, - orderTwoResp.order_id, - { - sessionId: "session2", - }, - ), - ); - console.log("status orderId two, sessionId session2", j2s(resp1)); - - const resp2 = succeedOrThrow( - await merchantClient.getOrderDetails( - merchantAdminAccessToken, - orderOneResp.order_id, - { - sessionId: "session2", - }, - ), - ); - console.log("status orderId one, sessionId session2", j2s(resp2)); - console.log(resp2); - } - - // Order three - - const orderThreeResp = succeedOrThrow( - await merchantClient.createOrder(merchantAdminAccessToken, { - order: { - version: 1, - summary: "Buy me", - fulfillment_url: "https://example.com/test", - choices: [ - { - amount: "TESTKUDOS:5", - description: "foo", - }, - { - amount: "TESTKUDOS:2", - description: "bla", - }, - ], - }, - }), - ); - - let orderThreeStatus = succeedOrThrow( - await merchantClient.getOrderDetails( - merchantAdminAccessToken, - orderThreeResp.order_id, - { - // Go back to session1 - sessionId: "session1", - }, - ), - ); - - t.assertTrue(orderThreeStatus.order_status === "unpaid"); - - const preparePayThreeResult = await walletClient.call( - WalletApiOperation.PreparePayForUriV2, - { - talerPayUri: orderThreeStatus.taler_pay_uri, - }, - ); - - // Repurchase should be detected - await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: preparePayThreeResult.transactionId, - txState: { - major: TransactionMajorState.Failed, - minor: TransactionMinorState.Repurchase, - }, - }); - - await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); - - { - const txnsNormal = await walletClient.call( - WalletApiOperation.GetTransactionsV2, - {}, - ); - - console.log(j2s(txnsNormal)); - - t.assertDeepEqual(txnsNormal.transactions.length, 2); - } - - // The list of all transactions should also include repurchases. - { - const txnsAll = await walletClient.call( - WalletApiOperation.GetTransactionsV2, - { - includeAll: true, - }, - ); - - console.log(j2s(txnsAll)); - - const numPayments = txnsAll.transactions.filter( - (x) => x.type === TransactionType.Payment, - ).length; - - t.assertDeepEqual(numPayments, 3); - } - - // Test that deleting the original transaction also deletes the repurchase transaction. - - await walletClient.call(WalletApiOperation.DeleteTransaction, { - transactionId: preparePayOneResult.transactionId, - }); - - { - const txnsAll = await walletClient.call( - WalletApiOperation.GetTransactionsV2, - { - includeAll: true, - }, - ); - - const numPayments = txnsAll.transactions.filter( - (x) => x.type === TransactionType.Payment, - ).length; - - t.assertDeepEqual(numPayments, 0); - } -} - -runRepurchaseV1Test.suites = ["wallet", "shared-env"]; diff --git a/packages/taler-harness/src/integrationtests/test-repurchase.ts b/packages/taler-harness/src/integrationtests/test-repurchase.ts @@ -21,6 +21,7 @@ import { ConfirmPayResultType, j2s, succeedOrThrow, + TalerMerchantApi, TalerMerchantInstanceHttpClient, TransactionMajorState, TransactionMinorState, @@ -33,9 +34,10 @@ import { } from "../harness/environments.js"; import { GlobalTestState, harnessHttpLib } from "../harness/harness.js"; +/** + * Check repurchase detection for legacy and version 1 contracts. + */ export async function runRepurchaseTest(t: GlobalTestState) { - // Set up test environment - const { walletClient, exchange, @@ -44,214 +46,191 @@ export async function runRepurchaseTest(t: GlobalTestState) { bankClient, } = await createSimpleTestkudosEnvironmentV3(t); - // Withdraw digital cash into the wallet. - await withdrawViaBankV3(t, { walletClient, bankClient, exchange, amount: "TESTKUDOS:20", }); - await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); const merchantClient = new TalerMerchantInstanceHttpClient( merchant.makeInstanceBaseUrl(), ); - const orderOneResp = succeedOrThrow( - await merchantClient.createOrder(merchantAdminAccessToken, { - order: { - summary: "Buy me", - amount: "TESTKUDOS:5", - fulfillment_url: "https://example.com/test", - }, - }), - ); - - let orderOneStatus = succeedOrThrow( - await merchantClient.getOrderDetails( - merchantAdminAccessToken, - orderOneResp.order_id, - { - sessionId: "session1", - }, - ), - ); - - t.assertTrue(orderOneStatus.order_status === "unpaid"); - - const preparePayOneResult = await walletClient.call( - WalletApiOperation.PreparePayForUriV2, - { - talerPayUri: orderOneStatus.taler_pay_uri, - }, - ); - - await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: preparePayOneResult.transactionId, - txState: { - major: TransactionMajorState.Dialog, - minor: TransactionMinorState.Proposed, - }, - }); - - const confirmPayResp = await walletClient.call( - WalletApiOperation.ConfirmPay, - { - transactionId: preparePayOneResult.transactionId, - choiceIndex: 0, - }, - ); - - t.assertTrue(confirmPayResp.type === ConfirmPayResultType.Done); - - const orderTwoResp = succeedOrThrow( - await merchantClient.createOrder(merchantAdminAccessToken, { - order: { + function makeOrder( + version: 0 | 1, + fulfillmentUrl: string, + ): TalerMerchantApi.Order { + if (version === 0) { + return { summary: "Buy me", amount: "TESTKUDOS:5", - fulfillment_url: "https://example.com/test", - }, - }), - ); - - let orderTwoStatus = succeedOrThrow( - await merchantClient.getOrderDetails( - merchantAdminAccessToken, - orderTwoResp.order_id, - { - sessionId: "session2", - }, - ), - ); - - t.assertTrue(orderTwoStatus.order_status === "unpaid"); - - const orderLongpollUrl = new URL( - `orders/${orderTwoResp.order_id}`, - merchant.makeInstanceBaseUrl(), - ); - if (orderTwoResp.token) { - orderLongpollUrl.searchParams.set("token", orderTwoResp.token); + fulfillment_url: fulfillmentUrl, + }; + } + return { + version: 1, + summary: "Buy me", + fulfillment_url: fulfillmentUrl, + choices: [ + { + amount: "TESTKUDOS:5", + description: "foo", + }, + { + amount: "TESTKUDOS:2", + description: "bla", + }, + ], + }; } - orderLongpollUrl.searchParams.set("timeout_ms", "60000"); - orderLongpollUrl.searchParams.set("session_id", "session2"); - - const longpollPromise = harnessHttpLib.fetch(orderLongpollUrl.href); - const preparePayTwoResult = await walletClient.call( - WalletApiOperation.PreparePayForUriV2, - { - talerPayUri: orderTwoStatus.taler_pay_uri, - }, - ); - - // Repurchase should be detected - await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: preparePayTwoResult.transactionId, - txState: { - major: TransactionMajorState.Failed, - minor: TransactionMinorState.Repurchase, - }, - }); - - t.logStep("start-wait-longpoll-promise"); - await longpollPromise; - t.logStep("done-wait-longpoll-promise"); - - // Order three - - const orderThreeResp = succeedOrThrow( - await merchantClient.createOrder(merchantAdminAccessToken, { - order: { - summary: "Buy me", - amount: "TESTKUDOS:5", - fulfillment_url: "https://example.com/test", - }, - }), - ); - - let orderThreeStatus = succeedOrThrow( - await merchantClient.getOrderDetails( - merchantAdminAccessToken, - orderThreeResp.order_id, - { - // Go back to session1 - sessionId: "session1", - }, - ), - ); - - t.assertTrue(orderThreeStatus.order_status === "unpaid"); - - const preparePayThreeResult = await walletClient.call( - WalletApiOperation.PreparePayForUriV2, - { - talerPayUri: orderThreeStatus.taler_pay_uri, - }, - ); - - // Repurchase should be detected - await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: preparePayThreeResult.transactionId, - txState: { - major: TransactionMajorState.Failed, - minor: TransactionMinorState.Repurchase, - }, - }); - - await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); - - { - const txnsNormal = await walletClient.call( - WalletApiOperation.GetTransactionsV2, - {}, - ); - - console.log(j2s(txnsNormal)); - - t.assertDeepEqual(txnsNormal.transactions.length, 2); - } - - // The list of all transactions should also include repurchases. - { - const txnsAll = await walletClient.call( - WalletApiOperation.GetTransactionsV2, - { - includeAll: true, - }, - ); - - console.log(j2s(txnsAll)); - - const numPayments = txnsAll.transactions.filter( - (x) => x.type === TransactionType.Payment, - ).length; - - t.assertDeepEqual(numPayments, 3); + async function runScenario(version: 0 | 1): Promise<void> { + await t.runSpanAsync(`contract-v${version}`, async () => { + const fulfillmentUrl = `https://example.com/repurchase-v${version}`; + const sessionOne = `repurchase-v${version}-session-1`; + const sessionTwo = `repurchase-v${version}-session-2`; + + const orderOneResp = succeedOrThrow( + await merchantClient.createOrder(merchantAdminAccessToken, { + order: makeOrder(version, fulfillmentUrl), + }), + ); + const orderOneStatus = succeedOrThrow( + await merchantClient.getOrderDetails( + merchantAdminAccessToken, + orderOneResp.order_id, + { sessionId: sessionOne }, + ), + ); + t.assertTrue(orderOneStatus.order_status === "unpaid"); + + const preparePayOneResult = await walletClient.call( + WalletApiOperation.PreparePayForUriV2, + { talerPayUri: orderOneStatus.taler_pay_uri }, + ); + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: preparePayOneResult.transactionId, + txState: { + major: TransactionMajorState.Dialog, + minor: version === 0 ? TransactionMinorState.Proposed : "*", + }, + }); + + const confirmPayResp = await walletClient.call( + WalletApiOperation.ConfirmPay, + { + transactionId: preparePayOneResult.transactionId, + choiceIndex: 0, + }, + ); + t.assertTrue(confirmPayResp.type === ConfirmPayResultType.Done); + + const orderTwoResp = succeedOrThrow( + await merchantClient.createOrder(merchantAdminAccessToken, { + order: makeOrder(version, fulfillmentUrl), + }), + ); + const orderTwoStatus = succeedOrThrow( + await merchantClient.getOrderDetails( + merchantAdminAccessToken, + orderTwoResp.order_id, + { sessionId: sessionTwo }, + ), + ); + t.assertTrue(orderTwoStatus.order_status === "unpaid"); + + const orderLongpollUrl = new URL( + `orders/${orderTwoResp.order_id}`, + merchant.makeInstanceBaseUrl(), + ); + if (orderTwoResp.token) { + orderLongpollUrl.searchParams.set("token", orderTwoResp.token); + } + orderLongpollUrl.searchParams.set("timeout_ms", "60000"); + orderLongpollUrl.searchParams.set("session_id", sessionTwo); + const longpollPromise = harnessHttpLib.fetch(orderLongpollUrl.href); + + const preparePayTwoResult = await walletClient.call( + WalletApiOperation.PreparePayForUriV2, + { talerPayUri: orderTwoStatus.taler_pay_uri }, + ); + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: preparePayTwoResult.transactionId, + txState: { + major: TransactionMajorState.Failed, + minor: TransactionMinorState.Repurchase, + }, + }); + await longpollPromise; + + const orderThreeResp = succeedOrThrow( + await merchantClient.createOrder(merchantAdminAccessToken, { + order: makeOrder(version, fulfillmentUrl), + }), + ); + const orderThreeStatus = succeedOrThrow( + await merchantClient.getOrderDetails( + merchantAdminAccessToken, + orderThreeResp.order_id, + { sessionId: sessionOne }, + ), + ); + t.assertTrue(orderThreeStatus.order_status === "unpaid"); + + const preparePayThreeResult = await walletClient.call( + WalletApiOperation.PreparePayForUriV2, + { talerPayUri: orderThreeStatus.taler_pay_uri }, + ); + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: preparePayThreeResult.transactionId, + txState: { + major: TransactionMajorState.Failed, + minor: TransactionMinorState.Repurchase, + }, + }); + + await walletClient.call( + WalletApiOperation.TestingWaitTransactionsFinal, + {}, + ); + const txnsNormal = await walletClient.call( + WalletApiOperation.GetTransactionsV2, + {}, + ); + console.log(j2s(txnsNormal)); + t.assertDeepEqual(txnsNormal.transactions.length, 2); + + const txnsAll = await walletClient.call( + WalletApiOperation.GetTransactionsV2, + { includeAll: true }, + ); + t.assertDeepEqual( + txnsAll.transactions.filter((x) => x.type === TransactionType.Payment) + .length, + 3, + ); + + await walletClient.call(WalletApiOperation.DeleteTransaction, { + transactionId: preparePayOneResult.transactionId, + }); + const txnsAfterDeletion = await walletClient.call( + WalletApiOperation.GetTransactionsV2, + { includeAll: true }, + ); + t.assertDeepEqual( + txnsAfterDeletion.transactions.filter( + (x) => x.type === TransactionType.Payment, + ).length, + 0, + ); + }); } - // Test that deleting the original transaction also deletes the repurchase transaction. - - await walletClient.call(WalletApiOperation.DeleteTransaction, { - transactionId: preparePayOneResult.transactionId, - }); - - { - const txnsAll = await walletClient.call( - WalletApiOperation.GetTransactionsV2, - { - includeAll: true, - }, - ); - - const numPayments = txnsAll.transactions.filter( - (x) => x.type === TransactionType.Payment, - ).length; - - t.assertDeepEqual(numPayments, 0); - } + await runScenario(0); + await runScenario(1); } runRepurchaseTest.suites = ["wallet", "shared-env"]; +runRepurchaseTest.timeoutMs = 120_000; diff --git a/packages/taler-harness/src/integrationtests/test-tops-aml-legi.ts b/packages/taler-harness/src/integrationtests/test-tops-aml-legi.ts @@ -1,91 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2025 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 { - j2s, - Logger, - succeedOrThrow, - TalerExchangeHttpClient, - TalerMerchantInstanceHttpClient, -} from "@gnu-taler/taler-util"; -import { startFakeChallenger } from "../harness/fake-challenger.js"; -import { GlobalTestState, harnessHttpLib } from "../harness/harness.js"; -import { - createTopsEnvironment, - doTopsAcceptTos, - doTopsKycAuth, -} from "../harness/tops.js"; - -export const logger = new Logger("test-tops-aml.ts"); - -export async function runTopsAmlLegiTest(t: GlobalTestState) { - // Set up test environment - const { - exchange, - officerAcc, - merchant, - exchangeBankAccount, - wireGatewayApi, - merchantAdminAccessToken, - bank, - } = await createTopsEnvironment(t); - - const challenger = await startFakeChallenger({ - port: 6001, - addressType: "postal-ch", - }); - - const merchantClient = new TalerMerchantInstanceHttpClient( - merchant.makeInstanceBaseUrl(), - ); - const exchangeClient = new TalerExchangeHttpClient(exchange.baseUrl, { - httpClient: harnessHttpLib, - }); - - // Do KYC auth transfer - const { accessToken } = await doTopsKycAuth(t, { - merchantClient, - exchangeBankAccount, - wireGatewayApi, - merchantAdminAccessToken, - bank, - }); - - // Accept ToS - await doTopsAcceptTos(t, { - accessToken, - exchangeClient, - merchantClient, - merchant, - merchantAdminAccessToken, - }); - - const legis = succeedOrThrow( - await exchangeClient.getAmlLegitimizations(officerAcc), - ); - - console.log(j2s(legis)); - - t.assertDeepEqual( - legis.measures[0].measures.measures[0].prog_name, - "check-tos", - ); -} - -runTopsAmlLegiTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-tops-aml-measures.ts b/packages/taler-harness/src/integrationtests/test-tops-aml-measures.ts @@ -204,6 +204,15 @@ export async function runTopsAmlMeasuresTest(t: GlobalTestState) { }); } + await t.runSpanAsync("repeat-postal-registration", async () => { + await decideReset(); + await decideMeasure("postal-registration"); + await fakeChallenger(challengerPostal, { + CONTACT_NAME: "Richard Stallman", + ADDRESS_LINES: "Bundesgasse 1\n1234 Bern", + }); + }); + { await decideMeasure("accept-tos"); await submitForm("accept-tos", { diff --git a/packages/taler-harness/src/integrationtests/test-tops-challenger-twice.ts b/packages/taler-harness/src/integrationtests/test-tops-challenger-twice.ts @@ -1,61 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2025 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 { Logger } from "@gnu-taler/taler-util"; -import { GlobalTestState } from "../harness/harness.js"; -import { setupMeasuresTestEnvironment } from "../harness/tops.js"; - -export const logger = new Logger("test-tops-aml-measures.ts"); - -/** - * Test that reproduces an exchange bug, where the exchange - * gets confused by the same account doing the same challenger - * authentication twice. - */ -export async function runTopsChallengerTwiceTest(t: GlobalTestState) { - const { - decideMeasure, - decideReset, - submitForm, - expectInvestigate, - expectNoInvestigate, - fakeChallenger, - challengerPostal, - } = await setupMeasuresTestEnvironment(t); - - { - await decideMeasure("postal-registration"); - await fakeChallenger(challengerPostal, { - CONTACT_NAME: "Richard Stallman", - ADDRESS_LINES: "Bundesgasse 1\n1234 Bern", - }); - } - - await decideReset(); - - { - await decideMeasure("postal-registration"); - await fakeChallenger(challengerPostal, { - CONTACT_NAME: "Richard Stallman", - ADDRESS_LINES: "Bundesgasse 1\n1234 Bern", - }); - } -} - -runTopsChallengerTwiceTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-tops-merchant-tos.ts b/packages/taler-harness/src/integrationtests/test-tops-merchant-tos.ts @@ -117,6 +117,20 @@ export async function runTopsMerchantTosTest(t: GlobalTestState) { t.assertTrue(myRow != null); t.assertDeepEqual(myRow.status, MerchantAccountKycStatus.READY); } + + await t.runSpanAsync("aml-legitimizations", async () => { + const legitimizations = succeedOrThrow( + await exchangeClient.getAmlLegitimizations(officerAcc), + ); + console.log(j2s(legitimizations)); + t.assertTrue( + legitimizations.measures.some((outcome) => + outcome.measures.measures.some( + (measure) => measure.prog_name === "check-tos", + ), + ), + ); + }); } runTopsMerchantTosTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-wallet-balance-notifications.ts b/packages/taler-harness/src/integrationtests/test-wallet-balance-notifications.ts @@ -1,122 +0,0 @@ -/* - 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 { - NotificationType, - TransactionMajorState, - TransactionMinorState, -} from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { createSimpleTestkudosEnvironmentV3 } from "../harness/environments.js"; -import { GlobalTestState } from "../harness/harness.js"; - -/** - * Test behavior when an order is deleted while the wallet is paying for it. - */ -export async function runWalletBalanceNotificationsTest(t: GlobalTestState) { - // Set up test environment - - const { walletClient, bankClient, exchange, walletService } = - await createSimpleTestkudosEnvironmentV3(t); - - const amount = "TESTKUDOS:20"; - - const user = await bankClient.createRandomBankUser(); - bankClient.setAuth({ - username: user.username, - password: user.password, - }); - - const wop = await bankClient.createWithdrawalOperation(user.username, amount); - - // Hand it to the wallet - - await walletClient.call(WalletApiOperation.GetWithdrawalDetailsForUri, { - talerWithdrawUri: wop.taler_withdraw_uri, - }); - - // Withdraw (AKA select) - - const balanceChangedNotif1 = walletClient.waitForNotificationCond( - (x) => x.type === NotificationType.BalanceChange, - ); - - const acceptRes = await walletClient.call( - WalletApiOperation.AcceptBankIntegratedWithdrawal, - { - exchangeBaseUrl: exchange.baseUrl, - talerWithdrawUri: wop.taler_withdraw_uri, - }, - ); - - t.logStep("wait-balance-notif-1"); - await balanceChangedNotif1; - t.logStep("done-wait-balance-notif-1"); - - const withdrawalFinishedCond = walletClient.waitForNotificationCond( - (x) => - x.type === NotificationType.TransactionStateTransition && - x.newTxState.major === TransactionMajorState.Done && - x.transactionId === acceptRes.transactionId, - ); - - // Confirm it - - await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: acceptRes.transactionId, - txState: { - major: TransactionMajorState.Pending, - minor: TransactionMinorState.BankConfirmTransfer, - }, - }); - - await bankClient.confirmWithdrawalOperation(user.username, { - withdrawalOperationId: wop.withdrawal_id, - }); - - await withdrawalFinishedCond; - - // Second withdrawal! - { - const wop2 = await bankClient.createWithdrawalOperation( - user.username, - "TESTKUDOS:5", - ); - - await walletClient.call(WalletApiOperation.GetWithdrawalDetailsForUri, { - talerWithdrawUri: wop2.taler_withdraw_uri, - }); - - const acceptRes = await walletClient.call( - WalletApiOperation.AcceptBankIntegratedWithdrawal, - { - exchangeBaseUrl: exchange.baseUrl, - talerWithdrawUri: wop2.taler_withdraw_uri, - }, - ); - - const bal = await walletClient.call(WalletApiOperation.GetBalances, {}); - t.assertAmountEquals(bal.balances[0].available, "TESTKUDOS:19.84"); - t.assertAmountEquals(bal.balances[0].pendingIncoming, "TESTKUDOS:4.90"); - - await walletService.stop(); - } -} - -runWalletBalanceNotificationsTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-wallet-cli-termination.ts b/packages/taler-harness/src/integrationtests/test-wallet-cli-termination.ts @@ -1,47 +0,0 @@ -/* - 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/> - */ - -/** - * Imports. - */ -import { AmountString } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; -import { createSimpleTestkudosEnvironmentV3 } from "../harness/environments.js"; -import { GlobalTestState, WalletCli } from "../harness/harness.js"; - -/** - * Test that run-until-done of taler-wallet-cli terminates. - */ -export async function runWalletCliTerminationTest(t: GlobalTestState) { - const coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")); - - const { exchange, bankClient } = - await createSimpleTestkudosEnvironmentV3(t, coinConfig, {}); - const wallet = new WalletCli(t, "termination"); - - await wallet.client.call(WalletApiOperation.WithdrawTestBalance, { - corebankApiBaseUrl: bankClient.baseUrl, - exchangeBaseUrl: exchange.baseUrl, - amount: "TESTKUDOS:20" as AmountString, - }); - await wallet.runUntilDone(); - - const balances = await wallet.client.call(WalletApiOperation.GetBalances, {}); - t.assertAmountEquals(balances.balances[0].available, "TESTKUDOS:19.84"); -} - -runWalletCliTerminationTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-wallet-exchange-migration-existing.ts b/packages/taler-harness/src/integrationtests/test-wallet-exchange-migration-existing.ts @@ -1,124 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2023 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 { - j2s, - ScopeType, - TalerCorebankApiClient, - TransactionType, -} from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { defaultCoinConfig } from "../harness/denomStructures.js"; -import { - createSimpleTestkudosEnvironmentV3, - withdrawViaBankV3, -} from "../harness/environments.js"; -import { ExchangeService, GlobalTestState } from "../harness/harness.js"; - -/** - * Run test for a base URL migration, where - * the wallet already has funds for the base URL we're migrating to. - */ -export async function runWalletExchangeMigrationExistingTest( - t: GlobalTestState, -) { - // Set up test environment - - const { walletClient, bank, exchange, exchangeBankAccount, commonDb } = - await createSimpleTestkudosEnvironmentV3(t); - - // Withdraw digital cash into the wallet. - const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl); - - await withdrawViaBankV3(t, { - walletClient, - bankClient, - exchange, - amount: "TESTKUDOS:20", - }); - - await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); - - await exchange.stop(); - - // Exchange running on a different port. - const exchange2 = ExchangeService.create(t, { - name: "testexchange-1", - currency: "TESTKUDOS", - httpPort: 8181, - hostname: "myexchange.localhost", - database: commonDb.connStr, - allowExistingMasterPriv: true, - }); - - exchange2.addCoinConfigList(defaultCoinConfig.map((x) => x("TESTKUDOS"))); - - await exchange2.addBankAccount("1", exchangeBankAccount); - - await exchange2.start(); - - const wres2 = await withdrawViaBankV3(t, { - walletClient, - bankClient, - exchange: exchange2, - amount: "TESTKUDOS:20", - }); - - await wres2.withdrawalFinishedCond; - - await walletClient.call( - WalletApiOperation.TestingPlanMigrateExchangeBaseUrl, - { - oldExchangeBaseUrl: exchange.baseUrl, - newExchangeBaseUrl: exchange2.baseUrl, - }, - ); - - await walletClient.call(WalletApiOperation.UpdateExchangeEntry, { - exchangeBaseUrl: exchange.baseUrl, - force: true, - }); - - try { - await walletClient.call(WalletApiOperation.TestingWaitExchangeReady, { - exchangeBaseUrl: exchange.baseUrl, - forceUpdate: true, - }); - } catch (e) {} - - const balances = await walletClient.call(WalletApiOperation.GetBalances, {}); - console.log(j2s(balances)); - - t.assertDeepEqual(balances.balances.length, 1); - const si = balances.balances[0].scopeInfo; - t.assertDeepEqual(si.type, ScopeType.Exchange); - t.assertDeepEqual(si.url, "http://myexchange.localhost:8181/"); - - const transactions = await walletClient.call( - WalletApiOperation.GetTransactionsV2, - {}, - ); - console.log(j2s(transactions)); - t.assertDeepEqual(transactions.transactions.length, 2); - const tx0 = transactions.transactions[0]; - t.assertDeepEqual(tx0.type, TransactionType.Withdrawal); - t.assertDeepEqual(tx0.exchangeBaseUrl, "http://myexchange.localhost:8181/"); -} - -runWalletExchangeMigrationExistingTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-wallet-exchange-migration.ts b/packages/taler-harness/src/integrationtests/test-wallet-exchange-migration.ts @@ -27,32 +27,56 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { defaultCoinConfig } from "../harness/denomStructures.js"; import { createSimpleTestkudosEnvironmentV3, + createWalletDaemonWithClient, withdrawViaBankV3, } from "../harness/environments.js"; import { ExchangeService, GlobalTestState } from "../harness/harness.js"; /** - * Run test for basic, bank-integrated withdrawal and payment. + * Run base URL migrations both with and without an existing target entry. */ export async function runWalletExchangeMigrationTest(t: GlobalTestState) { // Set up test environment - const { walletClient, bank, exchange, merchant, commonDb } = - await createSimpleTestkudosEnvironmentV3(t); + const { + walletClient: newTargetWallet, + bank, + exchange, + exchangeBankAccount, + commonDb, + } = await createSimpleTestkudosEnvironmentV3(t); + + const { walletClient: existingTargetWallet } = + await createWalletDaemonWithClient(t, { + name: "wallet-existing-target", + }); // Withdraw digital cash into the wallet. const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl); await withdrawViaBankV3(t, { - walletClient, + walletClient: newTargetWallet, + bankClient, + exchange, + amount: "TESTKUDOS:20", + }); + await withdrawViaBankV3(t, { + walletClient: existingTargetWallet, bankClient, exchange, amount: "TESTKUDOS:20", }); - await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); + await newTargetWallet.call( + WalletApiOperation.TestingWaitTransactionsFinal, + {}, + ); + await existingTargetWallet.call( + WalletApiOperation.TestingWaitTransactionsFinal, + {}, + ); - const diagBefore = await walletClient.call( + const diagBefore = await newTargetWallet.call( WalletApiOperation.GetDiagnostics, {}, ); @@ -69,31 +93,45 @@ export async function runWalletExchangeMigrationTest(t: GlobalTestState) { allowExistingMasterPriv: true, }); - await walletClient.call( - WalletApiOperation.TestingPlanMigrateExchangeBaseUrl, - { - oldExchangeBaseUrl: exchange.baseUrl, - newExchangeBaseUrl: exchange2.baseUrl, - }, - ); - exchange2.addCoinConfigList(defaultCoinConfig.map((x) => x("TESTKUDOS"))); + await exchange2.addBankAccount("1", exchangeBankAccount); await exchange2.start(); - await walletClient.call(WalletApiOperation.UpdateExchangeEntry, { - exchangeBaseUrl: exchange.baseUrl, - force: true, + const targetWithdrawal = await withdrawViaBankV3(t, { + walletClient: existingTargetWallet, + bankClient, + exchange: exchange2, + amount: "TESTKUDOS:20", }); - - try { - await walletClient.call(WalletApiOperation.TestingWaitExchangeReady, { + await targetWithdrawal.withdrawalFinishedCond; + + for (const walletClient of [newTargetWallet, existingTargetWallet]) { + await walletClient.call( + WalletApiOperation.TestingPlanMigrateExchangeBaseUrl, + { + oldExchangeBaseUrl: exchange.baseUrl, + newExchangeBaseUrl: exchange2.baseUrl, + }, + ); + + await walletClient.call(WalletApiOperation.UpdateExchangeEntry, { exchangeBaseUrl: exchange.baseUrl, - forceUpdate: true, + force: true, }); - } catch (e) {} - const balances = await walletClient.call(WalletApiOperation.GetBalances, {}); + try { + await walletClient.call(WalletApiOperation.TestingWaitExchangeReady, { + exchangeBaseUrl: exchange.baseUrl, + forceUpdate: true, + }); + } catch (e) {} + } + + const balances = await newTargetWallet.call( + WalletApiOperation.GetBalances, + {}, + ); console.log(j2s(balances)); t.assertDeepEqual(balances.balances.length, 1); @@ -104,11 +142,11 @@ export async function runWalletExchangeMigrationTest(t: GlobalTestState) { // Force a full /keys against the URL the entry now lives at. The migration // itself returns before the denomination loop, so anything it failed to // bring across only shows up on the next update. - await walletClient.call(WalletApiOperation.UpdateExchangeEntry, { + await newTargetWallet.call(WalletApiOperation.UpdateExchangeEntry, { exchangeBaseUrl: exchange2.baseUrl, force: true, }); - await walletClient.call(WalletApiOperation.TestingWaitExchangeReady, { + await newTargetWallet.call(WalletApiOperation.TestingWaitExchangeReady, { exchangeBaseUrl: exchange2.baseUrl, forceUpdate: true, }); @@ -118,7 +156,7 @@ export async function runWalletExchangeMigrationTest(t: GlobalTestState) { // old URL is invisible afterwards and the next /keys makes a second copy -- // leaving the denominations of live coins pointing at a family that a later // purge of the old URL would cascade-delete. - const diagAfter = await walletClient.call( + const diagAfter = await newTargetWallet.call( WalletApiOperation.GetDiagnostics, {}, ); @@ -127,7 +165,7 @@ export async function runWalletExchangeMigrationTest(t: GlobalTestState) { diagBefore.idbObjectStoreCounts?.["denominationFamilies"], ); - const transactions = await walletClient.call( + const transactions = await newTargetWallet.call( WalletApiOperation.GetTransactionsV2, {}, ); @@ -136,6 +174,32 @@ export async function runWalletExchangeMigrationTest(t: GlobalTestState) { const tx0 = transactions.transactions[0]; t.assertDeepEqual(tx0.type, TransactionType.Withdrawal); t.assertDeepEqual(tx0.exchangeBaseUrl, "http://myexchange.localhost:8181/"); + + await t.runSpanAsync("migration-with-existing-target", async () => { + const existingBalances = await existingTargetWallet.call( + WalletApiOperation.GetBalances, + {}, + ); + t.assertDeepEqual(existingBalances.balances.length, 1); + const existingScope = existingBalances.balances[0].scopeInfo; + t.assertDeepEqual(existingScope.type, ScopeType.Exchange); + t.assertDeepEqual(existingScope.url, "http://myexchange.localhost:8181/"); + + const existingTransactions = await existingTargetWallet.call( + WalletApiOperation.GetTransactionsV2, + {}, + ); + t.assertDeepEqual(existingTransactions.transactions.length, 2); + for (const transaction of existingTransactions.transactions) { + t.assertDeepEqual(transaction.type, TransactionType.Withdrawal); + if (transaction.type === TransactionType.Withdrawal) { + t.assertDeepEqual( + transaction.exchangeBaseUrl, + "http://myexchange.localhost:8181/", + ); + } + } + }); } runWalletExchangeMigrationTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts b/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts @@ -24,6 +24,8 @@ import { TalerCorebankApiClient, TransactionMajorState, TransactionMinorState, + TransactionType, + WalletNotification, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; @@ -143,11 +145,13 @@ export async function runWalletNotificationsTest(t: GlobalTestState) { await walletService.start(); await walletService.pingUntilAvailable(); + const allNotifications: WalletNotification[] = []; const walletClient = new WalletClient({ name: "wallet", unixPath: walletService.socketPath, onNotification(n) { console.log("got notification", n); + allNotifications.push(n); }, }); await walletClient.connect(); @@ -155,6 +159,7 @@ export async function runWalletNotificationsTest(t: GlobalTestState) { config: { testing: { skipDefaults: true, + emitObservabilityEvents: true, }, }, }); @@ -177,6 +182,10 @@ export async function runWalletNotificationsTest(t: GlobalTestState) { // Withdraw (AKA select) + const balanceChangedPromise = walletClient.waitForNotificationCond( + (x) => x.type === NotificationType.BalanceChange, + ); + const acceptRes = await walletClient.client.call( WalletApiOperation.AcceptBankIntegratedWithdrawal, { @@ -185,6 +194,8 @@ export async function runWalletNotificationsTest(t: GlobalTestState) { }, ); + await balanceChangedPromise; + const withdrawalFinishedReceivedPromise = walletClient.waitForNotificationCond((x) => { return ( @@ -209,6 +220,57 @@ export async function runWalletNotificationsTest(t: GlobalTestState) { }); await withdrawalFinishedReceivedPromise; + + await t.runSpanAsync("wallet-observability", async () => { + const requestObsEvents = allNotifications.filter( + (x) => x.type === NotificationType.RequestObservabilityEvent, + ); + const taskObsEvents = allNotifications.filter( + (x) => x.type === NotificationType.TaskObservabilityEvent, + ); + console.log(`request observability events: ${requestObsEvents.length}`); + console.log(`task observability events: ${taskObsEvents.length}`); + t.assertTrue(requestObsEvents.length > 30); + t.assertTrue(taskObsEvents.length > 30); + }); + + await t.runSpanAsync("pending-withdrawal-balance", async () => { + const secondWop = await bankClient.createWithdrawalOperation( + user.username, + "TESTKUDOS:5", + ); + await walletClient.call(WalletApiOperation.GetWithdrawalDetailsForUri, { + talerWithdrawUri: secondWop.taler_withdraw_uri, + }); + await walletClient.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, { + exchangeBaseUrl: exchange.baseUrl, + talerWithdrawUri: secondWop.taler_withdraw_uri, + }); + + const balances = await walletClient.call( + WalletApiOperation.GetBalances, + {}, + ); + t.assertAmountEquals(balances.balances[0].available, "TESTKUDOS:19.84"); + t.assertAmountEquals( + balances.balances[0].pendingIncoming, + "TESTKUDOS:4.90", + ); + }); + + const transactions = await walletClient.call( + WalletApiOperation.GetTransactionsV2, + {}, + ); + t.assertTrue( + transactions.transactions.some( + (tx) => + tx.type === TransactionType.Withdrawal && + tx.txState.major === TransactionMajorState.Done, + ), + ); + + await walletService.stop(); } runWalletNotificationsTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-wallet-observability.ts b/packages/taler-harness/src/integrationtests/test-wallet-observability.ts @@ -1,154 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2020, 2026 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 { - BasicAuth, - NotificationType, - TalerCorebankApiClient, - WalletNotification, -} from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; -import { withdrawViaBankV3 } from "../harness/environments.js"; -import { - BankService, - ExchangeService, - GlobalTestState, - WalletClient, - WalletService, - getTestHarnessPaytoForLabel, - setupDb, -} from "../harness/harness.js"; - -export async function runWalletObservabilityTest(t: GlobalTestState) { - // Set up test environment - - const db = await setupDb(t); - - const bank = await BankService.create(t, { - allowRegistrations: true, - currency: "TESTKUDOS", - database: db.connStr, - httpPort: 8082, - }); - - const exchange = ExchangeService.create(t, { - name: "testexchange-1", - currency: "TESTKUDOS", - httpPort: 8081, - database: db.connStr, - }); - - let receiverName = "Exchange"; - let exchangeBankUsername = "exchange"; - let exchangeBankPassword = "mypw-password"; - let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername); - - const wireGatewayAuth: BasicAuth = { - type: "basic", - username: exchangeBankUsername, - password: exchangeBankPassword, - }; - - await exchange.addBankAccount("1", { - wireGatewayAuth, - wireGatewayApiBaseUrl: new URL( - "accounts/exchange/taler-wire-gateway/", - bank.baseUrl, - ).href, - accountPaytoUri: exchangePaytoUri, - }); - - bank.setSuggestedExchange(exchange, exchangePaytoUri); - - await bank.start(); - - await bank.pingUntilAvailable(); - - const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, { - auth: { - username: "admin", - password: "admin-password", - }, - }); - - await bankClient.registerAccountExtended({ - name: receiverName, - password: exchangeBankPassword, - username: exchangeBankUsername, - is_taler_exchange: true, - payto_uri: exchangePaytoUri, - }); - - const coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")); - exchange.addCoinConfigList(coinConfig); - - await exchange.start(); - await exchange.pingUntilAvailable(); - - const walletService = new WalletService(t, { - name: "wallet", - useInMemoryDb: true, - }); - await walletService.start(); - await walletService.pingUntilAvailable(); - - const allNotifications: WalletNotification[] = []; - - const walletClient = new WalletClient({ - name: "wallet", - unixPath: walletService.socketPath, - onNotification(n) { - console.log("got notification", n); - allNotifications.push(n); - }, - }); - await walletClient.connect(); - await walletClient.client.call(WalletApiOperation.InitWallet, { - config: { - testing: { - skipDefaults: true, - emitObservabilityEvents: true, - }, - }, - }); - - const wres = await withdrawViaBankV3(t, { - amount: "TESTKUDOS:10", - bankClient, - exchange, - walletClient, - }); - await wres.withdrawalFinishedCond; - - const requestObsEvents = allNotifications.filter( - (x) => x.type === NotificationType.RequestObservabilityEvent, - ); - const taskObsEvents = allNotifications.filter( - (x) => x.type === NotificationType.TaskObservabilityEvent, - ); - - console.log(`req events: ${requestObsEvents.length}`); - console.log(`task events: ${taskObsEvents.length}`); - - t.assertTrue(requestObsEvents.length > 30); - t.assertTrue(taskObsEvents.length > 30); -} - -runWalletObservabilityTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-wallet-tokens-discount.ts b/packages/taler-harness/src/integrationtests/test-wallet-tokens-discount.ts @@ -22,6 +22,7 @@ import { AccessToken, DiscountListDetail, Duration, + Logger, Order, OrderInputType, OrderOutputType, @@ -40,7 +41,8 @@ import { withdrawViaBankV3, } from "../harness/environments.js"; import { GlobalTestState, WalletClient } from "../harness/harness.js"; -import { logger } from "./test-tops-challenger-twice.js"; + +const logger = new Logger("test-wallet-tokens-discount.ts"); export async function runWalletTokensDiscountTest(t: GlobalTestState) { let { diff --git a/packages/taler-harness/src/integrationtests/test-wallet-tokens.ts b/packages/taler-harness/src/integrationtests/test-wallet-tokens.ts @@ -22,6 +22,7 @@ import { ChoiceSelectionDetailType, Duration, j2s, + Logger, Order, OrderInputType, OrderOutputType, @@ -40,7 +41,8 @@ import { withdrawViaBankV3, } from "../harness/environments.js"; import { GlobalTestState } from "../harness/harness.js"; -import { logger } from "./test-tops-challenger-twice.js"; + +const logger = new Logger("test-wallet-tokens.ts"); export async function runWalletTokensTest(t: GlobalTestState) { let { diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-fakebank.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-fakebank.ts @@ -93,12 +93,15 @@ export async function runWithdrawalFakebankTest(t: GlobalTestState) { await exchange.runWirewatchOnce(); - await wallet.runUntilDone(); - - // Check balance - - const balResp = await wallet.client.call(WalletApiOperation.GetBalances, {}); - t.assertAmountEquals(balResp.balances[0].available, "TESTKUDOS:9.85"); + await t.runSpanAsync("wallet-cli-termination", async () => { + await wallet.runUntilDone(); + + const balResp = await wallet.client.call( + WalletApiOperation.GetBalances, + {}, + ); + t.assertAmountEquals(balResp.balances[0].available, "TESTKUDOS:9.85"); + }); } runWithdrawalFakebankTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-handover.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-handover.ts @@ -74,6 +74,14 @@ export async function runWithdrawalHandoverTest(t: GlobalTestState) { t.assertTrue(!!prepareResp.transactionId); + const preparedTransaction = await walletClient.call( + WalletApiOperation.GetTransactionById, + { + transactionId: prepareResp.transactionId, + }, + ); + console.log(`prepared transaction: ${j2s(preparedTransaction)}`); + const txns1 = await walletClient.call(WalletApiOperation.GetTransactions, { sort: "stable-ascending", }); diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-prepare.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-prepare.ts @@ -1,78 +0,0 @@ -/* - 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 { j2s, Logger } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState } from "../harness/harness.js"; -import { createSimpleTestkudosEnvironmentV3 } from "../harness/environments.js"; - -const logger = new Logger("test-withdrawal-prepare.ts"); - -/** - * Test the separate prepare step of a bank-integrated withdrawal. - */ -export async function runWithdrawalPrepareTest(t: GlobalTestState) { - // Set up test environment - - const { walletClient, bankClient, exchange } = - await createSimpleTestkudosEnvironmentV3(t); - - // Create a withdrawal operation - const user = await bankClient.createRandomBankUser(); - bankClient.setAuth(user); - const wop = await bankClient.createWithdrawalOperation( - user.username, - "TESTKUDOS:20", - ); - - const r1 = await walletClient.call( - WalletApiOperation.GetWithdrawalDetailsForUri, - { - talerWithdrawUri: wop.taler_withdraw_uri, - }, - ); - - console.log(j2s(r1)); - - //t.assertTrue(!r1.amount); - - // Withdraw - - const prepRes = await walletClient.call( - WalletApiOperation.PrepareBankIntegratedWithdrawal, - { - talerWithdrawUri: wop.taler_withdraw_uri, - }, - ); - - logger.info(`Prep res: ${j2s(prepRes)}`); - - // Make sure that we can get the transaction details for the prepared transaction - - const txDetail = await walletClient.call( - WalletApiOperation.GetTransactionById, - { - transactionId: prepRes.transactionId, - }, - ); - - logger.info(`Transaction details: ${j2s(txDetail)}`); -} - -runWithdrawalPrepareTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -61,7 +61,6 @@ import { runDonauKeychangeTest } from "./test-donau-keychange.js"; import { runDonauMinusTTest } from "./test-donau-minus-t.js"; import { runDonauMultiTest } from "./test-donau-multi.js"; import { runDonauTest } from "./test-donau.js"; -import { runExchangeBaseUrlCompletionTest } from "./test-exchange-base-url-completion.js"; import { runExchangeDenomStorageTest } from "./test-exchange-denom-storage.js"; import { runExchangeDepositTest } from "./test-exchange-deposit.js"; import { runExchangeEphemeralTest } from "./test-exchange-ephemeral.js"; @@ -87,7 +86,6 @@ import { runKycDecisionEventsTest } from "./test-kyc-decision-events.js"; import { runKycDecisionsTest } from "./test-kyc-decisions.js"; import { runKycDepositAggregateImplicitAuthTest } from "./test-kyc-deposit-aggregate-implicit-auth.js"; import { runKycDepositAggregateTest } from "./test-kyc-deposit-aggregate.js"; -import { runKycDepositDepositKyctransferTest } from "./test-kyc-deposit-deposit-kyctransfer.js"; import { runKycDepositDepositTest } from "./test-kyc-deposit-deposit.js"; import { runKycDepositKycauthTest } from "./test-kyc-deposit-kycauth.js"; import { runKycExchangeWalletTest } from "./test-kyc-exchange-wallet.js"; @@ -95,7 +93,6 @@ import { runKycFailRecoverSimpleTest } from "./test-kyc-fail-recover-simple.js"; import { runKycFormBadMeasureTest } from "./test-kyc-form-bad-measure.js"; import { runKycFormCompressionTest } from "./test-kyc-form-compression.js"; import { runKycFormValidationTest } from "./test-kyc-form-validation.js"; -import { runKycMerchantActivateBankAccountTest } from "./test-kyc-merchant-activate-bank-account.js"; import { runKycMerchantAggregateTest } from "./test-kyc-merchant-aggregate.js"; import { runKycMerchantDepositFormTest } from "./test-kyc-merchant-deposit-form.js"; import { runKycMerchantDepositRewriteTest } from "./test-kyc-merchant-deposit-rewrite.js"; @@ -119,8 +116,6 @@ import { runMerchantExchangeConfusionTest } from "./test-merchant-exchange-confu import { runMerchantExchangeDuplicateTest } from "./test-merchant-exchange-duplicate.js"; import { runMerchantTokenfamilyKeysTest } from "./test-merchant-tokenfamily-keys.js"; import { runMerchantInstanceCasingTest } from "./test-merchant-instance-casing.js"; -import { runMerchantInstancesDeleteTest } from "./test-merchant-instances-delete.js"; -import { runMerchantInstancesUrlsTest } from "./test-merchant-instances-urls.js"; import { runMerchantInstancesTest } from "./test-merchant-instances.js"; import { runMerchantKycAuthMultiTest } from "./test-merchant-kyc-auth-multi.js"; import { runMerchantKycNotificationsTest } from "./test-merchant-kyc-notifications.js"; @@ -131,7 +126,6 @@ import { runMerchantRefundApiTest } from "./test-merchant-refund-api.js"; import { runMerchantRefundFeesTest } from "./test-merchant-refund-fees.js"; import { runMerchantReportsTest } from "./test-merchant-reports.js"; import { runMerchantSelfProvisionActivationTest } from "./test-merchant-self-provision-activation.js"; -import { runMerchantSelfProvisionForgotPasswordTest } from "./test-merchant-self-provision-forgot-password.js"; import { runMerchantSelfProvisionInactiveAccountPermissionsTest } from "./test-merchant-self-provision-inactive-account-permissions.js"; import { runMerchantSpecPublicOrdersTest } from "./test-merchant-spec-public-orders.js"; import { runMerchantTemplatesTest } from "./test-merchant-templates.js"; @@ -141,23 +135,19 @@ import { runMultiExchangeTest } from "./test-multiexchange.js"; import { runOtpTest } from "./test-otp.js"; import { runPaivanaHeadTest } from "./test-paivana-head.js"; import { runPaivanaRepurchaseTest } from "./test-paivana-repurchase.js"; -import { runPaivanaTest } from "./test-paivana.js"; import { runPayPaidTest } from "./test-pay-paid.js"; import { runPaymentAbortTest } from "./test-payment-abort.js"; -import { runPaymentClaimAlreadyClaimedTest } from "./test-payment-claim-already-claimed.js"; import { runPaymentClaimCleanupTest } from "./test-payment-claim-cleanup.js"; import { runPaymentClaimTest } from "./test-payment-claim.js"; import { runPaymentDeletedTest } from "./test-payment-deleted.js"; import { runPaymentExpiredTest } from "./test-payment-expired.js"; import { runPaymentForgettableTest } from "./test-payment-forgettable.js"; -import { runPaymentIdempotencyTest } from "./test-payment-idempotency.js"; import { runPaymentMultipleTest } from "./test-payment-multiple.js"; import { runPaymentOrderGoneTest } from "./test-payment-order-gone.js"; import { runPaymentShareIdempotencyTest } from "./test-payment-share-idempotency.js"; import { runPaymentShareTest } from "./test-payment-share.js"; import { runPaymentTemplateTest } from "./test-payment-template.js"; import { runPaymentTransientTest } from "./test-payment-transient.js"; -import { runPaymentZeroTest } from "./test-payment-zero.js"; import { runPaymentTest } from "./test-payment.js"; import { runPaywallFlowTest } from "./test-paywall-flow.js"; import { runPeerAbortBalanceTest } from "./test-peer-abort-balance.js"; @@ -172,7 +162,6 @@ import { runRefundAutoTest } from "./test-refund-auto.js"; import { runRefundGoneTest } from "./test-refund-gone.js"; import { runRefundIncrementalTest } from "./test-refund-incremental.js"; import { runRefundTest } from "./test-refund.js"; -import { runRepurchaseV1Test } from "./test-repurchase-v1.js"; import { runRepurchaseTest } from "./test-repurchase.js"; import { runRevocationTest } from "./test-revocation.js"; import { runSimplePaymentTest } from "./test-simple-payment.js"; @@ -182,7 +171,6 @@ import { runTopsAmlBasicTest } from "./test-tops-aml-basic.js"; import { runTopsAmlCustomAddrPostalTest } from "./test-tops-aml-custom-addr-postal.js"; import { runTopsAmlCustomAddrSmsTest } from "./test-tops-aml-custom-addr-sms.js"; import { runTopsAmlKyxNaturalTest } from "./test-tops-aml-kyx-natural.js"; -import { runTopsAmlLegiTest } from "./test-tops-aml-legi.js"; import { runTopsAmlExpiredSuccessorTest } from "./test-tops-aml-expired-successor.js"; import { runTopsAmlMeasuresTest } from "./test-tops-aml-measures.js"; import { runTopsAmlP2pFreshWalletTest } from "./test-tops-aml-p2p-fresh-wallet.js"; @@ -190,7 +178,6 @@ import { runTopsAmlSanctionPreservesRulesTest } from "./test-tops-aml-sanction-p import { runTopsAmlStaleValidationTest } from "./test-tops-aml-stale-validation.js"; import { runTopsAmlNullCurrentRulesTest } from "./test-tops-aml-null-current-rules.js"; import { runTopsAmlPdfTest } from "./test-tops-aml-pdf.js"; -import { runTopsChallengerTwiceTest } from "./test-tops-challenger-twice.js"; import { runTopsMerchantSimpleKycauthTest } from "./test-tops-merchant-simple-kycauth.js"; import { runTopsMerchantSwtKycauthTest } from "./test-tops-merchant-swt-kycauth.js"; import { runTopsMerchantTosTest } from "./test-tops-merchant-tos.js"; @@ -199,11 +186,9 @@ import { runTopsNexusSwtTest } from "./test-tops-nexus-swt.js"; import { runTopsPeerTest } from "./test-tops-peer.js"; import { runTermOfServiceFormatTest } from "./test-tos-format.js"; import { runUtilMerchantClientTest } from "./test-util-merchant-client.js"; -import { runWalletBalanceNotificationsTest } from "./test-wallet-balance-notifications.js"; import { runWalletBalanceZeroTest } from "./test-wallet-balance-zero.js"; import { runWalletBalanceTest } from "./test-wallet-balance.js"; import { runWalletBbanTest } from "./test-wallet-bban.js"; -import { runWalletCliTerminationTest } from "./test-wallet-cli-termination.js"; import { runWalletContactsBasicTest } from "./test-wallet-contacts-basic.js"; import { runWalletCryptoWorkerTest } from "./test-wallet-cryptoworker.js"; import { runWalletDblessTest } from "./test-wallet-dbless.js"; @@ -215,7 +200,6 @@ import { runWalletProgressTokenTest } from "./test-wallet-progress-token.js"; import { runWalletDevExperimentsTest } from "./test-wallet-dev-experiments.js"; import { runWalletDevexpFakeprotoverTest } from "./test-wallet-devexp-fakeprotover.js"; import { runWalletExchangeFeaturesTest } from "./test-wallet-exchange-features.js"; -import { runWalletExchangeMigrationExistingTest } from "./test-wallet-exchange-migration-existing.js"; import { runWalletExchangeMigrationTest } from "./test-wallet-exchange-migration.js"; import { runWalletExchangeUpdateTest } from "./test-wallet-exchange-update.js"; import { runWalletGenDbTest } from "./test-wallet-gendb.js"; @@ -224,7 +208,6 @@ import { runWalletMailboxBasicTest } from "./test-wallet-mailbox-basic.js"; import { runWalletMissingTosTest } from "./test-wallet-missing-tos.js"; import { runWalletNetworkAvailabilityTest } from "./test-wallet-network-availability.js"; import { runWalletNotificationsTest } from "./test-wallet-notifications.js"; -import { runWalletObservabilityTest } from "./test-wallet-observability.js"; import { runWalletRefreshErrorsTest } from "./test-wallet-refresh-errors.js"; import { runWalletRefreshRedenominateTest } from "./test-wallet-refresh-redenominate.js"; import { runWalletRefreshTest } from "./test-wallet-refresh.js"; @@ -251,7 +234,6 @@ 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"; -import { runWithdrawalPrepareTest } from "./test-withdrawal-prepare.js"; import { runWithdrawalShortenTest } from "./test-withdrawal-shorten.js"; import { TodoBugUrl, validateTodoBugUrl } from "./todo.js"; @@ -297,24 +279,19 @@ const allTests: TestMainFunction[] = [ runCoinselLegacy2024Test, runKycChallengerTest, runExchangePurseTest, - runExchangeBaseUrlCompletionTest, runExchangeDepositTest, runExchangeEphemeralTest, runMerchantExchangeConfusionTest, runMerchantExchangeDuplicateTest, runMerchantTokenfamilyKeysTest, runMerchantInstanceCasingTest, - runMerchantInstancesDeleteTest, runMerchantInstancesTest, - runMerchantInstancesUrlsTest, runMerchantLongpollingTest, runMerchantRefundApiTest, runMerchantSpecPublicOrdersTest, runPaymentClaimTest, runPaymentClaimCleanupTest, - runPaymentClaimAlreadyClaimedTest, runPaymentForgettableTest, - runPaymentIdempotencyTest, runPaymentMultipleTest, runPaymentOrderGoneTest, runPaymentTest, @@ -323,7 +300,6 @@ const allTests: TestMainFunction[] = [ runPaymentTemplateTest, runPaymentAbortTest, runPaymentTransientTest, - runPaymentZeroTest, runPayPaidTest, runPeerRepairTest, runMultiExchangeTest, @@ -363,11 +339,8 @@ const allTests: TestMainFunction[] = [ runWalletDd48Test, runCurrencyScopeTest, runWalletRefreshTest, - runWalletCliTerminationTest, runOtpTest, - runWalletBalanceNotificationsTest, runExchangeManagementTest, - runWalletObservabilityTest, runWalletBadNetworkTest, runWalletProgressTokenTest, runWalletDevExperimentsTest, @@ -391,7 +364,6 @@ const allTests: TestMainFunction[] = [ runMerchantCategoriesTest, runMerchantSelfProvisionActivationTest, runWebMerchantLoginTest, - runMerchantSelfProvisionForgotPasswordTest, runMerchantSelfProvisionInactiveAccountPermissionsTest, runWithdrawalExternalTest, runWithdrawalIdempotentTest, @@ -409,13 +381,10 @@ const allTests: TestMainFunction[] = [ runKycMerchantDepositTest, runKycMerchantDepositRewriteTest, runKycMerchantAggregateTest, - runKycDepositDepositKyctransferTest, - runWithdrawalPrepareTest, runAccountRestrictionsTest, runRepurchaseTest, runWalletTransactionsTest, runKycWithdrawalVerbotenTest, - runKycMerchantActivateBankAccountTest, runKycDecisionsTest, runKnownAccountsTest, runKycDepositAggregateImplicitAuthTest, @@ -443,8 +412,6 @@ const allTests: TestMainFunction[] = [ runTopsAmlP2pFreshWalletTest, runTopsAmlSanctionPreservesRulesTest, runTopsAmlStaleValidationTest, - runTopsAmlLegiTest, - runTopsChallengerTwiceTest, runKycFormBadMeasureTest, runKycBalanceWithdrawalChangeManualTest, runUtilMerchantClientTest, @@ -453,7 +420,6 @@ const allTests: TestMainFunction[] = [ runExchangeKycAuthTest, runTopsPeerTest, runWalletExchangeMigrationTest, - runWalletExchangeMigrationExistingTest, runKycFormCompressionTest, runWalletDepositLargeTest, runDepositTwiceTest, @@ -470,7 +436,6 @@ const allTests: TestMainFunction[] = [ runTopsAmlPdfTest, runMerchantWireTest, runWalletExchangeFeaturesTest, - runRepurchaseV1Test, runMerchantBankBadWireTargetTest, runWalletBbanTest, runCurrencyScopeSeparationTest, @@ -485,7 +450,6 @@ const allTests: TestMainFunction[] = [ runWireMetadataTest, runMerchantPaytoReuseTest, runPreparedTransferTest, - runPaivanaTest, runPaivanaRepurchaseTest, runPaivanaHeadTest, runKycFormValidationTest,