diff options
Diffstat (limited to 'packages/taler-harness/src/harness/harness.ts')
-rw-r--r-- | packages/taler-harness/src/harness/harness.ts | 396 |
1 files changed, 75 insertions, 321 deletions
diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts index 926a0c93b..7db9d82bd 100644 --- a/packages/taler-harness/src/harness/harness.ts +++ b/packages/taler-harness/src/harness/harness.ts @@ -55,9 +55,11 @@ import { RewardCreateRequest, TippingReserveStatus, WalletNotification, + codecForAny, } from "@gnu-taler/taler-util"; import { createPlatformHttpLib, + expectSuccessResponseOrThrow, readSuccessResponseJsonOrThrow, } from "@gnu-taler/taler-util/http"; import { @@ -78,7 +80,6 @@ import { WalletNotificationWaiter, } from "@gnu-taler/taler-wallet-core/remote"; import { deepStrictEqual } from "assert"; -import axiosImp, { AxiosError } from "axios"; import { ChildProcess, spawn } from "child_process"; import * as fs from "fs"; import * as http from "http"; @@ -87,12 +88,9 @@ import * as path from "path"; import * as readline from "readline"; import { URL } from "url"; import { CoinConfig } from "./denomStructures.js"; -import { LibeufinNexusApi, LibeufinSandboxApi } from "./libeufin-apis.js"; const logger = new Logger("harness.ts"); -const axios = axiosImp.default; - export async function delayMs(ms: number): Promise<void> { return new Promise((resolve, reject) => { setTimeout(() => resolve(), ms); @@ -322,12 +320,6 @@ export class GlobalTestState { ); } - assertAxiosError(e: any): asserts e is AxiosError { - if (!e.isAxiosError) { - throw Error("expected axios error"); - } - } - assertTrue(b: boolean): asserts b { if (!b) { throw Error("test assertion failed"); @@ -558,7 +550,10 @@ export async function pingProc( while (true) { try { logger.trace(`pinging ${serviceName} at ${url}`); - const resp = await axios.get(url); + const resp = await harnessHttpLib.fetch(url); + if (resp.status !== 200) { + throw Error("non-200 status code"); + } logger.trace(`service ${serviceName} available`); return; } catch (e: any) { @@ -584,289 +579,6 @@ class BankServiceBase { } /** - * Work in progress. The key point is that both Sandbox and Nexus - * will be configured and started by this class. - */ -class LibEuFinBankService extends BankServiceBase implements BankServiceHandle { - sandboxProc: ProcessWrapper | undefined; - nexusProc: ProcessWrapper | undefined; - - http = createPlatformHttpLib({ - allowHttp: true, - enableThrottling: false, - }); - - static async create( - gc: GlobalTestState, - bc: BankConfig, - ): Promise<LibEuFinBankService> { - return new LibEuFinBankService(gc, bc, "foo"); - } - - get port() { - return this.bankConfig.httpPort; - } - get nexusPort() { - return this.bankConfig.httpPort + 1000; - } - - get nexusDbConn(): string { - return `jdbc:sqlite:${this.globalTestState.testDir}/libeufin-nexus.sqlite3`; - } - - get sandboxDbConn(): string { - return `jdbc:sqlite:${this.globalTestState.testDir}/libeufin-sandbox.sqlite3`; - } - - get nexusBaseUrl(): string { - return `http://localhost:${this.nexusPort}`; - } - - get baseUrlDemobank(): string { - let url = new URL("demobanks/default/", this.baseUrlNetloc); - return url.href; - } - - get bankAccessApiBaseUrl(): string { - let url = new URL("access-api/", this.baseUrlDemobank); - return url.href; - } - - get baseUrlNetloc(): string { - return `http://localhost:${this.bankConfig.httpPort}/`; - } - - get baseUrl(): string { - return this.bankAccessApiBaseUrl; - } - - async setSuggestedExchange( - e: ExchangeServiceInterface, - exchangePayto: string, - ) { - await sh( - this.globalTestState, - "libeufin-sandbox-set-default-exchange", - `libeufin-sandbox default-exchange ${e.baseUrl} ${exchangePayto}`, - { - ...process.env, - LIBEUFIN_SANDBOX_DB_CONNECTION: this.sandboxDbConn, - }, - ); - } - - // Create one at both sides: Sandbox and Nexus. - async createExchangeAccount( - accountName: string, - password: string, - ): Promise<HarnessExchangeBankAccount> { - logger.info("Create Exchange account(s)!"); - /** - * Many test cases try to create a Exchange account before - * starting the bank; that's because the Pybank did it entirely - * via the configuration file. - */ - await this.start(); - await this.pingUntilAvailable(); - await LibeufinSandboxApi.createDemobankAccount(accountName, password, { - baseUrl: this.bankAccessApiBaseUrl, - }); - let bankAccountLabel = accountName; - await LibeufinSandboxApi.createDemobankEbicsSubscriber( - { - hostID: "talertestEbicsHost", - userID: "exchangeEbicsUser", - partnerID: "exchangeEbicsPartner", - }, - bankAccountLabel, - { baseUrl: this.baseUrlDemobank }, - ); - - await LibeufinNexusApi.createUser( - { baseUrl: this.nexusBaseUrl }, - { - username: accountName, - password: password, - }, - ); - await LibeufinNexusApi.createEbicsBankConnection( - { baseUrl: this.nexusBaseUrl }, - { - name: "ebics-connection", // connection name. - ebicsURL: new URL("ebicsweb", this.baseUrlNetloc).href, - hostID: "talertestEbicsHost", - userID: "exchangeEbicsUser", - partnerID: "exchangeEbicsPartner", - }, - ); - await LibeufinNexusApi.connectBankConnection( - { baseUrl: this.nexusBaseUrl }, - "ebics-connection", - ); - await LibeufinNexusApi.fetchAccounts( - { baseUrl: this.nexusBaseUrl }, - "ebics-connection", - ); - await LibeufinNexusApi.importConnectionAccount( - { baseUrl: this.nexusBaseUrl }, - "ebics-connection", // connection name - accountName, // offered account label - `${accountName}-nexus-label`, // bank account label at Nexus - ); - await LibeufinNexusApi.createTwgFacade( - { baseUrl: this.nexusBaseUrl }, - { - name: "exchange-facade", - connectionName: "ebics-connection", - accountName: `${accountName}-nexus-label`, - currency: "EUR", - reserveTransferLevel: "report", - }, - ); - await LibeufinNexusApi.postPermission( - { baseUrl: this.nexusBaseUrl }, - { - action: "grant", - permission: { - subjectId: accountName, - subjectType: "user", - resourceType: "facade", - resourceId: "exchange-facade", // facade name - permissionName: "facade.talerWireGateway.transfer", - }, - }, - ); - await LibeufinNexusApi.postPermission( - { baseUrl: this.nexusBaseUrl }, - { - action: "grant", - permission: { - subjectId: accountName, - subjectType: "user", - resourceType: "facade", - resourceId: "exchange-facade", // facade name - permissionName: "facade.talerWireGateway.history", - }, - }, - ); - // Set fetch task. - await LibeufinNexusApi.postTask( - { baseUrl: this.nexusBaseUrl }, - `${accountName}-nexus-label`, - { - name: "wirewatch-task", - cronspec: "* * *", - type: "fetch", - params: { - level: "all", - rangeType: "all", - }, - }, - ); - await LibeufinNexusApi.postTask( - { baseUrl: this.nexusBaseUrl }, - `${accountName}-nexus-label`, - { - name: "aggregator-task", - cronspec: "* * *", - type: "submit", - params: {}, - }, - ); - let facadesResp = await LibeufinNexusApi.getAllFacades({ - baseUrl: this.nexusBaseUrl, - }); - let accountInfoResp = await LibeufinSandboxApi.demobankAccountInfo( - "admin", - "secret", - { baseUrl: this.bankAccessApiBaseUrl }, - accountName, // bank account label. - ); - return { - accountName: accountName, - accountPassword: password, - accountPaytoUri: accountInfoResp.data.paytoUri, - wireGatewayApiBaseUrl: facadesResp.data.facades[0].baseUrl, - }; - } - - async start(): Promise<void> { - /** - * Because many test cases try to create a Exchange bank - * account _before_ starting the bank (Pybank did it only via - * the config), it is possible that at this point Sandbox and - * Nexus are already running. Hence, this method only launches - * them if they weren't launched earlier. - */ - - // Only go ahead if BOTH aren't running. - if (this.sandboxProc || this.nexusProc) { - logger.info("Nexus or Sandbox already running, not taking any action."); - return; - } - await sh( - this.globalTestState, - "libeufin-sandbox-config-demobank", - `libeufin-sandbox config --currency=${this.bankConfig.currency} default`, - { - ...process.env, - LIBEUFIN_SANDBOX_DB_CONNECTION: this.sandboxDbConn, - LIBEUFIN_SANDBOX_ADMIN_PASSWORD: "secret", - }, - ); - this.sandboxProc = this.globalTestState.spawnService( - "libeufin-sandbox", - ["serve", "--port", `${this.port}`], - "libeufin-sandbox", - { - ...process.env, - LIBEUFIN_SANDBOX_DB_CONNECTION: this.sandboxDbConn, - LIBEUFIN_SANDBOX_ADMIN_PASSWORD: "secret", - }, - ); - await runCommand( - this.globalTestState, - "libeufin-nexus-superuser", - "libeufin-nexus", - ["superuser", "admin", "--password", "test"], - { - ...process.env, - LIBEUFIN_NEXUS_DB_CONNECTION: this.nexusDbConn, - }, - ); - this.nexusProc = this.globalTestState.spawnService( - "libeufin-nexus", - ["serve", "--port", `${this.nexusPort}`], - "libeufin-nexus", - { - ...process.env, - LIBEUFIN_NEXUS_DB_CONNECTION: this.nexusDbConn, - }, - ); - // need to wait here, because at this point - // a Ebics host needs to be created (RESTfully) - await this.pingUntilAvailable(); - LibeufinSandboxApi.createEbicsHost( - { baseUrl: this.baseUrlNetloc }, - "talertestEbicsHost", - ); - } - - async pingUntilAvailable(): Promise<void> { - await pingProc( - this.sandboxProc, - `http://localhost:${this.bankConfig.httpPort}`, - "libeufin-sandbox", - ); - await pingProc( - this.nexusProc, - `${this.nexusBaseUrl}/config`, - "libeufin-nexus", - ); - } -} - -/** * Implementation of the bank service using the "taler-fakebank-run" tool. */ export class FakebankService @@ -1152,6 +864,9 @@ export class ExchangeService implements ExchangeServiceInterface { "currency_round_unit", e.roundUnit ?? `${e.currency}:0.01`, ); + // Set to a high value to not break existing test cases where the merchant + // would cover all fees. + config.setString("exchange", "STEFAN_ABS", `${e.currency}:1`); config.setString( "exchange", "revocation_dir", @@ -1636,20 +1351,30 @@ export interface DeleteTippingReserveArgs { purge?: boolean; } +/** + * Default HTTP client handle for the integration test harness. + */ +export const harnessHttpLib = createPlatformHttpLib({ + allowHttp: true, + enableThrottling: false, +}); + export class MerchantApiClient { constructor( private baseUrl: string, public readonly auth: MerchantAuthConfiguration, ) {} - // FIXME: Migrate everything to this in favor of axios - http = createPlatformHttpLib({ allowHttp: true, enableThrottling: false }); + httpClient = createPlatformHttpLib({ allowHttp: true, enableThrottling: false }); async changeAuth(auth: MerchantAuthConfiguration): Promise<void> { const url = new URL("private/auth", this.baseUrl); - await axios.post(url.href, auth, { + const res = await this.httpClient.fetch(url.href, { + method: "POST", + body: auth, headers: this.makeAuthHeader(), }); + await expectSuccessResponseOrThrow(res); } async deleteTippingReserve(req: DeleteTippingReserveArgs): Promise<void> { @@ -1657,7 +1382,8 @@ export class MerchantApiClient { if (req.purge) { url.searchParams.set("purge", "YES"); } - const resp = await axios.delete(url.href, { + const resp = await this.httpClient.fetch(url.href, { + method: "DELETE", headers: this.makeAuthHeader(), }); logger.info(`delete status: ${resp.status}`); @@ -1668,7 +1394,7 @@ export class MerchantApiClient { req: CreateMerchantTippingReserveRequest, ): Promise<MerchantReserveCreateConfirmation> { const url = new URL("private/reserves", this.baseUrl); - const resp = await this.http.fetch(url.href, { + const resp = await this.httpClient.fetch(url.href, { method: "POST", body: req, headers: this.makeAuthHeader(), @@ -1684,7 +1410,7 @@ export class MerchantApiClient { console.log(this.makeAuthHeader()); const url = new URL("private", this.baseUrl); logger.info(`request url ${url.href}`); - const resp = await this.http.fetch(url.href, { + const resp = await this.httpClient.fetch(url.href, { method: "GET", headers: this.makeAuthHeader(), }); @@ -1694,7 +1420,7 @@ export class MerchantApiClient { async getPrivateTipReserves(): Promise<TippingReserveStatus> { console.log(this.makeAuthHeader()); const url = new URL("private/reserves", this.baseUrl); - const resp = await this.http.fetch(url.href, { + const resp = await this.httpClient.fetch(url.href, { method: "GET", headers: this.makeAuthHeader(), }); @@ -1704,33 +1430,37 @@ export class MerchantApiClient { async deleteInstance(instanceId: string) { const url = new URL(`management/instances/${instanceId}`, this.baseUrl); - await axios.delete(url.href, { + const resp = await this.httpClient.fetch(url.href, { + method: "DELETE", headers: this.makeAuthHeader(), }); + await expectSuccessResponseOrThrow(resp); } async createInstance(req: MerchantInstanceConfig): Promise<void> { const url = new URL("management/instances", this.baseUrl); - await axios.post(url.href, req, { + await this.httpClient.fetch(url.href, { + method: "POST", + body: req, headers: this.makeAuthHeader(), }); } async getInstances(): Promise<MerchantInstancesResponse> { const url = new URL("management/instances", this.baseUrl); - const resp = await axios.get(url.href, { + const resp = await this.httpClient.fetch(url.href, { headers: this.makeAuthHeader(), }); - return resp.data; + return resp.json(); } async getInstanceFullDetails(instanceId: string): Promise<any> { const url = new URL(`management/instances/${instanceId}`, this.baseUrl); try { - const resp = await axios.get(url.href, { + const resp = await this.httpClient.fetch(url.href, { headers: this.makeAuthHeader(), }); - return resp.data; + return resp.json(); } catch (e) { throw e; } @@ -1750,6 +1480,8 @@ export class MerchantApiClient { /** * FIXME: This should be deprecated in favor of MerchantApiClient + * + * @deprecated use MerchantApiClient instead */ export namespace MerchantPrivateApi { export async function createOrder( @@ -1760,10 +1492,15 @@ export namespace MerchantPrivateApi { ): Promise<MerchantPostOrderResponse> { const baseUrl = merchantService.makeInstanceBaseUrl(instanceName); let url = new URL("private/orders", baseUrl); - const resp = await axios.post(url.href, req, { + const resp = await harnessHttpLib.fetch(url.href, { + method: "POST", + body: req, headers: withAuthorization as Record<string, string>, }); - return codecForMerchantPostOrderResponse().decode(resp.data); + return readSuccessResponseJsonOrThrow( + resp, + codecForMerchantPostOrderResponse(), + ); } export async function createTemplate( @@ -1774,7 +1511,9 @@ export namespace MerchantPrivateApi { ) { const baseUrl = merchantService.makeInstanceBaseUrl(instanceName); let url = new URL("private/templates", baseUrl); - const resp = await axios.post(url.href, req, { + const resp = await harnessHttpLib.fetch(url.href, { + method: "POST", + body: req, headers: withAuthorization as Record<string, string>, }); if (resp.status !== 204) { @@ -1794,10 +1533,13 @@ export namespace MerchantPrivateApi { if (query.sessionId) { reqUrl.searchParams.set("session_id", query.sessionId); } - const resp = await axios.get(reqUrl.href, { + const resp = await harnessHttpLib.fetch(reqUrl.href, { headers: withAuthorization as Record<string, string>, }); - return codecForMerchantOrderPrivateStatusResponse().decode(resp.data); + return readSuccessResponseJsonOrThrow( + resp, + codecForMerchantOrderPrivateStatusResponse(), + ); } export async function giveRefund( @@ -1813,12 +1555,16 @@ export namespace MerchantPrivateApi { `private/orders/${r.orderId}/refund`, merchantService.makeInstanceBaseUrl(r.instance), ); - const resp = await axios.post(reqUrl.href, { - refund: r.amount, - reason: r.justification, + const resp = await harnessHttpLib.fetch(reqUrl.href, { + method: "POST", + body: { + refund: r.amount, + reason: r.justification, + }, }); + const respBody = await resp.json(); return { - talerRefundUri: resp.data.taler_refund_uri, + talerRefundUri: respBody.taler_refund_uri, }; } @@ -1830,9 +1576,9 @@ export namespace MerchantPrivateApi { `private/reserves`, merchantService.makeInstanceBaseUrl(instance), ); - const resp = await axios.get(reqUrl.href); + const resp = await harnessHttpLib.fetch(reqUrl.href); // FIXME: validate - return resp.data; + return resp.json(); } export async function giveTip( @@ -1844,9 +1590,12 @@ export namespace MerchantPrivateApi { `private/tips`, merchantService.makeInstanceBaseUrl(instance), ); - const resp = await axios.post(reqUrl.href, req); + const resp = await harnessHttpLib.fetch(reqUrl.href, { + method: "POST", + body: req, + }); // FIXME: validate - return resp.data; + return resp.json(); } } @@ -2052,7 +1801,12 @@ export class MerchantService implements MerchantServiceInterface { instanceConfig.defaultPayDelay ?? Duration.toTalerProtocolDuration(Duration.getForever()), }; - await axios.post(url, body); + const httpLib = createPlatformHttpLib({ + allowHttp: true, + enableThrottling: false, + }); + const resp = await httpLib.fetch(url, { method: "POST", body }); + await expectSuccessResponseOrThrow(resp); } makeInstanceBaseUrl(instanceName?: string): string { |