diff options
44 files changed, 1244 insertions, 530 deletions
| diff --git a/packages/demobank-ui/src/components/app.tsx b/packages/demobank-ui/src/components/app.tsx index d65398765..ea86da518 100644 --- a/packages/demobank-ui/src/components/app.tsx +++ b/packages/demobank-ui/src/components/app.tsx @@ -15,7 +15,7 @@   */  import { -  globalLogLevel, +  getGlobalLogLevel,    setGlobalLogLevelFromString,  } from "@gnu-taler/taler-util";  import { TranslationProvider } from "@gnu-taler/web-util/browser"; @@ -62,9 +62,7 @@ const App: FunctionalComponent = () => {    );  };  (window as any).setGlobalLogLevelFromString = setGlobalLogLevelFromString; -(window as any).getGlobaLevel = () => { -  return globalLogLevel; -}; +(window as any).getGlobalLevel = getGlobalLogLevel;  function localStorageProvider(): Map<unknown, unknown> {    const map = new Map(JSON.parse(localStorage.getItem("app-cache") || "[]")); diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts index c9202c60e..926a0c93b 100644 --- a/packages/taler-harness/src/harness/harness.ts +++ b/packages/taler-harness/src/harness/harness.ts @@ -467,12 +467,29 @@ export async function setupDb(t: GlobalTestState): Promise<DbInfo> {    };  } +/** + * Make sure that the taler-integrationtest-shared database exists. + * Don't delete it if it already exists. + */ +export async function setupSharedDb(t: GlobalTestState): Promise<DbInfo> { +  const dbname = "taler-integrationtest-shared"; +  const databases = await runCommand(t, "list-dbs", "psql", ["-Aqtl"]); +  if (databases.indexOf("taler-integrationtest-shared") < 0) { +    await runCommand(t, "createdb", "createdb", [dbname]); +  } +  return { +    connStr: `postgres:///${dbname}`, +    dbname, +  }; +} +  export interface BankConfig {    currency: string;    httpPort: number;    database: string;    allowRegistrations: boolean;    maxDebt?: string; +  overrideTestDir?: string;  }  export interface FakeBankConfig { @@ -518,6 +535,14 @@ function setCoin(config: Configuration, c: CoinConfig) {    }  } +function backoffStart(): number { +  return 10; +} + +function backoffIncrement(n: number): number { +  return Math.min(Math.floor(n * 1.5), 1000); +} +  /**   * Send an HTTP request until it succeeds or the process dies.   */ @@ -529,6 +554,7 @@ export async function pingProc(    if (!proc || proc.proc.exitCode !== null) {      throw Error(`service process ${serviceName} not started, can't ping`);    } +  let nextDelay = backoffStart();    while (true) {      try {        logger.trace(`pinging ${serviceName} at ${url}`); @@ -537,8 +563,9 @@ export async function pingProc(        return;      } catch (e: any) {        logger.warn(`service ${serviceName} not ready:`, e.toString()); -      //console.log(e); -      await delayMs(1000); +      logger.info(`waiting ${nextDelay}ms on ${serviceName}`); +      await delayMs(nextDelay); +      nextDelay = backoffIncrement(nextDelay);      }      if (!proc || proc.proc.exitCode != null || proc.proc.signalCode != null) {        throw Error(`service process ${serviceName} stopped unexpectedly`); @@ -857,31 +884,57 @@ export class FakebankService      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<FakebankService> {      const config = new Configuration(); -    setTalerPaths(config, gc.testDir + "/talerhome"); +    const testDir = bc.overrideTestDir ?? gc.testDir; +    setTalerPaths(config, testDir + "/talerhome");      config.setString("taler", "currency", bc.currency);      config.setString("bank", "http_port", `${bc.httpPort}`);      config.setString("bank", "serve", "http");      config.setString("bank", "max_debt_bank", `${bc.currency}:999999`);      config.setString("bank", "max_debt", bc.maxDebt ?? `${bc.currency}:100`);      config.setString("bank", "ram_limit", `${1024}`); -    const cfgFilename = gc.testDir + "/bank.conf"; -    config.write(cfgFilename); +    const cfgFilename = testDir + "/bank.conf"; +    config.write(cfgFilename, { excludeDefaults: true });      return new FakebankService(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("bank", "allow_registrations").orUndefined() ?? true, +      currency: config.getString("taler", "currency").required(), +      database: "none", +      httpPort: config.getNumber("bank", "http_port").required(), +      maxDebt: config.getString("bank", "max_debt").required(), +    }; +    return new FakebankService(gc, bc, cfgFilename); +  } +    setSuggestedExchange(e: ExchangeServiceInterface, exchangePayto: string) {      if (!!this.proc) {        throw Error("Can't set suggested exchange while bank is running.");      }      const config = Configuration.load(this.configFile);      config.setString("bank", "suggested_exchange", e.baseUrl); -    config.write(this.configFile); +    config.write(this.configFile, { excludeDefaults: true });    }    get baseUrl(): string { @@ -958,6 +1011,7 @@ export interface ExchangeConfig {    roundUnit?: string;    httpPort: number;    database: string; +  overrideTestDir?: string;  }  export interface ExchangeServiceInterface { @@ -968,8 +1022,13 @@ export interface ExchangeServiceInterface {  }  export class ExchangeService implements ExchangeServiceInterface { -  static fromExistingConfig(gc: GlobalTestState, exchangeName: string) { -    const cfgFilename = gc.testDir + `/exchange-${exchangeName}.conf`; +  static fromExistingConfig( +    gc: GlobalTestState, +    exchangeName: string, +    opts: { overridePath?: string }, +  ) { +    const testDir = opts.overridePath ?? gc.testDir; +    const cfgFilename = testDir + `/exchange-${exchangeName}.conf`;      const config = Configuration.load(cfgFilename);      const ec: ExchangeConfig = {        currency: config.getString("taler", "currency").required(), @@ -978,7 +1037,9 @@ export class ExchangeService implements ExchangeServiceInterface {        name: exchangeName,        roundUnit: config.getString("taler", "currency_round_unit").required(),      }; -    const privFile = config.getPath("exchange", "master_priv_file").required(); +    const privFile = config +      .getPath("exchange-offline", "master_priv_file") +      .required();      const eddsaPriv = fs.readFileSync(privFile);      const keyPair: EddsaKeyPair = {        eddsaPriv, @@ -1076,11 +1137,13 @@ export class ExchangeService implements ExchangeServiceInterface {    changeConfig(f: (config: Configuration) => void) {      const config = Configuration.load(this.configFilename);      f(config); -    config.write(this.configFilename); +    config.write(this.configFilename, { excludeDefaults: true });    }    static create(gc: GlobalTestState, e: ExchangeConfig) { +    const testDir = e.overrideTestDir ?? gc.testDir;      const config = new Configuration(); +    setTalerPaths(config, testDir + "/talerhome");      config.setString("taler", "currency", e.currency);      // Required by the exchange but not really used yet.      config.setString("exchange", "aml_threshold", `${e.currency}:1000000`); @@ -1089,7 +1152,6 @@ export class ExchangeService implements ExchangeServiceInterface {        "currency_round_unit",        e.roundUnit ?? `${e.currency}:0.01`,      ); -    setTalerPaths(config, gc.testDir + "/talerhome");      config.setString(        "exchange",        "revocation_dir", @@ -1124,10 +1186,16 @@ export class ExchangeService implements ExchangeServiceInterface {      fs.mkdirSync(path.dirname(masterPrivFile), { recursive: true }); +    if (fs.existsSync(masterPrivFile)) { +      throw new Error( +        "master priv file already exists, can't create new exchange config", +      ); +    } +      fs.writeFileSync(masterPrivFile, Buffer.from(exchangeMasterKey.eddsaPriv)); -    const cfgFilename = gc.testDir + `/exchange-${e.name}.conf`; -    config.write(cfgFilename); +    const cfgFilename = testDir + `/exchange-${e.name}.conf`; +    config.write(cfgFilename, { excludeDefaults: true });      return new ExchangeService(gc, e, cfgFilename, exchangeMasterKey);    } @@ -1136,13 +1204,13 @@ export class ExchangeService implements ExchangeServiceInterface {      offeredCoins.forEach((cc) =>        setCoin(config, cc(this.exchangeConfig.currency)),      ); -    config.write(this.configFilename); +    config.write(this.configFilename, { excludeDefaults: true });    }    addCoinConfigList(ccs: CoinConfig[]) {      const config = Configuration.load(this.configFilename);      ccs.forEach((cc) => setCoin(config, cc)); -    config.write(this.configFilename); +    config.write(this.configFilename, { excludeDefaults: true });    }    enableAgeRestrictions(maskStr: string) { @@ -1153,7 +1221,7 @@ export class ExchangeService implements ExchangeServiceInterface {        "age_groups",        maskStr,      ); -    config.write(this.configFilename); +    config.write(this.configFilename, { excludeDefaults: true });    }    get masterPub() { @@ -1174,7 +1242,7 @@ export class ExchangeService implements ExchangeServiceInterface {    ): Promise<void> {      const config = Configuration.load(this.configFilename);      await f(config); -    config.write(this.configFilename); +    config.write(this.configFilename, { excludeDefaults: true });    }    async addBankAccount( @@ -1214,7 +1282,7 @@ export class ExchangeService implements ExchangeServiceInterface {        "password",        exchangeBankAccount.accountPassword,      ); -    config.write(this.configFilename); +    config.write(this.configFilename, { excludeDefaults: true });    }    exchangeHttpProc: ProcessWrapper | undefined; @@ -1475,15 +1543,26 @@ export class ExchangeService implements ExchangeServiceInterface {      );    } -  async start(): Promise<void> { -    if (this.isRunning()) { -      throw Error("exchange is already running"); -    } +  async dbinit() {      await sh(        this.globalState,        "exchange-dbinit",        `taler-exchange-dbinit -c "${this.configFilename}"`,      ); +  } + +  async start( +    opts: { skipDbinit?: boolean; skipKeyup?: boolean } = {}, +  ): Promise<void> { +    if (this.isRunning()) { +      throw Error("exchange is already running"); +    } + +    const skipDbinit = opts.skipDbinit ?? false; + +    if (!skipDbinit) { +      await this.dbinit(); +    }      this.helperCryptoEddsaProc = this.globalState.spawnService(        "taler-exchange-secmod-eddsa", @@ -1514,7 +1593,14 @@ export class ExchangeService implements ExchangeServiceInterface {      );      await this.pingUntilAvailable(); -    await this.keyup(); + +    const skipKeyup = opts.skipKeyup ?? false; + +    if (!skipKeyup) { +      await this.keyup(); +    } else { +      logger.info("skipping keyup"); +    }    }    async pingUntilAvailable(): Promise<void> { @@ -1530,6 +1616,7 @@ export interface MerchantConfig {    currency: string;    httpPort: number;    database: string; +  overrideTestDir?: string;  }  export interface PrivateOrderStatusQuery { @@ -1775,8 +1862,13 @@ export interface CreateMerchantTippingReserveRequest {  }  export class MerchantService implements MerchantServiceInterface { -  static fromExistingConfig(gc: GlobalTestState, name: string) { -    const cfgFilename = gc.testDir + `/merchant-${name}.conf`; +  static fromExistingConfig( +    gc: GlobalTestState, +    name: string, +    opts: { overridePath?: string }, +  ) { +    const testDir = opts.overridePath ?? gc.testDir; +    const cfgFilename = testDir + `/merchant-${name}.conf`;      const config = Configuration.load(cfgFilename);      const mc: MerchantConfig = {        currency: config.getString("taler", "currency").required(), @@ -1846,13 +1938,24 @@ export class MerchantService implements MerchantServiceInterface {      }    } -  async start(): Promise<void> { +  async dbinit() {      await runCommand(        this.globalState,        "merchant-dbinit",        "taler-merchant-dbinit",        ["-c", this.configFilename],      ); +  } + +  /** +   * Start the merchant, +   */ +  async start(opts: { skipDbinit?: boolean } = {}): Promise<void> { +    const skipSetup = opts.skipDbinit ?? false; + +    if (!skipSetup) { +      await this.dbinit(); +    }      this.proc = this.globalState.spawnService(        "taler-merchant-httpd", @@ -1871,11 +1974,12 @@ export class MerchantService implements MerchantServiceInterface {      gc: GlobalTestState,      mc: MerchantConfig,    ): Promise<MerchantService> { +    const testDir = mc.overrideTestDir ?? gc.testDir;      const config = new Configuration();      config.setString("taler", "currency", mc.currency); -    const cfgFilename = gc.testDir + `/merchant-${mc.name}.conf`; -    setTalerPaths(config, gc.testDir + "/talerhome"); +    const cfgFilename = testDir + `/merchant-${mc.name}.conf`; +    setTalerPaths(config, testDir + "/talerhome");      config.setString("merchant", "serve", "tcp");      config.setString("merchant", "port", `${mc.httpPort}`);      config.setString( @@ -1884,7 +1988,7 @@ export class MerchantService implements MerchantServiceInterface {        "${TALER_DATA_HOME}/merchant/merchant.priv",      );      config.setString("merchantdb-postgres", "config", mc.database); -    config.write(cfgFilename); +    config.write(cfgFilename, { excludeDefaults: true });      return new MerchantService(gc, mc, cfgFilename);    } @@ -1902,7 +2006,7 @@ export class MerchantService implements MerchantServiceInterface {        this.merchantConfig.currency,      );      config.setString(`merchant-exchange-${e.name}`, "master_key", e.masterPub); -    config.write(this.configFilename); +    config.write(this.configFilename, { excludeDefaults: true });    }    async addDefaultInstance(): Promise<void> { @@ -1935,14 +2039,8 @@ export class MerchantService implements MerchantServiceInterface {        name: instanceConfig.name,        address: instanceConfig.address ?? {},        jurisdiction: instanceConfig.jurisdiction ?? {}, -      default_max_wire_fee: -        instanceConfig.defaultMaxWireFee ?? -        `${this.merchantConfig.currency}:1.0`, -      default_wire_fee_amortization: -        instanceConfig.defaultWireFeeAmortization ?? 3, -      default_max_deposit_fee: -        instanceConfig.defaultMaxDepositFee ?? -        `${this.merchantConfig.currency}:1.0`, +      // FIXME: In some tests, we might want to make this configurable +      use_stefan: true,        default_wire_transfer_delay:          instanceConfig.defaultWireTransferDelay ??          Duration.toTalerProtocolDuration( @@ -1984,9 +2082,6 @@ export interface PartialMerchantInstanceConfig {    paytoUris: string[];    address?: unknown;    jurisdiction?: unknown; -  defaultMaxWireFee?: string; -  defaultMaxDepositFee?: string; -  defaultWireFeeAmortization?: number;    defaultWireTransferDelay?: TalerProtocolDuration;    defaultPayDelay?: TalerProtocolDuration;  } @@ -2029,9 +2124,7 @@ export interface MerchantInstanceConfig {    name: string;    address: unknown;    jurisdiction: unknown; -  default_max_wire_fee: string; -  default_max_deposit_fee: string; -  default_wire_fee_amortization: number; +  use_stefan: boolean;    default_wire_transfer_delay: TalerProtocolDuration;    default_pay_delay: TalerProtocolDuration;  } @@ -2229,12 +2322,15 @@ export class WalletService {    }    async pingUntilAvailable(): Promise<void> { +    let nextDelay = backoffStart();      while (1) {        try {          await tryUnixConnect(this.socketPath);        } catch (e) { -        logger.info(`connection attempt failed: ${e}`); -        await delayMs(200); +        logger.info(`wallet connection attempt failed: ${e}`); +        logger.info(`waiting on wallet for ${nextDelay}ms`); +        await delayMs(nextDelay); +        nextDelay = backoffIncrement(nextDelay);          continue;        }        logger.info("connection to wallet-core succeeded"); diff --git a/packages/taler-harness/src/harness/helpers.ts b/packages/taler-harness/src/harness/helpers.ts index 3e91c8bd9..d41ffdd00 100644 --- a/packages/taler-harness/src/harness/helpers.ts +++ b/packages/taler-harness/src/harness/helpers.ts @@ -32,6 +32,7 @@ import {    NotificationType,    WalletNotification,    TransactionMajorState, +  Logger,  } from "@gnu-taler/taler-util";  import {    BankAccessApi, @@ -49,18 +50,24 @@ import {    DbInfo,    ExchangeService,    ExchangeServiceInterface, +  FakebankService,    getPayto,    GlobalTestState,    MerchantPrivateApi,    MerchantService,    MerchantServiceInterface,    setupDb, +  setupSharedDb,    WalletCli,    WalletClient,    WalletService,    WithAuthorization,  } from "./harness.js"; +import * as fs from "fs"; + +const logger = new Logger("helpers.ts"); +  /**   * @deprecated   */ @@ -94,6 +101,10 @@ export interface EnvOptions {    ageMaskSpec?: string;    mixedAgeRestriction?: boolean; + +  additionalExchangeConfig?(e: ExchangeService): void; +  additionalMerchantConfig?(m: MerchantService): void; +  additionalBankConfig?(b: BankService): void;  }  /** @@ -204,6 +215,179 @@ export async function createSimpleTestkudosEnvironment(    };  } +export async function useSharedTestkudosEnvironment(t: GlobalTestState) { +  const coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")); + +  const sharedDir = `/tmp/taler-harness@${process.env.USER}`; + +  fs.mkdirSync(sharedDir, { recursive: true }); + +  const db = await setupSharedDb(t); + +  let bank: FakebankService; + +  const prevSetupDone = fs.existsSync(sharedDir + "/setup-done"); + +  logger.info(`previous setup done: ${prevSetupDone}`); + + +  // Wallet has longer startup-time and no dependencies, +  // so we start it rather early. +  const walletStartProm = createWalletDaemonWithClient(t, { name: "wallet" }) + +  if (fs.existsSync(sharedDir + "/bank.conf")) { +    logger.info("reusing existing bank"); +    bank = BankService.fromExistingConfig(t, { +      overridePath: sharedDir, +    }); +  } else { +    logger.info("creating new bank config"); +    bank = await BankService.create(t, { +      allowRegistrations: true, +      currency: "TESTKUDOS", +      database: db.connStr, +      httpPort: 8082, +      overrideTestDir: sharedDir, +    }); +  } + +  logger.info("setting up exchange"); + +  const exchangeName = "testexchange-1"; +  const exchangeConfigFilename = sharedDir + `/exchange-${exchangeName}.conf`; + +  logger.info(`exchange config filename: ${exchangeConfigFilename}`); + +  let exchange: ExchangeService; + +  if (fs.existsSync(exchangeConfigFilename)) { +    logger.info("reusing existing exchange config"); +    exchange = ExchangeService.fromExistingConfig(t, exchangeName, { +      overridePath: sharedDir, +    }); +  } else { +    logger.info("creating new exchange config"); +    exchange = ExchangeService.create(t, { +      name: "testexchange-1", +      currency: "TESTKUDOS", +      httpPort: 8081, +      database: db.connStr, +      overrideTestDir: sharedDir, +    }); +  } + +  logger.info("setting up merchant"); + +  let merchant: MerchantService; +  const merchantName = "testmerchant-1"; +  const merchantConfigFilename = sharedDir + `/merchant-${merchantName}}`; + +  if (fs.existsSync(merchantConfigFilename)) { +    merchant = MerchantService.fromExistingConfig(t, merchantName, { +      overridePath: sharedDir, +    }); +  } else { +    merchant = await MerchantService.create(t, { +      name: "testmerchant-1", +      currency: "TESTKUDOS", +      httpPort: 8083, +      database: db.connStr, +      overrideTestDir: sharedDir, +    }); +  } + +  logger.info("creating bank account for exchange"); + +  const exchangeBankAccount = await bank.createExchangeAccount( +    "myexchange", +    "x", +  ); + +  logger.info("creating exchange bank account"); +  await exchange.addBankAccount("1", exchangeBankAccount); + +  bank.setSuggestedExchange(exchange, exchangeBankAccount.accountPaytoUri); + +  exchange.addCoinConfigList(coinConfig); + +  merchant.addExchange(exchange); + +  logger.info("basic setup done, starting services"); + +  if (!prevSetupDone) { +    // Must be done sequentially, due to a concurrency +    // issue in the *-dbinit tools. +    await exchange.dbinit(); +    await merchant.dbinit(); +  } + +  const bankStart = async () => { +    await bank.start(); +    await bank.pingUntilAvailable(); +  }; + +  const exchangeStart = async () => { +    await exchange.start({ +      skipDbinit: true, +      skipKeyup: prevSetupDone, +    }); +    await exchange.pingUntilAvailable(); +  }; + +  const merchStart = async () => { +    await merchant.start({ +      skipDbinit: true, +    }); +    await merchant.pingUntilAvailable(); + +    if (!prevSetupDone) { +      await merchant.addInstance({ +        id: "default", +        name: "Default Instance", +        paytoUris: [getPayto("merchant-default")], +        defaultWireTransferDelay: Duration.toTalerProtocolDuration( +          Duration.fromSpec({ minutes: 1 }), +        ), +      }); + +      await merchant.addInstance({ +        id: "minst1", +        name: "minst1", +        paytoUris: [getPayto("minst1")], +        defaultWireTransferDelay: Duration.toTalerProtocolDuration( +          Duration.fromSpec({ minutes: 1 }), +        ), +      }); +    } +  }; + +  await bankStart() + +  const res = await Promise.all([ +    exchangeStart(), +    merchStart(), +    undefined, +    walletStartProm, +  ]); + +  const walletClient = res[3].walletClient; +  const walletService = res[3].walletService; + +  fs.writeFileSync(sharedDir + "/setup-done", "OK"); + +  logger.info("setup done!"); + +  return { +    commonDb: db, +    exchange, +    merchant, +    walletClient, +    walletService, +    bank, +    exchangeBankAccount, +  }; +} +  /**   * Run a test case with a simple TESTKUDOS Taler environment, consisting   * of one exchange, one bank and one merchant. @@ -246,6 +430,9 @@ export async function createSimpleTestkudosEnvironmentV2(    bank.setSuggestedExchange(exchange, exchangeBankAccount.accountPaytoUri); +  if (opts.additionalBankConfig) { +    opts.additionalBankConfig(bank); +  }    await bank.start();    await bank.pingUntilAvailable(); @@ -272,11 +459,17 @@ export async function createSimpleTestkudosEnvironmentV2(      exchange.addCoinConfigList(coinConfig);    } +  if (opts.additionalExchangeConfig) { +    opts.additionalExchangeConfig(exchange); +  }    await exchange.start();    await exchange.pingUntilAvailable();    merchant.addExchange(exchange); +  if (opts.additionalMerchantConfig) { +    opts.additionalMerchantConfig(merchant); +  }    await merchant.start();    await merchant.pingUntilAvailable(); @@ -336,7 +529,7 @@ export async function createWalletDaemonWithClient(    const walletClient = new WalletClient({      unixPath: walletService.socketPath,      onNotification(n) { -      console.log("got notification", n); +      console.log(`got ${args.name} notification`, n);        if (args.handleNotification) {          args.handleNotification(n);        } @@ -610,6 +803,8 @@ export async function applyTimeTravel(  /**   * Make a simple payment and check that it succeeded. + *  + * @deprecated   */  export async function makeTestPayment(    t: GlobalTestState, diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts index ec99232f5..841e17dc7 100644 --- a/packages/taler-harness/src/index.ts +++ b/packages/taler-harness/src/index.ts @@ -170,6 +170,39 @@ const sandcastleCli = testingCli.subcommand("sandcastleArgs", "sandcastle", {    help: "Subcommands for handling GNU Taler sandcastle deployments.",  }); +const configCli = testingCli.subcommand("configArgs", "config", { +  help: "Subcommands for handling the Taler configuration.", +}); + +configCli.subcommand("show", "show").action(async (args) => { +  const config = Configuration.load(); +  const cfgStr = config.stringify({ +    diagnostics: true, +  }); +  console.log(cfgStr); +}); + +configCli +  .subcommand("get", "get") +  .requiredArgument("section", clk.STRING) +  .requiredArgument("option", clk.STRING) +  .flag("file", ["-f"]) +  .action(async (args) => { +    const config = Configuration.load(); +    let res; +    if (args.get.file) { +      res = config.getString(args.get.section, args.get.option); +    } else { +      res = config.getPath(args.get.section, args.get.option); +    } +    if (res.isDefined()) { +      console.log(res.value); +    } else { +      console.warn("not found"); +      process.exit(1); +    } +  }); +  const deploymentCli = testingCli.subcommand("deploymentArgs", "deployment", {    help: "Subcommands for handling GNU Taler deployments.",  }); @@ -208,8 +241,10 @@ deploymentCli      const bankAccessApiClient = new BankAccessApiClient({        baseUrl: args.tipTopup.bankAccessUrl, -      username: args.tipTopup.bankAccount, -      password: args.tipTopup.bankPassword, +      auth: { +        username: args.tipTopup.bankAccount, +        password: args.tipTopup.bankPassword, +      },        allowHttp: true,      }); @@ -219,10 +254,12 @@ deploymentCli      console.log("payto URI:", paytoUri); -    const transactions = await bankAccessApiClient.getTransactions(); +    const transactions = await bankAccessApiClient.getTransactions( +      args.tipTopup.bankAccount, +    );      console.log("transactions:", j2s(transactions)); -    await bankAccessApiClient.createTransaction({ +    await bankAccessApiClient.createTransaction(args.tipTopup.bankAccount, {        amount,        paytoUri,      }); diff --git a/packages/taler-harness/src/integrationtests/test-age-restrictions-mixed-merchant.ts b/packages/taler-harness/src/integrationtests/test-age-restrictions-mixed-merchant.ts index 8bf71b63d..af90ef1c5 100644 --- a/packages/taler-harness/src/integrationtests/test-age-restrictions-mixed-merchant.ts +++ b/packages/taler-harness/src/integrationtests/test-age-restrictions-mixed-merchant.ts @@ -17,12 +17,14 @@  /**   * Imports.   */ +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import { defaultCoinConfig } from "../harness/denomStructures.js"; -import { GlobalTestState, WalletCli } from "../harness/harness.js"; +import { GlobalTestState } from "../harness/harness.js";  import { -  createSimpleTestkudosEnvironment, -  withdrawViaBank, -  makeTestPayment, +  createSimpleTestkudosEnvironmentV2, +  createWalletDaemonWithClient, +  makeTestPaymentV2, +  withdrawViaBankV2,  } from "../harness/helpers.js";  /** @@ -32,11 +34,11 @@ export async function runAgeRestrictionsMixedMerchantTest(t: GlobalTestState) {    // Set up test environment    const { -    wallet: walletOne, +    walletClient: walletOne,      bank,      exchange,      merchant, -  } = await createSimpleTestkudosEnvironment( +  } = await createSimpleTestkudosEnvironmentV2(      t,      defaultCoinConfig.map((x) => x("TESTKUDOS")),      { @@ -45,20 +47,27 @@ export async function runAgeRestrictionsMixedMerchantTest(t: GlobalTestState) {      },    ); -  const walletTwo = new WalletCli(t, "walletTwo"); -  const walletThree = new WalletCli(t, "walletThree"); +  const { walletClient: walletTwo } = await createWalletDaemonWithClient(t, { +    name: "w2", +  }); + +  const { walletClient: walletThree } = await createWalletDaemonWithClient(t, { +    name: "w3", +  });    { -    const wallet = walletOne; +    const walletClient = walletOne; -    await withdrawViaBank(t, { -      wallet, +    const wres = await withdrawViaBankV2(t, { +      walletClient,        bank,        exchange,        amount: "TESTKUDOS:20",        restrictAge: 13,      }); +    await wres.withdrawalFinishedCond; +      const order = {        summary: "Buy me!",        amount: "TESTKUDOS:5", @@ -66,41 +75,46 @@ export async function runAgeRestrictionsMixedMerchantTest(t: GlobalTestState) {        minimum_age: 9,      }; -    await makeTestPayment(t, { wallet, merchant, order }); -    await wallet.runUntilDone(); +    await makeTestPaymentV2(t, { walletClient, merchant, order }); +    await walletClient.call( +      WalletApiOperation.TestingWaitTransactionsFinal, +      {}, +    );    }    { -    const wallet = walletTwo; - -    await withdrawViaBank(t, { -      wallet, +    const wres = await withdrawViaBankV2(t, { +      walletClient: walletTwo,        bank,        exchange,        amount: "TESTKUDOS:20",        restrictAge: 13,      }); + +    await wres.withdrawalFinishedCond; +      const order = {        summary: "Buy me!",        amount: "TESTKUDOS:5",        fulfillment_url: "taler://fulfillment-success/thx",      }; -    await makeTestPayment(t, { wallet, merchant, order }); -    await wallet.runUntilDone(); +    await makeTestPaymentV2(t, { walletClient: walletTwo, merchant, order }); +    await walletTwo.call(WalletApiOperation.TestingWaitTransactionsFinal, {});    }    { -    const wallet = walletThree; - -    await withdrawViaBank(t, { -      wallet, +    const wres = await withdrawViaBankV2(t, { +      walletClient: walletThree,        bank,        exchange,        amount: "TESTKUDOS:20",      }); + +    await wres.withdrawalFinishedCond; +      const order = {        summary: "Buy me!",        amount: "TESTKUDOS:5", @@ -108,8 +122,8 @@ export async function runAgeRestrictionsMixedMerchantTest(t: GlobalTestState) {        minimum_age: 9,      }; -    await makeTestPayment(t, { wallet, merchant, order }); -    await wallet.runUntilDone(); +    await makeTestPaymentV2(t, { walletClient: walletThree, merchant, order }); +    await walletThree.call(WalletApiOperation.TestingWaitTransactionsFinal, {});    }  } diff --git a/packages/taler-harness/src/integrationtests/test-bank-api.ts b/packages/taler-harness/src/integrationtests/test-bank-api.ts index a0e1c2085..9ac16980b 100644 --- a/packages/taler-harness/src/integrationtests/test-bank-api.ts +++ b/packages/taler-harness/src/integrationtests/test-bank-api.ts @@ -17,23 +17,22 @@  /**   * Imports.   */ -import { -  GlobalTestState, -  WalletCli, -  ExchangeService, -  setupDb, -  BankService, -  MerchantService, -  getPayto, -} from "../harness/harness.js";  import { createEddsaKeyPair, encodeCrock } from "@gnu-taler/taler-util"; -import { defaultCoinConfig } from "../harness/denomStructures.js";  import { -  BankApi,    BankAccessApi, +  BankApi,    CreditDebitIndicator,    WireGatewayApiClient,  } from "@gnu-taler/taler-wallet-core"; +import { defaultCoinConfig } from "../harness/denomStructures.js"; +import { +  BankService, +  ExchangeService, +  GlobalTestState, +  MerchantService, +  getPayto, +  setupDb, +} from "../harness/harness.js";  /**   * Run test for basic, bank-integrated withdrawal. diff --git a/packages/taler-harness/src/integrationtests/test-claim-loop.ts b/packages/taler-harness/src/integrationtests/test-claim-loop.ts index 921391283..32706c28b 100644 --- a/packages/taler-harness/src/integrationtests/test-claim-loop.ts +++ b/packages/taler-harness/src/integrationtests/test-claim-loop.ts @@ -17,13 +17,13 @@  /**   * Imports.   */ +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { URL } from "url";  import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";  import { -  createSimpleTestkudosEnvironment, -  withdrawViaBank, +  createSimpleTestkudosEnvironmentV2, +  withdrawViaBankV2,  } from "../harness/helpers.js"; -import { URL } from "url"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  /**   * Run test for the merchant's order lifecycle. @@ -34,10 +34,15 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  export async function runClaimLoopTest(t: GlobalTestState) {    // Set up test environment -  const { wallet, bank, exchange, merchant } = -    await createSimpleTestkudosEnvironment(t); +  const { walletClient, bank, exchange, merchant } = +    await createSimpleTestkudosEnvironmentV2(t); -  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); +  await withdrawViaBankV2(t, { +    walletClient, +    bank, +    exchange, +    amount: "TESTKUDOS:20", +  });    // Set up order.    const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", { @@ -61,7 +66,7 @@ export async function runClaimLoopTest(t: GlobalTestState) {    // Make wallet claim the unpaid order.    t.assertTrue(orderStatusBefore.order_status === "unpaid");    const talerPayUri = orderStatusBefore.taler_pay_uri; -  await wallet.client.call(WalletApiOperation.PreparePayForUri, { +  await walletClient.call(WalletApiOperation.PreparePayForUri, {      talerPayUri,    }); diff --git a/packages/taler-harness/src/integrationtests/test-denom-unoffered.ts b/packages/taler-harness/src/integrationtests/test-denom-unoffered.ts index 27c336ea6..5a471b9aa 100644 --- a/packages/taler-harness/src/integrationtests/test-denom-unoffered.ts +++ b/packages/taler-harness/src/integrationtests/test-denom-unoffered.ts @@ -18,22 +18,29 @@   * Imports.   */  import { PreparePayResultType, TalerErrorCode } from "@gnu-taler/taler-util"; -import { Wallet, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";  import { -  createSimpleTestkudosEnvironment, -  withdrawViaBank, +  createSimpleTestkudosEnvironmentV2, +  withdrawViaBankV2,  } from "../harness/helpers.js";  export async function runDenomUnofferedTest(t: GlobalTestState) {    // Set up test environment -  const { wallet, bank, exchange, merchant } = -    await createSimpleTestkudosEnvironment(t); +  const { walletClient, bank, exchange, merchant } = +    await createSimpleTestkudosEnvironmentV2(t);    // Withdraw digital cash into the wallet. -  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); +  const wres = await withdrawViaBankV2(t, { +    walletClient, +    bank, +    exchange, +    amount: "TESTKUDOS:20", +  }); + +  await wres.withdrawalFinishedCond;    // Make the exchange forget the denomination.    // Effectively we completely reset the exchange, @@ -67,7 +74,7 @@ export async function runDenomUnofferedTest(t: GlobalTestState) {    // Make wallet pay for the order -  const preparePayResult = await wallet.client.call( +  const preparePayResult = await walletClient.call(      WalletApiOperation.PreparePayForUri,      {        talerPayUri: orderStatus.taler_pay_uri, @@ -78,11 +85,11 @@ export async function runDenomUnofferedTest(t: GlobalTestState) {      preparePayResult.status === PreparePayResultType.PaymentPossible,    ); -  const confirmResp = await wallet.client.call(WalletApiOperation.ConfirmPay, { +  const confirmResp = await walletClient.call(WalletApiOperation.ConfirmPay, {      proposalId: preparePayResult.proposalId,    }); -  const tx = await wallet.client.call(WalletApiOperation.GetTransactionById, { +  const tx = await walletClient.call(WalletApiOperation.GetTransactionById, {      transactionId: confirmResp.transactionId,    }); @@ -96,21 +103,26 @@ export async function runDenomUnofferedTest(t: GlobalTestState) {      TalerErrorCode.MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND,    ); -  await wallet.client.call(WalletApiOperation.AddExchange, { +  await walletClient.call(WalletApiOperation.AddExchange, {      exchangeBaseUrl: exchange.baseUrl,      forceUpdate: true,    }); -  await wallet.client.call(WalletApiOperation.DeleteTransaction, { +  await walletClient.call(WalletApiOperation.DeleteTransaction, {      transactionId: confirmResp.transactionId,    });    // Now withdrawal should work again. -  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); +  await withdrawViaBankV2(t, { +    walletClient, +    bank, +    exchange, +    amount: "TESTKUDOS:20", +  }); -  await wallet.runUntilDone(); +  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); -  const txs = await wallet.client.call(WalletApiOperation.GetTransactions, {}); +  const txs = await walletClient.call(WalletApiOperation.GetTransactions, {});    console.log(JSON.stringify(txs, undefined, 2));  } diff --git a/packages/taler-harness/src/integrationtests/test-exchange-deposit.ts b/packages/taler-harness/src/integrationtests/test-exchange-deposit.ts index 6c0d070d0..05bbbfaa1 100644 --- a/packages/taler-harness/src/integrationtests/test-exchange-deposit.ts +++ b/packages/taler-harness/src/integrationtests/test-exchange-deposit.ts @@ -30,14 +30,15 @@ import {    depositCoin,    downloadExchangeInfo,    findDenomOrThrow, -  refreshCoin,    SynchronousCryptoWorkerFactoryPlain,    topupReserveWithDemobank,    Wallet,    withdrawCoin,  } from "@gnu-taler/taler-wallet-core";  import { GlobalTestState } from "../harness/harness.js"; -import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; +import { +  createSimpleTestkudosEnvironmentV2, +} from "../harness/helpers.js";  /**   * Run test for basic, bank-integrated withdrawal and payment. @@ -45,7 +46,7 @@ import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";  export async function runExchangeDepositTest(t: GlobalTestState) {    // Set up test environment -  const { bank, exchange } = await createSimpleTestkudosEnvironment(t); +  const { bank, exchange } = await createSimpleTestkudosEnvironmentV2(t);    const http = createPlatformHttpLib({      allowHttp: true, diff --git a/packages/taler-harness/src/integrationtests/test-fee-regression.ts b/packages/taler-harness/src/integrationtests/test-fee-regression.ts index 8c5a5bea4..e0dc4bc3b 100644 --- a/packages/taler-harness/src/integrationtests/test-fee-regression.ts +++ b/packages/taler-harness/src/integrationtests/test-fee-regression.ts @@ -19,18 +19,18 @@   */  import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import { -  GlobalTestState,    BankService,    ExchangeService, +  GlobalTestState,    MerchantService, -  setupDb, -  WalletCli,    getPayto, +  setupDb,  } from "../harness/harness.js";  import { -  withdrawViaBank, -  makeTestPayment, -  SimpleTestEnvironment, +  SimpleTestEnvironmentNg, +  createWalletDaemonWithClient, +  makeTestPaymentV2, +  withdrawViaBankV2,  } from "../harness/helpers.js";  /** @@ -39,7 +39,7 @@ import {   */  export async function createMyTestkudosEnvironment(    t: GlobalTestState, -): Promise<SimpleTestEnvironment> { +): Promise<SimpleTestEnvironmentNg> {    const db = await setupDb(t);    const bank = await BankService.create(t, { @@ -147,13 +147,19 @@ export async function createMyTestkudosEnvironment(    console.log("setup done!"); -  const wallet = new WalletCli(t); +  const { walletClient, walletService } = await createWalletDaemonWithClient( +    t, +    { +      name: "w1", +    }, +  );    return {      commonDb: db,      exchange,      merchant, -    wallet, +    walletClient, +    walletService,      bank,      exchangeBankAccount,    }; @@ -165,19 +171,21 @@ export async function createMyTestkudosEnvironment(  export async function runFeeRegressionTest(t: GlobalTestState) {    // Set up test environment -  const { wallet, bank, exchange, merchant } = +  const { walletClient, bank, exchange, merchant } =      await createMyTestkudosEnvironment(t);    // Withdraw digital cash into the wallet. -  await withdrawViaBank(t, { -    wallet, +  const wres = await withdrawViaBankV2(t, { +    walletClient,      bank,      exchange,      amount: "TESTKUDOS:1.92",    }); -  const coins = await wallet.client.call(WalletApiOperation.DumpCoins, {}); +  await wres.withdrawalFinishedCond; + +  const coins = await walletClient.call(WalletApiOperation.DumpCoins, {});    // Make sure we really withdraw one 0.64 and one 1.28 coin.    t.assertTrue(coins.coins.length === 2); @@ -188,11 +196,11 @@ export async function runFeeRegressionTest(t: GlobalTestState) {      fulfillment_url: "taler://fulfillment-success/thx",    }; -  await makeTestPayment(t, { wallet, merchant, order }); +  await makeTestPaymentV2(t, { walletClient, merchant, order }); -  await wallet.runUntilDone(); +  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); -  const txs = await wallet.client.call(WalletApiOperation.GetTransactions, {}); +  const txs = await walletClient.call(WalletApiOperation.GetTransactions, {});    t.assertAmountEquals(txs.transactions[1].amountEffective, "TESTKUDOS:1.30");    console.log(txs);  } diff --git a/packages/taler-harness/src/integrationtests/test-forced-selection.ts b/packages/taler-harness/src/integrationtests/test-forced-selection.ts index d0621f000..3425dadf1 100644 --- a/packages/taler-harness/src/integrationtests/test-forced-selection.ts +++ b/packages/taler-harness/src/integrationtests/test-forced-selection.ts @@ -20,7 +20,7 @@  import { j2s } from "@gnu-taler/taler-util";  import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import { GlobalTestState } from "../harness/harness.js"; -import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; +import { createSimpleTestkudosEnvironmentV2 } from "../harness/helpers.js";  /**   * Run test for forced denom/coin selection. @@ -28,14 +28,14 @@ import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";  export async function runForcedSelectionTest(t: GlobalTestState) {    // Set up test environment -  const { wallet, bank, exchange, merchant } = -    await createSimpleTestkudosEnvironment(t); +  const { walletClient, bank, exchange, merchant } = +    await createSimpleTestkudosEnvironmentV2(t); -  await wallet.client.call(WalletApiOperation.AddExchange, { +  await walletClient.call(WalletApiOperation.AddExchange, {      exchangeBaseUrl: exchange.baseUrl,    }); -  await wallet.client.call(WalletApiOperation.WithdrawTestBalance, { +  await walletClient.call(WalletApiOperation.WithdrawTestBalance, {      exchangeBaseUrl: exchange.baseUrl,      amount: "TESTKUDOS:10",      bankAccessApiBaseUrl: bank.bankAccessApiBaseUrl, @@ -49,13 +49,13 @@ export async function runForcedSelectionTest(t: GlobalTestState) {      },    }); -  await wallet.runUntilDone(); +  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); -  const coinDump = await wallet.client.call(WalletApiOperation.DumpCoins, {}); +  const coinDump = await walletClient.call(WalletApiOperation.DumpCoins, {});    console.log(coinDump);    t.assertDeepEqual(coinDump.coins.length, 3); -  const payResp = await wallet.client.call(WalletApiOperation.TestPay, { +  const payResp = await walletClient.call(WalletApiOperation.TestPay, {      amount: "TESTKUDOS:3",      merchantBaseUrl: merchant.makeInstanceBaseUrl(),      summary: "bla", diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-bankaccount.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-bankaccount.ts index a5396a472..f36168301 100644 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-bankaccount.ts +++ b/packages/taler-harness/src/integrationtests/test-libeufin-api-bankaccount.ts @@ -19,7 +19,6 @@   */  import { GlobalTestState } from "../harness/harness.js";  import { -  NexusUserBundle,    LibeufinNexusApi,    LibeufinNexusService,    LibeufinSandboxService, diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-nexus-balance.ts b/packages/taler-harness/src/integrationtests/test-libeufin-nexus-balance.ts index 5c10c029e..68b0174cc 100644 --- a/packages/taler-harness/src/integrationtests/test-libeufin-nexus-balance.ts +++ b/packages/taler-harness/src/integrationtests/test-libeufin-nexus-balance.ts @@ -23,7 +23,6 @@ import {    NexusUserBundle,    launchLibeufinServices,    LibeufinNexusApi, -  LibeufinCli,  } from "../harness/libeufin.js";  /** 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 1b5d50fd1..534b35278 100644 --- a/packages/taler-harness/src/integrationtests/test-merchant-instances-urls.ts +++ b/packages/taler-harness/src/integrationtests/test-merchant-instances-urls.ts @@ -67,12 +67,10 @@ export async function runMerchantInstancesUrlsTest(t: GlobalTestState) {    await clientForDefault.createInstance({      id: "default",      address: {}, -    default_max_deposit_fee: "TESTKUDOS:1", -    default_max_wire_fee: "TESTKUDOS:1", +    use_stefan: true,      default_pay_delay: Duration.toTalerProtocolDuration(        Duration.fromSpec({ seconds: 60 }),      ), -    default_wire_fee_amortization: 1,      default_wire_transfer_delay: Duration.toTalerProtocolDuration(        Duration.fromSpec({ seconds: 60 }),      ), @@ -92,12 +90,10 @@ export async function runMerchantInstancesUrlsTest(t: GlobalTestState) {    await clientForDefault.createInstance({      id: "myinst",      address: {}, -    default_max_deposit_fee: "TESTKUDOS:1", -    default_max_wire_fee: "TESTKUDOS:1",      default_pay_delay: Duration.toTalerProtocolDuration(        Duration.fromSpec({ seconds: 60 }),      ), -    default_wire_fee_amortization: 1, +    use_stefan: true,      default_wire_transfer_delay: Duration.toTalerProtocolDuration(        Duration.fromSpec({ seconds: 60 }),      ), diff --git a/packages/taler-harness/src/integrationtests/test-merchant-longpolling.ts b/packages/taler-harness/src/integrationtests/test-merchant-longpolling.ts index 7d2ba99b2..59f23fe5d 100644 --- a/packages/taler-harness/src/integrationtests/test-merchant-longpolling.ts +++ b/packages/taler-harness/src/integrationtests/test-merchant-longpolling.ts @@ -17,20 +17,20 @@  /**   * Imports.   */ -import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js"; -import { -  createSimpleTestkudosEnvironment, -  withdrawViaBank, -} from "../harness/helpers.js";  import { -  PreparePayResultType, -  codecForMerchantOrderStatusUnpaid,    ConfirmPayResultType, +  PreparePayResultType,    URL, +  codecForMerchantOrderStatusUnpaid,  } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import axiosImp from "axios"; +import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js"; +import { +  createSimpleTestkudosEnvironmentV2, +  withdrawViaBankV2 +} from "../harness/helpers.js";  const axios = axiosImp.default; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  /**   * Run test for basic, bank-integrated withdrawal. @@ -38,12 +38,19 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  export async function runMerchantLongpollingTest(t: GlobalTestState) {    // Set up test environment -  const { wallet, bank, exchange, merchant } = -    await createSimpleTestkudosEnvironment(t); +  const { walletClient, bank, exchange, merchant } = +    await createSimpleTestkudosEnvironmentV2(t);    // Withdraw digital cash into the wallet. -  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); +  const wres = await withdrawViaBankV2(t, { +    walletClient, +    bank, +    exchange, +    amount: "TESTKUDOS:20", +  }); + +  await wres.withdrawalFinishedCond;    /**     * ========================================================================= @@ -113,7 +120,7 @@ export async function runMerchantLongpollingTest(t: GlobalTestState) {     * =========================================================================     */ -  let preparePayResp = await wallet.client.call( +  let preparePayResp = await walletClient.call(      WalletApiOperation.PreparePayForUri,      {        talerPayUri: pubUnpaidStatus.taler_pay_uri, @@ -148,7 +155,7 @@ export async function runMerchantLongpollingTest(t: GlobalTestState) {      publicOrderStatusResp.data,    ); -  const confirmPayRes = await wallet.client.call( +  const confirmPayRes = await walletClient.call(      WalletApiOperation.ConfirmPay,      {        proposalId: proposalId, diff --git a/packages/taler-harness/src/integrationtests/test-payment-abort.ts b/packages/taler-harness/src/integrationtests/test-payment-abort.ts index 0ac953240..40438c583 100644 --- a/packages/taler-harness/src/integrationtests/test-payment-abort.ts +++ b/packages/taler-harness/src/integrationtests/test-payment-abort.ts @@ -24,7 +24,6 @@ import {  } from "../harness/helpers.js";  import {    FaultInjectionRequestContext, -  FaultInjectionResponseContext,  } from "../harness/faultInjection.js";  import {    codecForMerchantOrderStatusUnpaid, diff --git a/packages/taler-harness/src/integrationtests/test-payment-claim.ts b/packages/taler-harness/src/integrationtests/test-payment-claim.ts index e93d2c44c..eb219c1e7 100644 --- a/packages/taler-harness/src/integrationtests/test-payment-claim.ts +++ b/packages/taler-harness/src/integrationtests/test-payment-claim.ts @@ -17,18 +17,17 @@  /**   * Imports.   */ +import { PreparePayResultType, TalerErrorCode } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import {    GlobalTestState,    MerchantPrivateApi,    WalletCli,  } from "../harness/harness.js";  import { -  createSimpleTestkudosEnvironment, -  withdrawViaBank, +  createSimpleTestkudosEnvironmentV2, +  withdrawViaBankV2,  } from "../harness/helpers.js"; -import { PreparePayResultType } from "@gnu-taler/taler-util"; -import { TalerErrorCode } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  /**   * Run test for basic, bank-integrated withdrawal. @@ -36,14 +35,19 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  export async function runPaymentClaimTest(t: GlobalTestState) {    // Set up test environment -  const { wallet, bank, exchange, merchant } = -    await createSimpleTestkudosEnvironment(t); +  const { walletClient, bank, exchange, merchant } = +    await createSimpleTestkudosEnvironmentV2(t);    const walletTwo = new WalletCli(t, "two");    // Withdraw digital cash into the wallet. -  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); +  await withdrawViaBankV2(t, { +    walletClient, +    bank, +    exchange, +    amount: "TESTKUDOS:20", +  });    // Set up order. @@ -65,7 +69,7 @@ export async function runPaymentClaimTest(t: GlobalTestState) {    // Make wallet pay for the order -  const preparePayResult = await wallet.client.call( +  const preparePayResult = await walletClient.call(      WalletApiOperation.PreparePayForUri,      {        talerPayUri, @@ -82,7 +86,7 @@ export async function runPaymentClaimTest(t: GlobalTestState) {      });    }); -  await wallet.client.call(WalletApiOperation.ConfirmPay, { +  await walletClient.call(WalletApiOperation.ConfirmPay, {      proposalId: preparePayResult.proposalId,    }); diff --git a/packages/taler-harness/src/integrationtests/test-payment-forgettable.ts b/packages/taler-harness/src/integrationtests/test-payment-forgettable.ts index b66eda2f1..21d76397d 100644 --- a/packages/taler-harness/src/integrationtests/test-payment-forgettable.ts +++ b/packages/taler-harness/src/integrationtests/test-payment-forgettable.ts @@ -17,11 +17,12 @@  /**   * Imports.   */ +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import { GlobalTestState } from "../harness/harness.js";  import { -  createSimpleTestkudosEnvironment, -  withdrawViaBank, -  makeTestPayment, +  createSimpleTestkudosEnvironmentV2, +  makeTestPaymentV2, +  withdrawViaBankV2,  } from "../harness/helpers.js";  /** @@ -30,12 +31,19 @@ import {  export async function runPaymentForgettableTest(t: GlobalTestState) {    // Set up test environment -  const { wallet, bank, exchange, merchant } = -    await createSimpleTestkudosEnvironment(t); +  const { walletClient, bank, exchange, merchant } = +    await createSimpleTestkudosEnvironmentV2(t);    // Withdraw digital cash into the wallet. -  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); +  const wres = await withdrawViaBankV2(t, { +    walletClient, +    bank, +    exchange, +    amount: "TESTKUDOS:20", +  }); + +  await wres.withdrawalFinishedCond;    {      const order = { @@ -50,7 +58,7 @@ export async function runPaymentForgettableTest(t: GlobalTestState) {        },      }; -    await makeTestPayment(t, { wallet, merchant, order }); +    await makeTestPaymentV2(t, { walletClient, merchant, order });    }    console.log("testing with forgettable field without hash"); @@ -68,10 +76,10 @@ export async function runPaymentForgettableTest(t: GlobalTestState) {        },      }; -    await makeTestPayment(t, { wallet, merchant, order }); +    await makeTestPaymentV2(t, { walletClient, merchant, order });    } -  await wallet.runUntilDone(); +  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});  }  runPaymentForgettableTest.suites = ["wallet", "merchant"]; diff --git a/packages/taler-harness/src/integrationtests/test-payment-idempotency.ts b/packages/taler-harness/src/integrationtests/test-payment-idempotency.ts index 1099a8188..e16cf9dd1 100644 --- a/packages/taler-harness/src/integrationtests/test-payment-idempotency.ts +++ b/packages/taler-harness/src/integrationtests/test-payment-idempotency.ts @@ -17,13 +17,13 @@  /**   * Imports.   */ +import { PreparePayResultType } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";  import { -  createSimpleTestkudosEnvironment, -  withdrawViaBank, +  createSimpleTestkudosEnvironmentV2, +  withdrawViaBankV2,  } from "../harness/helpers.js"; -import { PreparePayResultType } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  /**   * Test the wallet-core payment API, especially that repeated operations @@ -32,12 +32,17 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  export async function runPaymentIdempotencyTest(t: GlobalTestState) {    // Set up test environment -  const { wallet, bank, exchange, merchant } = -    await createSimpleTestkudosEnvironment(t); +  const { walletClient, bank, exchange, merchant } = +    await createSimpleTestkudosEnvironmentV2(t);    // Withdraw digital cash into the wallet. -  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); +  await withdrawViaBankV2(t, { +    walletClient, +    bank, +    exchange, +    amount: "TESTKUDOS:20", +  });    // Set up order. @@ -59,14 +64,14 @@ export async function runPaymentIdempotencyTest(t: GlobalTestState) {    // Make wallet pay for the order -  const preparePayResult = await wallet.client.call( +  const preparePayResult = await walletClient.call(      WalletApiOperation.PreparePayForUri,      {        talerPayUri: orderStatus.taler_pay_uri,      },    ); -  const preparePayResultRep = await wallet.client.call( +  const preparePayResultRep = await walletClient.call(      WalletApiOperation.PreparePayForUri,      {        talerPayUri: orderStatus.taler_pay_uri, @@ -82,7 +87,7 @@ export async function runPaymentIdempotencyTest(t: GlobalTestState) {    const proposalId = preparePayResult.proposalId; -  const confirmPayResult = await wallet.client.call( +  const confirmPayResult = await walletClient.call(      WalletApiOperation.ConfirmPay,      {        proposalId: proposalId, @@ -91,7 +96,7 @@ export async function runPaymentIdempotencyTest(t: GlobalTestState) {    console.log("confirm pay result", confirmPayResult); -  await wallet.runUntilDone(); +  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});    // Check if payment was successful. @@ -101,7 +106,7 @@ export async function runPaymentIdempotencyTest(t: GlobalTestState) {    t.assertTrue(orderStatus.order_status === "paid"); -  const preparePayResultAfter = await wallet.client.call( +  const preparePayResultAfter = await walletClient.call(      WalletApiOperation.PreparePayForUri,      {        talerPayUri, diff --git a/packages/taler-harness/src/integrationtests/test-payment-share.ts b/packages/taler-harness/src/integrationtests/test-payment-share.ts index 2f853b91b..156571372 100644 --- a/packages/taler-harness/src/integrationtests/test-payment-share.ts +++ b/packages/taler-harness/src/integrationtests/test-payment-share.ts @@ -17,21 +17,17 @@  /**   * Imports.   */ +import { +  ConfirmPayResultType, +  PreparePayResultType, +} from "@gnu-taler/taler-util";  import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";  import {    createSimpleTestkudosEnvironmentV2, -  withdrawViaBankV2, -  makeTestPaymentV2,    createWalletDaemonWithClient, +  withdrawViaBankV2,  } from "../harness/helpers.js"; -import { -  ConfirmPayResultType, -  PreparePayResultType, -  j2s, -  parsePayUri, -  stringifyPayUri, -} from "@gnu-taler/taler-util";  /**   * Run test for basic, bank-integrated withdrawal and payment. diff --git a/packages/taler-harness/src/integrationtests/test-payment-template.ts b/packages/taler-harness/src/integrationtests/test-payment-template.ts index 41e43e28a..172791648 100644 --- a/packages/taler-harness/src/integrationtests/test-payment-template.ts +++ b/packages/taler-harness/src/integrationtests/test-payment-template.ts @@ -17,13 +17,12 @@  /**   * Imports.   */ -import { ConfirmPayResultType, Duration, PreparePayResultType, TalerProtocolTimestamp } from "@gnu-taler/taler-util"; +import { ConfirmPayResultType, Duration, PreparePayResultType } from "@gnu-taler/taler-util";  import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";  import { -  createSimpleTestkudosEnvironment, -  withdrawViaBank, -  makeTestPayment, +  createSimpleTestkudosEnvironmentV2, +  withdrawViaBankV2  } from "../harness/helpers.js";  /** @@ -32,8 +31,8 @@ import {  export async function runPaymentTemplateTest(t: GlobalTestState) {    // Set up test environment -  const { wallet, bank, exchange, merchant } = -    await createSimpleTestkudosEnvironment(t); +  const { walletClient, bank, exchange, merchant } = +    await createSimpleTestkudosEnvironmentV2(t);    await MerchantPrivateApi.createTemplate(merchant, "default", {      template_id: "template1", @@ -51,11 +50,11 @@ export async function runPaymentTemplateTest(t: GlobalTestState) {    // Withdraw digital cash into the wallet. -  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); +  await withdrawViaBankV2(t, { walletClient, bank, exchange, amount: "TESTKUDOS:20" });    // Request a template payment -  const preparePayResult = await wallet.client.call( +  const preparePayResult = await walletClient.call(      WalletApiOperation.PreparePayForTemplate,      {        talerPayTemplateUri: `taler+http://pay-template/localhost:${merchant.port}/template1?amount=TESTKUDOS:1`, @@ -71,7 +70,7 @@ export async function runPaymentTemplateTest(t: GlobalTestState) {    // Pay for it -  const r2 = await wallet.client.call(WalletApiOperation.ConfirmPay, { +  const r2 = await walletClient.call(WalletApiOperation.ConfirmPay, {      proposalId: preparePayResult.proposalId,    }); @@ -88,8 +87,7 @@ export async function runPaymentTemplateTest(t: GlobalTestState) {    );    t.assertTrue(orderStatus.order_status === "paid"); - -  await wallet.runUntilDone(); +  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});  }  runPaymentTemplateTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-payment-zero.ts b/packages/taler-harness/src/integrationtests/test-payment-zero.ts index 264cffd06..7423751a5 100644 --- a/packages/taler-harness/src/integrationtests/test-payment-zero.ts +++ b/packages/taler-harness/src/integrationtests/test-payment-zero.ts @@ -20,9 +20,9 @@  import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import { GlobalTestState } from "../harness/harness.js";  import { -  createSimpleTestkudosEnvironment, -  withdrawViaBank, -  makeTestPayment, +  createSimpleTestkudosEnvironmentV2, +  withdrawViaBankV2, +  makeTestPaymentV2,  } from "../harness/helpers.js";  import { TransactionMajorState } from "@gnu-taler/taler-util"; @@ -33,19 +33,19 @@ import { TransactionMajorState } from "@gnu-taler/taler-util";  export async function runPaymentZeroTest(t: GlobalTestState) {    // Set up test environment -  const { wallet, bank, exchange, merchant } = -    await createSimpleTestkudosEnvironment(t); +  const { walletClient, bank, exchange, merchant } = +    await createSimpleTestkudosEnvironmentV2(t);    // First, make a "free" payment when we don't even have    // any money in the    // Withdraw digital cash into the wallet. -  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); +  await withdrawViaBankV2(t, { walletClient, bank, exchange, amount: "TESTKUDOS:20" }); -  await wallet.runUntilDone(); +  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); -  await makeTestPayment(t, { -    wallet, +  await makeTestPaymentV2(t, { +    walletClient,      merchant,      order: {        summary: "I am free!", @@ -54,9 +54,9 @@ export async function runPaymentZeroTest(t: GlobalTestState) {      },    }); -  await wallet.runUntilDone(); +  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); -  const transactions = await wallet.client.call( +  const transactions = await walletClient.call(      WalletApiOperation.GetTransactions,      {},    ); diff --git a/packages/taler-harness/src/integrationtests/test-paywall-flow.ts b/packages/taler-harness/src/integrationtests/test-paywall-flow.ts index a9601c625..b0477a049 100644 --- a/packages/taler-harness/src/integrationtests/test-paywall-flow.ts +++ b/packages/taler-harness/src/integrationtests/test-paywall-flow.ts @@ -19,18 +19,22 @@   */  import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";  import { -  createSimpleTestkudosEnvironment, -  withdrawViaBank, -} from "../harness/helpers.js"; -import {    PreparePayResultType,    codecForMerchantOrderStatusUnpaid,    ConfirmPayResultType,    URL,  } from "@gnu-taler/taler-util"; -import axiosImp from "axios"; -const axios = axiosImp.default;  import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { +  createSimpleTestkudosEnvironmentV2, +  withdrawViaBankV2, +} from "../harness/helpers.js"; +import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; + +const httpLib = createPlatformHttpLib({ +  allowHttp: true, +  enableThrottling: false, +});  /**   * Run test for basic, bank-integrated withdrawal. @@ -38,12 +42,17 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  export async function runPaywallFlowTest(t: GlobalTestState) {    // Set up test environment -  const { wallet, bank, exchange, merchant } = -    await createSimpleTestkudosEnvironment(t); +  const { walletClient, bank, exchange, merchant } = +    await createSimpleTestkudosEnvironmentV2(t);    // Withdraw digital cash into the wallet. -  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); +  await withdrawViaBankV2(t, { +    walletClient, +    bank, +    exchange, +    amount: "TESTKUDOS:20", +  });    /**     * ========================================================================= @@ -77,9 +86,7 @@ export async function runPaywallFlowTest(t: GlobalTestState) {    t.assertTrue(orderStatus.already_paid_order_id === undefined);    let publicOrderStatusUrl = new URL(orderStatus.order_status_url); -  let publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, { -    validateStatus: () => true, -  }); +  let publicOrderStatusResp = await httpLib.fetch(publicOrderStatusUrl.href);    if (publicOrderStatusResp.status != 402) {      throw Error( @@ -88,12 +95,12 @@ export async function runPaywallFlowTest(t: GlobalTestState) {    }    let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode( -    publicOrderStatusResp.data, +    publicOrderStatusResp.json(),    );    console.log(pubUnpaidStatus); -  let preparePayResp = await wallet.client.call( +  let preparePayResp = await walletClient.call(      WalletApiOperation.PreparePayForUri,      {        talerPayUri: pubUnpaidStatus.taler_pay_uri, @@ -105,10 +112,8 @@ export async function runPaywallFlowTest(t: GlobalTestState) {    const proposalId = preparePayResp.proposalId;    console.log("requesting", publicOrderStatusUrl.href); -  publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, { -    validateStatus: () => true, -  }); -  console.log("response body", publicOrderStatusResp.data); +  publicOrderStatusResp = await httpLib.fetch(publicOrderStatusUrl.href); +  console.log("response body", publicOrderStatusResp.json());    if (publicOrderStatusResp.status != 402) {      throw Error(        `expected status 402 (after claiming), but got ${publicOrderStatusResp.status}`, @@ -116,26 +121,20 @@ export async function runPaywallFlowTest(t: GlobalTestState) {    }    pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode( -    publicOrderStatusResp.data, +    publicOrderStatusResp.json(),    ); -  const confirmPayRes = await wallet.client.call( -    WalletApiOperation.ConfirmPay, -    { -      proposalId: proposalId, -    }, -  ); +  const confirmPayRes = await walletClient.call(WalletApiOperation.ConfirmPay, { +    proposalId: proposalId, +  });    t.assertTrue(confirmPayRes.type === ConfirmPayResultType.Done); +  publicOrderStatusResp = await httpLib.fetch(publicOrderStatusUrl.href); -  publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, { -    validateStatus: () => true, -  }); - -  console.log(publicOrderStatusResp.data); +  console.log(publicOrderStatusResp.json());    if (publicOrderStatusResp.status != 200) { -    console.log(publicOrderStatusResp.data); +    console.log(publicOrderStatusResp.json());      throw Error(        `expected status 200 (after paying), but got ${publicOrderStatusResp.status}`,      ); @@ -158,7 +157,7 @@ export async function runPaywallFlowTest(t: GlobalTestState) {    // Pay with new taler://pay URI, which should    // have the new session ID!    // Wallet should now automatically re-play payment. -  preparePayResp = await wallet.client.call( +  preparePayResp = await walletClient.call(      WalletApiOperation.PreparePayForUri,      {        talerPayUri: talerPayUriOne, @@ -199,7 +198,7 @@ export async function runPaywallFlowTest(t: GlobalTestState) {    // Here the re-purchase detection should kick in,    // and the wallet should re-pay for the old order    // under the new session ID (mysession-three). -  preparePayResp = await wallet.client.call( +  preparePayResp = await walletClient.call(      WalletApiOperation.PreparePayForUri,      {        talerPayUri: orderStatus.taler_pay_uri, @@ -232,19 +231,17 @@ export async function runPaywallFlowTest(t: GlobalTestState) {    console.log("requesting public status", publicOrderStatusUrl);    // Ask the order status of the claimed-but-unpaid order -  publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, { -    validateStatus: () => true, -  }); +  publicOrderStatusResp = await httpLib.fetch(publicOrderStatusUrl.href);    if (publicOrderStatusResp.status != 402) {      throw Error(`expected status 402, but got ${publicOrderStatusResp.status}`);    }    pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode( -    publicOrderStatusResp.data, +    publicOrderStatusResp.json(),    ); -  console.log(publicOrderStatusResp.data); +  console.log(publicOrderStatusResp.json());    t.assertTrue(pubUnpaidStatus.already_paid_order_id === firstOrderId);  } diff --git a/packages/taler-harness/src/integrationtests/test-refund-auto.ts b/packages/taler-harness/src/integrationtests/test-refund-auto.ts index 4c2a2f94a..607080e68 100644 --- a/packages/taler-harness/src/integrationtests/test-refund-auto.ts +++ b/packages/taler-harness/src/integrationtests/test-refund-auto.ts @@ -17,13 +17,13 @@  /**   * Imports.   */ +import { Duration, durationFromSpec } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";  import { -  createSimpleTestkudosEnvironment, -  withdrawViaBank, +  createSimpleTestkudosEnvironmentV2, +  withdrawViaBankV2,  } from "../harness/helpers.js"; -import { Duration, durationFromSpec } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  /**   * Run test for basic, bank-integrated withdrawal. @@ -31,12 +31,17 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  export async function runRefundAutoTest(t: GlobalTestState) {    // Set up test environment -  const { wallet, bank, exchange, merchant } = -    await createSimpleTestkudosEnvironment(t); +  const { walletClient, bank, exchange, merchant } = +    await createSimpleTestkudosEnvironmentV2(t);    // Withdraw digital cash into the wallet. -  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); +  await withdrawViaBankV2(t, { +    walletClient, +    bank, +    exchange, +    amount: "TESTKUDOS:20", +  });    // Set up order.    const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", { @@ -61,13 +66,12 @@ export async function runRefundAutoTest(t: GlobalTestState) {    // Make wallet pay for the order -  const r1 = await wallet.client.call(WalletApiOperation.PreparePayForUri, { +  const r1 = await walletClient.call(WalletApiOperation.PreparePayForUri, {      talerPayUri: orderStatus.taler_pay_uri,    }); -  await wallet.client.call(WalletApiOperation.ConfirmPay, { -    // FIXME: should be validated, don't cast! -    proposalId: r1.proposalId, +  await walletClient.call(WalletApiOperation.ConfirmPay, { +    transactionId: r1.transactionId,    });    // Check if payment was successful. @@ -88,9 +92,9 @@ export async function runRefundAutoTest(t: GlobalTestState) {    console.log(ref);    // The wallet should now automatically pick up the refund. -  await wallet.runUntilDone(); +  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); -  const transactions = await wallet.client.call( +  const transactions = await walletClient.call(      WalletApiOperation.GetTransactions,      {},    ); diff --git a/packages/taler-harness/src/integrationtests/test-refund-incremental.ts b/packages/taler-harness/src/integrationtests/test-refund-incremental.ts index c8fcfc831..8e7e38b71 100644 --- a/packages/taler-harness/src/integrationtests/test-refund-incremental.ts +++ b/packages/taler-harness/src/integrationtests/test-refund-incremental.ts @@ -18,21 +18,21 @@   * Imports.   */  import { +  Amounts, +  Duration, +  TransactionType, +  durationFromSpec, +} from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import {    GlobalTestState, -  delayMs,    MerchantPrivateApi, +  delayMs,  } from "../harness/harness.js";  import { -  createSimpleTestkudosEnvironment, -  withdrawViaBank, +  createSimpleTestkudosEnvironmentV2, +  withdrawViaBankV2,  } from "../harness/helpers.js"; -import { -  TransactionType, -  Amounts, -  durationFromSpec, -  Duration, -} from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  /**   * Run test for basic, bank-integrated withdrawal. @@ -40,12 +40,17 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  export async function runRefundIncrementalTest(t: GlobalTestState) {    // Set up test environment -  const { wallet, bank, exchange, merchant } = -    await createSimpleTestkudosEnvironment(t); +  const { walletClient, bank, exchange, merchant } = +    await createSimpleTestkudosEnvironmentV2(t);    // Withdraw digital cash into the wallet. -  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); +  await withdrawViaBankV2(t, { +    walletClient, +    bank, +    exchange, +    amount: "TESTKUDOS:20", +  });    // Set up order. @@ -68,11 +73,11 @@ export async function runRefundIncrementalTest(t: GlobalTestState) {    // Make wallet pay for the order -  const r1 = await wallet.client.call(WalletApiOperation.PreparePayForUri, { +  const r1 = await walletClient.call(WalletApiOperation.PreparePayForUri, {      talerPayUri: orderStatus.taler_pay_uri,    }); -  await wallet.client.call(WalletApiOperation.ConfirmPay, { +  await walletClient.call(WalletApiOperation.ConfirmPay, {      proposalId: r1.proposalId,    }); @@ -94,15 +99,15 @@ export async function runRefundIncrementalTest(t: GlobalTestState) {    console.log("first refund increase response", ref);    { -    let wr = await wallet.client.call(WalletApiOperation.StartRefundQuery, { +    let wr = await walletClient.call(WalletApiOperation.StartRefundQuery, {        transactionId: r1.transactionId,      }); -    await wallet.runUntilDone(); -    console.log(wr); -    const txs = await wallet.client.call( -      WalletApiOperation.GetTransactions, +    await walletClient.call( +      WalletApiOperation.TestingWaitTransactionsFinal,        {},      ); +    console.log(wr); +    const txs = await walletClient.call(WalletApiOperation.GetTransactions, {});      console.log(        "transactions after applying first refund:",        JSON.stringify(txs, undefined, 2), @@ -136,10 +141,13 @@ export async function runRefundIncrementalTest(t: GlobalTestState) {    console.log("third refund increase response", ref);    { -    let wr = await wallet.client.call(WalletApiOperation.StartRefundQuery, { +    let wr = await walletClient.call(WalletApiOperation.StartRefundQuery, {        transactionId: r1.transactionId,      }); -    await wallet.runUntilDone();   +    await walletClient.call( +      WalletApiOperation.TestingWaitTransactionsFinal, +      {}, +    );      console.log(wr);    } @@ -153,25 +161,17 @@ export async function runRefundIncrementalTest(t: GlobalTestState) {    console.log(JSON.stringify(orderStatus, undefined, 2)); -  await wallet.runUntilDone(); +  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); -  const bal = await wallet.client.call(WalletApiOperation.GetBalances, {}); +  const bal = await walletClient.call(WalletApiOperation.GetBalances, {});    console.log(JSON.stringify(bal, undefined, 2));    { -    const txs = await wallet.client.call( -      WalletApiOperation.GetTransactions, -      {}, -    ); +    const txs = await walletClient.call(WalletApiOperation.GetTransactions, {});      console.log(JSON.stringify(txs, undefined, 2));      const txTypes = txs.transactions.map((x) => x.type); -    t.assertDeepEqual(txTypes, [ -      "withdrawal", -      "payment", -      "refund", -      "refund", -    ]); +    t.assertDeepEqual(txTypes, ["withdrawal", "payment", "refund", "refund"]);      for (const tx of txs.transactions) {        if (tx.type !== TransactionType.Refund) { diff --git a/packages/taler-harness/src/integrationtests/test-revocation.ts b/packages/taler-harness/src/integrationtests/test-revocation.ts index 0fbb4960e..04707e51a 100644 --- a/packages/taler-harness/src/integrationtests/test-revocation.ts +++ b/packages/taler-harness/src/integrationtests/test-revocation.ts @@ -213,3 +213,4 @@ export async function runRevocationTest(t: GlobalTestState) {  runRevocationTest.timeoutMs = 120000;  runRevocationTest.suites = ["wallet"]; +runRevocationTest.excludeByDefault = true;
\ No newline at end of file diff --git a/packages/taler-harness/src/integrationtests/test-simple-payment.ts b/packages/taler-harness/src/integrationtests/test-simple-payment.ts index 82fa5f21d..58ab61435 100644 --- a/packages/taler-harness/src/integrationtests/test-simple-payment.ts +++ b/packages/taler-harness/src/integrationtests/test-simple-payment.ts @@ -1,6 +1,6 @@  /*   This file is part of GNU Taler - (C) 2020 Taler Systems S.A. + (C) 2023 Taler Systems S.A.   GNU Taler is free software; you can redistribute it and/or modify it under the   terms of the GNU General Public License as published by the Free Software @@ -20,11 +20,10 @@  import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import { GlobalTestState } from "../harness/harness.js";  import { -  createSimpleTestkudosEnvironmentV2,    withdrawViaBankV2,    makeTestPaymentV2, +  useSharedTestkudosEnvironment,  } from "../harness/helpers.js"; -import { j2s } from "@gnu-taler/taler-util";  /**   * Run test for basic, bank-integrated withdrawal and payment. @@ -33,7 +32,7 @@ export async function runSimplePaymentTest(t: GlobalTestState) {    // Set up test environment    const { walletClient, bank, exchange, merchant } = -    await createSimpleTestkudosEnvironmentV2(t); +    await useSharedTestkudosEnvironment(t);    // Withdraw digital cash into the wallet. diff --git a/packages/taler-harness/src/integrationtests/test-tipping.ts b/packages/taler-harness/src/integrationtests/test-tipping.ts index 332f702d7..53d7f08c8 100644 --- a/packages/taler-harness/src/integrationtests/test-tipping.ts +++ b/packages/taler-harness/src/integrationtests/test-tipping.ts @@ -18,8 +18,8 @@   * Imports.   */  import { +  BankAccessApiClient,    WalletApiOperation, -  BankApi,    WireGatewayApiClient,  } from "@gnu-taler/taler-wallet-core";  import { @@ -28,7 +28,7 @@ import {    MerchantPrivateApi,    getWireMethodForTest,  } from "../harness/harness.js"; -import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; +import { createSimpleTestkudosEnvironmentV2 } from "../harness/helpers.js";  import { TransactionMajorState } from "@gnu-taler/taler-util";  /** @@ -37,10 +37,14 @@ import { TransactionMajorState } from "@gnu-taler/taler-util";  export async function runTippingTest(t: GlobalTestState) {    // Set up test environment -  const { wallet, bank, exchange, merchant, exchangeBankAccount } = -    await createSimpleTestkudosEnvironment(t); +  const { walletClient, bank, exchange, merchant, exchangeBankAccount } = +    await createSimpleTestkudosEnvironmentV2(t); -  const mbu = await BankApi.createRandomBankUser(bank); +  const bankAccessApiClient = new BankAccessApiClient({ +    allowHttp: true, +    baseUrl: bank.bankAccessApiBaseUrl, +  }); +  const mbu = await bankAccessApiClient.createRandomBankUser();    const merchantClient = new MerchantApiClient(      merchant.makeInstanceBaseUrl("default"), @@ -99,7 +103,7 @@ export async function runTippingTest(t: GlobalTestState) {    console.log("created tip", tip);    const doTip = async (): Promise<void> => { -    const ptr = await wallet.client.call(WalletApiOperation.PrepareReward, { +    const ptr = await walletClient.call(WalletApiOperation.PrepareReward, {        talerRewardUri: tip.taler_reward_uri,      }); @@ -108,19 +112,22 @@ export async function runTippingTest(t: GlobalTestState) {      t.assertAmountEquals(ptr.rewardAmountRaw, "TESTKUDOS:5");      t.assertAmountEquals(ptr.rewardAmountEffective, "TESTKUDOS:4.85"); -    await wallet.client.call(WalletApiOperation.AcceptReward, { +    await walletClient.call(WalletApiOperation.AcceptReward, {        walletRewardId: ptr.walletRewardId,      }); -    await wallet.runUntilDone(); +    await walletClient.call( +      WalletApiOperation.TestingWaitTransactionsFinal, +      {}, +    ); -    const bal = await wallet.client.call(WalletApiOperation.GetBalances, {}); +    const bal = await walletClient.call(WalletApiOperation.GetBalances, {});      console.log(bal);      t.assertAmountEquals(bal.balances[0].available, "TESTKUDOS:4.85"); -    const txns = await wallet.client.call( +    const txns = await walletClient.call(        WalletApiOperation.GetTransactions,        {},      ); @@ -128,7 +135,10 @@ export async function runTippingTest(t: GlobalTestState) {      console.log("Transactions:", JSON.stringify(txns, undefined, 2));      t.assertDeepEqual(txns.transactions[0].type, "reward"); -    t.assertDeepEqual(txns.transactions[0].txState.major, TransactionMajorState.Done); +    t.assertDeepEqual( +      txns.transactions[0].txState.major, +      TransactionMajorState.Done, +    );      t.assertAmountEquals(        txns.transactions[0].amountEffective,        "TESTKUDOS:4.85", diff --git a/packages/taler-harness/src/integrationtests/test-tos-format.ts b/packages/taler-harness/src/integrationtests/test-tos-format.ts new file mode 100644 index 000000000..e6087af9d --- /dev/null +++ b/packages/taler-harness/src/integrationtests/test-tos-format.ts @@ -0,0 +1,101 @@ +/* + This file is part of GNU Taler + (C) 2020 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Imports. + */ +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { GlobalTestState } from "../harness/harness.js"; +import { +  createSimpleTestkudosEnvironmentV2, +} from "../harness/helpers.js"; +import * as fs from "fs"; + +/** + * Run test for basic, bank-integrated withdrawal and payment. + */ +export async function runTermOfServiceFormatTest(t: GlobalTestState) { +  // Set up test environment +  const tosDir = t.testDir + `/tos/`; +  const langs = ["es", "en", "de"] + +  langs.forEach(l => { +    const langDir = tosDir + l + "/" +    fs.mkdirSync(langDir, { recursive: true }); +    fs.writeFileSync(langDir + "v1.txt", "text content"); +    fs.writeFileSync(langDir + "v1.md", "markdown content"); +    fs.writeFileSync(langDir + "v1.html", "html content"); +  }); + +  const { walletClient, exchange, } = +    await createSimpleTestkudosEnvironmentV2(t, undefined, { +      additionalExchangeConfig: (ex) => { +        ex.changeConfig((cfg) => { +          cfg.setString("exchange", "terms_etag", "v1") +          cfg.setString("exchange", "terms_dir", tosDir) +        }) +      } +    }); + + +  { +    const tos = await walletClient.client.call(WalletApiOperation.GetExchangeTos, { +      exchangeBaseUrl: exchange.baseUrl, +    }) + +    t.assertDeepEqual(tos.content, "text content"); +  } + +  { +    const tos = await walletClient.client.call(WalletApiOperation.GetExchangeTos, { +      exchangeBaseUrl: exchange.baseUrl, +      acceptedFormat: ["text/html"] +    }) + +    t.assertDeepEqual(tos.content, "html content"); +  } + +  { +    const tos = await walletClient.client.call(WalletApiOperation.GetExchangeTos, { +      exchangeBaseUrl: exchange.baseUrl, +      acceptedFormat: ["text/markdown"] +    }) + +    t.assertDeepEqual(tos.content, "markdown content"); +  } + +  { +    const tos = await walletClient.client.call(WalletApiOperation.GetExchangeTos, { +      exchangeBaseUrl: exchange.baseUrl, +      acceptedFormat: ["text/markdown", "text/html"] +    }) + +    // prefer markdown since its the first one in the list +    t.assertDeepEqual(tos.content, "markdown content"); +  } + +  { +    const tos = await walletClient.client.call(WalletApiOperation.GetExchangeTos, { +      exchangeBaseUrl: exchange.baseUrl, +      acceptedFormat: ["text/pdf", "text/html"] +    }) + +    // there is no pdf so fallback in html +    t.assertDeepEqual(tos.content, "html content"); +  } +} + +runTermOfServiceFormatTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-wallet-backup-basic.ts b/packages/taler-harness/src/integrationtests/test-wallet-backup-basic.ts index f36e2e4f1..5321cf5c7 100644 --- a/packages/taler-harness/src/integrationtests/test-wallet-backup-basic.ts +++ b/packages/taler-harness/src/integrationtests/test-wallet-backup-basic.ts @@ -22,7 +22,9 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import { GlobalTestState, WalletCli } from "../harness/harness.js";  import {    createSimpleTestkudosEnvironment, +  createSimpleTestkudosEnvironmentV2,    withdrawViaBank, +  withdrawViaBankV2,  } from "../harness/helpers.js";  import { SyncService } from "../harness/sync.js"; @@ -32,8 +34,8 @@ import { SyncService } from "../harness/sync.js";  export async function runWalletBackupBasicTest(t: GlobalTestState) {    // Set up test environment -  const { commonDb, merchant, wallet, bank, exchange } = -    await createSimpleTestkudosEnvironment(t); +  const { commonDb, merchant, walletClient, bank, exchange } = +    await createSimpleTestkudosEnvironmentV2(t);    const sync = await SyncService.create(t, {      currency: "TESTKUDOS", @@ -49,32 +51,32 @@ export async function runWalletBackupBasicTest(t: GlobalTestState) {    await sync.start();    await sync.pingUntilAvailable(); -  await wallet.client.call(WalletApiOperation.AddBackupProvider, { +  await walletClient.call(WalletApiOperation.AddBackupProvider, {      backupProviderBaseUrl: sync.baseUrl,      activate: false,      name: sync.baseUrl,    });    { -    const bi = await wallet.client.call(WalletApiOperation.GetBackupInfo, {}); +    const bi = await walletClient.call(WalletApiOperation.GetBackupInfo, {});      t.assertDeepEqual(bi.providers[0].active, false);    } -  await wallet.client.call(WalletApiOperation.AddBackupProvider, { +  await walletClient.call(WalletApiOperation.AddBackupProvider, {      backupProviderBaseUrl: sync.baseUrl,      activate: true,      name: sync.baseUrl,    });    { -    const bi = await wallet.client.call(WalletApiOperation.GetBackupInfo, {}); +    const bi = await walletClient.call(WalletApiOperation.GetBackupInfo, {});      t.assertDeepEqual(bi.providers[0].active, true);    } -  await wallet.client.call(WalletApiOperation.RunBackupCycle, {}); +  await walletClient.call(WalletApiOperation.RunBackupCycle, {});    { -    const bi = await wallet.client.call(WalletApiOperation.GetBackupInfo, {}); +    const bi = await walletClient.call(WalletApiOperation.GetBackupInfo, {});      console.log(bi);      t.assertDeepEqual(        bi.providers[0].paymentStatus.type, @@ -82,32 +84,32 @@ export async function runWalletBackupBasicTest(t: GlobalTestState) {      );    } -  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:10" }); +  await withdrawViaBankV2(t, { walletClient, bank, exchange, amount: "TESTKUDOS:10" }); -  await wallet.runUntilDone(); +  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); -  await wallet.client.call(WalletApiOperation.RunBackupCycle, {}); +  await walletClient.call(WalletApiOperation.RunBackupCycle, {});    { -    const bi = await wallet.client.call(WalletApiOperation.GetBackupInfo, {}); +    const bi = await walletClient.call(WalletApiOperation.GetBackupInfo, {});      console.log(bi);    } -  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:5" }); +  await withdrawViaBankV2(t, { walletClient, bank, exchange, amount: "TESTKUDOS:5" }); -  await wallet.client.call(WalletApiOperation.RunBackupCycle, {}); +  await walletClient.call(WalletApiOperation.RunBackupCycle, {});    { -    const bi = await wallet.client.call(WalletApiOperation.GetBackupInfo, {}); +    const bi = await walletClient.call(WalletApiOperation.GetBackupInfo, {});      console.log(bi);    } -  const backupRecovery = await wallet.client.call( +  const backupRecovery = await walletClient.call(      WalletApiOperation.ExportBackupRecovery,      {},    ); -  const txs = await wallet.client.call(WalletApiOperation.GetTransactions, {}); +  const txs = await walletClient.call(WalletApiOperation.GetTransactions, {});    console.log(`backed up transactions ${j2s(txs)}`);    const wallet2 = new WalletCli(t, "wallet2"); diff --git a/packages/taler-harness/src/integrationtests/test-wallet-backup-doublespend.ts b/packages/taler-harness/src/integrationtests/test-wallet-backup-doublespend.ts index 2aceab86e..e3e18d5a4 100644 --- a/packages/taler-harness/src/integrationtests/test-wallet-backup-doublespend.ts +++ b/packages/taler-harness/src/integrationtests/test-wallet-backup-doublespend.ts @@ -21,21 +21,22 @@ import { PreparePayResultType } from "@gnu-taler/taler-util";  import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import {    GlobalTestState, -  WalletCli,    MerchantPrivateApi, +  WalletCli,  } from "../harness/harness.js";  import { -  createSimpleTestkudosEnvironment, -  makeTestPayment, +  createSimpleTestkudosEnvironmentV2, +  makeTestPaymentV2,    withdrawViaBank, +  withdrawViaBankV2  } from "../harness/helpers.js";  import { SyncService } from "../harness/sync.js";  export async function runWalletBackupDoublespendTest(t: GlobalTestState) {    // Set up test environment -  const { commonDb, merchant, wallet, bank, exchange } = -    await createSimpleTestkudosEnvironment(t); +  const { commonDb, merchant, walletClient, bank, exchange } = +    await createSimpleTestkudosEnvironmentV2(t);    const sync = await SyncService.create(t, {      currency: "TESTKUDOS", @@ -51,21 +52,21 @@ export async function runWalletBackupDoublespendTest(t: GlobalTestState) {    await sync.start();    await sync.pingUntilAvailable(); -  await wallet.client.call(WalletApiOperation.AddBackupProvider, { +  await walletClient.call(WalletApiOperation.AddBackupProvider, {      backupProviderBaseUrl: sync.baseUrl,      activate: true,      name: sync.baseUrl,    }); -  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:10" }); +  await withdrawViaBankV2(t, { walletClient, bank, exchange, amount: "TESTKUDOS:10" }); -  await wallet.runUntilDone(); +  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); -  await wallet.client.call(WalletApiOperation.RunBackupCycle, {}); -  await wallet.runUntilDone(); -  await wallet.client.call(WalletApiOperation.RunBackupCycle, {}); +  await walletClient.call(WalletApiOperation.RunBackupCycle, {}); +  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); +  await walletClient.call(WalletApiOperation.RunBackupCycle, {}); -  const backupRecovery = await wallet.client.call( +  const backupRecovery = await walletClient.call(      WalletApiOperation.ExportBackupRecovery,      {},    ); @@ -80,23 +81,23 @@ export async function runWalletBackupDoublespendTest(t: GlobalTestState) {    console.log(      "wallet1 balance before spend:", -    await wallet.client.call(WalletApiOperation.GetBalances, {}), +    await walletClient.call(WalletApiOperation.GetBalances, {}),    ); -  await makeTestPayment(t, { +  await makeTestPaymentV2(t, {      merchant, -    wallet, +    walletClient,      order: {        summary: "foo",        amount: "TESTKUDOS:7",      },    }); -  await wallet.runUntilDone(); +  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});    console.log(      "wallet1 balance after spend:", -    await wallet.client.call(WalletApiOperation.GetBalances, {}), +    await walletClient.call(WalletApiOperation.GetBalances, {}),    );    { diff --git a/packages/taler-harness/src/integrationtests/test-wallet-balance.ts b/packages/taler-harness/src/integrationtests/test-wallet-balance.ts index b5b1917a6..0f75bd96e 100644 --- a/packages/taler-harness/src/integrationtests/test-wallet-balance.ts +++ b/packages/taler-harness/src/integrationtests/test-wallet-balance.ts @@ -30,7 +30,11 @@ import {    setupDb,    WalletCli,  } from "../harness/harness.js"; -import { withdrawViaBank } from "../harness/helpers.js"; +import { +  createSimpleTestkudosEnvironmentV2, +  withdrawViaBank, +  withdrawViaBankV2, +} from "../harness/helpers.js";  /**   * Test for wallet balance error messages / different types of insufficient balance. @@ -41,72 +45,17 @@ import { withdrawViaBank } from "../harness/helpers.js";  export async function runWalletBalanceTest(t: GlobalTestState) {    // Set up test environment -  const db = await setupDb(t); - -  const bank = await FakebankService.create(t, { -    allowRegistrations: true, -    currency: "TESTKUDOS", -    database: db.connStr, -    httpPort: 8082, -  }); - -  const exchange = ExchangeService.create(t, { -    name: "testexchange-1", -    currency: "TESTKUDOS", -    httpPort: 8081, -    database: db.connStr, -  }); - -  const merchant = await MerchantService.create(t, { -    name: "testmerchant-1", -    currency: "TESTKUDOS", -    httpPort: 8083, -    database: db.connStr, -  }); - -  const exchangeBankAccount = await bank.createExchangeAccount( -    "myexchange", -    "x", -  ); -  exchange.addBankAccount("1", exchangeBankAccount); - -  bank.setSuggestedExchange(exchange, exchangeBankAccount.accountPaytoUri); - -  await bank.start(); - -  await bank.pingUntilAvailable(); - -  const coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")); -  exchange.addCoinConfigList(coinConfig); - -  await exchange.start(); -  await exchange.pingUntilAvailable(); - -  merchant.addExchange(exchange); - -  await merchant.start(); -  await merchant.pingUntilAvailable(); - -  // Fakebank uses x-taler-bank, but merchant is configured to only accept sepa! -  const label = "mymerchant"; -  await merchant.addInstance({ -    id: "default", -    name: "Default Instance", -    paytoUris: [ -      `payto://iban/SANDBOXX/${getRandomIban(label)}?receiver-name=${label}`, -    ], -    defaultWireTransferDelay: Duration.toTalerProtocolDuration( -      Duration.fromSpec({ minutes: 1 }), -    ), -  }); - -  console.log("setup done!"); - -  const wallet = new WalletCli(t); +  const { merchant, walletClient, exchange, bank } = +    await createSimpleTestkudosEnvironmentV2(t);    // Withdraw digital cash into the wallet. -  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); +  await withdrawViaBankV2(t, { +    walletClient, +    bank, +    exchange, +    amount: "TESTKUDOS:20", +  });    const order = {      summary: "Buy me!", @@ -126,7 +75,7 @@ export async function runWalletBalanceTest(t: GlobalTestState) {    // Make wallet pay for the order -  const preparePayResult = await wallet.client.call( +  const preparePayResult = await walletClient.call(      WalletApiOperation.PreparePayForUri,      {        talerPayUri: orderStatus.taler_pay_uri, @@ -152,7 +101,7 @@ export async function runWalletBalanceTest(t: GlobalTestState) {      Amounts.isZero(preparePayResult.balanceDetails.balanceMerchantDepositable),    ); -  await wallet.runUntilDone(); +  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});  }  runWalletBalanceTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts b/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts index c384a6e74..58f564f34 100644 --- a/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts +++ b/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts @@ -37,7 +37,7 @@ import {    withdrawCoin,  } from "@gnu-taler/taler-wallet-core";  import { GlobalTestState } from "../harness/harness.js"; -import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; +import { createSimpleTestkudosEnvironmentV2 } from "../harness/helpers.js";  /**   * Run test for basic, bank-integrated withdrawal and payment. @@ -45,7 +45,7 @@ import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";  export async function runWalletDblessTest(t: GlobalTestState) {    // Set up test environment -  const { bank, exchange } = await createSimpleTestkudosEnvironment(t); +  const { bank, exchange } = await createSimpleTestkudosEnvironmentV2(t);    const http = createPlatformHttpLib({      allowHttp: true, diff --git a/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts b/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts index 810250f53..c70fd51d4 100644 --- a/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts +++ b/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts @@ -18,27 +18,24 @@   * Imports.   */  import { -  Amounts,    Duration,    NotificationType, -  PreparePayResultType,    TransactionMajorState,  } from "@gnu-taler/taler-util";  import { -  BankAccessApi, -  BankApi, +  BankAccessApiClient,    WalletApiOperation,  } from "@gnu-taler/taler-wallet-core";  import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";  import {    ExchangeService,    FakebankService, -  getRandomIban,    GlobalTestState,    MerchantService, -  setupDb,    WalletClient,    WalletService, +  getRandomIban, +  setupDb,  } from "../harness/harness.js";  /** @@ -126,10 +123,14 @@ export async function runWalletNotificationsTest(t: GlobalTestState) {      skipDefaults: true,    }); -  const user = await BankApi.createRandomBankUser(bank); -  const wop = await BankAccessApi.createWithdrawalOperation( -    bank, -    user, +  const bankAccessApiClient = new BankAccessApiClient({ +    allowHttp: true, +    baseUrl: bank.bankAccessApiBaseUrl, +  }); +  const user = await bankAccessApiClient.createRandomBankUser(); +  bankAccessApiClient.setAuth(user); +  const wop = await bankAccessApiClient.createWithdrawalOperation( +    user.username,      "TESTKUDOS:20",    ); @@ -163,7 +164,7 @@ export async function runWalletNotificationsTest(t: GlobalTestState) {    // Confirm it -  await BankApi.confirmWithdrawalOperation(bank, user, wop); +  await bankAccessApiClient.confirmWithdrawalOperation(user.username, wop);    await withdrawalFinishedReceivedPromise;  } diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-abort-bank.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-abort-bank.ts index bf2dc0133..c3069c317 100644 --- a/packages/taler-harness/src/integrationtests/test-withdrawal-abort-bank.ts +++ b/packages/taler-harness/src/integrationtests/test-withdrawal-abort-bank.ts @@ -20,8 +20,7 @@  import { TalerErrorCode } from "@gnu-taler/taler-util";  import {    WalletApiOperation, -  BankApi, -  BankAccessApi, +  BankAccessApiClient,  } from "@gnu-taler/taler-wallet-core";  import { GlobalTestState } from "../harness/harness.js";  import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; @@ -36,10 +35,14 @@ export async function runWithdrawalAbortBankTest(t: GlobalTestState) {    // Create a withdrawal operation -  const user = await BankApi.createRandomBankUser(bank); -  const wop = await BankAccessApi.createWithdrawalOperation( -    bank, -    user, +  const bankAccessApiClient = new BankAccessApiClient({ +    allowHttp: true, +    baseUrl: bank.bankAccessApiBaseUrl, +  }); +  const user = await bankAccessApiClient.createRandomBankUser(); +  bankAccessApiClient.setAuth(user); +  const wop = await bankAccessApiClient.createWithdrawalOperation( +    user.username,      "TESTKUDOS:10",    ); @@ -53,8 +56,7 @@ export async function runWithdrawalAbortBankTest(t: GlobalTestState) {    // Abort it -  await BankApi.abortWithdrawalOperation(bank, user, wop); -  //await BankApi.confirmWithdrawalOperation(bank, user, wop); +  await bankAccessApiClient.abortWithdrawalOperation(user.username, wop);    // Withdraw 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 396f0f03f..61687ec02 100644 --- a/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts +++ b/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts @@ -19,11 +19,7 @@   */  import { GlobalTestState } from "../harness/harness.js";  import { createSimpleTestkudosEnvironmentV2 } from "../harness/helpers.js"; -import { -  WalletApiOperation, -  BankApi, -  BankAccessApi, -} from "@gnu-taler/taler-wallet-core"; +import { BankAccessApiClient, WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import {    j2s,    NotificationType, @@ -44,10 +40,14 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {    // Create a withdrawal operation -  const user = await BankApi.createRandomBankUser(bank); -  const wop = await BankAccessApi.createWithdrawalOperation( -    bank, -    user, +  const bankAccessApiClient = new BankAccessApiClient({ +    allowHttp: true, +    baseUrl: bank.bankAccessApiBaseUrl, +  }); +  const user = await bankAccessApiClient.createRandomBankUser(); +  bankAccessApiClient.setAuth(user); +  const wop = await bankAccessApiClient.createWithdrawalOperation( +    user.username,      "TESTKUDOS:10",    ); @@ -129,7 +129,7 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {    // Confirm it -  await BankApi.confirmWithdrawalOperation(bank, user, wop); +  await bankAccessApiClient.confirmWithdrawalOperation(user.username, wop);    await withdrawalBankConfirmedCond; diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts index 065b134c4..06355b964 100644 --- a/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts +++ b/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts @@ -26,6 +26,7 @@ import {  } from "../harness/harness.js";  import {    BankAccessApi, +  BankAccessApiClient,    BankApi,    WalletApiOperation,  } from "@gnu-taler/taler-wallet-core"; @@ -112,8 +113,13 @@ export async function runWithdrawalFeesTest(t: GlobalTestState) {    const amount = "TESTKUDOS:7.5"; -  const user = await BankApi.createRandomBankUser(bank); -  const wop = await BankAccessApi.createWithdrawalOperation(bank, user, amount); +  const bankAccessApiClient = new BankAccessApiClient({ +    allowHttp: true, +    baseUrl: bank.bankAccessApiBaseUrl, +  }); +  const user = await bankAccessApiClient.createRandomBankUser(); +  bankAccessApiClient.setAuth(user); +  const wop = await bankAccessApiClient.createWithdrawalOperation(user.username, amount);    // Hand it to the wallet @@ -150,10 +156,7 @@ export async function runWithdrawalFeesTest(t: GlobalTestState) {    // Confirm it -  await BankApi.confirmWithdrawalOperation(bank, user, wop); - -  await exchange.runWirewatchOnce(); - +  await bankAccessApiClient.confirmWithdrawalOperation(user.username, wop);    await wallet.runUntilDone();    // Check balance diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-huge.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-huge.ts index f56e4d24d..a9d544ee0 100644 --- a/packages/taler-harness/src/integrationtests/test-withdrawal-huge.ts +++ b/packages/taler-harness/src/integrationtests/test-withdrawal-huge.ts @@ -27,7 +27,11 @@ import {  } from "../harness/harness.js";  import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";  import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; -import { NotificationType, TransactionMajorState, URL } from "@gnu-taler/taler-util"; +import { +  NotificationType, +  TransactionMajorState, +  URL, +} from "@gnu-taler/taler-util";  /**   * Withdraw a high amount.  Mostly intended as a perf test. @@ -84,7 +88,8 @@ export async function runWithdrawalHugeTest(t: GlobalTestState) {    await wallet.connect();    const withdrawalFinishedCond = wallet.waitForNotificationCond( -    (wn) => wn.type === NotificationType.TransactionStateTransition && +    (wn) => +      wn.type === NotificationType.TransactionStateTransition &&        wn.transactionId.startsWith("txn:withdrawal:") &&        wn.newTxState.major === TransactionMajorState.Done,    ); diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-manual.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-manual.ts index a356a5c1a..d49235f89 100644 --- a/packages/taler-harness/src/integrationtests/test-withdrawal-manual.ts +++ b/packages/taler-harness/src/integrationtests/test-withdrawal-manual.ts @@ -17,14 +17,14 @@  /**   * Imports.   */ -import { GlobalTestState } from "../harness/harness.js"; -import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; +import { AbsoluteTime, Logger, j2s } from "@gnu-taler/taler-util";  import { +  BankAccessApiClient,    WalletApiOperation, -  BankApi,    WireGatewayApiClient,  } from "@gnu-taler/taler-wallet-core"; -import { AbsoluteTime, j2s, Logger } from "@gnu-taler/taler-util"; +import { GlobalTestState } from "../harness/harness.js"; +import { createSimpleTestkudosEnvironmentV2 } from "../harness/helpers.js";  const logger = new Logger("test-withdrawal-manual.ts"); @@ -34,14 +34,19 @@ const logger = new Logger("test-withdrawal-manual.ts");  export async function runWithdrawalManualTest(t: GlobalTestState) {    // Set up test environment -  const { wallet, bank, exchange, exchangeBankAccount } = -    await createSimpleTestkudosEnvironment(t); +  const { walletClient, bank, exchange, exchangeBankAccount } = +    await createSimpleTestkudosEnvironmentV2(t);    // Create a withdrawal operation -  const user = await BankApi.createRandomBankUser(bank); +  const bankAccessApiClient = new BankAccessApiClient({ +    baseUrl: bank.bankAccessApiBaseUrl, +    allowHttp: true, +  }); + +  const user = await bankAccessApiClient.createRandomBankUser(); -  await wallet.client.call(WalletApiOperation.AddExchange, { +  await walletClient.call(WalletApiOperation.AddExchange, {      exchangeBaseUrl: exchange.baseUrl,    }); @@ -50,7 +55,7 @@ export async function runWithdrawalManualTest(t: GlobalTestState) {    logger.info("starting AcceptManualWithdrawal request");    // We expect this to return immediately. -  const wres = await wallet.client.call( +  const wres = await walletClient.call(      WalletApiOperation.AcceptManualWithdrawal,      {        exchangeBaseUrl: exchange.baseUrl, @@ -84,11 +89,11 @@ export async function runWithdrawalManualTest(t: GlobalTestState) {    await exchange.runWirewatchOnce(); -  await wallet.runUntilDone(); +  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});    // Check balance -  const balResp = await wallet.client.call(WalletApiOperation.GetBalances, {}); +  const balResp = await walletClient.call(WalletApiOperation.GetBalances, {});    t.assertAmountEquals("TESTKUDOS:9.72", balResp.balances[0].available);    await t.shutdown(); diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts index 58e2a20f7..cbdca04b9 100644 --- a/packages/taler-harness/src/integrationtests/testrunner.ts +++ b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -14,7 +14,7 @@   GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>   */ -import { CancellationToken, Logger, minimatch } from "@gnu-taler/taler-util"; +import { CancellationToken, Logger, minimatch, setGlobalLogLevelFromString } from "@gnu-taler/taler-util";  import * as child_process from "child_process";  import * as fs from "fs";  import * as os from "os"; @@ -104,6 +104,7 @@ import { runExchangeDepositTest } from "./test-exchange-deposit.js";  import { runPeerRepairTest } from "./test-peer-repair.js";  import { runPaymentShareTest } from "./test-payment-share.js";  import { runSimplePaymentTest } from "./test-simple-payment.js"; +import { runTermOfServiceFormatTest } from "./test-tos-format.js";  /**   * Test runner. @@ -200,6 +201,7 @@ const allTests: TestMainFunction[] = [    runWithdrawalFakebankTest,    runWithdrawalFeesTest,    runWithdrawalHugeTest, +  runTermOfServiceFormatTest,  ];  export interface TestRunSpec { diff --git a/packages/taler-harness/src/sandcastle-config.ts b/packages/taler-harness/src/sandcastle-config.ts new file mode 100644 index 000000000..a7f7233ac --- /dev/null +++ b/packages/taler-harness/src/sandcastle-config.ts @@ -0,0 +1,10 @@ +// Work in progress. +// TS-based schema for the sandcastle configuration. + +export interface SandcastleConfig { +  currency: string; +  merchant: { +    apiKey: string; +    baseUrl: string; +  }; +} diff --git a/packages/taler-util/src/logging.ts b/packages/taler-util/src/logging.ts index c4b2a3da0..79fc49cdd 100644 --- a/packages/taler-util/src/logging.ts +++ b/packages/taler-util/src/logging.ts @@ -32,13 +32,17 @@ export enum LogLevel {    None = "none",  } -export let globalLogLevel = LogLevel.Info; +let globalLogLevel = LogLevel.Info; +const byTagLogLevel: Record<string, LogLevel> = {}; + +export function getGlobalLogLevel(): string { +  return globalLogLevel; +}  export function setGlobalLogLevelFromString(logLevelStr: string): void {    globalLogLevel = getLevelForString(logLevelStr);  } -export const byTagLogLevel: Record<string, LogLevel> = {};  export function setLogLevelFromString(tag: string, logLevelStr: string): void {    byTagLogLevel[tag] = getLevelForString(logLevelStr);  } @@ -98,7 +102,7 @@ function writeNodeLog(   * and uses the corresponding console.* method to log in the browser.   */  export class Logger { -  constructor(private tag: string) {} +  constructor(private tag: string) { }    shouldLogTrace(): boolean {      const level = byTagLogLevel[this.tag] ?? globalLogLevel; diff --git a/packages/taler-util/src/talerconfig.ts b/packages/taler-util/src/talerconfig.ts index 59c789cae..d86c58678 100644 --- a/packages/taler-util/src/talerconfig.ts +++ b/packages/taler-util/src/talerconfig.ts @@ -1,6 +1,6 @@  /*   This file is part of GNU Taler - (C) 2020 Taler Systems S.A. + (C) 2020-2023 Taler Systems S.A.   GNU Taler is free software; you can redistribute it and/or modify it under the   terms of the GNU General Public License as published by the Free Software @@ -39,10 +39,30 @@ export class ConfigError extends Error {    }  } +enum EntryOrigin { +  /** +   * From a default file. +   */ +  DefaultFile = 1, +  /** +   * From a system/installation specific default value. +   */ +  DefaultSystem = 2, +  /** +   * Loaded from file or string +   */ +  Loaded = 3, +  /** +   * Changed after loading +   */ +  Changed = 4, +} +  interface Entry {    value: string;    sourceLine: number;    sourceFile: string; +  origin: EntryOrigin;  }  interface Section { @@ -159,12 +179,12 @@ export function pathsub(            const r = lookup(inner, depth + 1);            if (r !== undefined) { -            s = s.substr(0, start) + r + s.substr(p + 1); +            s = s.substring(0, start) + r + s.substring(p + 1);              l = start + r.length;              continue;            } else if (defaultValue !== undefined) {              const resolvedDefault = pathsub(defaultValue, lookup, depth + 1); -            s = s.substr(0, start) + resolvedDefault + s.substr(p + 1); +            s = s.substring(0, start) + resolvedDefault + s.substring(p + 1);              l = start + resolvedDefault.length;              continue;            } @@ -176,7 +196,7 @@ export function pathsub(          if (m && m[0]) {            const r = lookup(m[0], depth + 1);            if (r !== undefined) { -            s = s.substr(0, l) + r + s.substr(l + 1 + m[0].length); +            s = s.substring(0, l) + r + s.substring(l + 1 + m[0].length);              l = l + r.length;              continue;            } @@ -195,6 +215,7 @@ export interface LoadOptions {  export interface StringifyOptions {    diagnostics?: boolean; +  excludeDefaults?: boolean;  }  export interface LoadedFile { @@ -282,7 +303,11 @@ export class Configuration {    private nestLevel = 0; -  private loadFromFilename(filename: string, opts: LoadOptions = {}): void { +  private loadFromFilename( +    filename: string, +    isDefaultSource: boolean, +    opts: LoadOptions = {}, +  ): void {      filename = expandPath(filename);      const checkCycle = () => { @@ -309,7 +334,7 @@ export class Configuration {      const oldNestLevel = this.nestLevel;      this.nestLevel += 1;      try { -      this.loadFromString(s, { +      this.internalLoadFromString(s, isDefaultSource, {          ...opts,          filename: filename,        }); @@ -318,7 +343,11 @@ export class Configuration {      }    } -  private loadGlob(parentFilename: string, fileglob: string): void { +  private loadGlob( +    parentFilename: string, +    isDefaultSource: boolean, +    fileglob: string, +  ): void {      const resolvedParent = nodejs_fs.realpathSync(parentFilename);      const parentDir = nodejs_path.dirname(resolvedParent); @@ -339,12 +368,16 @@ export class Configuration {      for (const f of files) {        if (globMatch(tail, f)) {          const fullPath = nodejs_path.join(head, f); -        this.loadFromFilename(fullPath); +        this.loadFromFilename(fullPath, isDefaultSource);        }      }    } -  private loadSecret(sectionName: string, filename: string): void { +  private loadSecret( +    sectionName: string, +    filename: string, +    isDefaultSource: boolean, +  ): void {      const sec = this.provideSection(sectionName);      sec.secretFilename = filename;      const otherCfg = new Configuration(); @@ -354,7 +387,7 @@ export class Configuration {        sec.inaccessible = true;        return;      } -    otherCfg.loadFromFilename(filename, { +    otherCfg.loadFromFilename(filename, isDefaultSource, {        banDirectives: true,      });      const otherSec = otherCfg.provideSection(sectionName); @@ -363,7 +396,11 @@ export class Configuration {      }    } -  loadFromString(s: string, opts: LoadOptions = {}): void { +  private internalLoadFromString( +    s: string, +    isDefaultSource: boolean, +    opts: LoadOptions = {}, +  ): void {      let lineNo = 0;      const fn = opts.filename ?? "<input>";      const reComment = /^\s*#.*$/; @@ -399,7 +436,10 @@ export class Configuration {                );              }              const arg = directiveMatch[2].trim(); -            this.loadFromFilename(normalizeInlineFilename(opts.filename, arg)); +            this.loadFromFilename( +              normalizeInlineFilename(opts.filename, arg), +              isDefaultSource, +            );              break;            }            case "inline-secret": { @@ -419,7 +459,7 @@ export class Configuration {                opts.filename,                sp[1],              ); -            this.loadSecret(sp[0], secretFilename); +            this.loadSecret(sp[0], secretFilename, isDefaultSource);              break;            }            case "inline-matching": { @@ -429,7 +469,7 @@ export class Configuration {                  `invalid configuration, @inline-matching@ directive in ${fn}:${lineNo} can only be used from a file`,                );              } -            this.loadGlob(opts.filename, arg); +            this.loadGlob(opts.filename, isDefaultSource, arg);              break;            }            default: @@ -462,6 +502,9 @@ export class Configuration {            value: val,            sourceFile: opts.filename ?? "<unknown>",            sourceLine: lineNo, +          origin: isDefaultSource +            ? EntryOrigin.DefaultFile +            : EntryOrigin.Loaded,          };          continue;        } @@ -471,6 +514,10 @@ export class Configuration {      }    } +  loadFromString(s: string, opts: LoadOptions = {}): void { +    return this.internalLoadFromString(s, false, opts); +  } +    private provideSection(section: string): Section {      const secNorm = section.toUpperCase();      if (this.sectionMap[secNorm]) { @@ -496,6 +543,24 @@ export class Configuration {        value,        sourceLine: 0,        sourceFile: "<unknown>", +      origin: EntryOrigin.Changed, +    }; +  } + +  /** +   * Set a string value to a value from default locations. +   */ +  private setStringSystemDefault( +    section: string, +    option: string, +    value: string, +  ): void { +    const sec = this.provideSection(section); +    sec.entries[option.toUpperCase()] = { +      value, +      sourceLine: 0, +      sourceFile: "<unknown>", +      origin: EntryOrigin.DefaultSystem,      };    } @@ -559,7 +624,7 @@ export class Configuration {    lookupVariable(x: string, depth: number = 0): string | undefined {      // We loop up options in PATHS in upper case, as option names      // are case insensitive -    const val = this.findEntry("PATHS", x)?.value; +    let val = this.findEntry("PATHS", x)?.value;      if (val !== undefined) {        return pathsub(val, (v, d) => this.lookupVariable(v, d), depth);      } @@ -578,30 +643,63 @@ export class Configuration {      );    } -  loadFrom(dirname: string): void { +  loadDefaultsFromDir(dirname: string): void {      const files = nodejs_fs.readdirSync(dirname);      for (const f of files) {        const fn = nodejs_path.join(dirname, f); -      this.loadFromFilename(fn); +      this.loadFromFilename(fn, true);      }    }    private loadDefaults(): void { -    let bc = process.env["TALER_BASE_CONFIG"]; -    if (!bc) { +    let baseConfigDir = process.env["TALER_BASE_CONFIG"]; +    if (!baseConfigDir) {        /* Try to locate the configuration based on the location         * of the taler-config binary. */        const path = which("taler-config");        if (path) { -        bc = nodejs_fs.realpathSync( +        baseConfigDir = nodejs_fs.realpathSync(            nodejs_path.dirname(path) + "/../share/taler/config.d",          );        }      } -    if (!bc) { -      bc = "/usr/share/taler/config.d"; +    if (!baseConfigDir) { +      baseConfigDir = "/usr/share/taler/config.d";      } -    this.loadFrom(bc); + +    let installPrefix = process.env["TALER_PREFIX"]; +    if (!installPrefix) { +      /* Try to locate install path based on the location +       * of the taler-config binary. */ +      const path = which("taler-config"); +      if (path) { +        installPrefix = nodejs_fs.realpathSync( +          nodejs_path.dirname(path) + "/..", +        ); +      } +    } +    if (!installPrefix) { +      installPrefix = "/usr"; +    } + +    this.setStringSystemDefault( +      "PATHS", +      "LIBEXECDIR", +      `${installPrefix}/taler/libexec/`, +    ); +    this.setStringSystemDefault( +      "PATHS", +      "DOCDIR", +      `${installPrefix}/share/doc/taler/`, +    ); +    this.setStringSystemDefault("PATHS", "ICONDIR", `${installPrefix}/share/icons/`); +    this.setStringSystemDefault("PATHS", "LOCALEDIR", `${installPrefix}/share/locale/`); +    this.setStringSystemDefault("PATHS", "PREFIX", `${installPrefix}/`); +    this.setStringSystemDefault("PATHS", "BINDIR", `${installPrefix}/bin`); +    this.setStringSystemDefault("PATHS", "LIBDIR", `${installPrefix}/lib/taler/`); +    this.setStringSystemDefault("PATHS", "DATADIR", `${installPrefix}/share/taler/`); + +    this.loadDefaultsFromDir(baseConfigDir);    }    getDefaultConfigFilename(): string | undefined { @@ -631,11 +729,13 @@ export class Configuration {      const cfg = new Configuration();      cfg.loadDefaults();      if (filename) { -      cfg.loadFromFilename(filename); +      cfg.loadFromFilename(filename, false);      } else {        const fn = cfg.getDefaultConfigFilename();        if (fn) { -        cfg.loadFromFilename(fn); +        // It's the default filename for the main config file, +        // but we don't consider the values default values. +        cfg.loadFromFilename(fn, false);        }      }      cfg.hintEntrypoint = filename; @@ -657,26 +757,51 @@ export class Configuration {      }      for (const sectionName of Object.keys(this.sectionMap)) {        const sec = this.sectionMap[sectionName]; -      if (opts.diagnostics && sec.secretFilename) { -        s += `# Secret section from ${sec.secretFilename}\n`; -        s += `# Secret accessible: ${!sec.inaccessible}\n`; -      } -      s += `[${sectionName}]\n`; +      let headerWritten = false;        for (const optionName of Object.keys(sec.entries)) {          const entry = this.sectionMap[sectionName].entries[optionName]; +        if ( +          opts.excludeDefaults && +          (entry.origin === EntryOrigin.DefaultSystem || +            entry.origin === EntryOrigin.DefaultFile) +        ) { +          continue; +        } +        if (!headerWritten) { +          if (opts.diagnostics && sec.secretFilename) { +            s += `# Secret section from ${sec.secretFilename}\n`; +            s += `# Secret accessible: ${!sec.inaccessible}\n`; +          } +          s += `[${sectionName}]\n`; +          headerWritten = true; +        }          if (entry !== undefined) {            if (opts.diagnostics) { -            s += `# ${entry.sourceFile}:${entry.sourceLine}\n`; +            switch (entry.origin) { +              case EntryOrigin.DefaultFile: +              case EntryOrigin.Changed: +              case EntryOrigin.Loaded: +                s += `# ${entry.sourceFile}:${entry.sourceLine}\n`; +                break; +              case EntryOrigin.DefaultSystem: +                s += `# (system/installation default)\n`; +                break; +            }            }            s += `${optionName} = ${entry.value}\n`;          }        } -      s += "\n"; +      if (headerWritten) { +        s += "\n"; +      }      }      return s;    } -  write(filename: string): void { -    nodejs_fs.writeFileSync(filename, this.stringify()); +  write(filename: string, opts: { excludeDefaults?: boolean } = {}): void { +    nodejs_fs.writeFileSync( +      filename, +      this.stringify({ excludeDefaults: opts.excludeDefaults }), +    );    }  } diff --git a/packages/taler-wallet-core/src/bank-api-client.ts b/packages/taler-wallet-core/src/bank-api-client.ts index a7484b0b2..8e351cb48 100644 --- a/packages/taler-wallet-core/src/bank-api-client.ts +++ b/packages/taler-wallet-core/src/bank-api-client.ts @@ -267,8 +267,7 @@ export namespace BankAccessApi {  export interface BankAccessApiClientArgs {    baseUrl: string; -  username: string; -  password: string; +  auth?: { username: string; password: string };    enableThrottling?: boolean;    allowHttp?: boolean;  } @@ -330,7 +329,7 @@ export class WireGatewayApiClient {   * but it will be nice to have in utils to be used by others   */  export class BankAccessApiClient { -  httpLib; +  httpLib: HttpRequestLibrary;    constructor(private args: BankAccessApiClientArgs) {      this.httpLib = createPlatformHttpLib({ @@ -339,19 +338,33 @@ export class BankAccessApiClient {      });    } -  async getTransactions(): Promise<void> { +  setAuth(auth: { username: string; password: string }) { +    this.args.auth = auth; +  } + +  private makeAuthHeader(): Record<string, string> { +    if (!this.args.auth) { +      return {}; +    } +    const authHeaderValue = makeBasicAuthHeader( +      this.args.auth.username, +      this.args.auth.password, +    ); +    return { +      Authorization: authHeaderValue, +    }; +  } + +  async getTransactions(username: string): Promise<void> { +    const auth = this.args.auth;      const reqUrl = new URL( -      `accounts/${this.args.username}/transactions`, +      `accounts/${username}/transactions`,        this.args.baseUrl,      ); -    const authHeaderValue = makeBasicAuthHeader( -      this.args.username, -      this.args.password, -    );      const resp = await this.httpLib.fetch(reqUrl.href, {        method: "GET",        headers: { -        Authorization: authHeaderValue, +        ...this.makeAuthHeader(),        },      }); @@ -360,24 +373,128 @@ export class BankAccessApiClient {    }    async createTransaction( +    username: string,      req: BankAccessApiCreateTransactionRequest,    ): Promise<any> {      const reqUrl = new URL( -      `accounts/${this.args.username}/transactions`, +      `accounts/${username}/transactions`,        this.args.baseUrl,      ); -    const authHeaderValue = makeBasicAuthHeader( -      this.args.username, -      this.args.password, -    ); +      const resp = await this.httpLib.fetch(reqUrl.href, {        method: "POST",        body: req, -      headers: { -        Authorization: authHeaderValue, -      }, +      headers: this.makeAuthHeader(),      });      return await readSuccessResponseJsonOrThrow(resp, codecForAny());    } + +  async registerAccount( +    username: string, +    password: string, +    options: { +      iban?: string; +    }, +  ): Promise<BankUser> { +    const url = new URL("testing/register", this.args.baseUrl); +    const resp = await this.httpLib.fetch(url.href, { +      method: "POST", +      body: { +        username, +        password, +        iban: options?.iban, +      }, +    }); +    let paytoUri = `payto://x-taler-bank/localhost/${username}`; +    if (resp.status !== 200 && resp.status !== 202 && resp.status !== 204) { +      logger.error(`${j2s(await resp.json())}`); +      throw TalerError.fromDetail( +        TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, +        { +          httpStatusCode: resp.status, +        }, +      ); +    } +    try { +      // Pybank has no body, thus this might throw. +      const respJson = await resp.json(); +      // LibEuFin demobank returns payto URI in response +      if (respJson.paytoUri) { +        paytoUri = respJson.paytoUri; +      } +    } catch (e) { +      // Do nothing +    } +    return { +      password, +      username, +      accountPaytoUri: paytoUri, +    }; +  } + +  async createRandomBankUser(): Promise<BankUser> { +    const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase(); +    const password = "pw-" + encodeCrock(getRandomBytes(10)).toLowerCase(); +    // FIXME: This is just a temporary workaround, because demobank is running out of short IBANs +    const iban = generateIban("DE", 15); +    return await this.registerAccount(username, password, { +      iban, +    }); +  } + +  async createWithdrawalOperation( +    user: string, +    amount: string, +  ): Promise<WithdrawalOperationInfo> { +    const url = new URL(`accounts/${user}/withdrawals`, this.args.baseUrl); +    const resp = await this.httpLib.fetch(url.href, { +      method: "POST", +      body: { +        amount, +      }, +      headers: this.makeAuthHeader(), +    }); +    return readSuccessResponseJsonOrThrow( +      resp, +      codecForWithdrawalOperationInfo(), +    ); +  } + +  async confirmWithdrawalOperation( +    username: string, +    wopi: WithdrawalOperationInfo, +  ): Promise<void> { +    const url = new URL( +      `accounts/${username}/withdrawals/${wopi.withdrawal_id}/confirm`, +      this.args.baseUrl, +    ); +    logger.info(`confirming withdrawal operation via ${url.href}`); +    const resp = await this.httpLib.fetch(url.href, { +      method: "POST", +      body: {}, +      headers: this.makeAuthHeader(), +    }); + +    logger.info(`response status ${resp.status}`); +    const respJson = await readSuccessResponseJsonOrThrow(resp, codecForAny()); + +    // FIXME: We don't check the status here! +  } + +  async abortWithdrawalOperation( +    accountName: string, +    wopi: WithdrawalOperationInfo, +  ): Promise<void> { +    const url = new URL( +      `accounts/${accountName}/withdrawals/${wopi.withdrawal_id}/abort`, +      this.args.baseUrl, +    ); +    const resp = await this.httpLib.fetch(url.href, { +      method: "POST", +      body: {}, +      headers: this.makeAuthHeader(), +    }); +    await readSuccessResponseJsonOrThrow(resp, codecForAny()); +  }  } | 
