diff options
Diffstat (limited to 'packages')
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); | 
