taler-typescript-core

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

commit e70d28b93941fc22c3298fcce965b42b2311683e
parent b119d5c17108b9e97be8c06ceccf622139048f6f
Author: Florian Dold <florian@dold.me>
Date:   Fri,  4 Oct 2024 14:41:31 +0200

harness: log test steps

Diffstat:
Mpackages/taler-harness/src/harness/environments.ts | 176++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mpackages/taler-harness/src/harness/harness.ts | 44+++++++++++++++++++++++++++++++++++++-------
Mpackages/taler-harness/src/integrationtests/test-kyc-deposit-deposit-kyctransfer.ts | 4++--
Mpackages/taler-harness/src/integrationtests/test-kyc-merchant-deposit.ts | 262++++++++++++-------------------------------------------------------------------
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 12+++++++++++-
5 files changed, 264 insertions(+), 234 deletions(-)

diff --git a/packages/taler-harness/src/harness/environments.ts b/packages/taler-harness/src/harness/environments.ts @@ -27,6 +27,7 @@ import { AmlDecisionRequest, AmlDecisionRequestWithoutSignature, AmountString, + Configuration, ConfirmPayResultType, decodeCrock, Duration, @@ -45,7 +46,11 @@ import { TransactionMajorState, WalletNotification, } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { + createSyncCryptoApi, + EddsaKeyPairStrings, + WalletApiOperation, +} from "@gnu-taler/taler-wallet-core"; import { CoinConfig, defaultCoinConfig } from "./denomStructures.js"; import { FaultInjectedExchangeService, @@ -1092,3 +1097,172 @@ export async function postAmlDecision( t.assertDeepEqual(resp.status, HttpStatusCode.NoContent); } + +export interface KycEnvOptions { + coinConfig?: CoinConfig[]; + + adjustExchangeConfig?(config: Configuration): void; +} + +export interface KycTestEnv { + commonDb: DbInfo; + bankClient: TalerCorebankApiClient; + exchange: ExchangeService; + exchangeBankAccount: HarnessExchangeBankAccount; + walletClient: WalletClient; + walletService: WalletService; + amlKeypair: EddsaKeyPairStrings; + merchant: MerchantService; +} + +export async function createKycTestkudosEnvironment( + t: GlobalTestState, + opts: KycEnvOptions = {}, +): Promise<KycTestEnv> { + const db = await setupDb(t); + + let coinConfig: CoinConfig[]; + if (opts.coinConfig) { + coinConfig = opts.coinConfig; + } else { + coinConfig = defaultCoinConfig.map((x) => x("TESTKUDOS")); + } + + const bank = await BankService.create(t, { + allowRegistrations: true, + currency: "TESTKUDOS", + database: db.connStr, + httpPort: 8082, + }); + + const exchange = ExchangeService.create(t, { + name: "testexchange-1", + currency: "TESTKUDOS", + httpPort: 8081, + database: db.connStr, + }); + + let receiverName = "Exchange"; + let exchangeBankUsername = "exchange"; + let exchangeBankPassword = "mypw"; + let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername); + + const wireGatewayApiBaseUrl = new URL( + `accounts/${exchangeBankUsername}/taler-wire-gateway/`, + bank.corebankApiBaseUrl, + ).href; + + await exchange.addBankAccount("1", { + accountName: exchangeBankUsername, + accountPassword: exchangeBankPassword, + wireGatewayApiBaseUrl, + accountPaytoUri: exchangePaytoUri, + }); + + bank.setSuggestedExchange(exchange, exchangePaytoUri); + + await bank.start(); + + await bank.pingUntilAvailable(); + + const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, { + auth: { + username: "admin", + password: "adminpw", + }, + }); + + await bankClient.registerAccountExtended({ + name: receiverName, + password: exchangeBankPassword, + username: exchangeBankUsername, + is_taler_exchange: true, + payto_uri: exchangePaytoUri, + }); + + exchange.addCoinConfigList(coinConfig); + + const adjustExchangeConfig = opts.adjustExchangeConfig; + if (adjustExchangeConfig) { + await exchange.modifyConfig(async (config) => { + adjustExchangeConfig(config); + }); + } + + await exchange.start(); + + const cryptoApi = createSyncCryptoApi(); + const amlKeypair = await cryptoApi.createEddsaKeypair({}); + + await exchange.enableAmlAccount(amlKeypair.pub, "Alice"); + + const walletService = new WalletService(t, { + name: "wallet", + useInMemoryDb: true, + }); + await walletService.start(); + await walletService.pingUntilAvailable(); + + const walletClient = new WalletClient({ + name: "wallet", + unixPath: walletService.socketPath, + onNotification(n) { + console.log("wallet-core notification", n); + }, + }); + await walletClient.connect(); + await walletClient.client.call(WalletApiOperation.InitWallet, { + config: { + testing: { + skipDefaults: true, + }, + }, + }); + + const merchant = await MerchantService.create(t, { + name: "testmerchant-1", + currency: "TESTKUDOS", + httpPort: 8083, + database: db.connStr, + }); + + merchant.addExchange(exchange); + + await merchant.start(); + + await merchant.addInstanceWithWireAccount({ + id: "default", + name: "Default Instance", + paytoUris: [getTestHarnessPaytoForLabel("merchant-default")], + defaultWireTransferDelay: Duration.toTalerProtocolDuration( + Duration.fromSpec({ minutes: 1 }), + ), + }); + + await merchant.addInstanceWithWireAccount({ + id: "minst1", + name: "minst1", + paytoUris: [getTestHarnessPaytoForLabel("minst1")], + defaultWireTransferDelay: Duration.toTalerProtocolDuration( + Duration.fromSpec({ minutes: 1 }), + ), + }); + + t.logStep("env-setup-done"); + + return { + commonDb: db, + exchange, + amlKeypair, + walletClient, + walletService, + bankClient, + exchangeBankAccount: { + accountName: exchangeBankUsername, + accountPassword: exchangeBankPassword, + accountPaytoUri: exchangePaytoUri, + wireGatewayApiBaseUrl, + }, + merchant, + }; +} diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts @@ -277,12 +277,33 @@ export class GlobalTestState { servers: http.Server[]; inShutdown: boolean = false; stepSet: Set<string> = new Set(); + + spanStack: string[] = []; + constructor(params: GlobalTestParams) { this.testDir = params.testDir; this.procs = []; this.servers = []; } + async runSpanAsync( + spanName: string, + block: () => Promise<void>, + ): Promise<void> { + if (this.stepSet.has(spanName)) { + throw Error(`duplicate step (${spanName})`); + } + let steps = `${this.testDir}/steps.txt`; + fs.appendFileSync(steps, `START ${spanName}\n`); + try { + await block(); + } catch (e) { + fs.appendFileSync(steps, `FAIL ${(e as any).message}\n`); + throw e; + } + fs.appendFileSync(steps, `END ${spanName}\n`); + } + async assertThrowsTalerErrorAsync( block: () => Promise<void>, ): Promise<TalerError> { @@ -310,9 +331,13 @@ export class GlobalTestState { ); } - assertTrue(b: boolean): asserts b { + assertTrue(b: boolean, message?: string): asserts b { if (!b) { - throw Error("test assertion failed"); + if (message) { + throw Error(`test assertion failed: ${message}`); + } else { + throw Error("test assertion failed"); + } } } @@ -420,15 +445,17 @@ export class GlobalTestState { /** * Log that the test arrived a certain step. * - * The step name should be unique across the whole + * The step name should be unique across the whole test run. */ logStep(stepName: string): void { - // Now we just log, later we may report the steps that were done - // to easily see where the test hangs. - console.info(`STEP: ${stepName}`); if (this.stepSet.has(stepName)) { throw Error(`duplicate step (${stepName})`); } + // Now we just log, later we may report the steps that were done + // to easily see where the test hangs. + console.info(`STEP: ${stepName}`); + let steps = `${this.testDir}/steps.txt`; + fs.appendFileSync(steps, `STEP ${stepName}\n`); } } @@ -1781,7 +1808,8 @@ export class MerchantService implements MerchantServiceInterface { } /** - * Start the merchant, + * Start the merchant. + * Waits for the service to become fully available. */ async start(opts: { skipDbinit?: boolean } = {}): Promise<void> { const skipSetup = opts.skipDbinit ?? false; @@ -2049,6 +2077,8 @@ export async function runTestWithState( } else { console.error("FATAL: test failed with exception", e); } + let steps = `${gc.testDir}/steps.txt`; + fs.appendFileSync(steps, `FAIL ${(e as any).message}\n`); status = "fail"; } finally { await gc.shutdown(); diff --git a/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit-kyctransfer.ts b/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit-kyctransfer.ts @@ -207,8 +207,8 @@ async function createKycTestkudosEnvironment( walletService, bankClient, exchangeBankAccount: { - accountName: "", - accountPassword: "", + accountName: exchangeBankUsername, + accountPassword: exchangeBankPassword, accountPaytoUri: exchangePaytoUri, wireGatewayApiBaseUrl, }, diff --git a/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit.ts b/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2020 Taler Systems S.A. + (C) 2024 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -21,13 +21,11 @@ import { codecForAccountKycRedirects, codecForKycProcessClientInformation, codecForQueryInstancesResponse, - Duration, encodeCrock, hashPaytoUri, j2s, Logger, MerchantAccountKycRedirectsResponse, - TalerCorebankApiClient, TalerMerchantApi, WireGatewayApiClient, } from "@gnu-taler/taler-util"; @@ -36,229 +34,14 @@ import { readSuccessResponseJsonOrThrow, } from "@gnu-taler/taler-util/http"; import { - createSyncCryptoApi, - EddsaKeyPairStrings, - WalletApiOperation, -} from "@gnu-taler/taler-wallet-core"; -import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; -import { - BankService, - DbInfo, - ExchangeService, - getTestHarnessPaytoForLabel, - GlobalTestState, - HarnessExchangeBankAccount, - harnessHttpLib, - MerchantService, - setupDb, - WalletClient, - WalletService, -} from "../harness/harness.js"; -import { - EnvOptions, + createKycTestkudosEnvironment, postAmlDecisionNoRules, withdrawViaBankV3, } from "../harness/environments.js"; +import { GlobalTestState, harnessHttpLib } from "../harness/harness.js"; const logger = new Logger(`test-kyc-merchant-deposit.ts`); -interface KycTestEnv { - commonDb: DbInfo; - bankClient: TalerCorebankApiClient; - exchange: ExchangeService; - exchangeBankAccount: HarnessExchangeBankAccount; - walletClient: WalletClient; - walletService: WalletService; - amlKeypair: EddsaKeyPairStrings; - merchant: MerchantService; -} - -async function createKycTestkudosEnvironment( - t: GlobalTestState, - coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")), - opts: EnvOptions = {}, -): Promise<KycTestEnv> { - const db = await setupDb(t); - - const bank = await BankService.create(t, { - allowRegistrations: true, - currency: "TESTKUDOS", - database: db.connStr, - httpPort: 8082, - }); - - const exchange = ExchangeService.create(t, { - name: "testexchange-1", - currency: "TESTKUDOS", - httpPort: 8081, - database: db.connStr, - }); - - let receiverName = "Exchange"; - let exchangeBankUsername = "exchange"; - let exchangeBankPassword = "mypw"; - let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername); - - const wireGatewayApiBaseUrl = new URL( - `accounts/${exchangeBankUsername}/taler-wire-gateway/`, - bank.corebankApiBaseUrl, - ).href; - - await exchange.addBankAccount("1", { - accountName: exchangeBankUsername, - accountPassword: exchangeBankPassword, - wireGatewayApiBaseUrl, - accountPaytoUri: exchangePaytoUri, - }); - - bank.setSuggestedExchange(exchange, exchangePaytoUri); - - await bank.start(); - - const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, { - auth: { - username: "admin", - password: "adminpw", - }, - }); - - await bankClient.registerAccountExtended({ - name: receiverName, - password: exchangeBankPassword, - username: exchangeBankUsername, - is_taler_exchange: true, - payto_uri: exchangePaytoUri, - }); - - const ageMaskSpec = opts.ageMaskSpec; - - if (ageMaskSpec) { - exchange.enableAgeRestrictions(ageMaskSpec); - // Enable age restriction for all coins. - exchange.addCoinConfigList( - coinConfig.map((x) => ({ - ...x, - name: `${x.name}-age`, - ageRestricted: true, - })), - ); - // For mixed age restrictions, we also offer coins without age restrictions - if (opts.mixedAgeRestriction) { - exchange.addCoinConfigList( - coinConfig.map((x) => ({ ...x, ageRestricted: false })), - ); - } - } else { - exchange.addCoinConfigList(coinConfig); - } - - await exchange.modifyConfig(async (config) => { - config.setString("exchange", "enable_kyc", "yes"); - - config.setString("KYC-RULE-R1", "operation_type", "deposit"); - config.setString("KYC-RULE-R1", "enabled", "yes"); - config.setString("KYC-RULE-R1", "exposed", "yes"); - config.setString("KYC-RULE-R1", "is_and_combinator", "yes"); - config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:0"); - config.setString("KYC-RULE-R1", "timeframe", "1d"); - config.setString("KYC-RULE-R1", "next_measures", "M1"); - - config.setString("KYC-MEASURE-M1", "check_name", "C1"); - config.setString("KYC-MEASURE-M1", "context", "{}"); - config.setString("KYC-MEASURE-M1", "program", "P1"); - - config.setString("AML-PROGRAM-P1", "command", "/bin/true"); - config.setString("AML-PROGRAM-P1", "enabled", "true"); - config.setString("AML-PROGRAM-P1", "description", "this does nothing"); - config.setString("AML-PROGRAM-P1", "fallback", "M1"); - - config.setString("KYC-CHECK-C1", "type", "INFO"); - config.setString("KYC-CHECK-C1", "description", "my check!"); - config.setString("KYC-CHECK-C1", "fallback", "M1"); - }); - - await exchange.start(); - - const cryptoApi = createSyncCryptoApi(); - const amlKeypair = await cryptoApi.createEddsaKeypair({}); - - await exchange.enableAmlAccount(amlKeypair.pub, "Alice"); - - const walletService = new WalletService(t, { - name: "wallet", - useInMemoryDb: true, - }); - await walletService.start(); - await walletService.pingUntilAvailable(); - - const walletClient = new WalletClient({ - name: "wallet", - unixPath: walletService.socketPath, - onNotification(n) { - console.log("got notification", n); - }, - }); - await walletClient.connect(); - await walletClient.client.call(WalletApiOperation.InitWallet, { - config: { - testing: { - skipDefaults: true, - }, - }, - }); - - const merchant = await MerchantService.create(t, { - name: "testmerchant-1", - currency: "TESTKUDOS", - httpPort: 8083, - database: db.connStr, - }); - - merchant.addExchange(exchange); - - if (opts.additionalMerchantConfig) { - opts.additionalMerchantConfig(merchant); - } - await merchant.start(); - await merchant.pingUntilAvailable(); - - await merchant.addInstanceWithWireAccount({ - id: "default", - name: "Default Instance", - paytoUris: [getTestHarnessPaytoForLabel("merchant-default")], - defaultWireTransferDelay: Duration.toTalerProtocolDuration( - Duration.fromSpec({ minutes: 1 }), - ), - }); - - await merchant.addInstanceWithWireAccount({ - id: "minst1", - name: "minst1", - paytoUris: [getTestHarnessPaytoForLabel("minst1")], - defaultWireTransferDelay: Duration.toTalerProtocolDuration( - Duration.fromSpec({ minutes: 1 }), - ), - }); - - console.log("setup done!"); - - return { - commonDb: db, - exchange, - amlKeypair, - walletClient, - walletService, - bankClient, - merchant, - exchangeBankAccount: { - accountName: "", - accountPassword: "", - accountPaytoUri: "", - wireGatewayApiBaseUrl, - }, - }; -} - export async function runKycMerchantDepositTest(t: GlobalTestState) { // Set up test environment @@ -269,7 +52,32 @@ export async function runKycMerchantDepositTest(t: GlobalTestState) { exchange, exchangeBankAccount, amlKeypair, - } = await createKycTestkudosEnvironment(t); + } = await createKycTestkudosEnvironment(t, { + adjustExchangeConfig(config) { + config.setString("exchange", "enable_kyc", "yes"); + + config.setString("KYC-RULE-R1", "operation_type", "deposit"); + config.setString("KYC-RULE-R1", "enabled", "yes"); + config.setString("KYC-RULE-R1", "exposed", "yes"); + config.setString("KYC-RULE-R1", "is_and_combinator", "yes"); + config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:0"); + config.setString("KYC-RULE-R1", "timeframe", "1d"); + config.setString("KYC-RULE-R1", "next_measures", "M1"); + + config.setString("KYC-MEASURE-M1", "check_name", "C1"); + config.setString("KYC-MEASURE-M1", "context", "{}"); + config.setString("KYC-MEASURE-M1", "program", "P1"); + + config.setString("AML-PROGRAM-P1", "command", "/bin/true"); + config.setString("AML-PROGRAM-P1", "enabled", "true"); + config.setString("AML-PROGRAM-P1", "description", "this does nothing"); + config.setString("AML-PROGRAM-P1", "fallback", "M1"); + + config.setString("KYC-CHECK-C1", "type", "INFO"); + config.setString("KYC-CHECK-C1", "description", "my check!"); + config.setString("KYC-CHECK-C1", "fallback", "M1"); + }, + }); let accountPub: string; @@ -328,8 +136,15 @@ export async function runKycMerchantDepositTest(t: GlobalTestState) { logger.info(`mechant kyc status: ${j2s(kycRespOne)}`); + t.assertDeepEqual(kycRespOne.kyc_data[0].exchange_http_status, 404); + + t.assertTrue( + (kycRespOne.kyc_data[0].limits?.length ?? 0) > 0, + "kyc status should contain non-empty limits", + ); + // Order creation should fail! - { + await t.runSpanAsync("order creation should be rejected", async () => { let url = new URL("private/orders", merchant.makeInstanceBaseUrl()); const order = { summary: "Test", @@ -344,7 +159,8 @@ export async function runKycMerchantDepositTest(t: GlobalTestState) { }); logger.info(`order creation status: ${resp.status}`); - } + t.assertTrue(resp.status !== 200); + }); await wireGatewayApiClient.adminAddKycauth({ amount: "TESTKUDOS:0.1", diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -21,13 +21,13 @@ import * as fs from "fs"; import * as os from "os"; import * as path from "path"; import url from "url"; +import { getSharedTestDir } from "../harness/environments.js"; import { GlobalTestState, TestRunResult, runTestWithState, shouldLingerInTest, } from "../harness/harness.js"; -import { getSharedTestDir } from "../harness/environments.js"; import { runAccountRestrictionsTest } from "./test-account-restrictions.js"; import { runAgeRestrictionsDepositTest } from "./test-age-restrictions-deposit.js"; import { runAgeRestrictionsMerchantTest } from "./test-age-restrictions-merchant.js"; @@ -526,6 +526,16 @@ export async function runTests(spec: TestRunSpec) { harnessLogStream.close(); + const stepsFile = `${testDir}/steps.txt`; + if (spec.verbosity > 0 && fs.existsSync(stepsFile)) { + let stepsLog = fs.readFileSync(stepsFile, { + encoding: "utf-8", + }); + console.log("-- test steps --"); + console.log(stepsLog.trim()); + console.log("-- end --"); + } + console.log(`parent: got result ${JSON.stringify(result)}`); testResults.push(result);