diff options
Diffstat (limited to 'packages/taler-harness/src/harness')
-rw-r--r-- | packages/taler-harness/src/harness/harness.ts | 396 | ||||
-rw-r--r-- | packages/taler-harness/src/harness/libeufin-apis.ts | 519 | ||||
-rw-r--r-- | packages/taler-harness/src/harness/libeufin.ts | 177 |
3 files changed, 446 insertions, 646 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 { diff --git a/packages/taler-harness/src/harness/libeufin-apis.ts b/packages/taler-harness/src/harness/libeufin-apis.ts index cb9acdaa4..3c57eee07 100644 --- a/packages/taler-harness/src/harness/libeufin-apis.ts +++ b/packages/taler-harness/src/harness/libeufin-apis.ts @@ -6,8 +6,21 @@ */ import { URL } from "@gnu-taler/taler-util"; -import axiosImp from "axios"; -const axios = axiosImp.default; +import { + createPlatformHttpLib, + makeBasicAuthHeader, +} from "@gnu-taler/taler-util/http"; +import { + LibeufinNexusTransactions, + LibeufinSandboxAdminBankAccountBalance, + NexusBankConnections, + NexusFacadeListResponse, + NexusGetPermissionsResponse, + NexusNewTransactionsInfo, + NexusTask, + NexusTaskCollection, + NexusUserResponse, +} from "./libeufin.js"; export interface LibeufinSandboxServiceInterface { baseUrl: string; @@ -163,30 +176,13 @@ export interface LibeufinSandboxAddIncomingRequest { direction: string; } +const libeufinHttpLib = createPlatformHttpLib(); + /** * APIs spread across Legacy and Access, it is therefore * the "base URL" relative to which API every call addresses. */ export namespace LibeufinSandboxApi { - // Need Access API base URL. - export async function demobankAccountInfo( - username: string, - password: string, - libeufinSandboxService: LibeufinSandboxServiceInterface, - accountLabel: string, - ) { - let url = new URL( - `accounts/${accountLabel}`, - libeufinSandboxService.baseUrl, - ); - return await axios.get(url.href, { - auth: { - username: username, - password: password, - }, - }); - } - // Creates one bank account via the Access API. // Need the /demobanks/$id/access-api as the base URL export async function createDemobankAccount( @@ -194,12 +190,15 @@ export namespace LibeufinSandboxApi { password: string, libeufinSandboxService: LibeufinSandboxServiceInterface, iban: string | null = null, - ) { + ): Promise<void> { let url = new URL("testing/register", libeufinSandboxService.baseUrl); - await axios.post(url.href, { - username: username, - password: password, - iban: iban, + await libeufinHttpLib.fetch(url.href, { + method: "POST", + body: { + username: username, + password: password, + iban: iban, + }, }); } // Need /demobanks/$id as the base URL @@ -209,75 +208,57 @@ export namespace LibeufinSandboxApi { libeufinSandboxService: LibeufinSandboxServiceInterface, username: string = "admin", password: string = "secret", - ) { + ): Promise<void> { // baseUrl should already be pointed to one demobank. let url = new URL("ebics/subscribers", libeufinSandboxService.baseUrl); - await axios.post( - url.href, - { + await libeufinHttpLib.fetch(url.href, { + method: "POST", + body: { userID: req.userID, hostID: req.hostID, partnerID: req.partnerID, demobankAccountLabel: demobankAccountLabel, }, - { - auth: { - username: "admin", - password: "secret", - }, - }, - ); + }); } export async function rotateKeys( libeufinSandboxService: LibeufinSandboxServiceInterface, hostID: string, - ) { + ): Promise<void> { const baseUrl = libeufinSandboxService.baseUrl; let url = new URL(`admin/ebics/hosts/${hostID}/rotate-keys`, baseUrl); - await axios.post( - url.href, - {}, - { - auth: { - username: "admin", - password: "secret", - }, - }, - ); + await libeufinHttpLib.fetch(url.href, { + method: "POST", + body: {}, + }); } export async function createEbicsHost( libeufinSandboxService: LibeufinSandboxServiceInterface, hostID: string, - ) { + ): Promise<void> { const baseUrl = libeufinSandboxService.baseUrl; let url = new URL("admin/ebics/hosts", baseUrl); - await axios.post( - url.href, - { + await libeufinHttpLib.fetch(url.href, { + method: "POST", + body: { hostID, ebicsVersion: "2.5", }, - { - auth: { - username: "admin", - password: "secret", - }, - }, - ); + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + }); } export async function createBankAccount( libeufinSandboxService: LibeufinSandboxServiceInterface, req: BankAccountInfo, - ) { + ): Promise<void> { const baseUrl = libeufinSandboxService.baseUrl; let url = new URL(`admin/bank-accounts/${req.label}`, baseUrl); - await axios.post(url.href, req, { - auth: { - username: "admin", - password: "secret", - }, + await libeufinHttpLib.fetch(url.href, { + method: "POST", + body: req, + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); } @@ -288,14 +269,13 @@ export namespace LibeufinSandboxApi { export async function createEbicsSubscriber( libeufinSandboxService: LibeufinSandboxServiceInterface, req: CreateEbicsSubscriberRequest, - ) { + ): Promise<void> { const baseUrl = libeufinSandboxService.baseUrl; let url = new URL("admin/ebics/subscribers", baseUrl); - await axios.post(url.href, req, { - auth: { - username: "admin", - password: "secret", - }, + await libeufinHttpLib.fetch(url.href, { + method: "POST", + body: req, + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); } @@ -306,14 +286,13 @@ export namespace LibeufinSandboxApi { export async function createEbicsBankAccount( libeufinSandboxService: LibeufinSandboxServiceInterface, req: CreateEbicsBankAccountRequest, - ) { + ): Promise<void> { const baseUrl = libeufinSandboxService.baseUrl; let url = new URL("admin/ebics/bank-accounts", baseUrl); - await axios.post(url.href, req, { - auth: { - username: "admin", - password: "secret", - }, + await libeufinHttpLib.fetch(url.href, { + method: "POST", + body: req, + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); } @@ -321,17 +300,16 @@ export namespace LibeufinSandboxApi { libeufinSandboxService: LibeufinSandboxServiceInterface, accountLabel: string, req: SimulateIncomingTransactionRequest, - ) { + ): Promise<void> { const baseUrl = libeufinSandboxService.baseUrl; let url = new URL( `admin/bank-accounts/${accountLabel}/simulate-incoming-transaction`, baseUrl, ); - await axios.post(url.href, req, { - auth: { - username: "admin", - password: "secret", - }, + await libeufinHttpLib.fetch(url.href, { + method: "POST", + body: req, + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); } @@ -344,13 +322,10 @@ export namespace LibeufinSandboxApi { `admin/bank-accounts/${accountLabel}/transactions`, baseUrl, ); - const res = await axios.get(url.href, { - auth: { - username: "admin", - password: "secret", - }, + const res = await libeufinHttpLib.fetch(url.href, { + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); - return res.data as SandboxAccountTransactions; + return (await res.json()) as SandboxAccountTransactions; } export async function getCamt053( @@ -359,61 +334,50 @@ export namespace LibeufinSandboxApi { ): Promise<any> { const baseUrl = libeufinSandboxService.baseUrl; let url = new URL("admin/payments/camt", baseUrl); - return await axios.post( - url.href, - { + return await libeufinHttpLib.fetch(url.href, { + method: "POST", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + body: { bankaccount: accountLabel, type: 53, }, - { - auth: { - username: "admin", - password: "secret", - }, - }, - ); + }); } export async function getAccountInfoWithBalance( libeufinSandboxService: LibeufinSandboxServiceInterface, accountLabel: string, - ): Promise<any> { + ): Promise<LibeufinSandboxAdminBankAccountBalance> { const baseUrl = libeufinSandboxService.baseUrl; let url = new URL(`admin/bank-accounts/${accountLabel}`, baseUrl); - return await axios.get(url.href, { - auth: { - username: "admin", - password: "secret", - }, + const res = await libeufinHttpLib.fetch(url.href, { + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); + return res.json(); } } export namespace LibeufinNexusApi { export async function getAllConnections( nexus: LibeufinNexusServiceInterface, - ): Promise<any> { + ): Promise<NexusBankConnections> { let url = new URL("bank-connections", nexus.baseUrl); - const res = await axios.get(url.href, { - auth: { - username: "admin", - password: "test", - }, + const res = await libeufinHttpLib.fetch(url.href, { + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); - return res; + return res.json(); } export async function deleteBankConnection( libeufinNexusService: LibeufinNexusServiceInterface, req: DeleteBankConnectionRequest, - ): Promise<any> { + ): Promise<void> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL("bank-connections/delete-connection", baseUrl); - return await axios.post(url.href, req, { - auth: { - username: "admin", - password: "test", - }, + await libeufinHttpLib.fetch(url.href, { + method: "POST", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + body: req, }); } @@ -423,9 +387,10 @@ export namespace LibeufinNexusApi { ): Promise<void> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL("bank-connections", baseUrl); - await axios.post( - url.href, - { + await libeufinHttpLib.fetch(url.href, { + method: "POST", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + body: { source: "new", type: "ebics", name: req.name, @@ -437,13 +402,7 @@ export namespace LibeufinNexusApi { systemID: req.systemID, }, }, - { - auth: { - username: "admin", - password: "test", - }, - }, - ); + }); } export async function getBankAccount( @@ -452,12 +411,10 @@ export namespace LibeufinNexusApi { ): Promise<any> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL(`bank-accounts/${accountName}`, baseUrl); - return await axios.get(url.href, { - auth: { - username: "admin", - password: "test", - }, + const resp = await libeufinHttpLib.fetch(url.href, { + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); + return resp.json(); } export async function submitInitiatedPayment( @@ -470,16 +427,11 @@ export namespace LibeufinNexusApi { `bank-accounts/${accountName}/payment-initiations/${paymentId}/submit`, baseUrl, ); - await axios.post( - url.href, - {}, - { - auth: { - username: "admin", - password: "test", - }, - }, - ); + await libeufinHttpLib.fetch(url.href, { + method: "POST", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + body: {}, + }); } export async function fetchAccounts( @@ -491,16 +443,11 @@ export namespace LibeufinNexusApi { `bank-connections/${connectionName}/fetch-accounts`, baseUrl, ); - await axios.post( - url.href, - {}, - { - auth: { - username: "admin", - password: "test", - }, - }, - ); + await libeufinHttpLib.fetch(url.href, { + method: "POST", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + body: {}, + }); } export async function importConnectionAccount( @@ -514,37 +461,27 @@ export namespace LibeufinNexusApi { `bank-connections/${connectionName}/import-account`, baseUrl, ); - await axios.post( - url.href, - { + await libeufinHttpLib.fetch(url.href, { + method: "POST", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + body: { offeredAccountId, nexusBankAccountId, }, - { - auth: { - username: "admin", - password: "test", - }, - }, - ); + }); } export async function connectBankConnection( libeufinNexusService: LibeufinNexusServiceInterface, connectionName: string, - ) { + ): Promise<void> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL(`bank-connections/${connectionName}/connect`, baseUrl); - await axios.post( - url.href, - {}, - { - auth: { - username: "admin", - password: "test", - }, - }, - ); + await libeufinHttpLib.fetch(url.href, { + method: "POST", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + body: {}, + }); } export async function getPaymentInitiations( @@ -558,43 +495,33 @@ export namespace LibeufinNexusApi { `/bank-accounts/${accountName}/payment-initiations`, baseUrl, ); - let response = await axios.get(url.href, { - auth: { - username: username, - password: password, - }, + let response = await libeufinHttpLib.fetch(url.href, { + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); + const respJson = await response.json(); console.log( `Payment initiations of: ${accountName}`, - JSON.stringify(response.data, null, 2), + JSON.stringify(respJson, null, 2), ); } - export async function getConfig( - libeufinNexusService: LibeufinNexusServiceInterface, - ): Promise<void> { - const baseUrl = libeufinNexusService.baseUrl; - let url = new URL(`/config`, baseUrl); - let response = await axios.get(url.href); - } - // Uses the Anastasis API to get a list of transactions. export async function getAnastasisTransactions( libeufinNexusService: LibeufinNexusServiceInterface, anastasisBaseUrl: string, + // FIXME: Nail down type! params: {}, // of the request: {delta: 5, ..} username: string = "admin", password: string = "test", ): Promise<any> { let url = new URL("history/incoming", anastasisBaseUrl); - let response = await axios.get(url.href, { - params: params, - auth: { - username: username, - password: password, - }, + for (const [k, v] of Object.entries(params)) { + url.searchParams.set(k, String(v)); + } + let response = await libeufinHttpLib.fetch(url.href, { + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); - return response; + return response.json(); } // FIXME: this function should return some structured @@ -604,16 +531,13 @@ export namespace LibeufinNexusApi { accountName: string, username: string = "admin", password: string = "test", - ): Promise<any> { + ): Promise<LibeufinNexusTransactions> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL(`/bank-accounts/${accountName}/transactions`, baseUrl); - let response = await axios.get(url.href, { - auth: { - username: username, - password: password, - }, + let response = await libeufinHttpLib.fetch(url.href, { + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); - return response; + return response.json(); } export async function fetchTransactions( @@ -623,25 +547,21 @@ export namespace LibeufinNexusApi { level: string = "report", username: string = "admin", password: string = "test", - ): Promise<any> { + ): Promise<NexusNewTransactionsInfo> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL( `/bank-accounts/${accountName}/fetch-transactions`, baseUrl, ); - return await axios.post( - url.href, - { + const resp = await libeufinHttpLib.fetch(url.href, { + method: "POST", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + body: { rangeType: rangeType, level: level, }, - { - auth: { - username: username, - password: password, - }, - }, - ); + }); + return resp.json(); } export async function changePassword( @@ -649,97 +569,109 @@ export namespace LibeufinNexusApi { username: string, req: UpdateNexusUserRequest, auth: NexusAuth, - ) { + ): Promise<void> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL(`/users/${username}/password`, baseUrl); - await axios.post(url.href, req, auth); + await libeufinHttpLib.fetch(url.href, { + method: "POST", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + body: req, + }); } export async function getUser( libeufinNexusService: LibeufinNexusServiceInterface, auth: NexusAuth, - ): Promise<any> { + ): Promise<NexusUserResponse> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL(`/user`, baseUrl); - return await axios.get(url.href, auth); + const resp = await libeufinHttpLib.fetch(url.href, { + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + }); + return resp.json(); } export async function createUser( libeufinNexusService: LibeufinNexusServiceInterface, req: CreateNexusUserRequest, - ) { + ): Promise<void> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL(`/users`, baseUrl); - await axios.post(url.href, req, { - auth: { - username: "admin", - password: "test", - }, + await libeufinHttpLib.fetch(url.href, { + method: "POST", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + body: req, }); } export async function getAllPermissions( libeufinNexusService: LibeufinNexusServiceInterface, - ): Promise<any> { + ): Promise<NexusGetPermissionsResponse> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL(`/permissions`, baseUrl); - return await axios.get(url.href, { - auth: { - username: "admin", - password: "test", - }, + const resp = await libeufinHttpLib.fetch(url.href, { + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); + return resp.json(); } export async function postPermission( libeufinNexusService: LibeufinNexusServiceInterface, req: PostNexusPermissionRequest, - ) { + ): Promise<void> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL(`/permissions`, baseUrl); - await axios.post(url.href, req, { - auth: { - username: "admin", - password: "test", - }, + await libeufinHttpLib.fetch(url.href, { + method: "POST", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + body: req, + }); + } + + export async function getAllTasks( + libeufinNexusService: LibeufinNexusServiceInterface, + bankAccountName: string, + ): Promise<NexusTaskCollection> { + const baseUrl = libeufinNexusService.baseUrl; + let url = new URL(`/bank-accounts/${bankAccountName}/schedule`, baseUrl); + const resp = await libeufinHttpLib.fetch(url.href, { + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); + return resp.json(); } - export async function getTasks( + export async function getTask( libeufinNexusService: LibeufinNexusServiceInterface, bankAccountName: string, // When void, the request returns the list of all the // tasks under this bank account. - taskName: string | void, - ): Promise<any> { + taskName: string, + ): Promise<NexusTask> { const baseUrl = libeufinNexusService.baseUrl; - let url = new URL(`/bank-accounts/${bankAccountName}/schedule`, baseUrl); + let url = new URL( + `/bank-accounts/${bankAccountName}/schedule/${taskName}`, + baseUrl, + ); if (taskName) url = new URL(taskName, `${url.href}/`); - - // It's caller's responsibility to interpret the response. - return await axios.get(url.href, { - auth: { - username: "admin", - password: "test", - }, + const resp = await libeufinHttpLib.fetch(url.href, { + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); + return resp.json(); } export async function deleteTask( libeufinNexusService: LibeufinNexusServiceInterface, bankAccountName: string, taskName: string, - ) { + ): Promise<void> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL( `/bank-accounts/${bankAccountName}/schedule/${taskName}`, baseUrl, ); - await axios.delete(url.href, { - auth: { - username: "admin", - password: "test", - }, + await libeufinHttpLib.fetch(url.href, { + method: "DELETE", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); } @@ -747,53 +679,50 @@ export namespace LibeufinNexusApi { libeufinNexusService: LibeufinNexusServiceInterface, bankAccountName: string, req: PostNexusTaskRequest, - ): Promise<any> { + ): Promise<void> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL(`/bank-accounts/${bankAccountName}/schedule`, baseUrl); - return await axios.post(url.href, req, { - auth: { - username: "admin", - password: "test", - }, + await libeufinHttpLib.fetch(url.href, { + method: "POST", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + body: req, }); } export async function deleteFacade( libeufinNexusService: LibeufinNexusServiceInterface, facadeName: string, - ): Promise<any> { + ): Promise<void> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL(`facades/${facadeName}`, baseUrl); - return await axios.delete(url.href, { - auth: { - username: "admin", - password: "test", - }, + await libeufinHttpLib.fetch(url.href, { + method: "DELETE", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); } export async function getAllFacades( libeufinNexusService: LibeufinNexusServiceInterface, - ): Promise<any> { + ): Promise<NexusFacadeListResponse> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL("facades", baseUrl); - return await axios.get(url.href, { - auth: { - username: "admin", - password: "test", - }, + const resp = await libeufinHttpLib.fetch(url.href, { + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, }); + // FIXME: Just return validated, typed response here! + return resp.json(); } export async function createAnastasisFacade( libeufinNexusService: LibeufinNexusServiceInterface, req: CreateAnastasisFacadeRequest, - ) { + ): Promise<void> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL("facades", baseUrl); - await axios.post( - url.href, - { + await libeufinHttpLib.fetch(url.href, { + method: "POST", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + body: { name: req.name, type: "anastasis", config: { @@ -803,24 +732,19 @@ export namespace LibeufinNexusApi { reserveTransferLevel: req.reserveTransferLevel, }, }, - { - auth: { - username: "admin", - password: "test", - }, - }, - ); + }); } export async function createTwgFacade( libeufinNexusService: LibeufinNexusServiceInterface, req: CreateTalerWireGatewayFacadeRequest, - ) { + ): Promise<void> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL("facades", baseUrl); - await axios.post( - url.href, - { + await libeufinHttpLib.fetch(url.href, { + method: "POST", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + body: { name: req.name, type: "taler-wire-gateway", config: { @@ -830,33 +754,22 @@ export namespace LibeufinNexusApi { reserveTransferLevel: req.reserveTransferLevel, }, }, - { - auth: { - username: "admin", - password: "test", - }, - }, - ); + }); } export async function submitAllPaymentInitiations( libeufinNexusService: LibeufinNexusServiceInterface, accountId: string, - ) { + ): Promise<void> { const baseUrl = libeufinNexusService.baseUrl; let url = new URL( `/bank-accounts/${accountId}/submit-all-payment-initiations`, baseUrl, ); - await axios.post( - url.href, - {}, - { - auth: { - username: "admin", - password: "test", - }, - }, - ); + await libeufinHttpLib.fetch(url.href, { + method: "POST", + headers: { Authorization: makeBasicAuthHeader("admin", "secret") }, + body: {}, + }); } } diff --git a/packages/taler-harness/src/harness/libeufin.ts b/packages/taler-harness/src/harness/libeufin.ts index 8fd276fad..9f3e7a5a0 100644 --- a/packages/taler-harness/src/harness/libeufin.ts +++ b/packages/taler-harness/src/harness/libeufin.ts @@ -26,39 +26,32 @@ /** * Imports. */ -import axios from "axios"; -import { URL, Logger } from "@gnu-taler/taler-util"; +import { AmountString, Logger } from "@gnu-taler/taler-util"; import { - GlobalTestState, DbInfo, - pingProc, + GlobalTestState, ProcessWrapper, + getRandomIban, + pingProc, runCommand, setupDb, sh, - getRandomIban, } from "../harness/harness.js"; import { - LibeufinSandboxApi, - LibeufinNexusApi, + CreateAnastasisFacadeRequest, CreateEbicsBankAccountRequest, - LibeufinSandboxServiceInterface, - CreateTalerWireGatewayFacadeRequest, - SimulateIncomingTransactionRequest, - SandboxAccountTransactions, - DeleteBankConnectionRequest, CreateEbicsBankConnectionRequest, - UpdateNexusUserRequest, - NexusAuth, - CreateAnastasisFacadeRequest, - PostNexusTaskRequest, - PostNexusPermissionRequest, CreateNexusUserRequest, + CreateTalerWireGatewayFacadeRequest, + LibeufinNexusApi, + LibeufinSandboxApi, + LibeufinSandboxServiceInterface, + PostNexusPermissionRequest, } from "../harness/libeufin-apis.js"; const logger = new Logger("libeufin.ts"); -export { LibeufinSandboxApi, LibeufinNexusApi }; +export { LibeufinNexusApi, LibeufinSandboxApi }; export interface LibeufinServices { libeufinSandbox: LibeufinSandboxService; @@ -76,7 +69,7 @@ export interface LibeufinNexusConfig { databaseJdbcUri: string; } -interface LibeufinNexusMoneyMovement { +export interface LibeufinNexusMoneyMovement { amount: string; creditDebitIndicator: string; details: { @@ -103,11 +96,11 @@ interface LibeufinNexusMoneyMovement { }; } -interface LibeufinNexusBatches { +export interface LibeufinNexusBatches { batchTransactions: Array<LibeufinNexusMoneyMovement>; } -interface LibeufinNexusTransaction { +export interface LibeufinNexusTransaction { amount: string; creditDebitIndicator: string; status: string; @@ -118,7 +111,7 @@ interface LibeufinNexusTransaction { batches: Array<LibeufinNexusBatches>; } -interface LibeufinNexusTransactions { +export interface LibeufinNexusTransactions { transactions: Array<LibeufinNexusTransaction>; } @@ -182,6 +175,146 @@ export interface LibeufinPreparedPaymentDetails { nexusBankAccountName: string; } +export interface NexusBankConnection { + // connection type. For example "ebics". + type: string; + + // connection name as given by the user at + // the moment of creation. + name: string; +} + +export interface NexusBankConnections { + bankConnections: NexusBankConnection[]; +} + +export interface FacadeShowInfo { + // Name of the facade, same as the "fcid" parameter. + name: string; + + // Type of the facade. + // For example, "taler-wire-gateway". + type: string; + + // Bas URL of the facade. + baseUrl: string; + + // details depending on the facade type. + config: any; +} + +export interface FetchParams { + // Because transactions are delivered by banks in "batches", + // then every batch can have different qualities. This value + // lets the request specify which type of batch ought to be + // returned. Currently, the following two type are supported: + // + // 'report': typically includes only non booked transactions. + // 'statement': typically includes only booked transactions. + level: "report" | "statement" | "all"; + + // This type indicates the time range of the query. + // It allows the following values: + // + // 'latest': retrieves the last transactions from the bank. + // If there are older unread transactions, those will *not* + // be downloaded. + // + // 'all': retrieves all the transactions from the bank, + // until the oldest. + // + // 'previous-days': currently *not* implemented, it will allow + // the request to download transactions from + // today until N days before. + // + // 'since-last': retrieves all the transactions since the last + // time one was downloaded. + // + rangeType: "latest" | "all" | "previous-days" | "since-last"; +} + +export interface NexusTask { + // The resource being impacted by this operation. + // Typically a (Nexus) bank account being fetched + // or whose payments are submitted. In this cases, + // this value is the "bank-account" constant. + resourceType: string; + // Name of the resource. In case of "bank-account", that + // is the name under which the bank account was imported + // from the bank. + resourceId: string; + // Task name, equals 'taskId' + taskName: string; + // Values allowed are "fetch" or "submit". + taskType: string; + // FIXME: describe. + taskCronSpec: string; + // Only meaningful for "fetch" types. + taskParams: FetchParams; + // Timestamp in secons when the next iteration will run. + nextScheduledExecutionSec: number; + // Timestamp in seconds when the previous iteration ran. + prevScheduledExecutionSec: number; +} + +export interface NexusNewTransactionsInfo { + // How many transactions are new to Nexus. + newTransactions: number; + // How many transactions got downloaded by the request. + // Note that a transaction can be downloaded multiple + // times but only counts as new once. + downloadedTransactions: number; +} + + +export interface NexusUserResponse { + // User name + username: string; + + // Is this a super user? + superuser: boolean; +} + +export interface NexusTaskShortInfo { + cronspec: string; + type: "fetch" | "submit"; + params: FetchParams; +} + +export interface NexusTaskCollection { + // This field can contain *multiple* objects of the type sampled below. + schedule: { + [taskName: string]: NexusTaskShortInfo; + }; +} + +export interface NexusFacadeListResponse { + facades: FacadeShowInfo[]; +} + +export interface LibeufinSandboxAdminBankAccountBalance { + // Balance in the $currency:$amount format. + balance: AmountString; + // IBAN of the bank account identified by $accountLabel + iban: string; + // BIC of the bank account identified by $accountLabel + bic: string; + // Mentions $accountLabel + label: string; +} + +export interface LibeufinPermission { + subjectType: string; + subjectId: string; + resourceType: string; + resourceId: string; + permissionName: string; +} + +export interface NexusGetPermissionsResponse { + permissions: LibeufinPermission[]; +} + export class LibeufinSandboxService implements LibeufinSandboxServiceInterface { static async create( gc: GlobalTestState, |