aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-harness/src/harness/harness.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-harness/src/harness/harness.ts')
-rw-r--r--packages/taler-harness/src/harness/harness.ts438
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 {