gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: harness: add libeufin-bank in


From: gnunet
Subject: [taler-wallet-core] branch master updated: harness: add libeufin-bank integration test
Date: Sun, 24 Sep 2023 21:03:18 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new 7b93938e7 harness: add libeufin-bank integration test
7b93938e7 is described below

commit 7b93938e710c8673ae9a0381b8867705ae872d6f
Author: Florian Dold <florian@dold.me>
AuthorDate: Sun Sep 24 21:03:22 2023 +0200

    harness: add libeufin-bank integration test
---
 packages/taler-harness/src/env-full.ts             |   6 +-
 packages/taler-harness/src/harness/harness.ts      | 166 +++++++++++++--
 packages/taler-harness/src/harness/helpers.ts      |  14 +-
 .../src/integrationtests/test-bank-api.ts          |   6 +-
 .../src/integrationtests/test-deposit.ts           |   4 +-
 .../integrationtests/test-exchange-management.ts   |   6 +-
 .../integrationtests/test-exchange-timetravel.ts   |   6 +-
 .../src/integrationtests/test-fee-regression.ts    |   4 +-
 .../taler-harness/src/integrationtests/test-kyc.ts |   6 +-
 .../src/integrationtests/test-libeufin-bank.ts     | 222 +++++++++++++++++++++
 .../test-merchant-exchange-confusion.ts            |   6 +-
 .../test-merchant-instances-delete.ts              |   6 +-
 .../test-merchant-instances-urls.ts                |   6 +-
 .../integrationtests/test-merchant-instances.ts    |   8 +-
 .../src/integrationtests/test-payment-fault.ts     |   4 +-
 .../src/integrationtests/test-payment-multiple.ts  |   6 +-
 .../src/integrationtests/test-revocation.ts        |   6 +-
 .../test-timetravel-autorefresh.ts                 |   6 +-
 .../integrationtests/test-wallet-notifications.ts  |   4 +-
 .../src/integrationtests/test-wallettesting.ts     |   4 +-
 .../test-withdrawal-bank-integrated.ts             |  10 +-
 .../src/integrationtests/testrunner.ts             |   2 +
 packages/taler-util/src/bank-api-client.ts         |  41 +++-
 packages/taler-util/src/talerconfig.ts             |   6 +-
 packages/taler-util/src/wallet-types.ts            |  13 +-
 .../taler-wallet-core/src/operations/testing.ts    |  45 +++++
 packages/taler-wallet-core/src/wallet-api-types.ts |  12 ++
 packages/taler-wallet-core/src/wallet.ts           |   7 +
 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",
@@ -1004,6 +1006,15 @@ export type TestingWaitRefreshesFinal = {
   response: EmptyObject;
 };
 
+/**
+ * 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);

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]