diff options
Diffstat (limited to 'packages/taler-harness/src/harness/harness.ts')
| -rw-r--r-- | packages/taler-harness/src/harness/harness.ts | 438 |
1 files changed, 62 insertions, 376 deletions
diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts index 7db9d82bd..24e42099e 100644 --- a/packages/taler-harness/src/harness/harness.ts +++ b/packages/taler-harness/src/harness/harness.ts @@ -25,59 +25,45 @@ * Imports */ import { + AccountAddDetails, AmountJson, Amounts, - AmountString, - codecForMerchantOrderPrivateStatusResponse, - codecForMerchantPostOrderResponse, - codecForMerchantReserveCreateConfirmation, + BankAccessApiClient, Configuration, CoreApiResponse, - createEddsaKeyPair, Duration, - eddsaGetPublic, EddsaKeyPair, + Logger, + MerchantInstanceConfig, + PartialMerchantInstanceConfig, + TalerError, + WalletNotification, + createEddsaKeyPair, + eddsaGetPublic, encodeCrock, hash, j2s, - Logger, - MerchantInstancesResponse, - MerchantOrderPrivateStatusResponse, - MerchantPostOrderRequest, - MerchantPostOrderResponse, - MerchantReserveCreateConfirmation, - MerchantTemplateAddDetails, parsePaytoUri, stringToBytes, - TalerError, - TalerProtocolDuration, - RewardCreateConfirmation, - RewardCreateRequest, - TippingReserveStatus, - WalletNotification, - codecForAny, } from "@gnu-taler/taler-util"; import { + HttpRequestLibrary, createPlatformHttpLib, expectSuccessResponseOrThrow, - readSuccessResponseJsonOrThrow, } from "@gnu-taler/taler-util/http"; import { - BankApi, - BankServiceHandle, - HarnessExchangeBankAccount, - openPromise, WalletCoreApiClient, WalletCoreRequestType, WalletCoreResponseType, WalletOperations, + openPromise, } from "@gnu-taler/taler-wallet-core"; import { + RemoteWallet, + WalletNotificationWaiter, createRemoteWallet, getClientFromRemoteWallet, makeNotificationWaiter, - RemoteWallet, - WalletNotificationWaiter, } from "@gnu-taler/taler-wallet-core/remote"; import { deepStrictEqual } from "assert"; import { ChildProcess, spawn } from "child_process"; @@ -383,7 +369,11 @@ export class GlobalTestState { logger.warn(`could not start process (${command})`, err); }); proc.on("exit", (code, signal) => { - logger.warn(`process ${logName} exited ${j2s({ code, signal })}`); + if (code == 0 && signal == null) { + logger.info(`process ${logName} exited with success`); + } else { + logger.warn(`process ${logName} exited ${j2s({ code, signal })}`); + } }); const stderrLogFileName = this.testDir + `/${logName}-stderr.log`; const stderrLog = fs.createWriteStream(stderrLogFileName, { @@ -578,6 +568,13 @@ class BankServiceBase { ) {} } +export interface HarnessExchangeBankAccount { + accountName: string; + accountPassword: string; + accountPaytoUri: string; + wireGatewayApiBaseUrl: string; +} + /** * Implementation of the bank service using the "taler-fakebank-run" tool. */ @@ -587,7 +584,7 @@ export class FakebankService { proc: ProcessWrapper | undefined; - http = createPlatformHttpLib({ allowHttp: true, enableThrottling: false }); + http = createPlatformHttpLib({ enableThrottling: false }); // We store "created" accounts during setup and // register them after startup. @@ -695,13 +692,9 @@ export class FakebankService "bank", ); await this.pingUntilAvailable(); + const bankClient = new BankAccessApiClient(this.bankAccessApiBaseUrl); for (const acc of this.accounts) { - await BankApi.registerAccount( - this, - acc.accountName, - acc.accountPassword, - {}, - ); + await bankClient.registerAccount(acc.accountName, acc.accountPassword); } } @@ -714,6 +707,11 @@ export class FakebankService // Use libeufin bank instead of pybank. const useLibeufinBank = false; +export interface BankServiceHandle { + readonly bankAccessApiBaseUrl: string; + readonly http: HttpRequestLibrary; +} + export type BankService = BankServiceHandle; export const BankService = FakebankService; @@ -760,19 +758,19 @@ export class ExchangeService implements ExchangeServiceInterface { return new ExchangeService(gc, ec, cfgFilename, keyPair); } - private currentTimetravel: Duration | undefined; + private currentTimetravelOffsetMs: number | undefined; - setTimetravel(t: Duration | undefined): void { + setTimetravel(t: number | undefined): void { if (this.isRunning()) { throw Error("can't set time travel while the exchange is running"); } - this.currentTimetravel = t; + this.currentTimetravelOffsetMs = t; } private get timetravelArg(): string | undefined { - if (this.currentTimetravel && this.currentTimetravel.d_ms !== "forever") { + if (this.currentTimetravelOffsetMs != null) { // Convert to microseconds - return `--timetravel=+${this.currentTimetravel.d_ms * 1000}`; + return `--timetravel=+${this.currentTimetravelOffsetMs * 1000}`; } return undefined; } @@ -1334,282 +1332,19 @@ export interface MerchantConfig { overrideTestDir?: string; } -export interface PrivateOrderStatusQuery { - instance?: string; - orderId: string; - sessionId?: string; -} - export interface MerchantServiceInterface { makeInstanceBaseUrl(instanceName?: string): string; readonly port: number; readonly name: string; } -export interface DeleteTippingReserveArgs { - reservePub: string; - 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, - ) {} - - httpClient = createPlatformHttpLib({ allowHttp: true, enableThrottling: false }); - - async changeAuth(auth: MerchantAuthConfiguration): Promise<void> { - const url = new URL("private/auth", this.baseUrl); - const res = await this.httpClient.fetch(url.href, { - method: "POST", - body: auth, - headers: this.makeAuthHeader(), - }); - await expectSuccessResponseOrThrow(res); - } - - async deleteTippingReserve(req: DeleteTippingReserveArgs): Promise<void> { - const url = new URL(`private/reserves/${req.reservePub}`, this.baseUrl); - if (req.purge) { - url.searchParams.set("purge", "YES"); - } - const resp = await this.httpClient.fetch(url.href, { - method: "DELETE", - headers: this.makeAuthHeader(), - }); - logger.info(`delete status: ${resp.status}`); - return; - } - - async createTippingReserve( - req: CreateMerchantTippingReserveRequest, - ): Promise<MerchantReserveCreateConfirmation> { - const url = new URL("private/reserves", this.baseUrl); - const resp = await this.httpClient.fetch(url.href, { - method: "POST", - body: req, - headers: this.makeAuthHeader(), - }); - const respData = readSuccessResponseJsonOrThrow( - resp, - codecForMerchantReserveCreateConfirmation(), - ); - return respData; - } - - async getPrivateInstanceInfo(): Promise<any> { - console.log(this.makeAuthHeader()); - const url = new URL("private", this.baseUrl); - logger.info(`request url ${url.href}`); - const resp = await this.httpClient.fetch(url.href, { - method: "GET", - headers: this.makeAuthHeader(), - }); - return await resp.json(); - } - - async getPrivateTipReserves(): Promise<TippingReserveStatus> { - console.log(this.makeAuthHeader()); - const url = new URL("private/reserves", this.baseUrl); - const resp = await this.httpClient.fetch(url.href, { - method: "GET", - headers: this.makeAuthHeader(), - }); - // FIXME: Validate! - return await resp.json(); - } - - async deleteInstance(instanceId: string) { - const url = new URL(`management/instances/${instanceId}`, this.baseUrl); - const resp = await this.httpClient.fetch(url.href, { - method: "DELETE", - headers: this.makeAuthHeader(), - }); - await expectSuccessResponseOrThrow(resp); - } - - async createInstance(req: MerchantInstanceConfig): Promise<void> { - const url = new URL("management/instances", this.baseUrl); - await this.httpClient.fetch(url.href, { - method: "POST", - body: req, - headers: this.makeAuthHeader(), - }); - } - - async getInstances(): Promise<MerchantInstancesResponse> { - const url = new URL("management/instances", this.baseUrl); - const resp = await this.httpClient.fetch(url.href, { - headers: this.makeAuthHeader(), - }); - return resp.json(); - } - - async getInstanceFullDetails(instanceId: string): Promise<any> { - const url = new URL(`management/instances/${instanceId}`, this.baseUrl); - try { - const resp = await this.httpClient.fetch(url.href, { - headers: this.makeAuthHeader(), - }); - return resp.json(); - } catch (e) { - throw e; - } - } - - makeAuthHeader(): Record<string, string> { - switch (this.auth.method) { - case "external": - return {}; - case "token": - return { - Authorization: `Bearer ${this.auth.token}`, - }; - } - } -} - -/** - * FIXME: This should be deprecated in favor of MerchantApiClient - * - * @deprecated use MerchantApiClient instead - */ -export namespace MerchantPrivateApi { - export async function createOrder( - merchantService: MerchantServiceInterface, - instanceName: string, - req: MerchantPostOrderRequest, - withAuthorization: WithAuthorization = {}, - ): Promise<MerchantPostOrderResponse> { - const baseUrl = merchantService.makeInstanceBaseUrl(instanceName); - let url = new URL("private/orders", baseUrl); - const resp = await harnessHttpLib.fetch(url.href, { - method: "POST", - body: req, - headers: withAuthorization as Record<string, string>, - }); - return readSuccessResponseJsonOrThrow( - resp, - codecForMerchantPostOrderResponse(), - ); - } - - export async function createTemplate( - merchantService: MerchantServiceInterface, - instanceName: string, - req: MerchantTemplateAddDetails, - withAuthorization: WithAuthorization = {}, - ) { - const baseUrl = merchantService.makeInstanceBaseUrl(instanceName); - let url = new URL("private/templates", baseUrl); - const resp = await harnessHttpLib.fetch(url.href, { - method: "POST", - body: req, - headers: withAuthorization as Record<string, string>, - }); - if (resp.status !== 204) { - throw Error("failed to create template"); - } - } - - export async function queryPrivateOrderStatus( - merchantService: MerchantServiceInterface, - query: PrivateOrderStatusQuery, - withAuthorization: WithAuthorization = {}, - ): Promise<MerchantOrderPrivateStatusResponse> { - const reqUrl = new URL( - `private/orders/${query.orderId}`, - merchantService.makeInstanceBaseUrl(query.instance), - ); - if (query.sessionId) { - reqUrl.searchParams.set("session_id", query.sessionId); - } - const resp = await harnessHttpLib.fetch(reqUrl.href, { - headers: withAuthorization as Record<string, string>, - }); - return readSuccessResponseJsonOrThrow( - resp, - codecForMerchantOrderPrivateStatusResponse(), - ); - } - - export async function giveRefund( - merchantService: MerchantServiceInterface, - r: { - instance: string; - orderId: string; - amount: string; - justification: string; - }, - ): Promise<{ talerRefundUri: string }> { - const reqUrl = new URL( - `private/orders/${r.orderId}/refund`, - merchantService.makeInstanceBaseUrl(r.instance), - ); - const resp = await harnessHttpLib.fetch(reqUrl.href, { - method: "POST", - body: { - refund: r.amount, - reason: r.justification, - }, - }); - const respBody = await resp.json(); - return { - talerRefundUri: respBody.taler_refund_uri, - }; - } - - export async function queryTippingReserves( - merchantService: MerchantServiceInterface, - instance: string, - ): Promise<TippingReserveStatus> { - const reqUrl = new URL( - `private/reserves`, - merchantService.makeInstanceBaseUrl(instance), - ); - const resp = await harnessHttpLib.fetch(reqUrl.href); - // FIXME: validate - return resp.json(); - } - - export async function giveTip( - merchantService: MerchantServiceInterface, - instance: string, - req: RewardCreateRequest, - ): Promise<RewardCreateConfirmation> { - const reqUrl = new URL( - `private/tips`, - merchantService.makeInstanceBaseUrl(instance), - ); - const resp = await harnessHttpLib.fetch(reqUrl.href, { - method: "POST", - body: req, - }); - // FIXME: validate - return resp.json(); - } -} - -export interface CreateMerchantTippingReserveRequest { - // Amount that the merchant promises to put into the reserve - initial_balance: AmountString; - - // Exchange the merchant intends to use for tipping - exchange_url: string; - - // Desired wire method, for example "iban" or "x-taler-bank" - wire_method: string; -} - export class MerchantService implements MerchantServiceInterface { static fromExistingConfig( gc: GlobalTestState, @@ -1636,23 +1371,23 @@ export class MerchantService implements MerchantServiceInterface { private configFilename: string, ) {} - private currentTimetravel: Duration | undefined; + private currentTimetravelOffsetMs: number | undefined; private isRunning(): boolean { return !!this.proc; } - setTimetravel(t: Duration | undefined): void { + setTimetravel(t: number | undefined): void { if (this.isRunning()) { throw Error("can't set time travel while the exchange is running"); } - this.currentTimetravel = t; + this.currentTimetravelOffsetMs = t; } private get timetravelArg(): string | undefined { - if (this.currentTimetravel && this.currentTimetravel.d_ms !== "forever") { + if (this.currentTimetravelOffsetMs != null) { // Convert to microseconds - return `--timetravel=+${this.currentTimetravel.d_ms * 1000}`; + return `--timetravel=+${this.currentTimetravelOffsetMs * 1000}`; } return undefined; } @@ -1759,7 +1494,7 @@ export class MerchantService implements MerchantServiceInterface { } async addDefaultInstance(): Promise<void> { - return await this.addInstance({ + return await this.addInstanceWithWireAccount({ id: "default", name: "Default Instance", paytoUris: [getPayto("merchant-default")], @@ -1769,13 +1504,16 @@ export class MerchantService implements MerchantServiceInterface { }); } - async addInstance( + /** + * Add an instance together with a wire account. + */ + async addInstanceWithWireAccount( instanceConfig: PartialMerchantInstanceConfig, ): Promise<void> { if (!this.proc) { throw Error("merchant must be running to add instance"); } - logger.info("adding instance"); + logger.info(`adding instance '${instanceConfig.id}'`); const url = `http://localhost:${this.merchantConfig.httpPort}/management/instances`; const auth = instanceConfig.auth ?? { method: "external" }; @@ -1801,12 +1539,20 @@ export class MerchantService implements MerchantServiceInterface { instanceConfig.defaultPayDelay ?? Duration.toTalerProtocolDuration(Duration.getForever()), }; - const httpLib = createPlatformHttpLib({ - allowHttp: true, - enableThrottling: false, - }); - const resp = await httpLib.fetch(url, { method: "POST", body }); + const resp = await harnessHttpLib.fetch(url, { method: "POST", body }); await expectSuccessResponseOrThrow(resp); + + const accountCreateUrl = `http://localhost:${this.merchantConfig.httpPort}/instances/${instanceConfig.id}/private/accounts`; + for (const paytoUri of instanceConfig.paytoUris) { + const accountReq: AccountAddDetails = { + payto_uri: paytoUri, + }; + const acctResp = await harnessHttpLib.fetch(accountCreateUrl, { + method: "POST", + body: accountReq, + }); + await expectSuccessResponseOrThrow(acctResp); + } } makeInstanceBaseUrl(instanceName?: string): string { @@ -1823,66 +1569,6 @@ export class MerchantService implements MerchantServiceInterface { } } -export interface MerchantAuthConfiguration { - method: "external" | "token"; - token?: string; -} - -// FIXME: Why do we need this? Describe / fix! -export interface PartialMerchantInstanceConfig { - auth?: MerchantAuthConfiguration; - id: string; - name: string; - paytoUris: string[]; - address?: unknown; - jurisdiction?: unknown; - defaultWireTransferDelay?: TalerProtocolDuration; - defaultPayDelay?: TalerProtocolDuration; -} - -// FIXME: Move all these types into merchant-api-types.ts! - -type FacadeCredentials = NoFacadeCredentials | BasicAuthFacadeCredentials; -interface NoFacadeCredentials { - type: "none"; -} -interface BasicAuthFacadeCredentials { - type: "basic"; - - // Username to use to authenticate - username: string; - - // Password to use to authenticate - password: string; -} - -interface MerchantBankAccount { - // The payto:// URI where the wallet will send coins. - payto_uri: string; - - // Optional base URL for a facade where the - // merchant backend can see incoming wire - // transfers to reconcile its accounting - // with that of the exchange. Used by - // taler-merchant-wirewatch. - credit_facade_url?: string; - - // Credentials for accessing the credit facade. - credit_facade_credentials?: FacadeCredentials; -} - -export interface MerchantInstanceConfig { - accounts: MerchantBankAccount[]; - auth: MerchantAuthConfiguration; - id: string; - name: string; - address: unknown; - jurisdiction: unknown; - use_stefan: boolean; - default_wire_transfer_delay: TalerProtocolDuration; - default_pay_delay: TalerProtocolDuration; -} - type TestStatus = "pass" | "fail" | "skip"; export interface TestRunResult { |
