From b13bd85215ad64e7a2764ac7e7fee5945ffa1c07 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 29 Aug 2023 09:02:16 +0200 Subject: taler-harness: remove axios usage, renovate some tests --- packages/taler-harness/src/bench1.ts | 9 +- packages/taler-harness/src/harness/harness.ts | 396 +++------------- .../taler-harness/src/harness/libeufin-apis.ts | 519 +++++++++------------ packages/taler-harness/src/harness/libeufin.ts | 177 ++++++- packages/taler-harness/src/index.ts | 6 +- .../test-libeufin-api-bankaccount.ts | 2 +- .../test-libeufin-api-bankconnection.ts | 4 +- .../test-libeufin-api-facade-bad-request.ts | 25 +- .../integrationtests/test-libeufin-api-facade.ts | 10 +- .../test-libeufin-api-permissions.ts | 5 +- .../test-libeufin-api-sandbox-transactions.ts | 2 +- .../test-libeufin-api-scheduling.ts | 12 +- .../integrationtests/test-libeufin-api-users.ts | 4 +- .../src/integrationtests/test-libeufin-c5x.ts | 18 +- .../test-libeufin-facade-anastasis.ts | 4 +- .../src/integrationtests/test-libeufin-refund.ts | 2 +- .../test-libeufin-sandbox-wire-transfer-cli.ts | 4 +- .../test-merchant-exchange-confusion.ts | 16 +- .../test-merchant-instances-delete.ts | 22 +- .../test-merchant-instances-urls.ts | 9 +- .../integrationtests/test-merchant-instances.ts | 48 +- .../integrationtests/test-merchant-longpolling.ts | 33 +- .../integrationtests/test-merchant-refund-api.ts | 34 +- .../test-merchant-spec-public-orders.ts | 9 +- .../src/integrationtests/test-pay-paid.ts | 24 +- .../src/integrationtests/test-payment-abort.ts | 21 +- .../src/integrationtests/test-payment-claim.ts | 4 +- .../integrationtests/test-payment-idempotency.ts | 4 +- .../src/integrationtests/test-payment-template.ts | 3 +- .../src/integrationtests/test-payment-transient.ts | 37 +- .../src/integrationtests/test-paywall-flow.ts | 20 +- .../src/integrationtests/test-refund-auto.ts | 4 +- .../integrationtests/test-refund-incremental.ts | 4 +- .../src/integrationtests/test-wallet-balance.ts | 19 +- .../src/integrationtests/test-wallet-dbless.ts | 8 +- 35 files changed, 631 insertions(+), 887 deletions(-) (limited to 'packages/taler-harness/src') diff --git a/packages/taler-harness/src/bench1.ts b/packages/taler-harness/src/bench1.ts index 6aa444e0a..618eb683e 100644 --- a/packages/taler-harness/src/bench1.ts +++ b/packages/taler-harness/src/bench1.ts @@ -26,13 +26,13 @@ import { j2s, Logger, } from "@gnu-taler/taler-util"; -import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; import { AccessStats, createNativeWalletHost2, Wallet, WalletApiOperation, } from "@gnu-taler/taler-wallet-core"; +import { harnessHttpLib } from "./harness/harness.js"; /** * Entry point for the benchmark. @@ -46,11 +46,6 @@ export async function runBench1(configJson: any): Promise { // Validate the configuration file for this benchmark. const b1conf = codecForBench1Config().decode(configJson); - const myHttpLib = createPlatformHttpLib({ - enableThrottling: false, - allowHttp: true, - }); - const numIter = b1conf.iterations ?? 1; const numDeposits = b1conf.deposits ?? 5; const restartWallet = b1conf.restartAfter ?? 20; @@ -85,7 +80,7 @@ export async function runBench1(configJson: any): Promise { const res = await createNativeWalletHost2({ // No persistent DB storage. persistentStoragePath: undefined, - httpLib: myHttpLib, + httpLib: harnessHttpLib, config: { testing: { insecureTrustExchange: trustExchange, 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 { 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) { @@ -583,289 +578,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 { - 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 { - 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 { - /** - * 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 { - 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. */ @@ -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 { 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 { @@ -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 { 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 { 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 { 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 { 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 { 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 { 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, }); - 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, }); 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, }); - 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 { 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 { // 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { + ): Promise { 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 { + ): Promise { 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 { + ): Promise { 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 { 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 { 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 { 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 { - 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 { 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 { + ): Promise { 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 { + ): Promise { 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 { 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 { + ): Promise { 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 { 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 { + ): Promise { 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 { 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 { + 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 { + taskName: string, + ): Promise { 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 { 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 { + ): Promise { 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 { + ): Promise { 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 { + ): Promise { 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 { 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 { 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 { 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; } -interface LibeufinNexusTransaction { +export interface LibeufinNexusTransaction { amount: string; creditDebitIndicator: string; status: string; @@ -118,7 +111,7 @@ interface LibeufinNexusTransaction { batches: Array; } -interface LibeufinNexusTransactions { +export interface LibeufinNexusTransactions { transactions: Array; } @@ -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, diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts index 0efaea9ad..cd688ed89 100644 --- a/packages/taler-harness/src/index.ts +++ b/packages/taler-harness/src/index.ts @@ -191,12 +191,12 @@ configCli 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); + } else { + res = config.getString(args.get.section, args.get.option); } if (res.isDefined()) { - console.log(res.getValue()); + console.log(res.required()); } else { console.warn("not found"); process.exit(1); 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 f36168301..e5e3dfe64 100644 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-bankaccount.ts +++ b/packages/taler-harness/src/integrationtests/test-libeufin-api-bankaccount.ts @@ -101,7 +101,7 @@ export async function runLibeufinApiBankaccountTest(t: GlobalTestState) { nexus, "local-mock", ); - let el = findNexusPayment("mock subject", transactions.data); + let el = findNexusPayment("mock subject", transactions); t.assertTrue(el instanceof Object); } diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-bankconnection.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-bankconnection.ts index 912b7b2ac..243500dc9 100644 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-bankconnection.ts +++ b/packages/taler-harness/src/integrationtests/test-libeufin-api-bankconnection.ts @@ -45,12 +45,12 @@ export async function runLibeufinApiBankconnectionTest(t: GlobalTestState) { }); let connections = await LibeufinNexusApi.getAllConnections(nexus); - t.assertTrue(connections.data["bankConnections"].length == 1); + t.assertTrue(connections.bankConnections.length == 1); await LibeufinNexusApi.deleteBankConnection(nexus, { bankConnectionId: "bankconnection-api-test-connection", }); connections = await LibeufinNexusApi.getAllConnections(nexus); - t.assertTrue(connections.data["bankConnections"].length == 0); + t.assertTrue(connections.bankConnections.length == 0); } runLibeufinApiBankconnectionTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-facade-bad-request.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-facade-bad-request.ts index a1da9e0da..27cc81588 100644 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-facade-bad-request.ts +++ b/packages/taler-harness/src/integrationtests/test-libeufin-api-facade-bad-request.ts @@ -18,15 +18,16 @@ * Imports. */ import { URL } from "@gnu-taler/taler-util"; -import axiosImp from "axios"; -import { GlobalTestState } from "../harness/harness.js"; +import { GlobalTestState, harnessHttpLib } from "../harness/harness.js"; import { launchLibeufinServices, NexusUserBundle, SandboxUserBundle, } from "../harness/libeufin.js"; - -const axios = axiosImp.default; +import { + createPlatformHttpLib, + makeBasicAuthHeader, +} from "@gnu-taler/taler-util/http"; export async function runLibeufinApiFacadeBadRequestTest(t: GlobalTestState) { /** @@ -50,21 +51,17 @@ export async function runLibeufinApiFacadeBadRequestTest(t: GlobalTestState) { console.log("malformed facade"); const baseUrl = libeufinServices.libeufinNexus.baseUrl; let url = new URL("facades", baseUrl); - let resp = await axios.post( - url.href, - { + let resp = await harnessHttpLib.fetch(url.href, { + method: "POST", + body: { name: "malformed-facade", type: "taler-wire-gateway", config: {}, // malformation here. }, - { - auth: { - username: "admin", - password: "test", - }, - validateStatus: () => true, + headers: { + Authorization: makeBasicAuthHeader("admin", "test"), }, - ); + }); t.assertTrue(resp.status == 400); } diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-facade.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-facade.ts index 946c565d4..a819dd481 100644 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-facade.ts +++ b/packages/taler-harness/src/integrationtests/test-libeufin-api-facade.ts @@ -51,20 +51,20 @@ export async function runLibeufinApiFacadeTest(t: GlobalTestState) { libeufinServices.libeufinNexus, ); // check that original facade shows up. - t.assertTrue(resp.data["facades"][0]["name"] == user01nexus.twgReq["name"]); + t.assertTrue(resp.facades[0].name == user01nexus.twgReq["name"]); - const twgBaseUrl: string = resp.data["facades"][0]["baseUrl"]; + const twgBaseUrl: string = resp.facades[0]["baseUrl"]; t.assertTrue(typeof twgBaseUrl === "string"); t.assertTrue(twgBaseUrl.startsWith("http://")); t.assertTrue(twgBaseUrl.endsWith("/")); // delete it. - resp = await LibeufinNexusApi.deleteFacade( + await LibeufinNexusApi.deleteFacade( libeufinServices.libeufinNexus, user01nexus.twgReq["name"], ); - // check that no facades show up. - t.assertTrue(!resp.data.hasOwnProperty("facades")); + resp = await LibeufinNexusApi.getAllFacades(libeufinServices.libeufinNexus); + t.assertTrue(!resp.hasOwnProperty("facades")); } runLibeufinApiFacadeTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-permissions.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-permissions.ts index f8f2d7d80..56443c20a 100644 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-permissions.ts +++ b/packages/taler-harness/src/integrationtests/test-libeufin-api-permissions.ts @@ -46,7 +46,8 @@ export async function runLibeufinApiPermissionsTest(t: GlobalTestState) { user01nexus.twgTransferPermission, ); let transferPermission = await LibeufinNexusApi.getAllPermissions(nexus); - let element = transferPermission.data["permissions"].pop(); + let element = transferPermission["permissions"].pop(); + t.assertTrue(!!element); t.assertTrue( element["permissionName"] == "facade.talerwiregateway.transfer" && element["subjectId"] == "username-01", @@ -58,7 +59,7 @@ export async function runLibeufinApiPermissionsTest(t: GlobalTestState) { await LibeufinNexusApi.postPermission(nexus, denyTransfer); transferPermission = await LibeufinNexusApi.getAllPermissions(nexus); - t.assertTrue(transferPermission.data["permissions"].length == 0); + t.assertTrue(transferPermission["permissions"].length == 0); } runLibeufinApiPermissionsTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-sandbox-transactions.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-sandbox-transactions.ts index f1ff69a6d..6cfc55aa6 100644 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-sandbox-transactions.ts +++ b/packages/taler-harness/src/integrationtests/test-libeufin-api-sandbox-transactions.ts @@ -64,6 +64,6 @@ export async function runLibeufinApiSandboxTransactionsTest( sandbox, "mock-account", ); - t.assertAmountEquals(ret.data.balance, "EUR:2.1"); + t.assertAmountEquals(ret.balance, "EUR:2.1"); } runLibeufinApiSandboxTransactionsTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-scheduling.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-scheduling.ts index 95f4bfaa0..15ed2ab78 100644 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-scheduling.ts +++ b/packages/taler-harness/src/integrationtests/test-libeufin-api-scheduling.ts @@ -54,19 +54,19 @@ export async function runLibeufinApiSchedulingTest(t: GlobalTestState) { rangeType: "all", }, }); - let resp = await LibeufinNexusApi.getTasks( + let resp = await LibeufinNexusApi.getTask( nexus, user01nexus.localAccountName, "test-task", ); - t.assertTrue(resp.data["taskName"] == "test-task"); + t.assertTrue(resp.taskName == "test-task"); await LibeufinNexusApi.deleteTask( nexus, user01nexus.localAccountName, "test-task", ); try { - await LibeufinNexusApi.getTasks( + await LibeufinNexusApi.getTask( nexus, user01nexus.localAccountName, "test-task", @@ -82,19 +82,19 @@ export async function runLibeufinApiSchedulingTest(t: GlobalTestState) { type: "submit", params: {}, }); - resp = await LibeufinNexusApi.getTasks( + resp = await LibeufinNexusApi.getTask( nexus, user01nexus.localAccountName, "test-task", ); - t.assertTrue(resp.data["taskName"] == "test-task"); + t.assertTrue(resp.taskName == "test-task"); await LibeufinNexusApi.deleteTask( nexus, user01nexus.localAccountName, "test-task", ); try { - await LibeufinNexusApi.getTasks( + await LibeufinNexusApi.getTask( nexus, user01nexus.localAccountName, "test-task", diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-api-users.ts b/packages/taler-harness/src/integrationtests/test-libeufin-api-users.ts index bc3103c7e..662b22bbe 100644 --- a/packages/taler-harness/src/integrationtests/test-libeufin-api-users.ts +++ b/packages/taler-harness/src/integrationtests/test-libeufin-api-users.ts @@ -56,8 +56,8 @@ export async function runLibeufinApiUsersTest(t: GlobalTestState) { password: "got-changed", }, }); - console.log(resp.data); - t.assertTrue(resp.data["username"] == "one" && !resp.data["superuser"]); + console.log(resp); + t.assertTrue(resp["username"] == "one" && !resp["superuser"]); } runLibeufinApiUsersTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-c5x.ts b/packages/taler-harness/src/integrationtests/test-libeufin-c5x.ts index c6dfca99b..5097bc4d3 100644 --- a/packages/taler-harness/src/integrationtests/test-libeufin-c5x.ts +++ b/packages/taler-harness/src/integrationtests/test-libeufin-c5x.ts @@ -79,7 +79,7 @@ export async function runLibeufinC5xTest(t: GlobalTestState) { libeufinServices.libeufinNexus, user01nexus.localAccountName, ); - t.assertTrue(nexusTxs.data["transactions"].length == 0); + t.assertTrue(nexusTxs["transactions"].length == 0); // Addressing one payment to user 01 await libeufinServices.libeufinSandbox.makeTransaction( @@ -95,8 +95,8 @@ export async function runLibeufinC5xTest(t: GlobalTestState) { "all", // range "report", // C52 ); - t.assertTrue(expectOne.data.newTransactions == 1); - t.assertTrue(expectOne.data.downloadedTransactions == 1); + t.assertTrue(expectOne.newTransactions == 1); + t.assertTrue(expectOne.downloadedTransactions == 1); /* Expect zero payments being downloaded because the * previous request consumed already the one pending @@ -108,8 +108,8 @@ export async function runLibeufinC5xTest(t: GlobalTestState) { "all", // range "report", // C52 ); - t.assertTrue(expectZero.data.newTransactions == 0); - t.assertTrue(expectZero.data.downloadedTransactions == 0); + t.assertTrue(expectZero.newTransactions == 0); + t.assertTrue(expectZero.downloadedTransactions == 0); /** * A statement should still account zero payments because @@ -121,8 +121,8 @@ export async function runLibeufinC5xTest(t: GlobalTestState) { "all", // range "statement", // C53 ); - t.assertTrue(expectZero.data.newTransactions == 0); - t.assertTrue(expectZero.data.downloadedTransactions == 0); + t.assertTrue(expectZero.newTransactions == 0); + t.assertTrue(expectZero.downloadedTransactions == 0); /** * Ticking now. That books any pending transaction. @@ -141,7 +141,7 @@ export async function runLibeufinC5xTest(t: GlobalTestState) { "all", // range "statement", // C53 ); - t.assertTrue(expectOne.data.downloadedTransactions == 1); - t.assertTrue(expectOne.data.newTransactions == 0); + t.assertTrue(expectOne.downloadedTransactions == 1); + t.assertTrue(expectOne.newTransactions == 0); } runLibeufinC5xTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-facade-anastasis.ts b/packages/taler-harness/src/integrationtests/test-libeufin-facade-anastasis.ts index 10c73fed5..0efd55f44 100644 --- a/packages/taler-harness/src/integrationtests/test-libeufin-facade-anastasis.ts +++ b/packages/taler-harness/src/integrationtests/test-libeufin-facade-anastasis.ts @@ -53,9 +53,9 @@ export async function runLibeufinAnastasisFacadeTest(t: GlobalTestState) { ); // check that original facade shows up. t.assertTrue( - resp.data["facades"][0]["name"] == user01nexus.anastasisReq["name"], + resp["facades"][0]["name"] == user01nexus.anastasisReq["name"], ); - const anastasisBaseUrl: string = resp.data["facades"][0]["baseUrl"]; +const anastasisBaseUrl: string = resp["facades"][0]["baseUrl"]; t.assertTrue(typeof anastasisBaseUrl === "string"); t.assertTrue(anastasisBaseUrl.startsWith("http://")); t.assertTrue(anastasisBaseUrl.endsWith("/")); diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-refund.ts b/packages/taler-harness/src/integrationtests/test-libeufin-refund.ts index 9d90121a0..d37363bab 100644 --- a/packages/taler-harness/src/integrationtests/test-libeufin-refund.ts +++ b/packages/taler-harness/src/integrationtests/test-libeufin-refund.ts @@ -77,7 +77,7 @@ export async function runLibeufinRefundTest(t: GlobalTestState) { libeufinServices.libeufinNexus, user01nexus.localAccountName, ); - t.assertTrue(nexusTxs.data["transactions"].length == 1); + t.assertTrue(nexusTxs["transactions"].length == 1); // Submit the reimbursement await LibeufinNexusApi.submitInitiatedPayment( diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-sandbox-wire-transfer-cli.ts b/packages/taler-harness/src/integrationtests/test-libeufin-sandbox-wire-transfer-cli.ts index e6b074d3f..be467e2f1 100644 --- a/packages/taler-harness/src/integrationtests/test-libeufin-sandbox-wire-transfer-cli.ts +++ b/packages/taler-harness/src/integrationtests/test-libeufin-sandbox-wire-transfer-cli.ts @@ -79,7 +79,7 @@ export async function runLibeufinSandboxWireTransferCliTest( sandbox, "mock-account-2", ); - console.log(ret.data.balance); - t.assertTrue(ret.data.balance == "EUR:1.89"); + console.log(ret.balance); + t.assertTrue(ret.balance == "EUR:1.89"); } runLibeufinSandboxWireTransferCliTest.suites = ["libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-merchant-exchange-confusion.ts b/packages/taler-harness/src/integrationtests/test-merchant-exchange-confusion.ts index 2fccd0b97..e18cd7a0f 100644 --- a/packages/taler-harness/src/integrationtests/test-merchant-exchange-confusion.ts +++ b/packages/taler-harness/src/integrationtests/test-merchant-exchange-confusion.ts @@ -23,8 +23,6 @@ import { PreparePayResultType, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import axiosImp from "axios"; -const axios = axiosImp.default; import { URL } from "url"; import { defaultCoinConfig } from "../harness/denomStructures.js"; import { @@ -36,6 +34,7 @@ import { ExchangeService, getPayto, GlobalTestState, + harnessHttpLib, MerchantPrivateApi, MerchantService, setupDb, @@ -45,6 +44,7 @@ import { FaultyMerchantTestEnvironment, withdrawViaBank, } from "../harness/helpers.js"; +import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; /** * Run a test case with a simple TESTKUDOS Taler environment, consisting @@ -186,9 +186,7 @@ export async function runMerchantExchangeConfusionTest(t: GlobalTestState) { t.assertTrue(orderStatus.already_paid_order_id === undefined); let publicOrderStatusUrl = orderStatus.order_status_url; - let publicOrderStatusResp = await axios.get(publicOrderStatusUrl, { - validateStatus: () => true, - }); + let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl); if (publicOrderStatusResp.status != 402) { throw Error( @@ -197,7 +195,7 @@ export async function runMerchantExchangeConfusionTest(t: GlobalTestState) { } let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode( - publicOrderStatusResp.data, + await publicOrderStatusResp.json(), ); console.log(pubUnpaidStatus); @@ -221,9 +219,7 @@ export async function runMerchantExchangeConfusionTest(t: GlobalTestState) { console.log("requesting", orderUrlWithHash.href); - publicOrderStatusResp = await axios.get(orderUrlWithHash.href, { - validateStatus: () => true, - }); + publicOrderStatusResp = await harnessHttpLib.fetch(orderUrlWithHash.href); if (publicOrderStatusResp.status != 402) { throw Error( @@ -232,7 +228,7 @@ export async function runMerchantExchangeConfusionTest(t: GlobalTestState) { } pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode( - publicOrderStatusResp.data, + await publicOrderStatusResp.json(), ); const confirmPayRes = await wallet.client.call( diff --git a/packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts b/packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts index 09231cdd8..e6e5bff76 100644 --- a/packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts +++ b/packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts @@ -17,9 +17,7 @@ /** * Imports. */ -import { URL } from "@gnu-taler/taler-util"; -import axiosImp from "axios"; -const axios = axiosImp.default; +import { TalerError, URL } from "@gnu-taler/taler-util"; import { ExchangeService, GlobalTestState, @@ -27,7 +25,9 @@ import { MerchantService, setupDb, getPayto, + harnessHttpLib, } from "../harness/harness.js"; +import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; /** * Test instance deletion and authentication for it @@ -61,15 +61,17 @@ export async function runMerchantInstancesDeleteTest(t: GlobalTestState) { const baseUrl = merchant.makeInstanceBaseUrl(); { - const r = await axios.get(new URL("config", baseUrl).href); - console.log(r.data); - t.assertDeepEqual(r.data.currency, "TESTKUDOS"); + const r = await harnessHttpLib.fetch(new URL("config", baseUrl).href); + const data = await r.json(); + console.log(data); + t.assertDeepEqual(data.currency, "TESTKUDOS"); } // Instances should initially be empty { - const r = await axios.get(new URL("management/instances", baseUrl).href); - t.assertDeepEqual(r.data.instances, []); + const r = await harnessHttpLib.fetch(new URL("management/instances", baseUrl).href); + const data = await r.json(); + t.assertDeepEqual(data.instances, []); } // Add an instance, no auth! @@ -121,8 +123,8 @@ export async function runMerchantInstancesDeleteTest(t: GlobalTestState) { await unauthMerchantClient.deleteInstance("myinst"); }); console.log("Got expected exception", exc); - t.assertAxiosError(exc); - t.assertDeepEqual(exc.response?.status, 401); + t.assertTrue(exc instanceof TalerError); + t.assertDeepEqual(exc.errorDetail.httpStatusCode, 401); } } 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 534b35278..18a09c76b 100644 --- a/packages/taler-harness/src/integrationtests/test-merchant-instances-urls.ts +++ b/packages/taler-harness/src/integrationtests/test-merchant-instances-urls.ts @@ -18,8 +18,6 @@ * Imports. */ import { Duration } from "@gnu-taler/taler-util"; -import axiosImp from "axios"; -const axios = axiosImp.default; import { ExchangeService, GlobalTestState, @@ -27,14 +25,14 @@ import { MerchantService, setupDb, getPayto, + harnessHttpLib, } from "../harness/harness.js"; +import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; /** * Do basic checks on instance management and authentication. */ export async function runMerchantInstancesUrlsTest(t: GlobalTestState) { - // Set up test environment - const db = await setupDb(t); const exchange = ExchangeService.create(t, { @@ -111,11 +109,10 @@ export async function runMerchantInstancesUrlsTest(t: GlobalTestState) { }); async function check(url: string, token: string, expectedStatus: number) { - const resp = await axios.get(url, { + const resp = await harnessHttpLib.fetch(url, { headers: { Authorization: `Bearer ${token}`, }, - validateStatus: () => true, }); console.log( `checking ${url}, expected ${expectedStatus}, got ${resp.status}`, diff --git a/packages/taler-harness/src/integrationtests/test-merchant-instances.ts b/packages/taler-harness/src/integrationtests/test-merchant-instances.ts index 78626ea3d..f7d89c543 100644 --- a/packages/taler-harness/src/integrationtests/test-merchant-instances.ts +++ b/packages/taler-harness/src/integrationtests/test-merchant-instances.ts @@ -18,8 +18,6 @@ * Imports. */ import { URL } from "@gnu-taler/taler-util"; -import axiosImp from "axios"; -const axios = axiosImp.default; import { ExchangeService, GlobalTestState, @@ -27,7 +25,9 @@ import { MerchantService, setupDb, getPayto, + harnessHttpLib, } from "../harness/harness.js"; +import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; /** * Do basic checks on instance management and authentication. @@ -61,15 +61,19 @@ export async function runMerchantInstancesTest(t: GlobalTestState) { const baseUrl = merchant.makeInstanceBaseUrl(); { - const r = await axios.get(new URL("config", baseUrl).href); - console.log(r.data); - t.assertDeepEqual(r.data.currency, "TESTKUDOS"); + const r = await harnessHttpLib.fetch(new URL("config", baseUrl).href); + const data = await r.json(); + console.log(data); + t.assertDeepEqual(data.currency, "TESTKUDOS"); } // Instances should initially be empty { - const r = await axios.get(new URL("management/instances", baseUrl).href); - t.assertDeepEqual(r.data.instances, []); + const r = await harnessHttpLib.fetch( + new URL("management/instances", baseUrl).href, + ); + const data = await r.json(); + t.assertDeepEqual(data.instances, []); } // Add an instance, no auth! @@ -104,11 +108,14 @@ export async function runMerchantInstancesTest(t: GlobalTestState) { // Check that a "malformed" bearer Authorization header gets ignored { const url = merchant.makeInstanceBaseUrl(); - const resp = await axios.get(new URL("management/instances", url).href, { - headers: { - Authorization: "foo bar-baz", + const resp = await harnessHttpLib.fetch( + new URL("management/instances", url).href, + { + headers: { + Authorization: "foo bar-baz", + }, }, - }); + ); t.assertDeepEqual(resp.status, 200); } @@ -130,9 +137,7 @@ export async function runMerchantInstancesTest(t: GlobalTestState) { }); console.log(exc); - - t.assertAxiosError(exc); - t.assertTrue(exc.response?.status === 401); + t.assertTrue(exc.errorDetail.httpStatusCode === 401); merchantClient = new MerchantApiClient(merchant.makeInstanceBaseUrl(), { method: "token", @@ -145,12 +150,15 @@ export async function runMerchantInstancesTest(t: GlobalTestState) { // Now, try some variations. { const url = merchant.makeInstanceBaseUrl(); - const resp = await axios.get(new URL("management/instances", url).href, { - headers: { - // Note the spaces - Authorization: "Bearer secret-token:foobar", + const resp = await harnessHttpLib.fetch( + new URL("management/instances", url).href, + { + headers: { + // Note the spaces + Authorization: "Bearer secret-token:foobar", + }, }, - }); + ); t.assertDeepEqual(resp.status, 200); } @@ -176,7 +184,7 @@ export async function runMerchantInstancesTest(t: GlobalTestState) { await unauthMerchantClient.deleteInstance("myinst"); }); console.log(exc); - t.assertAxiosError(exc); + t.assertTrue(exc.errorDetail.httpStatusCode === 401); t.assertDeepEqual(exc.response?.status, 401); } } diff --git a/packages/taler-harness/src/integrationtests/test-merchant-longpolling.ts b/packages/taler-harness/src/integrationtests/test-merchant-longpolling.ts index 59f23fe5d..8d271c5d1 100644 --- a/packages/taler-harness/src/integrationtests/test-merchant-longpolling.ts +++ b/packages/taler-harness/src/integrationtests/test-merchant-longpolling.ts @@ -24,20 +24,18 @@ import { 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 { GlobalTestState, MerchantPrivateApi, harnessHttpLib } from "../harness/harness.js"; import { createSimpleTestkudosEnvironmentV2, - withdrawViaBankV2 + withdrawViaBankV2, } from "../harness/helpers.js"; -const axios = axiosImp.default; +import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; /** * Run test for basic, bank-integrated withdrawal. */ export async function runMerchantLongpollingTest(t: GlobalTestState) { // Set up test environment - const { walletClient, bank, exchange, merchant } = await createSimpleTestkudosEnvironmentV2(t); @@ -83,9 +81,7 @@ export async function runMerchantLongpollingTest(t: GlobalTestState) { // First, request order status without longpolling { console.log("requesting", publicOrderStatusUrl.href); - let publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, { - validateStatus: () => true, - }); + let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href); if (publicOrderStatusResp.status != 402) { throw Error( @@ -98,9 +94,7 @@ export async function runMerchantLongpollingTest(t: GlobalTestState) { publicOrderStatusUrl.searchParams.set("timeout_ms", "500"); console.log("requesting", publicOrderStatusUrl.href); - let publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, { - validateStatus: () => true, - }); + let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href); if (publicOrderStatusResp.status != 402) { throw Error( @@ -109,7 +103,7 @@ export async function runMerchantLongpollingTest(t: GlobalTestState) { } let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode( - publicOrderStatusResp.data, + await publicOrderStatusResp.json(), ); console.log(pubUnpaidStatus); @@ -135,9 +129,7 @@ export async function runMerchantLongpollingTest(t: GlobalTestState) { preparePayResp.contractTermsHash, ); - let publicOrderStatusPromise = axios.get(publicOrderStatusUrl.href, { - validateStatus: () => true, - }); + let publicOrderStatusPromise = harnessHttpLib.fetch(publicOrderStatusUrl.href); t.assertTrue(preparePayResp.status === PreparePayResultType.PaymentPossible); @@ -152,15 +144,12 @@ export async function runMerchantLongpollingTest(t: GlobalTestState) { } pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode( - publicOrderStatusResp.data, + await publicOrderStatusResp.json(), ); - const confirmPayRes = await walletClient.call( - WalletApiOperation.ConfirmPay, - { - proposalId: proposalId, - }, - ); + const confirmPayRes = await walletClient.call(WalletApiOperation.ConfirmPay, { + proposalId: proposalId, + }); t.assertTrue(confirmPayRes.type === ConfirmPayResultType.Done); } diff --git a/packages/taler-harness/src/integrationtests/test-merchant-refund-api.ts b/packages/taler-harness/src/integrationtests/test-merchant-refund-api.ts index 5d9b23fa7..8efac1fc1 100644 --- a/packages/taler-harness/src/integrationtests/test-merchant-refund-api.ts +++ b/packages/taler-harness/src/integrationtests/test-merchant-refund-api.ts @@ -17,12 +17,14 @@ /** * Imports. */ +import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; import { GlobalTestState, MerchantPrivateApi, MerchantServiceInterface, WalletCli, ExchangeServiceInterface, + harnessHttpLib, } from "../harness/harness.js"; import { createSimpleTestkudosEnvironment, @@ -34,8 +36,6 @@ import { PreparePayResultType, Duration, } from "@gnu-taler/taler-util"; -import axiosImp from "axios"; -const axios = axiosImp.default; import { WalletApiOperation, BankServiceHandle, @@ -136,23 +136,19 @@ async function testRefundApiWithFulfillmentUrl( preparePayResult.contractTermsHash, ); - let publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, { - validateStatus: () => true, - }); - console.log(publicOrderStatusResp.data); + let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href); + const respData = await publicOrderStatusResp.json(); t.assertTrue(publicOrderStatusResp.status === 200); - t.assertAmountEquals(publicOrderStatusResp.data.refund_amount, "TESTKUDOS:5"); + t.assertAmountEquals(respData.refund_amount, "TESTKUDOS:5"); publicOrderStatusUrl = new URL( `orders/${orderId}`, merchant.makeInstanceBaseUrl(), ); console.log(`requesting order status via '${publicOrderStatusUrl.href}'`); - publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, { - validateStatus: () => true, - }); + publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href); console.log(publicOrderStatusResp.status); - console.log(publicOrderStatusResp.data); + console.log(await publicOrderStatusResp.json()); // We didn't give any authentication, so we should get a fulfillment URL back t.assertTrue(publicOrderStatusResp.status === 403); } @@ -252,22 +248,20 @@ async function testRefundApiWithFulfillmentMessage( preparePayResult.contractTermsHash, ); - let publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, { - validateStatus: () => true, - }); - console.log(publicOrderStatusResp.data); + let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href); + let respData = await publicOrderStatusResp.json(); + console.log(respData); t.assertTrue(publicOrderStatusResp.status === 200); - t.assertAmountEquals(publicOrderStatusResp.data.refund_amount, "TESTKUDOS:5"); + t.assertAmountEquals(respData.refund_amount, "TESTKUDOS:5"); publicOrderStatusUrl = new URL( `orders/${orderId}`, merchant.makeInstanceBaseUrl(), ); - publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, { - validateStatus: () => true, - }); - console.log(publicOrderStatusResp.data); + publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href); + respData = await publicOrderStatusResp.json(); + console.log(respData); // We didn't give any authentication, so we should get a fulfillment URL back t.assertTrue(publicOrderStatusResp.status === 403); } diff --git a/packages/taler-harness/src/integrationtests/test-merchant-spec-public-orders.ts b/packages/taler-harness/src/integrationtests/test-merchant-spec-public-orders.ts index 975ba707b..fca368dad 100644 --- a/packages/taler-harness/src/integrationtests/test-merchant-spec-public-orders.ts +++ b/packages/taler-harness/src/integrationtests/test-merchant-spec-public-orders.ts @@ -24,7 +24,6 @@ import { encodeCrock, getRandomBytes, } from "@gnu-taler/taler-util"; -import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { BankService, @@ -33,17 +32,13 @@ import { MerchantPrivateApi, MerchantService, WalletCli, + harnessHttpLib, } from "../harness/harness.js"; import { createSimpleTestkudosEnvironment, withdrawViaBank, } from "../harness/helpers.js"; -const httpLib = createPlatformHttpLib({ - allowHttp: true, - enableThrottling: false, -}); - interface Context { merchant: MerchantService; merchantBaseUrl: string; @@ -51,6 +46,8 @@ interface Context { exchange: ExchangeService; } +const httpLib = harnessHttpLib; + async function testWithClaimToken( t: GlobalTestState, c: Context, diff --git a/packages/taler-harness/src/integrationtests/test-pay-paid.ts b/packages/taler-harness/src/integrationtests/test-pay-paid.ts index 2ef91e4a8..a377b7237 100644 --- a/packages/taler-harness/src/integrationtests/test-pay-paid.ts +++ b/packages/taler-harness/src/integrationtests/test-pay-paid.ts @@ -17,7 +17,7 @@ /** * Imports. */ -import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js"; +import { GlobalTestState, MerchantPrivateApi, harnessHttpLib } from "../harness/harness.js"; import { withdrawViaBank, createFaultInjectedMerchantTestkudosEnvironment, @@ -28,8 +28,6 @@ import { ConfirmPayResultType, URL, } from "@gnu-taler/taler-util"; -import axiosImp from "axios"; -const axios = axiosImp.default; import { FaultInjectionRequestContext } from "../harness/faultInjection.js"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; @@ -86,9 +84,7 @@ export async function runPayPaidTest(t: GlobalTestState) { t.assertTrue(orderStatus.already_paid_order_id === undefined); let publicOrderStatusUrl = orderStatus.order_status_url; - let publicOrderStatusResp = await axios.get(publicOrderStatusUrl, { - validateStatus: () => true, - }); + let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl); if (publicOrderStatusResp.status != 402) { throw Error( @@ -97,7 +93,7 @@ export async function runPayPaidTest(t: GlobalTestState) { } let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode( - publicOrderStatusResp.data, + publicOrderStatusResp.json(), ); console.log(pubUnpaidStatus); @@ -113,9 +109,7 @@ export async function runPayPaidTest(t: GlobalTestState) { const proposalId = preparePayResp.proposalId; - publicOrderStatusResp = await axios.get(publicOrderStatusUrl, { - validateStatus: () => true, - }); + publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl); if (publicOrderStatusResp.status != 402) { throw Error( @@ -124,7 +118,7 @@ export async function runPayPaidTest(t: GlobalTestState) { } pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode( - publicOrderStatusResp.data, + publicOrderStatusResp.json(), ); const confirmPayRes = await wallet.client.call( @@ -136,14 +130,12 @@ export async function runPayPaidTest(t: GlobalTestState) { t.assertTrue(confirmPayRes.type === ConfirmPayResultType.Done); - publicOrderStatusResp = await axios.get(publicOrderStatusUrl, { - validateStatus: () => true, - }); + publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl); - 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}`, ); diff --git a/packages/taler-harness/src/integrationtests/test-payment-abort.ts b/packages/taler-harness/src/integrationtests/test-payment-abort.ts index 40438c583..05ca7a543 100644 --- a/packages/taler-harness/src/integrationtests/test-payment-abort.ts +++ b/packages/taler-harness/src/integrationtests/test-payment-abort.ts @@ -17,14 +17,12 @@ /** * Imports. */ -import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js"; +import { GlobalTestState, MerchantPrivateApi, harnessHttpLib } from "../harness/harness.js"; import { withdrawViaBank, createFaultInjectedMerchantTestkudosEnvironment, } from "../harness/helpers.js"; -import { - FaultInjectionRequestContext, -} from "../harness/faultInjection.js"; +import { FaultInjectionRequestContext } from "../harness/faultInjection.js"; import { codecForMerchantOrderStatusUnpaid, ConfirmPayResultType, @@ -35,9 +33,6 @@ import { URL, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import axiosImp from "axios"; - -const axios = axiosImp.default; export async function runPaymentAbortTest(t: GlobalTestState) { // Set up test environment @@ -75,9 +70,7 @@ export async function runPaymentAbortTest(t: GlobalTestState) { t.assertTrue(orderStatus.already_paid_order_id === undefined); let publicOrderStatusUrl = orderStatus.order_status_url; - let publicOrderStatusResp = await axios.get(publicOrderStatusUrl, { - validateStatus: () => true, - }); + let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl); if (publicOrderStatusResp.status != 402) { throw Error( @@ -86,7 +79,7 @@ export async function runPaymentAbortTest(t: GlobalTestState) { } let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode( - publicOrderStatusResp.data, + publicOrderStatusResp.json(), ); console.log(pubUnpaidStatus); @@ -102,9 +95,7 @@ export async function runPaymentAbortTest(t: GlobalTestState) { const proposalId = preparePayResp.proposalId; - publicOrderStatusResp = await axios.get(publicOrderStatusUrl, { - validateStatus: () => true, - }); + publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl); if (publicOrderStatusResp.status != 402) { throw Error( @@ -113,7 +104,7 @@ export async function runPaymentAbortTest(t: GlobalTestState) { } pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode( - publicOrderStatusResp.data, + publicOrderStatusResp.json(), ); faultyMerchant.faultProxy.addFault({ diff --git a/packages/taler-harness/src/integrationtests/test-payment-claim.ts b/packages/taler-harness/src/integrationtests/test-payment-claim.ts index eb219c1e7..3e52cb5dd 100644 --- a/packages/taler-harness/src/integrationtests/test-payment-claim.ts +++ b/packages/taler-harness/src/integrationtests/test-payment-claim.ts @@ -42,13 +42,15 @@ export async function runPaymentClaimTest(t: GlobalTestState) { // Withdraw digital cash into the wallet. - await withdrawViaBankV2(t, { + const wres = await withdrawViaBankV2(t, { walletClient, bank, exchange, amount: "TESTKUDOS:20", }); + await wres.withdrawalFinishedCond; + // Set up order. const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", { diff --git a/packages/taler-harness/src/integrationtests/test-payment-idempotency.ts b/packages/taler-harness/src/integrationtests/test-payment-idempotency.ts index e16cf9dd1..6373c2393 100644 --- a/packages/taler-harness/src/integrationtests/test-payment-idempotency.ts +++ b/packages/taler-harness/src/integrationtests/test-payment-idempotency.ts @@ -37,13 +37,15 @@ export async function runPaymentIdempotencyTest(t: GlobalTestState) { // Withdraw digital cash into the wallet. - await withdrawViaBankV2(t, { + const wres = await withdrawViaBankV2(t, { walletClient, bank, exchange, amount: "TESTKUDOS:20", }); + await wres.withdrawalFinishedCond; + // Set up order. const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", { diff --git a/packages/taler-harness/src/integrationtests/test-payment-template.ts b/packages/taler-harness/src/integrationtests/test-payment-template.ts index 172791648..707be52e1 100644 --- a/packages/taler-harness/src/integrationtests/test-payment-template.ts +++ b/packages/taler-harness/src/integrationtests/test-payment-template.ts @@ -50,7 +50,8 @@ export async function runPaymentTemplateTest(t: GlobalTestState) { // Withdraw digital cash into the wallet. - await withdrawViaBankV2(t, { walletClient, bank, exchange, amount: "TESTKUDOS:20" }); + const wres = await withdrawViaBankV2(t, { walletClient, bank, exchange, amount: "TESTKUDOS:20" }); + await wres.withdrawalFinishedCond; // Request a template payment diff --git a/packages/taler-harness/src/integrationtests/test-payment-transient.ts b/packages/taler-harness/src/integrationtests/test-payment-transient.ts index 33a9716d5..c2a8e37c5 100644 --- a/packages/taler-harness/src/integrationtests/test-payment-transient.ts +++ b/packages/taler-harness/src/integrationtests/test-payment-transient.ts @@ -17,23 +17,22 @@ /** * Imports. */ -import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js"; import { - withdrawViaBank, - createFaultInjectedMerchantTestkudosEnvironment, -} from "../harness/helpers.js"; -import { FaultInjectionResponseContext } from "../harness/faultInjection.js"; -import { - codecForMerchantOrderStatusUnpaid, ConfirmPayResultType, PreparePayResultType, TalerErrorCode, TalerErrorDetail, URL, + codecForMerchantOrderStatusUnpaid, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import axiosImp from "axios"; -const axios = axiosImp.default; +import { FaultInjectionResponseContext } from "../harness/faultInjection.js"; +import { GlobalTestState, MerchantPrivateApi, harnessHttpLib } from "../harness/harness.js"; +import { + createFaultInjectedMerchantTestkudosEnvironment, + withdrawViaBank, +} from "../harness/helpers.js"; + /** * Run test for a payment where the merchant has a transient @@ -75,9 +74,7 @@ export async function runPaymentTransientTest(t: GlobalTestState) { t.assertTrue(orderStatus.already_paid_order_id === undefined); let publicOrderStatusUrl = orderStatus.order_status_url; - let publicOrderStatusResp = await axios.get(publicOrderStatusUrl, { - validateStatus: () => true, - }); + let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl); if (publicOrderStatusResp.status != 402) { throw Error( @@ -86,7 +83,7 @@ export async function runPaymentTransientTest(t: GlobalTestState) { } let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode( - publicOrderStatusResp.data, + publicOrderStatusResp.json(), ); console.log(pubUnpaidStatus); @@ -102,9 +99,7 @@ export async function runPaymentTransientTest(t: GlobalTestState) { const proposalId = preparePayResp.proposalId; - publicOrderStatusResp = await axios.get(publicOrderStatusUrl, { - validateStatus: () => true, - }); + publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl); if (publicOrderStatusResp.status != 402) { throw Error( @@ -113,7 +108,7 @@ export async function runPaymentTransientTest(t: GlobalTestState) { } pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode( - publicOrderStatusResp.data, + publicOrderStatusResp.json(), ); let faultInjected = false; @@ -165,14 +160,12 @@ export async function runPaymentTransientTest(t: GlobalTestState) { // Now ask the merchant if paid console.log("requesting", publicOrderStatusUrl); - publicOrderStatusResp = await axios.get(publicOrderStatusUrl, { - validateStatus: () => true, - }); + publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl); - 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}`, ); diff --git a/packages/taler-harness/src/integrationtests/test-paywall-flow.ts b/packages/taler-harness/src/integrationtests/test-paywall-flow.ts index b0477a049..5f63d4fac 100644 --- a/packages/taler-harness/src/integrationtests/test-paywall-flow.ts +++ b/packages/taler-harness/src/integrationtests/test-paywall-flow.ts @@ -17,7 +17,7 @@ /** * Imports. */ -import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js"; +import { GlobalTestState, MerchantPrivateApi, harnessHttpLib } from "../harness/harness.js"; import { PreparePayResultType, codecForMerchantOrderStatusUnpaid, @@ -29,12 +29,6 @@ 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. @@ -47,13 +41,15 @@ export async function runPaywallFlowTest(t: GlobalTestState) { // Withdraw digital cash into the wallet. - await withdrawViaBankV2(t, { + const wres = await withdrawViaBankV2(t, { walletClient, bank, exchange, amount: "TESTKUDOS:20", }); + await wres.withdrawalFinishedCond; + /** * ========================================================================= * Create an order and let the wallet pay under a session ID @@ -86,7 +82,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 httpLib.fetch(publicOrderStatusUrl.href); + let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href); if (publicOrderStatusResp.status != 402) { throw Error( @@ -112,7 +108,7 @@ export async function runPaywallFlowTest(t: GlobalTestState) { const proposalId = preparePayResp.proposalId; console.log("requesting", publicOrderStatusUrl.href); - publicOrderStatusResp = await httpLib.fetch(publicOrderStatusUrl.href); + publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href); console.log("response body", publicOrderStatusResp.json()); if (publicOrderStatusResp.status != 402) { throw Error( @@ -129,7 +125,7 @@ export async function runPaywallFlowTest(t: GlobalTestState) { }); t.assertTrue(confirmPayRes.type === ConfirmPayResultType.Done); - publicOrderStatusResp = await httpLib.fetch(publicOrderStatusUrl.href); + publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href); console.log(publicOrderStatusResp.json()); @@ -231,7 +227,7 @@ export async function runPaywallFlowTest(t: GlobalTestState) { console.log("requesting public status", publicOrderStatusUrl); // Ask the order status of the claimed-but-unpaid order - publicOrderStatusResp = await httpLib.fetch(publicOrderStatusUrl.href); + publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href); if (publicOrderStatusResp.status != 402) { throw Error(`expected status 402, but got ${publicOrderStatusResp.status}`); diff --git a/packages/taler-harness/src/integrationtests/test-refund-auto.ts b/packages/taler-harness/src/integrationtests/test-refund-auto.ts index 607080e68..5648835d5 100644 --- a/packages/taler-harness/src/integrationtests/test-refund-auto.ts +++ b/packages/taler-harness/src/integrationtests/test-refund-auto.ts @@ -36,13 +36,15 @@ export async function runRefundAutoTest(t: GlobalTestState) { // Withdraw digital cash into the wallet. - await withdrawViaBankV2(t, { + const wres = await withdrawViaBankV2(t, { walletClient, bank, exchange, amount: "TESTKUDOS:20", }); + await wres.withdrawalFinishedCond; + // Set up order. const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", { order: { diff --git a/packages/taler-harness/src/integrationtests/test-refund-incremental.ts b/packages/taler-harness/src/integrationtests/test-refund-incremental.ts index 8e7e38b71..8ac0948f2 100644 --- a/packages/taler-harness/src/integrationtests/test-refund-incremental.ts +++ b/packages/taler-harness/src/integrationtests/test-refund-incremental.ts @@ -45,13 +45,15 @@ export async function runRefundIncrementalTest(t: GlobalTestState) { // Withdraw digital cash into the wallet. - await withdrawViaBankV2(t, { + const wres = await withdrawViaBankV2(t, { walletClient, bank, exchange, amount: "TESTKUDOS:20", }); + await wres.withdrawalFinishedCond; + // Set up order. const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", { diff --git a/packages/taler-harness/src/integrationtests/test-wallet-balance.ts b/packages/taler-harness/src/integrationtests/test-wallet-balance.ts index 0f75bd96e..15b0fd427 100644 --- a/packages/taler-harness/src/integrationtests/test-wallet-balance.ts +++ b/packages/taler-harness/src/integrationtests/test-wallet-balance.ts @@ -17,22 +17,11 @@ /** * Imports. */ -import { Amounts, Duration, PreparePayResultType } from "@gnu-taler/taler-util"; +import { Amounts, PreparePayResultType } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; -import { - ExchangeService, - FakebankService, - getRandomIban, - GlobalTestState, - MerchantPrivateApi, - MerchantService, - setupDb, - WalletCli, -} from "../harness/harness.js"; +import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js"; import { createSimpleTestkudosEnvironmentV2, - withdrawViaBank, withdrawViaBankV2, } from "../harness/helpers.js"; @@ -50,13 +39,15 @@ export async function runWalletBalanceTest(t: GlobalTestState) { // Withdraw digital cash into the wallet. - await withdrawViaBankV2(t, { + const wres = await withdrawViaBankV2(t, { walletClient, bank, exchange, amount: "TESTKUDOS:20", }); + await wres.withdrawalFinishedCond; + const order = { summary: "Buy me!", amount: "TESTKUDOS:5", diff --git a/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts b/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts index 58f564f34..153ae93d8 100644 --- a/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts +++ b/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts @@ -23,7 +23,6 @@ import { j2s, TalerError, } from "@gnu-taler/taler-util"; -import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; import { checkReserve, CryptoDispatcher, @@ -36,7 +35,7 @@ import { Wallet, withdrawCoin, } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState } from "../harness/harness.js"; +import { GlobalTestState, harnessHttpLib } from "../harness/harness.js"; import { createSimpleTestkudosEnvironmentV2 } from "../harness/helpers.js"; /** @@ -47,10 +46,7 @@ export async function runWalletDblessTest(t: GlobalTestState) { const { bank, exchange } = await createSimpleTestkudosEnvironmentV2(t); - const http = createPlatformHttpLib({ - allowHttp: true, - enableThrottling: false, - }); + const http = harnessHttpLib; const cryptiDisp = new CryptoDispatcher( new SynchronousCryptoWorkerFactoryPlain(), ); -- cgit v1.2.3 From 55bdc161b58ddf4f24e32dff9acd8011a4364327 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 29 Aug 2023 09:45:45 +0200 Subject: taler-harness: add exchange-purse test --- .../src/integrationtests/test-exchange-purse.ts | 224 +++++++++++++++++++++ .../src/integrationtests/testrunner.ts | 9 +- packages/taler-util/src/payto.ts | 22 ++ packages/taler-util/src/taler-crypto.ts | 5 +- packages/taler-wallet-core/src/index.ts | 8 +- .../src/operations/pay-peer-common.ts | 22 -- .../src/operations/pay-peer-pull-credit.ts | 2 +- .../src/operations/pay-peer-push-credit.ts | 2 +- 8 files changed, 261 insertions(+), 33 deletions(-) create mode 100644 packages/taler-harness/src/integrationtests/test-exchange-purse.ts (limited to 'packages/taler-harness/src') diff --git a/packages/taler-harness/src/integrationtests/test-exchange-purse.ts b/packages/taler-harness/src/integrationtests/test-exchange-purse.ts new file mode 100644 index 000000000..5a1d02692 --- /dev/null +++ b/packages/taler-harness/src/integrationtests/test-exchange-purse.ts @@ -0,0 +1,224 @@ +/* + This file is part of GNU Taler + (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 + 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 + */ + +/** + * Imports. + */ +import { + AbsoluteTime, + ContractTermsUtil, + decodeCrock, + Duration, + encodeCrock, + getRandomBytes, + hash, + j2s, + PeerContractTerms, + TalerError, + TalerPreciseTimestamp, +} from "@gnu-taler/taler-util"; +import { + checkReserve, + CryptoDispatcher, + downloadExchangeInfo, + EncryptContractRequest, + findDenomOrThrow, + SpendCoinDetails, + SynchronousCryptoWorkerFactoryPlain, + topupReserveWithDemobank, + Wallet, + withdrawCoin, +} from "@gnu-taler/taler-wallet-core"; +import { GlobalTestState, harnessHttpLib } from "../harness/harness.js"; +import { createSimpleTestkudosEnvironmentV2 } from "../harness/helpers.js"; + +/** + * Test the exchange's purse API. + */ +export async function runExchangePurseTest(t: GlobalTestState) { + // Set up test environment + + const { bank, exchange } = await createSimpleTestkudosEnvironmentV2(t); + + const http = harnessHttpLib; + const cryptoDisp = new CryptoDispatcher( + new SynchronousCryptoWorkerFactoryPlain(), + ); + const cryptoApi = cryptoDisp.cryptoApi; + + try { + // Withdraw digital cash into the wallet. + + const exchangeInfo = await downloadExchangeInfo(exchange.baseUrl, http); + + const reserveKeyPair = await cryptoApi.createEddsaKeypair({}); + + let reserveUrl = new URL( + `reserves/${reserveKeyPair.pub}`, + exchange.baseUrl, + ); + reserveUrl.searchParams.set("timeout_ms", "30000"); + const longpollReq = http.fetch(reserveUrl.href, { + method: "GET", + }); + + await topupReserveWithDemobank({ + amount: "TESTKUDOS:10", + http, + reservePub: reserveKeyPair.pub, + bankAccessApiBaseUrl: bank.bankAccessApiBaseUrl, + exchangeInfo, + }); + + console.log("waiting for longpoll request"); + const resp = await longpollReq; + console.log(`got response, status ${resp.status}`); + + console.log(exchangeInfo); + + await checkReserve(http, exchange.baseUrl, reserveKeyPair.pub); + + const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8", { + denomselAllowLate: Wallet.defaultConfig.testing.denomselAllowLate, + }); + + const coin = await withdrawCoin({ + http, + cryptoApi, + reserveKeyPair: { + reservePriv: reserveKeyPair.priv, + reservePub: reserveKeyPair.pub, + }, + denom: d1, + exchangeBaseUrl: exchange.baseUrl, + }); + + const amount = "TESTKUDOS:5"; + const purseFee = "TESTKUDOS:0"; + + const mergeTimestamp = TalerPreciseTimestamp.now(); + + const contractTerms: PeerContractTerms = { + amount, + summary: "Hello", + purse_expiration: AbsoluteTime.toProtocolTimestamp( + AbsoluteTime.addDuration( + AbsoluteTime.now(), + Duration.fromSpec({ minutes: 1 }), + ), + ), + }; + + const mergeReservePair = await cryptoApi.createEddsaKeypair({}); + const pursePair = await cryptoApi.createEddsaKeypair({}); + const mergePair = await cryptoApi.createEddsaKeypair({}); + const contractPair = await cryptoApi.createEddsaKeypair({}); + const contractEncNonce = encodeCrock(getRandomBytes(24)); + + const pursePub = pursePair.pub; + + const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms); + + const purseSigResp = await cryptoApi.signPurseCreation({ + hContractTerms, + mergePub: mergePair.pub, + minAge: 0, + purseAmount: amount, + purseExpiration: contractTerms.purse_expiration, + pursePriv: pursePair.priv, + }); + + const coinSpend: SpendCoinDetails = { + ageCommitmentProof: undefined, + coinPriv: coin.coinPriv, + coinPub: coin.coinPub, + contribution: amount, + denomPubHash: coin.denomPubHash, + denomSig: coin.denomSig, + }; + + const depositSigsResp = await cryptoApi.signPurseDeposits({ + exchangeBaseUrl: exchange.baseUrl, + pursePub: pursePair.pub, + coins: [coinSpend], + }); + + const encryptContractRequest: EncryptContractRequest = { + contractTerms: contractTerms, + mergePriv: mergePair.priv, + pursePriv: pursePair.priv, + pursePub: pursePair.pub, + contractPriv: contractPair.priv, + contractPub: contractPair.pub, + nonce: contractEncNonce, + }; + + const econtractResp = await cryptoApi.encryptContractForMerge( + encryptContractRequest, + ); + + const econtractHash = encodeCrock( + hash(decodeCrock(econtractResp.econtract.econtract)), + ); + + const createPurseUrl = new URL( + `purses/${pursePair.pub}/create`, + exchange.baseUrl, + ); + + const reqBody = { + amount: amount, + merge_pub: mergePair.pub, + purse_sig: purseSigResp.sig, + h_contract_terms: hContractTerms, + purse_expiration: contractTerms.purse_expiration, + deposits: depositSigsResp.deposits, + min_age: 0, + econtract: econtractResp.econtract, + }; + + const httpResp = await http.fetch(createPurseUrl.href, { + method: "POST", + body: reqBody, + }); + + const respBody = await httpResp.json(); + + console.log("status", httpResp.status); + + console.log(j2s(respBody)); + + const mergeUrl = new URL(`purses/${pursePub}/merge`, exchange.baseUrl); + mergeUrl.searchParams.set("timeout_ms", "300"); + const statusResp = await http.fetch(mergeUrl.href, {}); + + const statusRespBody = await statusResp.json(); + + console.log(j2s(statusRespBody)); + + t.assertTrue(statusRespBody.merge_timestamp === undefined); + } catch (e) { + if (e instanceof TalerError) { + console.log(e); + console.log(j2s(e.errorDetail)); + } else { + console.log(e); + } + throw e; + } +} + +runExchangePurseTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts index cbdca04b9..226fd6b09 100644 --- a/packages/taler-harness/src/integrationtests/testrunner.ts +++ b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -14,7 +14,12 @@ GNU Taler; see the file COPYING. If not, see */ -import { CancellationToken, Logger, minimatch, setGlobalLogLevelFromString } 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"; @@ -105,6 +110,7 @@ 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"; +import { runExchangePurseTest } from "./test-exchange-purse.js"; /** * Test runner. @@ -137,6 +143,7 @@ const allTests: TestMainFunction[] = [ runFeeRegressionTest, runForcedSelectionTest, runKycTest, + runExchangePurseTest, runExchangeDepositTest, runLibeufinAnastasisFacadeTest, runLibeufinApiBankaccountTest, diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts index dd35b44be..2b0af4cc2 100644 --- a/packages/taler-util/src/payto.ts +++ b/packages/taler-util/src/payto.ts @@ -239,3 +239,25 @@ export function parsePaytoUri(s: string): PaytoUri | undefined { isKnown: false, }; } + +export function talerPaytoFromExchangeReserve( + exchangeBaseUrl: string, + reservePub: string, +): string { + const url = new URL(exchangeBaseUrl); + let proto: string; + if (url.protocol === "http:") { + proto = "taler-reserve-http"; + } else if (url.protocol === "https:") { + proto = "taler-reserve"; + } else { + throw Error(`unsupported exchange base URL protocol (${url.protocol})`); + } + + let path = url.pathname; + if (!path.endsWith("/")) { + path = path + "/"; + } + + return `payto://${proto}/${url.host}${url.pathname}${reservePub}`; +} diff --git a/packages/taler-util/src/taler-crypto.ts b/packages/taler-util/src/taler-crypto.ts index d52edc1e5..9425a9320 100644 --- a/packages/taler-util/src/taler-crypto.ts +++ b/packages/taler-util/src/taler-crypto.ts @@ -1004,7 +1004,7 @@ export enum TalerSignaturePurpose { SYNC_BACKUP_UPLOAD = 1450, } -export const enum WalletAccountMergeFlags { +export enum WalletAccountMergeFlags { /** * Not a legal mode! */ @@ -1281,7 +1281,8 @@ export namespace AgeRestriction { } const PublishedAgeRestrictionBaseKey: Edx25519PublicKey = decodeCrock( - "CH0VKFDZ2GWRWHQBBGEK9MWV5YDQVJ0RXEE0KYT3NMB69F0R96TG"); + "CH0VKFDZ2GWRWHQBBGEK9MWV5YDQVJ0RXEE0KYT3NMB69F0R96TG", + ); export async function restrictionCommitSeeded( ageMask: number, diff --git a/packages/taler-wallet-core/src/index.ts b/packages/taler-wallet-core/src/index.ts index 8dd06fe2b..d64f7d5e6 100644 --- a/packages/taler-wallet-core/src/index.ts +++ b/packages/taler-wallet-core/src/index.ts @@ -51,12 +51,8 @@ export * from "./operations/refresh.js"; export * from "./dbless.js"; -export { - nativeCryptoR, - nativeCrypto, - nullCrypto, - TalerCryptoInterface, -} from "./crypto/cryptoImplementation.js"; +export * from "./crypto/cryptoTypes.js"; +export * from "./crypto/cryptoImplementation.js"; export * from "./util/timer.js"; export * from "./util/denominations.js"; diff --git a/packages/taler-wallet-core/src/operations/pay-peer-common.ts b/packages/taler-wallet-core/src/operations/pay-peer-common.ts index 1bc2e8d49..4fdfecb4d 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-common.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-common.ts @@ -420,28 +420,6 @@ export const codecForExchangePurseStatus = (): Codec => .property("merge_timestamp", codecOptional(codecForTimestamp)) .build("ExchangePurseStatus"); -export function talerPaytoFromExchangeReserve( - exchangeBaseUrl: string, - reservePub: string, -): string { - const url = new URL(exchangeBaseUrl); - let proto: string; - if (url.protocol === "http:") { - proto = "taler-reserve-http"; - } else if (url.protocol === "https:") { - proto = "taler-reserve"; - } else { - throw Error(`unsupported exchange base URL protocol (${url.protocol})`); - } - - let path = url.pathname; - if (!path.endsWith("/")) { - path = path + "/"; - } - - return `payto://${proto}/${url.host}${url.pathname}${reservePub}`; -} - export async function getMergeReserveInfo( ws: InternalWalletState, req: { diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts index 88b441cdd..954300264 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts @@ -45,6 +45,7 @@ import { j2s, makeErrorDetail, stringifyTalerUri, + talerPaytoFromExchangeReserve, } from "@gnu-taler/taler-util"; import { readSuccessResponseJsonOrErrorCode, @@ -74,7 +75,6 @@ import { import { codecForExchangePurseStatus, getMergeReserveInfo, - talerPaytoFromExchangeReserve, } from "./pay-peer-common.js"; import { constructTransactionIdentifier, diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts index e76b934fa..c552d63f0 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts @@ -47,6 +47,7 @@ import { j2s, makeErrorDetail, parsePayPushUri, + talerPaytoFromExchangeReserve, } from "@gnu-taler/taler-util"; import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; import { @@ -71,7 +72,6 @@ import { updateExchangeFromUrl } from "./exchanges.js"; import { codecForExchangePurseStatus, getMergeReserveInfo, - talerPaytoFromExchangeReserve, } from "./pay-peer-common.js"; import { TransitionInfo, -- cgit v1.2.3 From 9402aeef5b111c3a9bf51a7b204044ba19de8607 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 29 Aug 2023 10:16:18 +0200 Subject: taler-harness: clean up shared test env on start --- packages/taler-harness/src/harness/helpers.ts | 13 +++++---- .../src/integrationtests/testrunner.ts | 33 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) (limited to 'packages/taler-harness/src') diff --git a/packages/taler-harness/src/harness/helpers.ts b/packages/taler-harness/src/harness/helpers.ts index d41ffdd00..d1d0ea104 100644 --- a/packages/taler-harness/src/harness/helpers.ts +++ b/packages/taler-harness/src/harness/helpers.ts @@ -215,10 +215,14 @@ export async function createSimpleTestkudosEnvironment( }; } +export function getSharedTestDir(): string { + return `/tmp/taler-harness@${process.env.USER}`; +} + export async function useSharedTestkudosEnvironment(t: GlobalTestState) { const coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")); - const sharedDir = `/tmp/taler-harness@${process.env.USER}`; + const sharedDir = getSharedTestDir(); fs.mkdirSync(sharedDir, { recursive: true }); @@ -230,10 +234,9 @@ export async function useSharedTestkudosEnvironment(t: GlobalTestState) { 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" }) + const walletStartProm = createWalletDaemonWithClient(t, { name: "wallet" }); if (fs.existsSync(sharedDir + "/bank.conf")) { logger.info("reusing existing bank"); @@ -361,7 +364,7 @@ export async function useSharedTestkudosEnvironment(t: GlobalTestState) { } }; - await bankStart() + await bankStart(); const res = await Promise.all([ exchangeStart(), @@ -803,7 +806,7 @@ export async function applyTimeTravel( /** * Make a simple payment and check that it succeeded. - * + * * @deprecated */ export async function makeTestPayment( diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts index 226fd6b09..501af98a4 100644 --- a/packages/taler-harness/src/integrationtests/testrunner.ts +++ b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -27,10 +27,12 @@ import * as path from "path"; import url from "url"; import { GlobalTestState, + runCommand, runTestWithState, shouldLingerInTest, TestRunResult, } from "../harness/harness.js"; +import { spawnSync } from "child_process"; import { runAgeRestrictionsMerchantTest } from "./test-age-restrictions-merchant.js"; import { runBankApiTest } from "./test-bank-api.js"; import { runClaimLoopTest } from "./test-claim-loop.js"; @@ -111,6 +113,7 @@ import { runPaymentShareTest } from "./test-payment-share.js"; import { runSimplePaymentTest } from "./test-simple-payment.js"; import { runTermOfServiceFormatTest } from "./test-tos-format.js"; import { runExchangePurseTest } from "./test-exchange-purse.js"; +import { getSharedTestDir } from "../harness/helpers.js"; /** * Test runner. @@ -264,7 +267,37 @@ interface RunTestChildInstruction { testRootDir: string; } +function purgeSharedTestEnvironment() { + const rmRes = spawnSync("rm", ["-rf", `${getSharedTestDir()}`]); + if (rmRes.status != 0) { + logger.warn("can't delete shared test directory"); + } + const psqlRes = spawnSync("psql", ["-Aqtl"], { + encoding: "utf-8", + }); + if (psqlRes.status != 0) { + logger.warn("could not list available postgres databases"); + return; + } + if (psqlRes.output[1]!!.indexOf("taler-integrationtest-shared") >= 0) { + const dropRes = spawnSync("dropdb", ["taler-integrationtest-shared"], { + encoding: "utf-8", + }); + if (dropRes.status != 0) { + logger.warn("could not drop taler-integrationtest-shared database"); + return; + } + } +} + export async function runTests(spec: TestRunSpec) { + if (!process.env.TALER_HARNESS_KEEP) { + logger.info("purging shared test environment"); + purgeSharedTestEnvironment(); + } else { + logger.info("keeping shared test environment"); + } + const testRootDir = fs.mkdtempSync( path.join(os.tmpdir(), "taler-integrationtests-"), ); -- cgit v1.2.3 From ebb1c58e7a2be01e42f35dfe6d890a08cf992c79 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 29 Aug 2023 13:44:50 +0200 Subject: wallet-core: remove usage of /wire --- .../integrationtests/test-exchange-timetravel.ts | 86 ++++++++--- .../test-merchant-spec-public-orders.ts | 74 ++++----- packages/taler-harness/src/lint.ts | 6 +- packages/taler-util/src/taler-types.ts | 135 +++++++++++++--- packages/taler-wallet-core/src/bank-api-client.ts | 2 +- packages/taler-wallet-core/src/db.ts | 1 + packages/taler-wallet-core/src/dbless.ts | 9 +- packages/taler-wallet-core/src/dev-experiments.ts | 2 +- .../src/operations/backup/index.ts | 2 +- .../taler-wallet-core/src/operations/exchanges.ts | 170 +++++++++------------ .../taler-wallet-core/src/operations/merchants.ts | 2 +- .../src/operations/pay-peer-pull-debit.ts | 2 +- .../src/operations/pay-peer-push-credit.ts | 2 +- .../taler-wallet-core/src/operations/recoup.ts | 2 +- .../taler-wallet-core/src/operations/reward.ts | 2 +- .../taler-wallet-core/src/operations/testing.ts | 2 +- .../taler-wallet-core/src/operations/withdraw.ts | 6 +- 17 files changed, 317 insertions(+), 188 deletions(-) (limited to 'packages/taler-harness/src') diff --git a/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts b/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts index dee00d1ff..d8f8767e6 100644 --- a/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts +++ b/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts @@ -19,10 +19,16 @@ */ import { AbsoluteTime, + Amounts, codecForExchangeKeysJson, DenominationPubKey, + DenomKeyType, Duration, durationFromSpec, + encodeCrock, + ExchangeKeysJson, + hashDenomPub, + Logger, } from "@gnu-taler/taler-util"; import { createPlatformHttpLib, @@ -40,6 +46,52 @@ import { } from "../harness/harness.js"; import { withdrawViaBank } from "../harness/helpers.js"; +const logger = new Logger("test-exchange-timetravel.ts"); + +interface DenomInfo { + denomPub: DenominationPubKey; + expireDeposit: string; +} + +function getDenomInfoFromKeys(ek: ExchangeKeysJson): DenomInfo[] { + const denomInfos: DenomInfo[] = []; + for (const denomGroup of ek.denominations) { + switch (denomGroup.cipher) { + case "RSA": + case "RSA+age_restricted": { + let ageMask = 0; + if (denomGroup.cipher === "RSA+age_restricted") { + ageMask = denomGroup.age_mask; + } + for (const denomIn of denomGroup.denoms) { + const denomPub: DenominationPubKey = { + age_mask: ageMask, + cipher: DenomKeyType.Rsa, + rsa_public_key: denomIn.rsa_pub, + }; + denomInfos.push({ + denomPub, + expireDeposit: AbsoluteTime.stringify( + AbsoluteTime.fromProtocolTimestamp(denomIn.stamp_expire_deposit), + ), + }); + } + break; + } + case "CS+age_restricted": + case "CS": + logger.warn("Clause-Schnorr denominations not supported"); + continue; + default: + logger.warn( + `denomination type ${(denomGroup as any).cipher} not supported`, + ); + continue; + } + } + return denomInfos; +} + async function applyTimeTravel( timetravelDuration: Duration, s: { @@ -144,7 +196,7 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) { await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:15" }); - const keysResp1 = await http.get(exchange.baseUrl + "keys"); + const keysResp1 = await http.fetch(exchange.baseUrl + "keys"); const keys1 = await readSuccessResponseJsonOrThrow( keysResp1, codecForExchangeKeysJson(), @@ -163,7 +215,7 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) { merchant, }); - const keysResp2 = await http.get(exchange.baseUrl + "keys"); + const keysResp2 = await http.fetch(exchange.baseUrl + "keys"); const keys2 = await readSuccessResponseJsonOrThrow( keysResp2, codecForExchangeKeysJson(), @@ -173,41 +225,31 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) { JSON.stringify(keys2, undefined, 2), ); - const denomPubs1 = keys1.denoms.map((x) => { - return { - denomPub: x.denom_pub, - expireDeposit: AbsoluteTime.stringify( - AbsoluteTime.fromProtocolTimestamp(x.stamp_expire_deposit), - ), - }; - }); + const denomPubs1 = getDenomInfoFromKeys(keys1); + const denomPubs2 = getDenomInfoFromKeys(keys2); - const denomPubs2 = keys2.denoms.map((x) => { - return { - denomPub: x.denom_pub, - expireDeposit: AbsoluteTime.stringify( - AbsoluteTime.fromProtocolTimestamp(x.stamp_expire_deposit), - ), - }; - }); const dps2 = new Set(denomPubs2.map((x) => x.denomPub)); console.log("=== KEYS RESPONSE 1 ==="); console.log( "list issue date", - AbsoluteTime.stringify(AbsoluteTime.fromProtocolTimestamp(keys1.list_issue_date)), + AbsoluteTime.stringify( + AbsoluteTime.fromProtocolTimestamp(keys1.list_issue_date), + ), ); - console.log("num denoms", keys1.denoms.length); + console.log("num denoms", denomPubs1.length); console.log("denoms", JSON.stringify(denomPubs1, undefined, 2)); console.log("=== KEYS RESPONSE 2 ==="); console.log( "list issue date", - AbsoluteTime.stringify(AbsoluteTime.fromProtocolTimestamp(keys2.list_issue_date)), + AbsoluteTime.stringify( + AbsoluteTime.fromProtocolTimestamp(keys2.list_issue_date), + ), ); - console.log("num denoms", keys2.denoms.length); + console.log("num denoms", denomPubs2.length); console.log("denoms", JSON.stringify(denomPubs2, undefined, 2)); for (const da of denomPubs1) { diff --git a/packages/taler-harness/src/integrationtests/test-merchant-spec-public-orders.ts b/packages/taler-harness/src/integrationtests/test-merchant-spec-public-orders.ts index fca368dad..e959e813b 100644 --- a/packages/taler-harness/src/integrationtests/test-merchant-spec-public-orders.ts +++ b/packages/taler-harness/src/integrationtests/test-merchant-spec-public-orders.ts @@ -72,7 +72,7 @@ async function testWithClaimToken( let talerPayUri: string; { - const httpResp = await httpLib.get( + const httpResp = await httpLib.fetch( new URL(`orders/${orderId}`, merchantBaseUrl).href, ); const r = await httpResp.json(); @@ -83,7 +83,7 @@ async function testWithClaimToken( { const url = new URL(`orders/${orderId}`, merchantBaseUrl); url.searchParams.set("token", claimToken); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); t.assertDeepEqual(httpResp.status, 402); console.log(r); @@ -94,7 +94,7 @@ async function testWithClaimToken( { const url = new URL(`orders/${orderId}`, merchantBaseUrl); url.searchParams.set("token", claimToken); - const httpResp = await httpLib.get(url.href, { + const httpResp = await httpLib.fetch(url.href, { headers: { Accept: "text/html", }, @@ -120,7 +120,7 @@ async function testWithClaimToken( const url = new URL(`orders/${orderId}`, merchantBaseUrl); const hcWrong = encodeCrock(getRandomBytes(64)); url.searchParams.set("h_contract", hcWrong); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 403); @@ -131,7 +131,7 @@ async function testWithClaimToken( const url = new URL(`orders/${orderId}`, merchantBaseUrl); const ctWrong = encodeCrock(getRandomBytes(16)); url.searchParams.set("token", ctWrong); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 403); @@ -141,7 +141,7 @@ async function testWithClaimToken( { const url = new URL(`orders/${orderId}`, merchantBaseUrl); url.searchParams.set("token", claimToken); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 402); @@ -151,7 +151,7 @@ async function testWithClaimToken( { const url = new URL(`orders/${orderId}`, merchantBaseUrl); url.searchParams.set("h_contract", contractTermsHash); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 402); @@ -160,7 +160,7 @@ async function testWithClaimToken( // claimed, unpaid, access without credentials { const url = new URL(`orders/${orderId}`, merchantBaseUrl); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 202); @@ -178,7 +178,7 @@ async function testWithClaimToken( // paid, access without credentials { const url = new URL(`orders/${orderId}`, merchantBaseUrl); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 202); @@ -189,7 +189,7 @@ async function testWithClaimToken( const url = new URL(`orders/${orderId}`, merchantBaseUrl); const hcWrong = encodeCrock(getRandomBytes(64)); url.searchParams.set("h_contract", hcWrong); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 403); @@ -200,7 +200,7 @@ async function testWithClaimToken( const url = new URL(`orders/${orderId}`, merchantBaseUrl); const ctWrong = encodeCrock(getRandomBytes(16)); url.searchParams.set("token", ctWrong); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 403); @@ -210,7 +210,7 @@ async function testWithClaimToken( { const url = new URL(`orders/${orderId}`, merchantBaseUrl); url.searchParams.set("h_contract", contractTermsHash); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 200); @@ -220,7 +220,7 @@ async function testWithClaimToken( { const url = new URL(`orders/${orderId}`, merchantBaseUrl); url.searchParams.set("token", claimToken); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 200); @@ -232,7 +232,7 @@ async function testWithClaimToken( { const url = new URL(`orders/${orderId}`, merchantBaseUrl); url.searchParams.set("token", claimToken); - const httpResp = await httpLib.get(url.href, { + const httpResp = await httpLib.fetch(url.href, { headers: { Accept: "text/html" }, }); t.assertDeepEqual(httpResp.status, 200); @@ -269,7 +269,7 @@ async function testWithClaimToken( { const url = new URL(`orders/${apOrderId}`, merchantBaseUrl); url.searchParams.set("token", apToken); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 402); @@ -280,7 +280,7 @@ async function testWithClaimToken( const url = new URL(`orders/${apOrderId}`, merchantBaseUrl); url.searchParams.set("token", apToken); url.searchParams.set("session_id", sessionId); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 402); @@ -293,7 +293,7 @@ async function testWithClaimToken( const url = new URL(`orders/${apOrderId}`, merchantBaseUrl); url.searchParams.set("token", apToken); url.searchParams.set("session_id", sessionId); - const httpResp = await httpLib.get(url.href, { + const httpResp = await httpLib.fetch(url.href, { headers: { Accept: "text/html" }, }); t.assertDeepEqual(httpResp.status, 302); @@ -326,7 +326,7 @@ async function testWithoutClaimToken( let talerPayUri: string; { - const httpResp = await httpLib.get( + const httpResp = await httpLib.fetch( new URL(`orders/${orderId}`, merchantBaseUrl).href, ); const r = await httpResp.json(); @@ -336,7 +336,7 @@ async function testWithoutClaimToken( { const url = new URL(`orders/${orderId}`, merchantBaseUrl); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); t.assertDeepEqual(httpResp.status, 402); console.log(r); @@ -346,7 +346,7 @@ async function testWithoutClaimToken( { const url = new URL(`orders/${orderId}`, merchantBaseUrl); - const httpResp = await httpLib.get(url.href, { + const httpResp = await httpLib.fetch(url.href, { headers: { Accept: "text/html", }, @@ -374,7 +374,7 @@ async function testWithoutClaimToken( const url = new URL(`orders/${orderId}`, merchantBaseUrl); const hcWrong = encodeCrock(getRandomBytes(64)); url.searchParams.set("h_contract", hcWrong); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 403); @@ -385,7 +385,7 @@ async function testWithoutClaimToken( const url = new URL(`orders/${orderId}`, merchantBaseUrl); const ctWrong = encodeCrock(getRandomBytes(16)); url.searchParams.set("token", ctWrong); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 403); @@ -394,7 +394,7 @@ async function testWithoutClaimToken( // claimed, unpaid, no claim token { const url = new URL(`orders/${orderId}`, merchantBaseUrl); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 402); @@ -404,7 +404,7 @@ async function testWithoutClaimToken( { const url = new URL(`orders/${orderId}`, merchantBaseUrl); url.searchParams.set("h_contract", contractTermsHash); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 402); @@ -413,7 +413,7 @@ async function testWithoutClaimToken( // claimed, unpaid, access without credentials { const url = new URL(`orders/${orderId}`, merchantBaseUrl); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); // No credentials, but the order doesn't require a claim token. @@ -434,7 +434,7 @@ async function testWithoutClaimToken( // paid, access without credentials { const url = new URL(`orders/${orderId}`, merchantBaseUrl); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 200); @@ -445,7 +445,7 @@ async function testWithoutClaimToken( const url = new URL(`orders/${orderId}`, merchantBaseUrl); const hcWrong = encodeCrock(getRandomBytes(64)); url.searchParams.set("h_contract", hcWrong); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 403); @@ -456,7 +456,7 @@ async function testWithoutClaimToken( const url = new URL(`orders/${orderId}`, merchantBaseUrl); const ctWrong = encodeCrock(getRandomBytes(16)); url.searchParams.set("token", ctWrong); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 403); @@ -466,7 +466,7 @@ async function testWithoutClaimToken( { const url = new URL(`orders/${orderId}`, merchantBaseUrl); url.searchParams.set("h_contract", contractTermsHash); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 200); @@ -475,7 +475,7 @@ async function testWithoutClaimToken( // paid, JSON { const url = new URL(`orders/${orderId}`, merchantBaseUrl); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 200); @@ -486,7 +486,7 @@ async function testWithoutClaimToken( // paid, HTML { const url = new URL(`orders/${orderId}`, merchantBaseUrl); - const httpResp = await httpLib.get(url.href, { + const httpResp = await httpLib.fetch(url.href, { headers: { Accept: "text/html" }, }); t.assertDeepEqual(httpResp.status, 200); @@ -523,7 +523,7 @@ async function testWithoutClaimToken( { const url = new URL(`orders/${apOrderId}`, merchantBaseUrl); url.searchParams.set("token", apToken); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 402); @@ -534,7 +534,7 @@ async function testWithoutClaimToken( const url = new URL(`orders/${apOrderId}`, merchantBaseUrl); url.searchParams.set("token", apToken); url.searchParams.set("session_id", sessionId); - const httpResp = await httpLib.get(url.href); + const httpResp = await httpLib.fetch(url.href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(httpResp.status, 402); @@ -547,7 +547,7 @@ async function testWithoutClaimToken( const url = new URL(`orders/${apOrderId}`, merchantBaseUrl); url.searchParams.set("token", apToken); url.searchParams.set("session_id", sessionId); - const httpResp = await httpLib.get(url.href, { + const httpResp = await httpLib.fetch(url.href, { headers: { Accept: "text/html" }, }); t.assertDeepEqual(httpResp.status, 302); @@ -572,14 +572,14 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { const merchantBaseUrl = merchant.makeInstanceBaseUrl(); { - const httpResp = await httpLib.get(new URL("config", merchantBaseUrl).href); + const httpResp = await httpLib.fetch(new URL("config", merchantBaseUrl).href); const r = await httpResp.json(); console.log(r); t.assertDeepEqual(r.currency, "TESTKUDOS"); } { - const httpResp = await httpLib.get( + const httpResp = await httpLib.fetch( new URL("orders/foo", merchantBaseUrl).href, ); const r = await httpResp.json(); @@ -589,7 +589,7 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) { } { - const httpResp = await httpLib.get( + const httpResp = await httpLib.fetch( new URL("orders/foo", merchantBaseUrl).href, { headers: { diff --git a/packages/taler-harness/src/lint.ts b/packages/taler-harness/src/lint.ts index f13049710..6d8e679db 100644 --- a/packages/taler-harness/src/lint.ts +++ b/packages/taler-harness/src/lint.ts @@ -407,7 +407,7 @@ export async function checkExchangeHttpd( { const mgmtUrl = new URL("management/keys", baseUrl); - const resp = await httpLib.get(mgmtUrl.href); + const resp = await httpLib.fetch(mgmtUrl.href); const futureKeys = await readSuccessResponseJsonOrThrow( resp, @@ -431,7 +431,7 @@ export async function checkExchangeHttpd( { const keysUrl = new URL("keys", baseUrl); - const resp = await Promise.race([httpLib.get(keysUrl.href), delayMs(2000)]); + const resp = await Promise.race([httpLib.fetch(keysUrl.href), delayMs(2000)]); if (!resp) { context.numErr++; @@ -467,7 +467,7 @@ export async function checkExchangeHttpd( { const keysUrl = new URL("wire", baseUrl); - const resp = await Promise.race([httpLib.get(keysUrl.href), delayMs(2000)]); + const resp = await Promise.race([httpLib.fetch(keysUrl.href), delayMs(2000)]); if (!resp) { context.numErr++; diff --git a/packages/taler-util/src/taler-types.ts b/packages/taler-util/src/taler-types.ts index 4d4a60d91..a78df7452 100644 --- a/packages/taler-util/src/taler-types.ts +++ b/packages/taler-util/src/taler-types.ts @@ -25,7 +25,7 @@ * Imports. */ -import { codecForAmountString } from "./amounts.js"; +import { Amounts, codecForAmountString } from "./amounts.js"; import { buildCodecForObject, buildCodecForUnion, @@ -719,16 +719,12 @@ export class ExchangeSignKeyJson { * Structure that the exchange gives us in /keys. */ export class ExchangeKeysJson { - /** * Canonical, public base URL of the exchange. */ base_url: string; - /** - * List of offered denominations. - */ - denoms: ExchangeDenomination[]; + currency: string; /** * The exchange's master public key. @@ -764,6 +760,111 @@ export class ExchangeKeysJson { reserve_closing_delay: TalerProtocolDuration; global_fees: GlobalFees[]; + + accounts: AccountInfo[]; + + wire_fees: { [methodName: string]: WireFeesJson[] }; + + denominations: DenomGroup[]; +} + +export type DenomGroup = + | DenomGroupRsa + | DenomGroupCs + | DenomGroupRsaAgeRestricted + | DenomGroupCsAgeRestricted; + +export interface DenomGroupCommon { + // How much are coins of this denomination worth? + value: AmountString; + + // Fee charged by the exchange for withdrawing a coin of this denomination. + fee_withdraw: AmountString; + + // Fee charged by the exchange for depositing a coin of this denomination. + fee_deposit: AmountString; + + // Fee charged by the exchange for refreshing a coin of this denomination. + fee_refresh: AmountString; + + // Fee charged by the exchange for refunding a coin of this denomination. + fee_refund: AmountString; + + // XOR of all the SHA-512 hash values of the denominations' public keys + // in this group. Note that for hashing, the binary format of the + // public keys is used, and not their base32 encoding. + hash: HashCodeString; +} + +export interface DenomCommon { + // Signature of TALER_DenominationKeyValidityPS. + master_sig: EddsaSignatureString; + + // When does the denomination key become valid? + stamp_start: TalerProtocolTimestamp; + + // When is it no longer possible to deposit coins + // of this denomination? + stamp_expire_withdraw: TalerProtocolTimestamp; + + // Timestamp indicating by when legal disputes relating to these coins must + // be settled, as the exchange will afterwards destroy its evidence relating to + // transactions involving this coin. + stamp_expire_legal: TalerProtocolTimestamp; + + stamp_expire_deposit: TalerProtocolTimestamp; + + // Set to 'true' if the exchange somehow "lost" + // the private key. The denomination was not + // necessarily revoked, but still cannot be used + // to withdraw coins at this time (theoretically, + // the private key could be recovered in the + // future; coins signed with the private key + // remain valid). + lost?: boolean; +} + +export type RsaPublicKeySring = string; +export type AgeMask = number; + +/** + * 32-byte value representing a point on Curve25519. + */ +export type Cs25519Point = string; + +export interface DenomGroupRsa extends DenomGroupCommon { + cipher: "RSA"; + + denoms: ({ + rsa_pub: RsaPublicKeySring; + } & DenomCommon)[]; +} + +export interface DenomGroupRsaAgeRestricted extends DenomGroupCommon { + cipher: "RSA+age_restricted"; + age_mask: AgeMask; + + denoms: ({ + rsa_pub: RsaPublicKeySring; + } & DenomCommon)[]; +} + +export interface DenomGroupCs extends DenomGroupCommon { + cipher: "CS"; + age_mask: AgeMask; + + denoms: ({ + cs_pub: Cs25519Point; + } & DenomCommon)[]; +} + +export interface DenomGroupCsAgeRestricted extends DenomGroupCommon { + cipher: "CS+age_restricted"; + age_mask: AgeMask; + + denoms: ({ + cs_pub: Cs25519Point; + } & DenomCommon)[]; } export interface GlobalFees { @@ -847,10 +948,10 @@ export interface AccountInfo { debit_restrictions?: any; } -export interface ExchangeWireJson { - accounts: AccountInfo[]; - fees: { [methodName: string]: WireFeesJson[] }; -} +/** + * @deprecated + */ +export interface ExchangeWireJson {} /** * Proposal returned from the contract URL. @@ -1404,10 +1505,13 @@ export const codecForGlobalFees = (): Codec => .property("master_sig", codecForString()) .build("GlobalFees"); +// FIXME: Validate properly! +export const codecForNgDenominations: Codec = codecForAny(); + export const codecForExchangeKeysJson = (): Codec => buildCodecForObject() - .property("denoms", codecForList(codecForDenomination())) .property("base_url", codecForString()) + .property("currency", codecForString()) .property("master_public_key", codecForString()) .property("auditors", codecForList(codecForAuditor())) .property("list_issue_date", codecForTimestamp) @@ -1416,6 +1520,9 @@ export const codecForExchangeKeysJson = (): Codec => .property("version", codecForString()) .property("reserve_closing_delay", codecForDuration) .property("global_fees", codecForList(codecForGlobalFees())) + .property("accounts", codecForList(codecForAccountInfo())) + .property("wire_fees", codecForMap(codecForList(codecForWireFeesJson()))) + .property("denominations", codecForList(codecForNgDenominations)) .build("ExchangeKeysJson"); export const codecForWireFeesJson = (): Codec => @@ -1436,12 +1543,6 @@ export const codecForAccountInfo = (): Codec => .property("debit_restrictions", codecForAny()) .build("AccountInfo"); -export const codecForExchangeWireJson = (): Codec => - buildCodecForObject() - .property("accounts", codecForList(codecForAccountInfo())) - .property("fees", codecForMap(codecForList(codecForWireFeesJson()))) - .build("ExchangeWireJson"); - export const codecForProposal = (): Codec => buildCodecForObject() .property("contract_terms", codecForAny()) diff --git a/packages/taler-wallet-core/src/bank-api-client.ts b/packages/taler-wallet-core/src/bank-api-client.ts index 8e351cb48..3174667f1 100644 --- a/packages/taler-wallet-core/src/bank-api-client.ts +++ b/packages/taler-wallet-core/src/bank-api-client.ts @@ -224,7 +224,7 @@ export namespace BankAccessApi { `accounts/${bankUser.username}`, bank.bankAccessApiBaseUrl, ); - const resp = await bank.http.get(url.href, { + const resp = await bank.http.fetch(url.href, { headers: { Authorization: makeBasicAuthHeader( bankUser.username, diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 1d0d3a6e5..c5f8b6448 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -352,6 +352,7 @@ export interface DenomFees { export interface DenominationRecord { currency: string; + // FIXME: Use binary encoding of amount instead? amountVal: number; amountFrac: number; diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts index 4dfdff3f7..5532345ae 100644 --- a/packages/taler-wallet-core/src/dbless.ts +++ b/packages/taler-wallet-core/src/dbless.ts @@ -137,7 +137,7 @@ export async function topupReserveWithDemobank( throw Error("no suggested exchange"); } const plainPaytoUris = - exchangeInfo.wire.accounts.map((x) => x.payto_uri) ?? []; + exchangeInfo.keys.accounts.map((x) => x.payto_uri) ?? []; if (plainPaytoUris.length <= 0) { throw new Error(); } @@ -338,7 +338,10 @@ export async function refreshCoin(req: { logger.info("requesting melt done"); - const meltHttpResp = await http.postJson(meltReqUrl.href, meltReqBody); + const meltHttpResp = await http.fetch(meltReqUrl.href, { + method: "POST", + body: meltReqBody, + }); const meltResponse = await readSuccessResponseJsonOrThrow( meltHttpResp, @@ -386,7 +389,7 @@ export async function createFakebankReserve(args: { exchangeInfo: ExchangeInfo; }): Promise { const { http, fakebankBaseUrl, amount, reservePub } = args; - const paytoUri = args.exchangeInfo.wire.accounts[0].payto_uri; + const paytoUri = args.exchangeInfo.keys.accounts[0].payto_uri; const pt = parsePaytoUri(paytoUri); if (!pt) { throw Error("failed to parse payto URI"); diff --git a/packages/taler-wallet-core/src/dev-experiments.ts b/packages/taler-wallet-core/src/dev-experiments.ts index 113e9bede..176ed09d9 100644 --- a/packages/taler-wallet-core/src/dev-experiments.ts +++ b/packages/taler-wallet-core/src/dev-experiments.ts @@ -70,7 +70,7 @@ export class DevExperimentHttpLib implements HttpRequestLibrary { opt?: HttpRequestOptions | undefined, ): Promise { logger.trace(`devexperiment httplib ${url}`); - return this.underlyingLib.get(url, opt); + return this.underlyingLib.fetch(url, opt); } postJson( diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index 236ef1e0f..e35765165 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -661,7 +661,7 @@ export async function addBackupProvider( } }); const termsUrl = new URL("config", canonUrl); - const resp = await ws.http.get(termsUrl.href); + const resp = await ws.http.fetch(termsUrl.href); const terms = await readSuccessResponseJsonOrThrow( resp, codecForSyncTermsOfServiceResponse(), diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 8bf70fa27..c6b46e360 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -19,12 +19,14 @@ */ import { AbsoluteTime, + AccountInfo, Amounts, CancellationToken, canonicalizeBaseUrl, codecForExchangeKeysJson, - codecForExchangeWireJson, + DenomGroup, DenominationPubKey, + DenomKeyType, Duration, durationFromSpec, encodeCrock, @@ -51,6 +53,7 @@ import { URL, WireFee, WireFeeMap, + WireFeesJson, WireInfo, } from "@gnu-taler/taler-util"; import { @@ -84,43 +87,6 @@ import { const logger = new Logger("exchanges.ts"); -function denominationRecordFromKeys( - exchangeBaseUrl: string, - exchangeMasterPub: string, - listIssueDate: TalerProtocolTimestamp, - denomIn: ExchangeDenomination, -): DenominationRecord { - let denomPub: DenominationPubKey; - denomPub = denomIn.denom_pub; - const denomPubHash = encodeCrock(hashDenomPub(denomPub)); - const value = Amounts.parseOrThrow(denomIn.value); - const d: DenominationRecord = { - denomPub, - denomPubHash, - exchangeBaseUrl, - exchangeMasterPub, - fees: { - feeDeposit: Amounts.stringify(denomIn.fee_deposit), - feeRefresh: Amounts.stringify(denomIn.fee_refresh), - feeRefund: Amounts.stringify(denomIn.fee_refund), - feeWithdraw: Amounts.stringify(denomIn.fee_withdraw), - }, - isOffered: true, - isRevoked: false, - masterSig: denomIn.master_sig, - stampExpireDeposit: denomIn.stamp_expire_deposit, - stampExpireLegal: denomIn.stamp_expire_legal, - stampExpireWithdraw: denomIn.stamp_expire_withdraw, - stampStart: denomIn.stamp_start, - verificationStatus: DenominationVerificationStatus.Unverified, - amountFrac: value.fraction, - amountVal: value.value, - currency: value.currency, - listIssueDate, - }; - return d; -} - export function getExchangeRequestTimeout(): Duration { return Duration.fromSpec({ seconds: 5, @@ -145,7 +111,7 @@ export async function downloadExchangeWithTermsOfService( Accept: contentType, }; - const resp = await http.get(reqUrl.href, { + const resp = await http.fetch(reqUrl.href, { headers, timeout, }); @@ -241,7 +207,7 @@ export async function acceptExchangeTermsOfService( async function validateWireInfo( ws: InternalWalletState, versionCurrent: number, - wireInfo: ExchangeWireJson, + wireInfo: ExchangeKeysDownloadResult, masterPublicKey: string, ): Promise { for (const a of wireInfo.accounts) { @@ -267,9 +233,9 @@ async function validateWireInfo( } logger.trace("account validation done"); const feesForType: WireFeeMap = {}; - for (const wireMethod of Object.keys(wireInfo.fees)) { + for (const wireMethod of Object.keys(wireInfo.wireFees)) { const feeList: WireFee[] = []; - for (const x of wireInfo.fees[wireMethod]) { + for (const x of wireInfo.wireFees[wireMethod]) { const startStamp = x.start_date; const endStamp = x.end_date; const fee: WireFee = { @@ -343,7 +309,6 @@ async function validateGlobalFees( } export interface ExchangeInfo { - wire: ExchangeWireJson; keys: ExchangeKeysDownloadResult; } @@ -351,11 +316,6 @@ export async function downloadExchangeInfo( exchangeBaseUrl: string, http: HttpRequestLibrary, ): Promise { - const wireInfo = await downloadExchangeWireInfo( - exchangeBaseUrl, - http, - Duration.getForever(), - ); const keysInfo = await downloadExchangeKeysInfo( exchangeBaseUrl, http, @@ -363,33 +323,9 @@ export async function downloadExchangeInfo( ); return { keys: keysInfo, - wire: wireInfo, }; } -/** - * Fetch wire information for an exchange. - * - * @param exchangeBaseUrl Exchange base URL, assumed to be already normalized. - */ -async function downloadExchangeWireInfo( - exchangeBaseUrl: string, - http: HttpRequestLibrary, - timeout: Duration, -): Promise { - const reqUrl = new URL("wire", exchangeBaseUrl); - - const resp = await http.get(reqUrl.href, { - timeout, - }); - const wireInfo = await readSuccessResponseJsonOrThrow( - resp, - codecForExchangeWireJson(), - ); - - return wireInfo; -} - export async function provideExchangeRecordInTx( ws: InternalWalletState, tx: GetReadWriteAccess<{ @@ -434,6 +370,8 @@ interface ExchangeKeysDownloadResult { recoup: Recoup[]; listIssueDate: TalerProtocolTimestamp; globalFees: GlobalFees[]; + accounts: AccountInfo[]; + wireFees: { [methodName: string]: WireFeesJson[] }; } /** @@ -446,7 +384,7 @@ async function downloadExchangeKeysInfo( ): Promise { const keysUrl = new URL("keys", baseUrl); - const resp = await http.get(keysUrl.href, { + const resp = await http.fetch(keysUrl.href, { timeout, }); const exchangeKeysJsonUnchecked = await readSuccessResponseJsonOrThrow( @@ -454,7 +392,7 @@ async function downloadExchangeKeysInfo( codecForExchangeKeysJson(), ); - if (exchangeKeysJsonUnchecked.denoms.length === 0) { + if (exchangeKeysJsonUnchecked.denominations.length === 0) { throw TalerError.fromDetail( TalerErrorCode.WALLET_EXCHANGE_DENOMINATIONS_INSUFFICIENT, { @@ -481,23 +419,72 @@ async function downloadExchangeKeysInfo( ); } - const currency = Amounts.parseOrThrow( - exchangeKeysJsonUnchecked.denoms[0].value, - ).currency.toUpperCase(); + const currency = exchangeKeysJsonUnchecked.currency; + + const currentDenominations: DenominationRecord[] = []; + + for (const denomGroup of exchangeKeysJsonUnchecked.denominations) { + switch (denomGroup.cipher) { + case "RSA": + case "RSA+age_restricted": { + let ageMask = 0; + if (denomGroup.cipher === "RSA+age_restricted") { + ageMask = denomGroup.age_mask; + } + for (const denomIn of denomGroup.denoms) { + const denomPub: DenominationPubKey = { + age_mask: ageMask, + cipher: DenomKeyType.Rsa, + rsa_public_key: denomIn.rsa_pub, + }; + const denomPubHash = encodeCrock(hashDenomPub(denomPub)); + const value = Amounts.parseOrThrow(denomGroup.value); + const rec: DenominationRecord = { + denomPub, + denomPubHash, + exchangeBaseUrl: baseUrl, + exchangeMasterPub: exchangeKeysJsonUnchecked.master_public_key, + isOffered: true, + isRevoked: false, + amountFrac: value.fraction, + amountVal: value.value, + currency: value.currency, + stampExpireDeposit: denomIn.stamp_expire_deposit, + stampExpireLegal: denomIn.stamp_expire_legal, + stampExpireWithdraw: denomIn.stamp_expire_withdraw, + stampStart: denomIn.stamp_start, + verificationStatus: DenominationVerificationStatus.Unverified, + masterSig: denomIn.master_sig, + listIssueDate: exchangeKeysJsonUnchecked.list_issue_date, + fees: { + feeDeposit: Amounts.stringify(denomGroup.fee_deposit), + feeRefresh: Amounts.stringify(denomGroup.fee_refresh), + feeRefund: Amounts.stringify(denomGroup.fee_refund), + feeWithdraw: Amounts.stringify(denomGroup.fee_withdraw), + }, + }; + currentDenominations.push(rec); + } + break; + } + case "CS+age_restricted": + case "CS": + logger.warn("Clause-Schnorr denominations not supported"); + continue; + default: + logger.warn( + `denomination type ${(denomGroup as any).cipher} not supported`, + ); + continue; + } + } return { masterPublicKey: exchangeKeysJsonUnchecked.master_public_key, currency, baseUrl: exchangeKeysJsonUnchecked.base_url, auditors: exchangeKeysJsonUnchecked.auditors, - currentDenominations: exchangeKeysJsonUnchecked.denoms.map((d) => - denominationRecordFromKeys( - baseUrl, - exchangeKeysJsonUnchecked.master_public_key, - exchangeKeysJsonUnchecked.list_issue_date, - d, - ), - ), + currentDenominations, protocolVersion: exchangeKeysJsonUnchecked.version, signingKeys: exchangeKeysJsonUnchecked.signkeys, reserveClosingDelay: exchangeKeysJsonUnchecked.reserve_closing_delay, @@ -509,6 +496,8 @@ async function downloadExchangeKeysInfo( recoup: exchangeKeysJsonUnchecked.recoup ?? [], listIssueDate: exchangeKeysJsonUnchecked.list_issue_date, globalFees: exchangeKeysJsonUnchecked.global_fees, + accounts: exchangeKeysJsonUnchecked.accounts, + wireFees: exchangeKeysJsonUnchecked.wire_fees, }; } @@ -654,14 +643,7 @@ export async function updateExchangeFromUrlHandler( } } - logger.trace("updating exchange /wire info"); - const wireInfoDownload = await downloadExchangeWireInfo( - exchangeBaseUrl, - ws.http, - timeout, - ); - - logger.trace("validating exchange /wire info"); + logger.trace("validating exchange wire info"); const version = LibtoolVersion.parseVersion(keysInfo.protocolVersion); if (!version) { @@ -672,7 +654,7 @@ export async function updateExchangeFromUrlHandler( const wireInfo = await validateWireInfo( ws, version.current, - wireInfoDownload, + keysInfo, keysInfo.masterPublicKey, ); diff --git a/packages/taler-wallet-core/src/operations/merchants.ts b/packages/taler-wallet-core/src/operations/merchants.ts index c47ec4a0a..a148953f0 100644 --- a/packages/taler-wallet-core/src/operations/merchants.ts +++ b/packages/taler-wallet-core/src/operations/merchants.ts @@ -41,7 +41,7 @@ export async function getMerchantInfo( } const configUrl = new URL("config", canonBaseUrl); - const resp = await ws.http.get(configUrl.href); + const resp = await ws.http.fetch(configUrl.href); const configResp = await readSuccessResponseJsonOrThrow( resp, diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts index eca3bc91b..8ba84585c 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts @@ -530,7 +530,7 @@ export async function preparePeerPullDebit( const getContractUrl = new URL(`contracts/${contractPub}`, exchangeBaseUrl); - const contractHttpResp = await ws.http.get(getContractUrl.href); + const contractHttpResp = await ws.http.fetch(getContractUrl.href); const contractResp = await readSuccessResponseJsonOrThrow( contractHttpResp, diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts index c552d63f0..47e9eaddd 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts @@ -165,7 +165,7 @@ export async function preparePeerPushCredit( const getPurseUrl = new URL(`purses/${pursePub}/deposit`, exchangeBaseUrl); - const purseHttpResp = await ws.http.get(getPurseUrl.href); + const purseHttpResp = await ws.http.fetch(getPurseUrl.href); const purseStatus = await readSuccessResponseJsonOrThrow( purseHttpResp, diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts index abeca1119..6a18e5de6 100644 --- a/packages/taler-wallet-core/src/operations/recoup.ts +++ b/packages/taler-wallet-core/src/operations/recoup.ts @@ -358,7 +358,7 @@ export async function processRecoupGroup( ); logger.info(`querying reserve status for recoup via ${reserveUrl}`); - const resp = await ws.http.get(reserveUrl.href); + const resp = await ws.http.fetch(reserveUrl.href); const result = await readSuccessResponseJsonOrThrow( resp, diff --git a/packages/taler-wallet-core/src/operations/reward.ts b/packages/taler-wallet-core/src/operations/reward.ts index 47956f15f..69c888d7a 100644 --- a/packages/taler-wallet-core/src/operations/reward.ts +++ b/packages/taler-wallet-core/src/operations/reward.ts @@ -161,7 +161,7 @@ export async function prepareTip( res.merchantBaseUrl, ); logger.trace("checking tip status from", tipStatusUrl.href); - const merchantResp = await ws.http.get(tipStatusUrl.href); + const merchantResp = await ws.http.fetch(tipStatusUrl.href); const tipPickupStatus = await readSuccessResponseJsonOrThrow( merchantResp, codecForTipPickupGetResponse(), diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts index 3090549d5..aff92622a 100644 --- a/packages/taler-wallet-core/src/operations/testing.ts +++ b/packages/taler-wallet-core/src/operations/testing.ts @@ -293,7 +293,7 @@ async function checkPayment( ): Promise { const reqUrl = new URL(`private/orders/${orderId}`, merchantBackend.baseUrl); reqUrl.searchParams.set("order_id", orderId); - const resp = await http.get(reqUrl.href, { + const resp = await http.fetch(reqUrl.href, { headers: getMerchantAuthHeader(merchantBackend), }); return readSuccessResponseJsonOrThrow(resp, codecForCheckPaymentResponse()); diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 673129928..040d191e1 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -554,7 +554,7 @@ export async function getBankWithdrawalInfo( const configReqUrl = new URL("config", uriResult.bankIntegrationApiBaseUrl); - const configResp = await http.get(configReqUrl.href); + const configResp = await http.fetch(configReqUrl.href); const config = await readSuccessResponseJsonOrThrow( configResp, codecForTalerConfigResponse(), @@ -582,7 +582,7 @@ export async function getBankWithdrawalInfo( logger.info(`bank withdrawal status URL: ${reqUrl.href}}`); - const resp = await http.get(reqUrl.href); + const resp = await http.fetch(reqUrl.href); const status = await readSuccessResponseJsonOrThrow( resp, codecForWithdrawOperationStatusResponse(), @@ -2098,7 +2098,7 @@ async function processReserveBankStatus( const bankStatusUrl = getBankStatusUrl(bankInfo.talerWithdrawUri); - const statusResp = await ws.http.get(bankStatusUrl, { + const statusResp = await ws.http.fetch(bankStatusUrl, { timeout: getReserveRequestTimeout(withdrawalGroup), }); const status = await readSuccessResponseJsonOrThrow( -- cgit v1.2.3 From f86e23255a65d252e5aff52469cc2f417b0ac11c Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 29 Aug 2023 13:54:32 +0200 Subject: -organize imports --- .../taler-harness/src/integrationtests/test-exchange-timetravel.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'packages/taler-harness/src') diff --git a/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts b/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts index d8f8767e6..5ae97c3da 100644 --- a/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts +++ b/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts @@ -19,15 +19,12 @@ */ import { AbsoluteTime, - Amounts, codecForExchangeKeysJson, DenominationPubKey, DenomKeyType, Duration, durationFromSpec, - encodeCrock, ExchangeKeysJson, - hashDenomPub, Logger, } from "@gnu-taler/taler-util"; import { @@ -38,11 +35,11 @@ import { makeNoFeeCoinConfig } from "../harness/denomStructures.js"; import { BankService, ExchangeService, + getPayto, GlobalTestState, MerchantService, setupDb, WalletCli, - getPayto, } from "../harness/harness.js"; import { withdrawViaBank } from "../harness/helpers.js"; -- cgit v1.2.3