diff options
28 files changed, 552 insertions, 80 deletions
diff --git a/packages/taler-harness/src/env-full.ts b/packages/taler-harness/src/env-full.ts index 210d38e32..bb2cb8c47 100644 --- a/packages/taler-harness/src/env-full.ts +++ b/packages/taler-harness/src/env-full.ts @@ -25,7 +25,7 @@ import { ExchangeService, FakebankService, MerchantService, - getPayto, + generateRandomPayto, } from "./harness/harness.js"; /** @@ -82,7 +82,7 @@ export async function runEnvFull(t: GlobalTestState): Promise<void> { await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], defaultWireTransferDelay: Duration.toTalerProtocolDuration( Duration.fromSpec({ minutes: 1 }), ), @@ -91,7 +91,7 @@ export async function runEnvFull(t: GlobalTestState): Promise<void> { await merchant.addInstanceWithWireAccount({ id: "minst1", name: "minst1", - paytoUris: [getPayto("minst1")], + paytoUris: [generateRandomPayto("minst1")], defaultWireTransferDelay: Duration.toTalerProtocolDuration( Duration.fromSpec({ minutes: 1 }), ), diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts index edb0071c8..8f2d40d6e 100644 --- a/packages/taler-harness/src/harness/harness.ts +++ b/packages/taler-harness/src/harness/harness.ts @@ -565,7 +565,7 @@ class BankServiceBase { protected globalTestState: GlobalTestState, protected bankConfig: BankConfig, protected configFile: string, - ) { } + ) {} } export interface HarnessExchangeBankAccount { @@ -580,7 +580,8 @@ export interface HarnessExchangeBankAccount { */ export class FakebankService extends BankServiceBase - implements BankServiceHandle { + implements BankServiceHandle +{ proc: ProcessWrapper | undefined; http = createPlatformHttpLib({ enableThrottling: false }); @@ -664,7 +665,7 @@ export class FakebankService return { accountName: accountName, accountPassword: password, - accountPaytoUri: getPayto(accountName), + accountPaytoUri: generateRandomPayto(accountName), wireGatewayApiBaseUrl: `http://localhost:${this.bankConfig.httpPort}/accounts/${accountName}/taler-wire-gateway/`, }; } @@ -702,6 +703,140 @@ export class FakebankService } } +/** + * Implementation of the bank service using the "taler-fakebank-run" tool. + */ +export class LibeufinBankService + extends BankServiceBase + implements BankServiceHandle +{ + proc: ProcessWrapper | undefined; + + http = createPlatformHttpLib({ enableThrottling: false }); + + // We store "created" accounts during setup and + // register them after startup. + private accounts: { + accountName: string; + accountPassword: string; + }[] = []; + + /** + * Create a new fakebank service handle. + * + * First generates the configuration for the fakebank and + * then creates a fakebank handle, but doesn't start the fakebank + * service yet. + */ + static async create( + gc: GlobalTestState, + bc: BankConfig, + ): Promise<LibeufinBankService> { + const config = new Configuration(); + const testDir = bc.overrideTestDir ?? gc.testDir; + setTalerPaths(config, testDir + "/talerhome"); + config.setString("libeufin-bankdb", "config", bc.database); + config.setString("libeufin-bank", "currency", bc.currency); + config.setString("libeufin-bank", "port", `${bc.httpPort}`); + config.setString("libeufin-bank", "serve", "tcp"); + config.setString( + "libeufin-bank", + "DEFAULT_CUSTOMER_DEBT_LIMIT", + `${bc.currency}:500`, + ); + config.setString( + "libeufin-bank", + "DEFAULT_ADMIN_DEBT_LIMIT", + `${bc.currency}:999999`, + ); + config.setString( + "libeufin-bank", + "registration_bonus", + `${bc.currency}:100`, + ); + config.setString("libeufin-bank", "registration_bonus_enabled", `yes`); + config.setString("libeufin-bank", "max_auth_token_duration", "1h"); + const cfgFilename = testDir + "/bank.conf"; + config.write(cfgFilename, { excludeDefaults: true }); + + return new LibeufinBankService(gc, bc, cfgFilename); + } + + static fromExistingConfig( + gc: GlobalTestState, + opts: { overridePath?: string }, + ): FakebankService { + const testDir = opts.overridePath ?? gc.testDir; + const cfgFilename = testDir + `/bank.conf`; + const config = Configuration.load(cfgFilename); + const bc: BankConfig = { + allowRegistrations: + config.getYesNo("libeufin-bank", "allow_registrations").orUndefined() ?? + true, + currency: config.getString("libeufin-bank", "currency").required(), + database: config + .getString("libeufin-bankdb", "config") + .required(), + httpPort: config.getNumber("libeufin-bank", "port").required(), + maxDebt: config + .getString("libeufin-bank", "DEFAULT_CUSTOMER_DEBT_LIMIT") + .required(), + }; + return new FakebankService(gc, bc, cfgFilename); + } + + setSuggestedExchange(e: ExchangeServiceInterface) { + if (!!this.proc) { + throw Error("Can't set suggested exchange while bank is running."); + } + const config = Configuration.load(this.configFile); + config.setString("libeufin-bank", "suggested_withdrawal_exchange", e.baseUrl); + config.write(this.configFile, { excludeDefaults: true }); + } + + get baseUrl(): string { + return `http://localhost:${this.bankConfig.httpPort}/`; + } + + get bankAccessApiBaseUrl(): string { + return this.baseUrl; + } + + get port() { + return this.bankConfig.httpPort; + } + + async start(): Promise<void> { + logger.info("starting libeufin-bank"); + if (this.proc) { + logger.info("libeufin-bank already running, not starting again"); + return; + } + + await sh( + this.globalTestState, + "libeufin-bank-dbinit", + `libeufin-bank dbinit -r -c "${this.configFile}"`, + ); + + this.proc = this.globalTestState.spawnService( + "libeufin-bank", + ["serve", "-c", this.configFile], + "libeufin-bank-httpd", + ); + await this.pingUntilAvailable(); + const bankClient = new TalerCorebankApiClient(this.bankAccessApiBaseUrl); + for (const acc of this.accounts) { + await bankClient.registerAccount(acc.accountName, acc.accountPassword); + } + } + + async pingUntilAvailable(): Promise<void> { + const url = `http://localhost:${this.bankConfig.httpPort}/config`; + await pingProc(this.proc, url, "libeufin-bank"); + } +} + // Use libeufin bank instead of pybank. const useLibeufinBank = false; @@ -1011,7 +1146,7 @@ export class ExchangeService implements ExchangeServiceInterface { private exchangeConfig: ExchangeConfig, private configFilename: string, private keyPair: EddsaKeyPair, - ) { } + ) {} get name() { return this.exchangeConfig.name; @@ -1367,7 +1502,7 @@ export class MerchantService implements MerchantServiceInterface { private globalState: GlobalTestState, private merchantConfig: MerchantConfig, private configFilename: string, - ) { } + ) {} private currentTimetravelOffsetMs: number | undefined; @@ -1495,7 +1630,7 @@ export class MerchantService implements MerchantServiceInterface { return await this.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], auth: { method: "external", }, @@ -1658,6 +1793,7 @@ export async function runTestWithState( e.message, `error detail: ${j2s(e.errorDetail)}`, ); + console.error(e.stack); } else { console.error("FATAL: test failed with exception", e); } @@ -1705,7 +1841,7 @@ export class WalletService { constructor( private globalState: GlobalTestState, private opts: WalletServiceOptions, - ) { } + ) {} get socketPath() { const unixPath = path.join( @@ -1814,7 +1950,7 @@ export class WalletClient { return client.call(operation, payload); } - constructor(private args: WalletClientArgs) { } + constructor(private args: WalletClientArgs) {} async connect(): Promise<void> { const waiter = this.waiter; @@ -1881,9 +2017,11 @@ export class WalletCli { ? `--crypto-worker=${cliOpts.cryptoWorkerType}` : ""; const logName = `wallet-${self.name}`; - const command = `taler-wallet-cli ${self.timetravelArg ?? "" - } ${cryptoWorkerArg} --no-throttle -LTRACE --skip-defaults --wallet-db '${self.dbfile - }' api '${op}' ${shellWrap(JSON.stringify(payload))}`; + const command = `taler-wallet-cli ${ + self.timetravelArg ?? "" + } ${cryptoWorkerArg} --no-throttle -LTRACE --skip-defaults --wallet-db '${ + self.dbfile + }' api '${op}' ${shellWrap(JSON.stringify(payload))}`; const resp = await sh(self.globalTestState, logName, command); logger.info("--- wallet core response ---"); logger.info(resp); @@ -1966,7 +2104,7 @@ export class WalletCli { } } -export function getRandomIban(salt: string | null = null): string { +export function generateRandomTestIban(salt: string | null = null): string { function getBban(salt: string | null): string { if (!salt) return Math.random().toString().substring(2, 6); let hashed = hash(stringToBytes(salt)); @@ -1998,9 +2136,9 @@ export function getWireMethodForTest(): string { * Generate a payto address, whose authority depends * on whether the banking is served by euFin or Pybank. */ -export function getPayto(label: string): string { +export function generateRandomPayto(label: string): string { if (useLibeufinBank) - return `payto://iban/SANDBOXX/${getRandomIban( + return `payto://iban/SANDBOXX/${generateRandomTestIban( label, )}?receiver-name=${label}`; return `payto://x-taler-bank/localhost/${label}`; diff --git a/packages/taler-harness/src/harness/helpers.ts b/packages/taler-harness/src/harness/helpers.ts index 27980857c..68b7d087c 100644 --- a/packages/taler-harness/src/harness/helpers.ts +++ b/packages/taler-harness/src/harness/helpers.ts @@ -56,7 +56,7 @@ import { WalletClient, WalletService, WithAuthorization, - getPayto, + generateRandomPayto, setupDb, setupSharedDb, } from "./harness.js"; @@ -236,7 +236,7 @@ export async function useSharedTestkudosEnvironment(t: GlobalTestState) { await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], defaultWireTransferDelay: Duration.toTalerProtocolDuration( Duration.fromSpec({ minutes: 1 }), ), @@ -245,7 +245,7 @@ export async function useSharedTestkudosEnvironment(t: GlobalTestState) { await merchant.addInstanceWithWireAccount({ id: "minst1", name: "minst1", - paytoUris: [getPayto("minst1")], + paytoUris: [generateRandomPayto("minst1")], defaultWireTransferDelay: Duration.toTalerProtocolDuration( Duration.fromSpec({ minutes: 1 }), ), @@ -368,7 +368,7 @@ export async function createSimpleTestkudosEnvironmentV2( await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], defaultWireTransferDelay: Duration.toTalerProtocolDuration( Duration.fromSpec({ minutes: 1 }), ), @@ -377,7 +377,7 @@ export async function createSimpleTestkudosEnvironmentV2( await merchant.addInstanceWithWireAccount({ id: "minst1", name: "minst1", - paytoUris: [getPayto("minst1")], + paytoUris: [generateRandomPayto("minst1")], defaultWireTransferDelay: Duration.toTalerProtocolDuration( Duration.fromSpec({ minutes: 1 }), ), @@ -512,13 +512,13 @@ export async function createFaultInjectedMerchantTestkudosEnvironment( await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], }); await merchant.addInstanceWithWireAccount({ id: "minst1", name: "minst1", - paytoUris: [getPayto("minst1")], + paytoUris: [generateRandomPayto("minst1")], }); console.log("setup done!"); diff --git a/packages/taler-harness/src/integrationtests/test-bank-api.ts b/packages/taler-harness/src/integrationtests/test-bank-api.ts index 740e89c30..b87a4043b 100644 --- a/packages/taler-harness/src/integrationtests/test-bank-api.ts +++ b/packages/taler-harness/src/integrationtests/test-bank-api.ts @@ -30,7 +30,7 @@ import { ExchangeService, GlobalTestState, MerchantService, - getPayto, + generateRandomPayto, setupDb, } from "../harness/harness.js"; @@ -88,13 +88,13 @@ export async function runBankApiTest(t: GlobalTestState) { await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], }); await merchant.addInstanceWithWireAccount({ id: "minst1", name: "minst1", - paytoUris: [getPayto("minst1")], + paytoUris: [generateRandomPayto("minst1")], }); console.log("setup done!"); diff --git a/packages/taler-harness/src/integrationtests/test-deposit.ts b/packages/taler-harness/src/integrationtests/test-deposit.ts index 7e1bb2a5c..d4bfa3da5 100644 --- a/packages/taler-harness/src/integrationtests/test-deposit.ts +++ b/packages/taler-harness/src/integrationtests/test-deposit.ts @@ -23,7 +23,7 @@ import { TransactionMinorState, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState, getPayto } from "../harness/harness.js"; +import { GlobalTestState, generateRandomPayto } from "../harness/harness.js"; import { createSimpleTestkudosEnvironmentV2, withdrawViaBankV2, @@ -75,7 +75,7 @@ export async function runDepositTest(t: GlobalTestState) { WalletApiOperation.CreateDepositGroup, { amount: "TESTKUDOS:10", - depositPaytoUri: getPayto("foo"), + depositPaytoUri: generateRandomPayto("foo"), transactionId: depositTxId, }, ); diff --git a/packages/taler-harness/src/integrationtests/test-exchange-management.ts b/packages/taler-harness/src/integrationtests/test-exchange-management.ts index 329012e42..fbee50385 100644 --- a/packages/taler-harness/src/integrationtests/test-exchange-management.ts +++ b/packages/taler-harness/src/integrationtests/test-exchange-management.ts @@ -36,7 +36,7 @@ import { GlobalTestState, MerchantService, WalletCli, - getPayto, + generateRandomPayto, setupDb, } from "../harness/harness.js"; @@ -105,13 +105,13 @@ export async function runExchangeManagementTest( await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], }); await merchant.addInstanceWithWireAccount({ id: "minst1", name: "minst1", - paytoUris: [getPayto("minst1")], + paytoUris: [generateRandomPayto("minst1")], }); console.log("setup done!"); diff --git a/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts b/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts index 2ef7683b3..efa21e1a0 100644 --- a/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts +++ b/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts @@ -35,7 +35,7 @@ import { makeNoFeeCoinConfig } from "../harness/denomStructures.js"; import { BankService, ExchangeService, - getPayto, + generateRandomPayto, GlobalTestState, MerchantService, setupDb, @@ -151,13 +151,13 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) { await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], }); await merchant.addInstanceWithWireAccount({ id: "minst1", name: "minst1", - paytoUris: [getPayto("minst1")], + paytoUris: [generateRandomPayto("minst1")], }); console.log("setup done!"); diff --git a/packages/taler-harness/src/integrationtests/test-fee-regression.ts b/packages/taler-harness/src/integrationtests/test-fee-regression.ts index 2d84b3a7c..f164606c4 100644 --- a/packages/taler-harness/src/integrationtests/test-fee-regression.ts +++ b/packages/taler-harness/src/integrationtests/test-fee-regression.ts @@ -23,7 +23,7 @@ import { ExchangeService, GlobalTestState, MerchantService, - getPayto, + generateRandomPayto, setupDb, } from "../harness/harness.js"; import { @@ -142,7 +142,7 @@ export async function createMyTestkudosEnvironment( await merchant.addInstanceWithWireAccount({ id: "minst1", name: "minst1", - paytoUris: [getPayto("minst1")], + paytoUris: [generateRandomPayto("minst1")], }); console.log("setup done!"); diff --git a/packages/taler-harness/src/integrationtests/test-kyc.ts b/packages/taler-harness/src/integrationtests/test-kyc.ts index 4fc725bc3..d646995d6 100644 --- a/packages/taler-harness/src/integrationtests/test-kyc.ts +++ b/packages/taler-harness/src/integrationtests/test-kyc.ts @@ -34,7 +34,7 @@ import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; import { BankService, ExchangeService, - getPayto, + generateRandomPayto, GlobalTestState, MerchantService, setupDb, @@ -162,7 +162,7 @@ export async function createKycTestkudosEnvironment( await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], defaultWireTransferDelay: Duration.toTalerProtocolDuration( Duration.fromSpec({ minutes: 1 }), ), @@ -171,7 +171,7 @@ export async function createKycTestkudosEnvironment( await merchant.addInstanceWithWireAccount({ id: "minst1", name: "minst1", - paytoUris: [getPayto("minst1")], + paytoUris: [generateRandomPayto("minst1")], defaultWireTransferDelay: Duration.toTalerProtocolDuration( Duration.fromSpec({ minutes: 1 }), ), diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-bank.ts b/packages/taler-harness/src/integrationtests/test-libeufin-bank.ts new file mode 100644 index 000000000..66b4c0b80 --- /dev/null +++ b/packages/taler-harness/src/integrationtests/test-libeufin-bank.ts @@ -0,0 +1,222 @@ +/* + 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 { + TalerCorebankApiClient, + CreditDebitIndicator, + WireGatewayApiClient, + createEddsaKeyPair, + encodeCrock, + Logger, + j2s, + NotificationType, + TransactionMajorState, + TransactionMinorState, +} from "@gnu-taler/taler-util"; +import { defaultCoinConfig } from "../harness/denomStructures.js"; +import { + ExchangeService, + GlobalTestState, + LibeufinBankService, + MerchantService, + generateRandomPayto, + generateRandomTestIban, + setupDb, +} from "../harness/harness.js"; +import { createWalletDaemonWithClient } from "../harness/helpers.js"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; + +const logger = new Logger("test-libeufin-bank.ts"); + +/** + * Run test for the basic functionality of libeufin-bank. + */ +export async function runLibeufinBankTest(t: GlobalTestState) { + // Set up test environment + + const db = await setupDb(t); + + const bank = await LibeufinBankService.create(t, { + currency: "TESTKUDOS", + httpPort: 8082, + database: db.connStr, + allowRegistrations: true, + }); + + const exchange = ExchangeService.create(t, { + name: "testexchange-1", + currency: "TESTKUDOS", + httpPort: 8081, + database: db.connStr, + }); + + const merchant = await MerchantService.create(t, { + name: "testmerchant-1", + currency: "TESTKUDOS", + httpPort: 8083, + database: db.connStr, + }); + + const exchangeIban = generateRandomTestIban(); + const exchangeBankUsername = "exchange"; + const exchangeBankPw = "mypw"; + const exchangePlainPayto = `payto://iban/${exchangeIban}`; + const exchangeExtendedPayto = `payto://iban/${exchangeIban}?receiver-name=Exchange`; + const wireGatewayApiBaseUrl = new URL( + "accounts/exchange/taler-wire-gateway/", + bank.baseUrl, + ).href; + + logger.info("creating bank account for the exchange"); + + exchange.addBankAccount("1", { + wireGatewayApiBaseUrl, + accountName: exchangeBankUsername, + accountPassword: exchangeBankPw, + accountPaytoUri: exchangeExtendedPayto, + }); + + bank.setSuggestedExchange(exchange); + + await bank.start(); + + await bank.pingUntilAvailable(); + + exchange.addOfferedCoins(defaultCoinConfig); + + await exchange.start(); + await exchange.pingUntilAvailable(); + + merchant.addExchange(exchange); + + await merchant.start(); + await merchant.pingUntilAvailable(); + + await merchant.addInstanceWithWireAccount({ + id: "default", + name: "Default Instance", + paytoUris: [generateRandomPayto("merchant-default")], + }); + + await merchant.addInstanceWithWireAccount({ + id: "minst1", + name: "minst1", + paytoUris: [generateRandomPayto("minst1")], + }); + + const { walletClient } = await createWalletDaemonWithClient(t, { + name: "wallet", + }); + + console.log("setup done!"); + + const bankClient = new TalerCorebankApiClient(bank.bankAccessApiBaseUrl); + + // register exchange bank account + await bankClient.registerAccountExtended({ + name: "Exchange", + password: exchangeBankPw, + username: exchangeBankUsername, + is_taler_exchange: true, + internal_payto_uri: exchangePlainPayto, + }); + + const bankUser = await bankClient.registerAccount("user1", "pw1"); + bankClient.setAuth({ + username: "user1", + password: "pw1", + }); + + // Make sure that registering twice results in a 409 Conflict + // { + // const e = await t.assertThrowsTalerErrorAsync(async () => { + // await bankClient.registerAccount("user1", "pw2"); + // }); + // t.assertTrue(e.errorDetail.httpStatusCode === 409); + // } + + let balResp = await bankClient.getAccountBalance(bankUser.username); + + console.log(balResp); + + // Check that we got the sign-up bonus. + t.assertAmountEquals(balResp.balance.amount, "TESTKUDOS:100"); + t.assertTrue( + balResp.balance.credit_debit_indicator === CreditDebitIndicator.Credit, + ); + + const res = createEddsaKeyPair(); + + const wireGatewayApiClient = new WireGatewayApiClient(wireGatewayApiBaseUrl, { + auth: { + username: exchangeBankUsername, + password: exchangeBankPw, + }, + }); + + await wireGatewayApiClient.adminAddIncoming({ + amount: "TESTKUDOS:115", + debitAccountPayto: bankUser.accountPaytoUri, + reservePub: encodeCrock(res.eddsaPub), + }); + + balResp = await bankClient.getAccountBalance(bankUser.username); + t.assertAmountEquals(balResp.balance.amount, "TESTKUDOS:15"); + t.assertTrue( + balResp.balance.credit_debit_indicator === CreditDebitIndicator.Debit, + ); + + const wop = await bankClient.createWithdrawalOperation( + bankUser.username, + "TESTKUDOS:10", + ); + + const r1 = await walletClient.client.call( + WalletApiOperation.GetWithdrawalDetailsForUri, + { + talerWithdrawUri: wop.taler_withdraw_uri, + }, + ); + + console.log(j2s(r1)); + + const r2 = await walletClient.client.call( + WalletApiOperation.AcceptBankIntegratedWithdrawal, + { + exchangeBaseUrl: exchange.baseUrl, + talerWithdrawUri: wop.taler_withdraw_uri, + }, + ); + + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: r2.transactionId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankConfirmTransfer, + }, + }); + + await bankClient.confirmWithdrawalOperation(bankUser.username, { + withdrawalOperationId: wop.withdrawal_id, + }); + + await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); +} + +runLibeufinBankTest.suites = ["fakebank"]; diff --git a/packages/taler-harness/src/integrationtests/test-merchant-exchange-confusion.ts b/packages/taler-harness/src/integrationtests/test-merchant-exchange-confusion.ts index 2f79041d6..35e3267b1 100644 --- a/packages/taler-harness/src/integrationtests/test-merchant-exchange-confusion.ts +++ b/packages/taler-harness/src/integrationtests/test-merchant-exchange-confusion.ts @@ -33,7 +33,7 @@ import { import { BankService, ExchangeService, - getPayto, + generateRandomPayto, GlobalTestState, harnessHttpLib, MerchantService, @@ -112,13 +112,13 @@ export async function createConfusedMerchantTestkudosEnvironment( await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], }); await merchant.addInstanceWithWireAccount({ id: "minst1", name: "minst1", - paytoUris: [getPayto("minst1")], + paytoUris: [generateRandomPayto("minst1")], }); console.log("setup done!"); diff --git a/packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts b/packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts index ff567d33d..4508b9976 100644 --- a/packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts +++ b/packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts @@ -22,7 +22,7 @@ import { ExchangeService, GlobalTestState, MerchantService, - getPayto, + generateRandomPayto, harnessHttpLib, setupDb, } from "../harness/harness.js"; @@ -78,7 +78,7 @@ export async function runMerchantInstancesDeleteTest(t: GlobalTestState) { await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], auth: { method: "external", }, @@ -88,7 +88,7 @@ export async function runMerchantInstancesDeleteTest(t: GlobalTestState) { await merchant.addInstanceWithWireAccount({ id: "myinst", name: "Second Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], auth: { method: "external", }, diff --git a/packages/taler-harness/src/integrationtests/test-merchant-instances-urls.ts b/packages/taler-harness/src/integrationtests/test-merchant-instances-urls.ts index 071288b0f..a037a01c5 100644 --- a/packages/taler-harness/src/integrationtests/test-merchant-instances-urls.ts +++ b/packages/taler-harness/src/integrationtests/test-merchant-instances-urls.ts @@ -22,7 +22,7 @@ import { ExchangeService, GlobalTestState, MerchantService, - getPayto, + generateRandomPayto, harnessHttpLib, setupDb, } from "../harness/harness.js"; @@ -74,7 +74,7 @@ export async function runMerchantInstancesUrlsTest(t: GlobalTestState) { name: "My Default Instance", accounts: [ { - payto_uri: getPayto("bar"), + payto_uri: generateRandomPayto("bar"), }, ], auth: { @@ -97,7 +97,7 @@ export async function runMerchantInstancesUrlsTest(t: GlobalTestState) { name: "My Second Instance", accounts: [ { - payto_uri: getPayto("bar"), + payto_uri: generateRandomPayto("bar"), }, ], auth: { diff --git a/packages/taler-harness/src/integrationtests/test-merchant-instances.ts b/packages/taler-harness/src/integrationtests/test-merchant-instances.ts index 27de8a0a0..a77e9ca51 100644 --- a/packages/taler-harness/src/integrationtests/test-merchant-instances.ts +++ b/packages/taler-harness/src/integrationtests/test-merchant-instances.ts @@ -23,7 +23,7 @@ import { GlobalTestState, MerchantService, setupDb, - getPayto, + generateRandomPayto, harnessHttpLib, } from "../harness/harness.js"; @@ -78,7 +78,7 @@ export async function runMerchantInstancesTest(t: GlobalTestState) { await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], auth: { method: "external", }, @@ -88,7 +88,7 @@ export async function runMerchantInstancesTest(t: GlobalTestState) { await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], auth: { method: "external", }, @@ -98,7 +98,7 @@ export async function runMerchantInstancesTest(t: GlobalTestState) { await merchant.addInstanceWithWireAccount({ id: "myinst", name: "Second Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], auth: { method: "external", }, diff --git a/packages/taler-harness/src/integrationtests/test-payment-fault.ts b/packages/taler-harness/src/integrationtests/test-payment-fault.ts index 8076e2fb4..63244a4e3 100644 --- a/packages/taler-harness/src/integrationtests/test-payment-fault.ts +++ b/packages/taler-harness/src/integrationtests/test-payment-fault.ts @@ -39,7 +39,7 @@ import { GlobalTestState, MerchantService, WalletCli, - getPayto, + generateRandomPayto, setupDb, } from "../harness/harness.js"; @@ -116,7 +116,7 @@ export async function runPaymentFaultTest(t: GlobalTestState) { await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], }); const merchantClient = new MerchantApiClient(merchant.makeInstanceBaseUrl()); diff --git a/packages/taler-harness/src/integrationtests/test-payment-multiple.ts b/packages/taler-harness/src/integrationtests/test-payment-multiple.ts index 4ef5e3bff..0caa3c3e7 100644 --- a/packages/taler-harness/src/integrationtests/test-payment-multiple.ts +++ b/packages/taler-harness/src/integrationtests/test-payment-multiple.ts @@ -25,7 +25,7 @@ import { ExchangeService, GlobalTestState, MerchantService, - getPayto, + generateRandomPayto, setupDb, } from "../harness/harness.js"; import { @@ -87,13 +87,13 @@ async function setupTest(t: GlobalTestState): Promise<{ await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], }); await merchant.addInstanceWithWireAccount({ id: "minst1", name: "minst1", - paytoUris: [getPayto("minst1")], + paytoUris: [generateRandomPayto("minst1")], }); console.log("setup done!"); diff --git a/packages/taler-harness/src/integrationtests/test-revocation.ts b/packages/taler-harness/src/integrationtests/test-revocation.ts index 0cb6987ad..9ed2d6206 100644 --- a/packages/taler-harness/src/integrationtests/test-revocation.ts +++ b/packages/taler-harness/src/integrationtests/test-revocation.ts @@ -27,7 +27,7 @@ import { setupDb, BankService, delayMs, - getPayto, + generateRandomPayto, WalletClient, } from "../harness/harness.js"; import { @@ -125,13 +125,13 @@ async function createTestEnvironment( await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], }); await merchant.addInstanceWithWireAccount({ id: "minst1", name: "minst1", - paytoUris: [getPayto("minst1")], + paytoUris: [generateRandomPayto("minst1")], }); console.log("setup done!"); diff --git a/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts b/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts index b94f7757c..449142809 100644 --- a/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts +++ b/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts @@ -32,7 +32,7 @@ import { makeNoFeeCoinConfig } from "../harness/denomStructures.js"; import { BankService, ExchangeService, - getPayto, + generateRandomPayto, GlobalTestState, MerchantService, setupDb, @@ -97,13 +97,13 @@ export async function runTimetravelAutorefreshTest(t: GlobalTestState) { await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], }); await merchant.addInstanceWithWireAccount({ id: "minst1", name: "minst1", - paytoUris: [getPayto("minst1")], + paytoUris: [generateRandomPayto("minst1")], }); console.log("setup done!"); diff --git a/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts b/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts index 2496f4887..0b5bc45ef 100644 --- a/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts +++ b/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts @@ -32,7 +32,7 @@ import { MerchantService, WalletClient, WalletService, - getRandomIban, + generateRandomTestIban, setupDb, } from "../harness/harness.js"; @@ -94,7 +94,7 @@ export async function runWalletNotificationsTest(t: GlobalTestState) { id: "default", name: "Default Instance", paytoUris: [ - `payto://iban/SANDBOXX/${getRandomIban(label)}?receiver-name=${label}`, + `payto://iban/SANDBOXX/${generateRandomTestIban(label)}?receiver-name=${label}`, ], defaultWireTransferDelay: Duration.toTalerProtocolDuration( Duration.fromSpec({ minutes: 1 }), diff --git a/packages/taler-harness/src/integrationtests/test-wallettesting.ts b/packages/taler-harness/src/integrationtests/test-wallettesting.ts index 4fa870f1c..6d58ae1f2 100644 --- a/packages/taler-harness/src/integrationtests/test-wallettesting.ts +++ b/packages/taler-harness/src/integrationtests/test-wallettesting.ts @@ -32,7 +32,7 @@ import { MerchantService, setupDb, WalletCli, - getPayto, + generateRandomPayto, } from "../harness/harness.js"; import { SimpleTestEnvironment } from "../harness/helpers.js"; @@ -94,7 +94,7 @@ export async function createMyEnvironment( await merchant.addInstanceWithWireAccount({ id: "default", name: "Default Instance", - paytoUris: [getPayto("merchant-default")], + paytoUris: [generateRandomPayto("merchant-default")], }); console.log("setup done!"); diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts index 8c8853f4a..817da5865 100644 --- a/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts +++ b/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts @@ -41,12 +41,12 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) { // Create a withdrawal operation - const bankAccessApiClient = new TalerCorebankApiClient( + const corebankApiClient = new TalerCorebankApiClient( bank.bankAccessApiBaseUrl, ); - const user = await bankAccessApiClient.createRandomBankUser(); - bankAccessApiClient.setAuth(user); - const wop = await bankAccessApiClient.createWithdrawalOperation( + const user = await corebankApiClient.createRandomBankUser(); + corebankApiClient.setAuth(user); + const wop = await corebankApiClient.createWithdrawalOperation( user.username, "TESTKUDOS:10", ); @@ -129,7 +129,7 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) { // Confirm it - await bankAccessApiClient.confirmWithdrawalOperation(user.username, { + await corebankApiClient.confirmWithdrawalOperation(user.username, { withdrawalOperationId: wop.withdrawal_id, }); diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts index 071871837..cf5691fe3 100644 --- a/packages/taler-harness/src/integrationtests/testrunner.ts +++ b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -92,6 +92,7 @@ import { runWithdrawalFeesTest } from "./test-withdrawal-fees.js"; import { runWithdrawalHugeTest } from "./test-withdrawal-huge.js"; import { runWithdrawalManualTest } from "./test-withdrawal-manual.js"; import { runWalletGenDbTest } from "./test-wallet-gendb.js"; +import { runLibeufinBankTest } from "./test-libeufin-bank.js"; /** * Test runner. @@ -173,6 +174,7 @@ const allTests: TestMainFunction[] = [ runStoredBackupsTest, runPaymentExpiredTest, runWalletGenDbTest, + runLibeufinBankTest, ]; export interface TestRunSpec { diff --git a/packages/taler-util/src/bank-api-client.ts b/packages/taler-util/src/bank-api-client.ts index d42317f91..a8cd4b0da 100644 --- a/packages/taler-util/src/bank-api-client.ts +++ b/packages/taler-util/src/bank-api-client.ts @@ -264,7 +264,7 @@ export class TalerCorebankApiClient { const resp = await this.httpLib.fetch(url.href, { headers: this.makeAuthHeader(), }); - return await resp.json(); + return readSuccessResponseJsonOrThrow(resp, codecForAny()); } async getTransactions(username: string): Promise<void> { @@ -295,6 +295,30 @@ export class TalerCorebankApiClient { return await readSuccessResponseJsonOrThrow(resp, codecForAny()); } + async registerAccountExtended(req: RegisterAccountRequest): Promise<void> { + const url = new URL("accounts", this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body: req, + }); + + if ( + resp.status !== 200 && + resp.status !== 201 && + resp.status !== 202 && + resp.status !== 204 + ) { + logger.error(`unexpected status ${resp.status} from POST ${url.href}`); + logger.error(`${j2s(await resp.json())}`); + throw TalerError.fromDetail( + TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, + { + httpStatusCode: resp.status, + }, + ); + } + } + /** * Register a new account and return information about it. * @@ -311,7 +335,13 @@ export class TalerCorebankApiClient { name: username, }, }); - if (resp.status !== 200 && resp.status !== 202 && resp.status !== 204) { + if ( + resp.status !== 200 && + resp.status !== 201 && + resp.status !== 202 && + resp.status !== 204 + ) { + logger.error(`unexpected status ${resp.status} from POST ${url.href}`); logger.error(`${j2s(await resp.json())}`); throw TalerError.fromDetail( TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, @@ -320,8 +350,13 @@ export class TalerCorebankApiClient { }, ); } + // FIXME: Corebank should directly return this info! const infoUrl = new URL(`accounts/${username}`, this.baseUrl); - const infoResp = await this.httpLib.fetch(infoUrl.href); + const infoResp = await this.httpLib.fetch(infoUrl.href, { + headers: { + Authorization: makeBasicAuthHeader(username, password), + }, + }); // FIXME: Validate! const acctInfo: AccountData = await readSuccessResponseJsonOrThrow( infoResp, diff --git a/packages/taler-util/src/talerconfig.ts b/packages/taler-util/src/talerconfig.ts index e9eb71279..f817d9bcb 100644 --- a/packages/taler-util/src/talerconfig.ts +++ b/packages/taler-util/src/talerconfig.ts @@ -143,9 +143,9 @@ export function expandPath(path: string): string { export function pathsub( x: string, lookup: (s: string, depth: number) => string | undefined, - depth = 0, + recursionDepth = 0, ): string { - if (depth >= 128) { + if (recursionDepth >= 128) { throw Error("recursion in path substitution"); } let s = x; @@ -201,7 +201,7 @@ export function pathsub( } else { const m = /^[a-zA-Z-_][a-zA-Z0-9-_]*/.exec(s.substring(l + 1)); if (m && m[0]) { - const r = lookup(m[0], depth + 1); + const r = lookup(m[0], recursionDepth + 1); if (r !== undefined) { s = s.substring(0, l) + r + s.substring(l + 1 + m[0].length); l = l + r.length; diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index 87985fa2a..c5c2c375c 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -73,7 +73,13 @@ import { codecForAbsoluteTime, codecForTimestamp, } from "./time.js"; -import { OrderShortInfo, TransactionType } from "./transactions-types.js"; +import { + OrderShortInfo, + TransactionMajorState, + TransactionMinorState, + TransactionState, + TransactionType, +} from "./transactions-types.js"; /** * Identifier for a transaction in the wallet. @@ -2715,3 +2721,8 @@ export interface WalletContractData { maxDepositFee: AmountString; minimumAge?: number; } + +export interface TestingWaitTransactionRequest { + transactionId: string; + txState: TransactionState; +} diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts index 9b5dd2a19..b21f1992c 100644 --- a/packages/taler-wallet-core/src/operations/testing.ts +++ b/packages/taler-wallet-core/src/operations/testing.ts @@ -23,6 +23,7 @@ import { ConfirmPayResultType, Duration, IntegrationTestV2Args, + j2s, Logger, NotificationType, RegisterAccountRequest, @@ -31,6 +32,7 @@ import { TestPayResult, TransactionMajorState, TransactionMinorState, + TransactionState, TransactionType, WithdrawTestBalanceRequest, } from "@gnu-taler/taler-util"; @@ -494,6 +496,49 @@ async function waitUntilPendingReady( cancelNotifs(); } +/** + * Wait until a transaction is in a particular state. + */ +export async function waitTransactionState( + ws: InternalWalletState, + transactionId: string, + txState: TransactionState, +): Promise<void> { + logger.info( + `starting waiting for ${transactionId} to be in ${JSON.stringify( + txState, + )})`, + ); + ws.ensureTaskLoopRunning(); + let p: OpenedPromise<void> | undefined = undefined; + const cancelNotifs = ws.addNotificationListener((notif) => { + if (!p) { + return; + } + if (notif.type === NotificationType.TransactionStateTransition) { + p.resolve(); + } + }); + while (1) { + p = openPromise(); + const tx = await getTransactionById(ws, { + transactionId, + }); + if ( + tx.txState.major === txState.major && + tx.txState.minor === txState.minor + ) { + break; + } + // Wait until transaction state changed + await p.promise; + } + logger.info( + `done waiting for ${transactionId} to be in ${JSON.stringify(txState)}`, + ); + cancelNotifs(); +} + export async function runIntegrationTest2( ws: InternalWalletState, args: IntegrationTestV2Args, diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts index 3520a05cb..6d66e7ad3 100644 --- a/packages/taler-wallet-core/src/wallet-api-types.ts +++ b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -104,6 +104,7 @@ import { TestPayArgs, TestPayResult, TestingSetTimetravelRequest, + TestingWaitTransactionRequest, Transaction, TransactionByIdRequest, TransactionsRequest, @@ -214,6 +215,7 @@ export enum WalletApiOperation { ValidateIban = "validateIban", TestingWaitTransactionsFinal = "testingWaitTransactionsFinal", TestingWaitRefreshesFinal = "testingWaitRefreshesFinal", + TestingWaitTransactionState = "testingWaitTransactionState", TestingSetTimetravel = "testingSetTimetravel", GetScopedCurrencyInfo = "getScopedCurrencyInfo", ListStoredBackups = "listStoredBackups", @@ -1005,6 +1007,15 @@ export type TestingWaitRefreshesFinal = { }; /** + * Wait until a transaction is in a particular state. + */ +export type TestingWaitTransactionStateOp = { + op: WalletApiOperation.TestingWaitTransactionState; + request: TestingWaitTransactionRequest; + response: EmptyObject; +}; + +/** * Set a coin as (un-)suspended. * Suspended coins won't be used for payments. */ @@ -1108,6 +1119,7 @@ export type WalletOperations = { [WalletApiOperation.TestingWaitTransactionsFinal]: TestingWaitTransactionsFinal; [WalletApiOperation.TestingWaitRefreshesFinal]: TestingWaitRefreshesFinal; [WalletApiOperation.TestingSetTimetravel]: TestingSetTimetravelOp; + [WalletApiOperation.TestingWaitTransactionState]: TestingWaitTransactionStateOp; [WalletApiOperation.GetScopedCurrencyInfo]: GetScopedCurrencyInfoOp; [WalletApiOperation.CreateStoredBackup]: CreateStoredBackupsOp; [WalletApiOperation.ListStoredBackups]: ListStoredBackupsOp; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 75f1a33a9..ccc7ec094 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -127,6 +127,7 @@ import { codecForRecoverStoredBackupRequest, codecForTestingSetTimetravelRequest, setDangerousTimetravel, + TestingWaitTransactionRequest, } from "@gnu-taler/taler-util"; import type { HttpRequestLibrary } from "@gnu-taler/taler-util/http"; import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; @@ -250,6 +251,7 @@ import { runIntegrationTest, runIntegrationTest2, testPay, + waitTransactionState, waitUntilDone, waitUntilRefreshesDone, withdrawTestBalance, @@ -1414,6 +1416,11 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( const resp = await getBackupRecovery(ws); return resp; } + case WalletApiOperation.TestingWaitTransactionState: { + const req = payload as TestingWaitTransactionRequest; + await waitTransactionState(ws, req.transactionId, req.txState); + return {}; + } case WalletApiOperation.GetScopedCurrencyInfo: { // Ignore result, just validate in this mock implementation codecForGetCurrencyInfoRequest().decode(payload); |