harness: add libeufin-bank integration test
This commit is contained in:
parent
bdd906c887
commit
7b93938e71
@ -25,7 +25,7 @@ import {
|
|||||||
ExchangeService,
|
ExchangeService,
|
||||||
FakebankService,
|
FakebankService,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
getPayto,
|
generateRandomPayto,
|
||||||
} from "./harness/harness.js";
|
} from "./harness/harness.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,7 +82,7 @@ export async function runEnvFull(t: GlobalTestState): Promise<void> {
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
||||||
Duration.fromSpec({ minutes: 1 }),
|
Duration.fromSpec({ minutes: 1 }),
|
||||||
),
|
),
|
||||||
@ -91,7 +91,7 @@ export async function runEnvFull(t: GlobalTestState): Promise<void> {
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "minst1",
|
id: "minst1",
|
||||||
name: "minst1",
|
name: "minst1",
|
||||||
paytoUris: [getPayto("minst1")],
|
paytoUris: [generateRandomPayto("minst1")],
|
||||||
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
||||||
Duration.fromSpec({ minutes: 1 }),
|
Duration.fromSpec({ minutes: 1 }),
|
||||||
),
|
),
|
||||||
|
@ -565,7 +565,7 @@ class BankServiceBase {
|
|||||||
protected globalTestState: GlobalTestState,
|
protected globalTestState: GlobalTestState,
|
||||||
protected bankConfig: BankConfig,
|
protected bankConfig: BankConfig,
|
||||||
protected configFile: string,
|
protected configFile: string,
|
||||||
) { }
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HarnessExchangeBankAccount {
|
export interface HarnessExchangeBankAccount {
|
||||||
@ -580,7 +580,8 @@ export interface HarnessExchangeBankAccount {
|
|||||||
*/
|
*/
|
||||||
export class FakebankService
|
export class FakebankService
|
||||||
extends BankServiceBase
|
extends BankServiceBase
|
||||||
implements BankServiceHandle {
|
implements BankServiceHandle
|
||||||
|
{
|
||||||
proc: ProcessWrapper | undefined;
|
proc: ProcessWrapper | undefined;
|
||||||
|
|
||||||
http = createPlatformHttpLib({ enableThrottling: false });
|
http = createPlatformHttpLib({ enableThrottling: false });
|
||||||
@ -664,7 +665,7 @@ export class FakebankService
|
|||||||
return {
|
return {
|
||||||
accountName: accountName,
|
accountName: accountName,
|
||||||
accountPassword: password,
|
accountPassword: password,
|
||||||
accountPaytoUri: getPayto(accountName),
|
accountPaytoUri: generateRandomPayto(accountName),
|
||||||
wireGatewayApiBaseUrl: `http://localhost:${this.bankConfig.httpPort}/accounts/${accountName}/taler-wire-gateway/`,
|
wireGatewayApiBaseUrl: `http://localhost:${this.bankConfig.httpPort}/accounts/${accountName}/taler-wire-gateway/`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -702,6 +703,140 @@ export class FakebankService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the bank service using the "taler-fakebank-run" tool.
|
||||||
|
*/
|
||||||
|
export class LibeufinBankService
|
||||||
|
extends BankServiceBase
|
||||||
|
implements BankServiceHandle
|
||||||
|
{
|
||||||
|
proc: ProcessWrapper | undefined;
|
||||||
|
|
||||||
|
http = createPlatformHttpLib({ enableThrottling: false });
|
||||||
|
|
||||||
|
// We store "created" accounts during setup and
|
||||||
|
// register them after startup.
|
||||||
|
private accounts: {
|
||||||
|
accountName: string;
|
||||||
|
accountPassword: string;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new fakebank service handle.
|
||||||
|
*
|
||||||
|
* First generates the configuration for the fakebank and
|
||||||
|
* then creates a fakebank handle, but doesn't start the fakebank
|
||||||
|
* service yet.
|
||||||
|
*/
|
||||||
|
static async create(
|
||||||
|
gc: GlobalTestState,
|
||||||
|
bc: BankConfig,
|
||||||
|
): Promise<LibeufinBankService> {
|
||||||
|
const config = new Configuration();
|
||||||
|
const testDir = bc.overrideTestDir ?? gc.testDir;
|
||||||
|
setTalerPaths(config, testDir + "/talerhome");
|
||||||
|
config.setString("libeufin-bankdb", "config", bc.database);
|
||||||
|
config.setString("libeufin-bank", "currency", bc.currency);
|
||||||
|
config.setString("libeufin-bank", "port", `${bc.httpPort}`);
|
||||||
|
config.setString("libeufin-bank", "serve", "tcp");
|
||||||
|
config.setString(
|
||||||
|
"libeufin-bank",
|
||||||
|
"DEFAULT_CUSTOMER_DEBT_LIMIT",
|
||||||
|
`${bc.currency}:500`,
|
||||||
|
);
|
||||||
|
config.setString(
|
||||||
|
"libeufin-bank",
|
||||||
|
"DEFAULT_ADMIN_DEBT_LIMIT",
|
||||||
|
`${bc.currency}:999999`,
|
||||||
|
);
|
||||||
|
config.setString(
|
||||||
|
"libeufin-bank",
|
||||||
|
"registration_bonus",
|
||||||
|
`${bc.currency}:100`,
|
||||||
|
);
|
||||||
|
config.setString("libeufin-bank", "registration_bonus_enabled", `yes`);
|
||||||
|
config.setString("libeufin-bank", "max_auth_token_duration", "1h");
|
||||||
|
const cfgFilename = testDir + "/bank.conf";
|
||||||
|
config.write(cfgFilename, { excludeDefaults: true });
|
||||||
|
|
||||||
|
return new LibeufinBankService(gc, bc, cfgFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromExistingConfig(
|
||||||
|
gc: GlobalTestState,
|
||||||
|
opts: { overridePath?: string },
|
||||||
|
): FakebankService {
|
||||||
|
const testDir = opts.overridePath ?? gc.testDir;
|
||||||
|
const cfgFilename = testDir + `/bank.conf`;
|
||||||
|
const config = Configuration.load(cfgFilename);
|
||||||
|
const bc: BankConfig = {
|
||||||
|
allowRegistrations:
|
||||||
|
config.getYesNo("libeufin-bank", "allow_registrations").orUndefined() ??
|
||||||
|
true,
|
||||||
|
currency: config.getString("libeufin-bank", "currency").required(),
|
||||||
|
database: config
|
||||||
|
.getString("libeufin-bankdb", "config")
|
||||||
|
.required(),
|
||||||
|
httpPort: config.getNumber("libeufin-bank", "port").required(),
|
||||||
|
maxDebt: config
|
||||||
|
.getString("libeufin-bank", "DEFAULT_CUSTOMER_DEBT_LIMIT")
|
||||||
|
.required(),
|
||||||
|
};
|
||||||
|
return new FakebankService(gc, bc, cfgFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSuggestedExchange(e: ExchangeServiceInterface) {
|
||||||
|
if (!!this.proc) {
|
||||||
|
throw Error("Can't set suggested exchange while bank is running.");
|
||||||
|
}
|
||||||
|
const config = Configuration.load(this.configFile);
|
||||||
|
config.setString("libeufin-bank", "suggested_withdrawal_exchange", e.baseUrl);
|
||||||
|
config.write(this.configFile, { excludeDefaults: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
get baseUrl(): string {
|
||||||
|
return `http://localhost:${this.bankConfig.httpPort}/`;
|
||||||
|
}
|
||||||
|
|
||||||
|
get bankAccessApiBaseUrl(): string {
|
||||||
|
return this.baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
get port() {
|
||||||
|
return this.bankConfig.httpPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
async start(): Promise<void> {
|
||||||
|
logger.info("starting libeufin-bank");
|
||||||
|
if (this.proc) {
|
||||||
|
logger.info("libeufin-bank already running, not starting again");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await sh(
|
||||||
|
this.globalTestState,
|
||||||
|
"libeufin-bank-dbinit",
|
||||||
|
`libeufin-bank dbinit -r -c "${this.configFile}"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.proc = this.globalTestState.spawnService(
|
||||||
|
"libeufin-bank",
|
||||||
|
["serve", "-c", this.configFile],
|
||||||
|
"libeufin-bank-httpd",
|
||||||
|
);
|
||||||
|
await this.pingUntilAvailable();
|
||||||
|
const bankClient = new TalerCorebankApiClient(this.bankAccessApiBaseUrl);
|
||||||
|
for (const acc of this.accounts) {
|
||||||
|
await bankClient.registerAccount(acc.accountName, acc.accountPassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async pingUntilAvailable(): Promise<void> {
|
||||||
|
const url = `http://localhost:${this.bankConfig.httpPort}/config`;
|
||||||
|
await pingProc(this.proc, url, "libeufin-bank");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Use libeufin bank instead of pybank.
|
// Use libeufin bank instead of pybank.
|
||||||
const useLibeufinBank = false;
|
const useLibeufinBank = false;
|
||||||
|
|
||||||
@ -1011,7 +1146,7 @@ export class ExchangeService implements ExchangeServiceInterface {
|
|||||||
private exchangeConfig: ExchangeConfig,
|
private exchangeConfig: ExchangeConfig,
|
||||||
private configFilename: string,
|
private configFilename: string,
|
||||||
private keyPair: EddsaKeyPair,
|
private keyPair: EddsaKeyPair,
|
||||||
) { }
|
) {}
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
return this.exchangeConfig.name;
|
return this.exchangeConfig.name;
|
||||||
@ -1367,7 +1502,7 @@ export class MerchantService implements MerchantServiceInterface {
|
|||||||
private globalState: GlobalTestState,
|
private globalState: GlobalTestState,
|
||||||
private merchantConfig: MerchantConfig,
|
private merchantConfig: MerchantConfig,
|
||||||
private configFilename: string,
|
private configFilename: string,
|
||||||
) { }
|
) {}
|
||||||
|
|
||||||
private currentTimetravelOffsetMs: number | undefined;
|
private currentTimetravelOffsetMs: number | undefined;
|
||||||
|
|
||||||
@ -1495,7 +1630,7 @@ export class MerchantService implements MerchantServiceInterface {
|
|||||||
return await this.addInstanceWithWireAccount({
|
return await this.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
auth: {
|
auth: {
|
||||||
method: "external",
|
method: "external",
|
||||||
},
|
},
|
||||||
@ -1658,6 +1793,7 @@ export async function runTestWithState(
|
|||||||
e.message,
|
e.message,
|
||||||
`error detail: ${j2s(e.errorDetail)}`,
|
`error detail: ${j2s(e.errorDetail)}`,
|
||||||
);
|
);
|
||||||
|
console.error(e.stack);
|
||||||
} else {
|
} else {
|
||||||
console.error("FATAL: test failed with exception", e);
|
console.error("FATAL: test failed with exception", e);
|
||||||
}
|
}
|
||||||
@ -1705,7 +1841,7 @@ export class WalletService {
|
|||||||
constructor(
|
constructor(
|
||||||
private globalState: GlobalTestState,
|
private globalState: GlobalTestState,
|
||||||
private opts: WalletServiceOptions,
|
private opts: WalletServiceOptions,
|
||||||
) { }
|
) {}
|
||||||
|
|
||||||
get socketPath() {
|
get socketPath() {
|
||||||
const unixPath = path.join(
|
const unixPath = path.join(
|
||||||
@ -1814,7 +1950,7 @@ export class WalletClient {
|
|||||||
return client.call(operation, payload);
|
return client.call(operation, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private args: WalletClientArgs) { }
|
constructor(private args: WalletClientArgs) {}
|
||||||
|
|
||||||
async connect(): Promise<void> {
|
async connect(): Promise<void> {
|
||||||
const waiter = this.waiter;
|
const waiter = this.waiter;
|
||||||
@ -1881,8 +2017,10 @@ export class WalletCli {
|
|||||||
? `--crypto-worker=${cliOpts.cryptoWorkerType}`
|
? `--crypto-worker=${cliOpts.cryptoWorkerType}`
|
||||||
: "";
|
: "";
|
||||||
const logName = `wallet-${self.name}`;
|
const logName = `wallet-${self.name}`;
|
||||||
const command = `taler-wallet-cli ${self.timetravelArg ?? ""
|
const command = `taler-wallet-cli ${
|
||||||
} ${cryptoWorkerArg} --no-throttle -LTRACE --skip-defaults --wallet-db '${self.dbfile
|
self.timetravelArg ?? ""
|
||||||
|
} ${cryptoWorkerArg} --no-throttle -LTRACE --skip-defaults --wallet-db '${
|
||||||
|
self.dbfile
|
||||||
}' api '${op}' ${shellWrap(JSON.stringify(payload))}`;
|
}' api '${op}' ${shellWrap(JSON.stringify(payload))}`;
|
||||||
const resp = await sh(self.globalTestState, logName, command);
|
const resp = await sh(self.globalTestState, logName, command);
|
||||||
logger.info("--- wallet core response ---");
|
logger.info("--- wallet core response ---");
|
||||||
@ -1966,7 +2104,7 @@ export class WalletCli {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRandomIban(salt: string | null = null): string {
|
export function generateRandomTestIban(salt: string | null = null): string {
|
||||||
function getBban(salt: string | null): string {
|
function getBban(salt: string | null): string {
|
||||||
if (!salt) return Math.random().toString().substring(2, 6);
|
if (!salt) return Math.random().toString().substring(2, 6);
|
||||||
let hashed = hash(stringToBytes(salt));
|
let hashed = hash(stringToBytes(salt));
|
||||||
@ -1998,9 +2136,9 @@ export function getWireMethodForTest(): string {
|
|||||||
* Generate a payto address, whose authority depends
|
* Generate a payto address, whose authority depends
|
||||||
* on whether the banking is served by euFin or Pybank.
|
* on whether the banking is served by euFin or Pybank.
|
||||||
*/
|
*/
|
||||||
export function getPayto(label: string): string {
|
export function generateRandomPayto(label: string): string {
|
||||||
if (useLibeufinBank)
|
if (useLibeufinBank)
|
||||||
return `payto://iban/SANDBOXX/${getRandomIban(
|
return `payto://iban/SANDBOXX/${generateRandomTestIban(
|
||||||
label,
|
label,
|
||||||
)}?receiver-name=${label}`;
|
)}?receiver-name=${label}`;
|
||||||
return `payto://x-taler-bank/localhost/${label}`;
|
return `payto://x-taler-bank/localhost/${label}`;
|
||||||
|
@ -56,7 +56,7 @@ import {
|
|||||||
WalletClient,
|
WalletClient,
|
||||||
WalletService,
|
WalletService,
|
||||||
WithAuthorization,
|
WithAuthorization,
|
||||||
getPayto,
|
generateRandomPayto,
|
||||||
setupDb,
|
setupDb,
|
||||||
setupSharedDb,
|
setupSharedDb,
|
||||||
} from "./harness.js";
|
} from "./harness.js";
|
||||||
@ -236,7 +236,7 @@ export async function useSharedTestkudosEnvironment(t: GlobalTestState) {
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
||||||
Duration.fromSpec({ minutes: 1 }),
|
Duration.fromSpec({ minutes: 1 }),
|
||||||
),
|
),
|
||||||
@ -245,7 +245,7 @@ export async function useSharedTestkudosEnvironment(t: GlobalTestState) {
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "minst1",
|
id: "minst1",
|
||||||
name: "minst1",
|
name: "minst1",
|
||||||
paytoUris: [getPayto("minst1")],
|
paytoUris: [generateRandomPayto("minst1")],
|
||||||
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
||||||
Duration.fromSpec({ minutes: 1 }),
|
Duration.fromSpec({ minutes: 1 }),
|
||||||
),
|
),
|
||||||
@ -368,7 +368,7 @@ export async function createSimpleTestkudosEnvironmentV2(
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
||||||
Duration.fromSpec({ minutes: 1 }),
|
Duration.fromSpec({ minutes: 1 }),
|
||||||
),
|
),
|
||||||
@ -377,7 +377,7 @@ export async function createSimpleTestkudosEnvironmentV2(
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "minst1",
|
id: "minst1",
|
||||||
name: "minst1",
|
name: "minst1",
|
||||||
paytoUris: [getPayto("minst1")],
|
paytoUris: [generateRandomPayto("minst1")],
|
||||||
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
||||||
Duration.fromSpec({ minutes: 1 }),
|
Duration.fromSpec({ minutes: 1 }),
|
||||||
),
|
),
|
||||||
@ -512,13 +512,13 @@ export async function createFaultInjectedMerchantTestkudosEnvironment(
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
});
|
});
|
||||||
|
|
||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "minst1",
|
id: "minst1",
|
||||||
name: "minst1",
|
name: "minst1",
|
||||||
paytoUris: [getPayto("minst1")],
|
paytoUris: [generateRandomPayto("minst1")],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("setup done!");
|
console.log("setup done!");
|
||||||
|
@ -30,7 +30,7 @@ import {
|
|||||||
ExchangeService,
|
ExchangeService,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
getPayto,
|
generateRandomPayto,
|
||||||
setupDb,
|
setupDb,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
|
|
||||||
@ -88,13 +88,13 @@ export async function runBankApiTest(t: GlobalTestState) {
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
});
|
});
|
||||||
|
|
||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "minst1",
|
id: "minst1",
|
||||||
name: "minst1",
|
name: "minst1",
|
||||||
paytoUris: [getPayto("minst1")],
|
paytoUris: [generateRandomPayto("minst1")],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("setup done!");
|
console.log("setup done!");
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
TransactionMinorState,
|
TransactionMinorState,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||||
import { GlobalTestState, getPayto } from "../harness/harness.js";
|
import { GlobalTestState, generateRandomPayto } from "../harness/harness.js";
|
||||||
import {
|
import {
|
||||||
createSimpleTestkudosEnvironmentV2,
|
createSimpleTestkudosEnvironmentV2,
|
||||||
withdrawViaBankV2,
|
withdrawViaBankV2,
|
||||||
@ -75,7 +75,7 @@ export async function runDepositTest(t: GlobalTestState) {
|
|||||||
WalletApiOperation.CreateDepositGroup,
|
WalletApiOperation.CreateDepositGroup,
|
||||||
{
|
{
|
||||||
amount: "TESTKUDOS:10",
|
amount: "TESTKUDOS:10",
|
||||||
depositPaytoUri: getPayto("foo"),
|
depositPaytoUri: generateRandomPayto("foo"),
|
||||||
transactionId: depositTxId,
|
transactionId: depositTxId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -36,7 +36,7 @@ import {
|
|||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
WalletCli,
|
WalletCli,
|
||||||
getPayto,
|
generateRandomPayto,
|
||||||
setupDb,
|
setupDb,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
|
|
||||||
@ -105,13 +105,13 @@ export async function runExchangeManagementTest(
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
});
|
});
|
||||||
|
|
||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "minst1",
|
id: "minst1",
|
||||||
name: "minst1",
|
name: "minst1",
|
||||||
paytoUris: [getPayto("minst1")],
|
paytoUris: [generateRandomPayto("minst1")],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("setup done!");
|
console.log("setup done!");
|
||||||
|
@ -35,7 +35,7 @@ import { makeNoFeeCoinConfig } from "../harness/denomStructures.js";
|
|||||||
import {
|
import {
|
||||||
BankService,
|
BankService,
|
||||||
ExchangeService,
|
ExchangeService,
|
||||||
getPayto,
|
generateRandomPayto,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
setupDb,
|
setupDb,
|
||||||
@ -151,13 +151,13 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) {
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
});
|
});
|
||||||
|
|
||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "minst1",
|
id: "minst1",
|
||||||
name: "minst1",
|
name: "minst1",
|
||||||
paytoUris: [getPayto("minst1")],
|
paytoUris: [generateRandomPayto("minst1")],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("setup done!");
|
console.log("setup done!");
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
ExchangeService,
|
ExchangeService,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
getPayto,
|
generateRandomPayto,
|
||||||
setupDb,
|
setupDb,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
import {
|
import {
|
||||||
@ -142,7 +142,7 @@ export async function createMyTestkudosEnvironment(
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "minst1",
|
id: "minst1",
|
||||||
name: "minst1",
|
name: "minst1",
|
||||||
paytoUris: [getPayto("minst1")],
|
paytoUris: [generateRandomPayto("minst1")],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("setup done!");
|
console.log("setup done!");
|
||||||
|
@ -34,7 +34,7 @@ import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
|
|||||||
import {
|
import {
|
||||||
BankService,
|
BankService,
|
||||||
ExchangeService,
|
ExchangeService,
|
||||||
getPayto,
|
generateRandomPayto,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
setupDb,
|
setupDb,
|
||||||
@ -162,7 +162,7 @@ export async function createKycTestkudosEnvironment(
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
||||||
Duration.fromSpec({ minutes: 1 }),
|
Duration.fromSpec({ minutes: 1 }),
|
||||||
),
|
),
|
||||||
@ -171,7 +171,7 @@ export async function createKycTestkudosEnvironment(
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "minst1",
|
id: "minst1",
|
||||||
name: "minst1",
|
name: "minst1",
|
||||||
paytoUris: [getPayto("minst1")],
|
paytoUris: [generateRandomPayto("minst1")],
|
||||||
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
||||||
Duration.fromSpec({ minutes: 1 }),
|
Duration.fromSpec({ minutes: 1 }),
|
||||||
),
|
),
|
||||||
|
@ -0,0 +1,222 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2020 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 <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
TalerCorebankApiClient,
|
||||||
|
CreditDebitIndicator,
|
||||||
|
WireGatewayApiClient,
|
||||||
|
createEddsaKeyPair,
|
||||||
|
encodeCrock,
|
||||||
|
Logger,
|
||||||
|
j2s,
|
||||||
|
NotificationType,
|
||||||
|
TransactionMajorState,
|
||||||
|
TransactionMinorState,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import { defaultCoinConfig } from "../harness/denomStructures.js";
|
||||||
|
import {
|
||||||
|
ExchangeService,
|
||||||
|
GlobalTestState,
|
||||||
|
LibeufinBankService,
|
||||||
|
MerchantService,
|
||||||
|
generateRandomPayto,
|
||||||
|
generateRandomTestIban,
|
||||||
|
setupDb,
|
||||||
|
} from "../harness/harness.js";
|
||||||
|
import { createWalletDaemonWithClient } from "../harness/helpers.js";
|
||||||
|
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||||
|
|
||||||
|
const logger = new Logger("test-libeufin-bank.ts");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run test for the basic functionality of libeufin-bank.
|
||||||
|
*/
|
||||||
|
export async function runLibeufinBankTest(t: GlobalTestState) {
|
||||||
|
// Set up test environment
|
||||||
|
|
||||||
|
const db = await setupDb(t);
|
||||||
|
|
||||||
|
const bank = await LibeufinBankService.create(t, {
|
||||||
|
currency: "TESTKUDOS",
|
||||||
|
httpPort: 8082,
|
||||||
|
database: db.connStr,
|
||||||
|
allowRegistrations: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const exchange = ExchangeService.create(t, {
|
||||||
|
name: "testexchange-1",
|
||||||
|
currency: "TESTKUDOS",
|
||||||
|
httpPort: 8081,
|
||||||
|
database: db.connStr,
|
||||||
|
});
|
||||||
|
|
||||||
|
const merchant = await MerchantService.create(t, {
|
||||||
|
name: "testmerchant-1",
|
||||||
|
currency: "TESTKUDOS",
|
||||||
|
httpPort: 8083,
|
||||||
|
database: db.connStr,
|
||||||
|
});
|
||||||
|
|
||||||
|
const exchangeIban = generateRandomTestIban();
|
||||||
|
const exchangeBankUsername = "exchange";
|
||||||
|
const exchangeBankPw = "mypw";
|
||||||
|
const exchangePlainPayto = `payto://iban/${exchangeIban}`;
|
||||||
|
const exchangeExtendedPayto = `payto://iban/${exchangeIban}?receiver-name=Exchange`;
|
||||||
|
const wireGatewayApiBaseUrl = new URL(
|
||||||
|
"accounts/exchange/taler-wire-gateway/",
|
||||||
|
bank.baseUrl,
|
||||||
|
).href;
|
||||||
|
|
||||||
|
logger.info("creating bank account for the exchange");
|
||||||
|
|
||||||
|
exchange.addBankAccount("1", {
|
||||||
|
wireGatewayApiBaseUrl,
|
||||||
|
accountName: exchangeBankUsername,
|
||||||
|
accountPassword: exchangeBankPw,
|
||||||
|
accountPaytoUri: exchangeExtendedPayto,
|
||||||
|
});
|
||||||
|
|
||||||
|
bank.setSuggestedExchange(exchange);
|
||||||
|
|
||||||
|
await bank.start();
|
||||||
|
|
||||||
|
await bank.pingUntilAvailable();
|
||||||
|
|
||||||
|
exchange.addOfferedCoins(defaultCoinConfig);
|
||||||
|
|
||||||
|
await exchange.start();
|
||||||
|
await exchange.pingUntilAvailable();
|
||||||
|
|
||||||
|
merchant.addExchange(exchange);
|
||||||
|
|
||||||
|
await merchant.start();
|
||||||
|
await merchant.pingUntilAvailable();
|
||||||
|
|
||||||
|
await merchant.addInstanceWithWireAccount({
|
||||||
|
id: "default",
|
||||||
|
name: "Default Instance",
|
||||||
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
|
});
|
||||||
|
|
||||||
|
await merchant.addInstanceWithWireAccount({
|
||||||
|
id: "minst1",
|
||||||
|
name: "minst1",
|
||||||
|
paytoUris: [generateRandomPayto("minst1")],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { walletClient } = await createWalletDaemonWithClient(t, {
|
||||||
|
name: "wallet",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("setup done!");
|
||||||
|
|
||||||
|
const bankClient = new TalerCorebankApiClient(bank.bankAccessApiBaseUrl);
|
||||||
|
|
||||||
|
// register exchange bank account
|
||||||
|
await bankClient.registerAccountExtended({
|
||||||
|
name: "Exchange",
|
||||||
|
password: exchangeBankPw,
|
||||||
|
username: exchangeBankUsername,
|
||||||
|
is_taler_exchange: true,
|
||||||
|
internal_payto_uri: exchangePlainPayto,
|
||||||
|
});
|
||||||
|
|
||||||
|
const bankUser = await bankClient.registerAccount("user1", "pw1");
|
||||||
|
bankClient.setAuth({
|
||||||
|
username: "user1",
|
||||||
|
password: "pw1",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make sure that registering twice results in a 409 Conflict
|
||||||
|
// {
|
||||||
|
// const e = await t.assertThrowsTalerErrorAsync(async () => {
|
||||||
|
// await bankClient.registerAccount("user1", "pw2");
|
||||||
|
// });
|
||||||
|
// t.assertTrue(e.errorDetail.httpStatusCode === 409);
|
||||||
|
// }
|
||||||
|
|
||||||
|
let balResp = await bankClient.getAccountBalance(bankUser.username);
|
||||||
|
|
||||||
|
console.log(balResp);
|
||||||
|
|
||||||
|
// Check that we got the sign-up bonus.
|
||||||
|
t.assertAmountEquals(balResp.balance.amount, "TESTKUDOS:100");
|
||||||
|
t.assertTrue(
|
||||||
|
balResp.balance.credit_debit_indicator === CreditDebitIndicator.Credit,
|
||||||
|
);
|
||||||
|
|
||||||
|
const res = createEddsaKeyPair();
|
||||||
|
|
||||||
|
const wireGatewayApiClient = new WireGatewayApiClient(wireGatewayApiBaseUrl, {
|
||||||
|
auth: {
|
||||||
|
username: exchangeBankUsername,
|
||||||
|
password: exchangeBankPw,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await wireGatewayApiClient.adminAddIncoming({
|
||||||
|
amount: "TESTKUDOS:115",
|
||||||
|
debitAccountPayto: bankUser.accountPaytoUri,
|
||||||
|
reservePub: encodeCrock(res.eddsaPub),
|
||||||
|
});
|
||||||
|
|
||||||
|
balResp = await bankClient.getAccountBalance(bankUser.username);
|
||||||
|
t.assertAmountEquals(balResp.balance.amount, "TESTKUDOS:15");
|
||||||
|
t.assertTrue(
|
||||||
|
balResp.balance.credit_debit_indicator === CreditDebitIndicator.Debit,
|
||||||
|
);
|
||||||
|
|
||||||
|
const wop = await bankClient.createWithdrawalOperation(
|
||||||
|
bankUser.username,
|
||||||
|
"TESTKUDOS:10",
|
||||||
|
);
|
||||||
|
|
||||||
|
const r1 = await walletClient.client.call(
|
||||||
|
WalletApiOperation.GetWithdrawalDetailsForUri,
|
||||||
|
{
|
||||||
|
talerWithdrawUri: wop.taler_withdraw_uri,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(j2s(r1));
|
||||||
|
|
||||||
|
const r2 = await walletClient.client.call(
|
||||||
|
WalletApiOperation.AcceptBankIntegratedWithdrawal,
|
||||||
|
{
|
||||||
|
exchangeBaseUrl: exchange.baseUrl,
|
||||||
|
talerWithdrawUri: wop.taler_withdraw_uri,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
|
||||||
|
transactionId: r2.transactionId,
|
||||||
|
txState: {
|
||||||
|
major: TransactionMajorState.Pending,
|
||||||
|
minor: TransactionMinorState.BankConfirmTransfer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await bankClient.confirmWithdrawalOperation(bankUser.username, {
|
||||||
|
withdrawalOperationId: wop.withdrawal_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
runLibeufinBankTest.suites = ["fakebank"];
|
@ -33,7 +33,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
BankService,
|
BankService,
|
||||||
ExchangeService,
|
ExchangeService,
|
||||||
getPayto,
|
generateRandomPayto,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
harnessHttpLib,
|
harnessHttpLib,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
@ -112,13 +112,13 @@ export async function createConfusedMerchantTestkudosEnvironment(
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
});
|
});
|
||||||
|
|
||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "minst1",
|
id: "minst1",
|
||||||
name: "minst1",
|
name: "minst1",
|
||||||
paytoUris: [getPayto("minst1")],
|
paytoUris: [generateRandomPayto("minst1")],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("setup done!");
|
console.log("setup done!");
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
ExchangeService,
|
ExchangeService,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
getPayto,
|
generateRandomPayto,
|
||||||
harnessHttpLib,
|
harnessHttpLib,
|
||||||
setupDb,
|
setupDb,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
@ -78,7 +78,7 @@ export async function runMerchantInstancesDeleteTest(t: GlobalTestState) {
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
auth: {
|
auth: {
|
||||||
method: "external",
|
method: "external",
|
||||||
},
|
},
|
||||||
@ -88,7 +88,7 @@ export async function runMerchantInstancesDeleteTest(t: GlobalTestState) {
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "myinst",
|
id: "myinst",
|
||||||
name: "Second Instance",
|
name: "Second Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
auth: {
|
auth: {
|
||||||
method: "external",
|
method: "external",
|
||||||
},
|
},
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
ExchangeService,
|
ExchangeService,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
getPayto,
|
generateRandomPayto,
|
||||||
harnessHttpLib,
|
harnessHttpLib,
|
||||||
setupDb,
|
setupDb,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
@ -74,7 +74,7 @@ export async function runMerchantInstancesUrlsTest(t: GlobalTestState) {
|
|||||||
name: "My Default Instance",
|
name: "My Default Instance",
|
||||||
accounts: [
|
accounts: [
|
||||||
{
|
{
|
||||||
payto_uri: getPayto("bar"),
|
payto_uri: generateRandomPayto("bar"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
auth: {
|
auth: {
|
||||||
@ -97,7 +97,7 @@ export async function runMerchantInstancesUrlsTest(t: GlobalTestState) {
|
|||||||
name: "My Second Instance",
|
name: "My Second Instance",
|
||||||
accounts: [
|
accounts: [
|
||||||
{
|
{
|
||||||
payto_uri: getPayto("bar"),
|
payto_uri: generateRandomPayto("bar"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
auth: {
|
auth: {
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
setupDb,
|
setupDb,
|
||||||
getPayto,
|
generateRandomPayto,
|
||||||
harnessHttpLib,
|
harnessHttpLib,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ export async function runMerchantInstancesTest(t: GlobalTestState) {
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
auth: {
|
auth: {
|
||||||
method: "external",
|
method: "external",
|
||||||
},
|
},
|
||||||
@ -88,7 +88,7 @@ export async function runMerchantInstancesTest(t: GlobalTestState) {
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
auth: {
|
auth: {
|
||||||
method: "external",
|
method: "external",
|
||||||
},
|
},
|
||||||
@ -98,7 +98,7 @@ export async function runMerchantInstancesTest(t: GlobalTestState) {
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "myinst",
|
id: "myinst",
|
||||||
name: "Second Instance",
|
name: "Second Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
auth: {
|
auth: {
|
||||||
method: "external",
|
method: "external",
|
||||||
},
|
},
|
||||||
|
@ -39,7 +39,7 @@ import {
|
|||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
WalletCli,
|
WalletCli,
|
||||||
getPayto,
|
generateRandomPayto,
|
||||||
setupDb,
|
setupDb,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ export async function runPaymentFaultTest(t: GlobalTestState) {
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
});
|
});
|
||||||
|
|
||||||
const merchantClient = new MerchantApiClient(merchant.makeInstanceBaseUrl());
|
const merchantClient = new MerchantApiClient(merchant.makeInstanceBaseUrl());
|
||||||
|
@ -25,7 +25,7 @@ import {
|
|||||||
ExchangeService,
|
ExchangeService,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
getPayto,
|
generateRandomPayto,
|
||||||
setupDb,
|
setupDb,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
import {
|
import {
|
||||||
@ -87,13 +87,13 @@ async function setupTest(t: GlobalTestState): Promise<{
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
});
|
});
|
||||||
|
|
||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "minst1",
|
id: "minst1",
|
||||||
name: "minst1",
|
name: "minst1",
|
||||||
paytoUris: [getPayto("minst1")],
|
paytoUris: [generateRandomPayto("minst1")],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("setup done!");
|
console.log("setup done!");
|
||||||
|
@ -27,7 +27,7 @@ import {
|
|||||||
setupDb,
|
setupDb,
|
||||||
BankService,
|
BankService,
|
||||||
delayMs,
|
delayMs,
|
||||||
getPayto,
|
generateRandomPayto,
|
||||||
WalletClient,
|
WalletClient,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
import {
|
import {
|
||||||
@ -125,13 +125,13 @@ async function createTestEnvironment(
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
});
|
});
|
||||||
|
|
||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "minst1",
|
id: "minst1",
|
||||||
name: "minst1",
|
name: "minst1",
|
||||||
paytoUris: [getPayto("minst1")],
|
paytoUris: [generateRandomPayto("minst1")],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("setup done!");
|
console.log("setup done!");
|
||||||
|
@ -32,7 +32,7 @@ import { makeNoFeeCoinConfig } from "../harness/denomStructures.js";
|
|||||||
import {
|
import {
|
||||||
BankService,
|
BankService,
|
||||||
ExchangeService,
|
ExchangeService,
|
||||||
getPayto,
|
generateRandomPayto,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
setupDb,
|
setupDb,
|
||||||
@ -97,13 +97,13 @@ export async function runTimetravelAutorefreshTest(t: GlobalTestState) {
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
});
|
});
|
||||||
|
|
||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "minst1",
|
id: "minst1",
|
||||||
name: "minst1",
|
name: "minst1",
|
||||||
paytoUris: [getPayto("minst1")],
|
paytoUris: [generateRandomPayto("minst1")],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("setup done!");
|
console.log("setup done!");
|
||||||
|
@ -32,7 +32,7 @@ import {
|
|||||||
MerchantService,
|
MerchantService,
|
||||||
WalletClient,
|
WalletClient,
|
||||||
WalletService,
|
WalletService,
|
||||||
getRandomIban,
|
generateRandomTestIban,
|
||||||
setupDb,
|
setupDb,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ export async function runWalletNotificationsTest(t: GlobalTestState) {
|
|||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [
|
paytoUris: [
|
||||||
`payto://iban/SANDBOXX/${getRandomIban(label)}?receiver-name=${label}`,
|
`payto://iban/SANDBOXX/${generateRandomTestIban(label)}?receiver-name=${label}`,
|
||||||
],
|
],
|
||||||
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
|
||||||
Duration.fromSpec({ minutes: 1 }),
|
Duration.fromSpec({ minutes: 1 }),
|
||||||
|
@ -32,7 +32,7 @@ import {
|
|||||||
MerchantService,
|
MerchantService,
|
||||||
setupDb,
|
setupDb,
|
||||||
WalletCli,
|
WalletCli,
|
||||||
getPayto,
|
generateRandomPayto,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
import { SimpleTestEnvironment } from "../harness/helpers.js";
|
import { SimpleTestEnvironment } from "../harness/helpers.js";
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ export async function createMyEnvironment(
|
|||||||
await merchant.addInstanceWithWireAccount({
|
await merchant.addInstanceWithWireAccount({
|
||||||
id: "default",
|
id: "default",
|
||||||
name: "Default Instance",
|
name: "Default Instance",
|
||||||
paytoUris: [getPayto("merchant-default")],
|
paytoUris: [generateRandomPayto("merchant-default")],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("setup done!");
|
console.log("setup done!");
|
||||||
|
@ -41,12 +41,12 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
// Create a withdrawal operation
|
// Create a withdrawal operation
|
||||||
|
|
||||||
const bankAccessApiClient = new TalerCorebankApiClient(
|
const corebankApiClient = new TalerCorebankApiClient(
|
||||||
bank.bankAccessApiBaseUrl,
|
bank.bankAccessApiBaseUrl,
|
||||||
);
|
);
|
||||||
const user = await bankAccessApiClient.createRandomBankUser();
|
const user = await corebankApiClient.createRandomBankUser();
|
||||||
bankAccessApiClient.setAuth(user);
|
corebankApiClient.setAuth(user);
|
||||||
const wop = await bankAccessApiClient.createWithdrawalOperation(
|
const wop = await corebankApiClient.createWithdrawalOperation(
|
||||||
user.username,
|
user.username,
|
||||||
"TESTKUDOS:10",
|
"TESTKUDOS:10",
|
||||||
);
|
);
|
||||||
@ -129,7 +129,7 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
// Confirm it
|
// Confirm it
|
||||||
|
|
||||||
await bankAccessApiClient.confirmWithdrawalOperation(user.username, {
|
await corebankApiClient.confirmWithdrawalOperation(user.username, {
|
||||||
withdrawalOperationId: wop.withdrawal_id,
|
withdrawalOperationId: wop.withdrawal_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ import { runWithdrawalFeesTest } from "./test-withdrawal-fees.js";
|
|||||||
import { runWithdrawalHugeTest } from "./test-withdrawal-huge.js";
|
import { runWithdrawalHugeTest } from "./test-withdrawal-huge.js";
|
||||||
import { runWithdrawalManualTest } from "./test-withdrawal-manual.js";
|
import { runWithdrawalManualTest } from "./test-withdrawal-manual.js";
|
||||||
import { runWalletGenDbTest } from "./test-wallet-gendb.js";
|
import { runWalletGenDbTest } from "./test-wallet-gendb.js";
|
||||||
|
import { runLibeufinBankTest } from "./test-libeufin-bank.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test runner.
|
* Test runner.
|
||||||
@ -173,6 +174,7 @@ const allTests: TestMainFunction[] = [
|
|||||||
runStoredBackupsTest,
|
runStoredBackupsTest,
|
||||||
runPaymentExpiredTest,
|
runPaymentExpiredTest,
|
||||||
runWalletGenDbTest,
|
runWalletGenDbTest,
|
||||||
|
runLibeufinBankTest,
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface TestRunSpec {
|
export interface TestRunSpec {
|
||||||
|
@ -264,7 +264,7 @@ export class TalerCorebankApiClient {
|
|||||||
const resp = await this.httpLib.fetch(url.href, {
|
const resp = await this.httpLib.fetch(url.href, {
|
||||||
headers: this.makeAuthHeader(),
|
headers: this.makeAuthHeader(),
|
||||||
});
|
});
|
||||||
return await resp.json();
|
return readSuccessResponseJsonOrThrow(resp, codecForAny());
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTransactions(username: string): Promise<void> {
|
async getTransactions(username: string): Promise<void> {
|
||||||
@ -295,6 +295,30 @@ export class TalerCorebankApiClient {
|
|||||||
return await readSuccessResponseJsonOrThrow(resp, codecForAny());
|
return await readSuccessResponseJsonOrThrow(resp, codecForAny());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async registerAccountExtended(req: RegisterAccountRequest): Promise<void> {
|
||||||
|
const url = new URL("accounts", this.baseUrl);
|
||||||
|
const resp = await this.httpLib.fetch(url.href, {
|
||||||
|
method: "POST",
|
||||||
|
body: req,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
resp.status !== 200 &&
|
||||||
|
resp.status !== 201 &&
|
||||||
|
resp.status !== 202 &&
|
||||||
|
resp.status !== 204
|
||||||
|
) {
|
||||||
|
logger.error(`unexpected status ${resp.status} from POST ${url.href}`);
|
||||||
|
logger.error(`${j2s(await resp.json())}`);
|
||||||
|
throw TalerError.fromDetail(
|
||||||
|
TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR,
|
||||||
|
{
|
||||||
|
httpStatusCode: resp.status,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a new account and return information about it.
|
* Register a new account and return information about it.
|
||||||
*
|
*
|
||||||
@ -311,7 +335,13 @@ export class TalerCorebankApiClient {
|
|||||||
name: username,
|
name: username,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (resp.status !== 200 && resp.status !== 202 && resp.status !== 204) {
|
if (
|
||||||
|
resp.status !== 200 &&
|
||||||
|
resp.status !== 201 &&
|
||||||
|
resp.status !== 202 &&
|
||||||
|
resp.status !== 204
|
||||||
|
) {
|
||||||
|
logger.error(`unexpected status ${resp.status} from POST ${url.href}`);
|
||||||
logger.error(`${j2s(await resp.json())}`);
|
logger.error(`${j2s(await resp.json())}`);
|
||||||
throw TalerError.fromDetail(
|
throw TalerError.fromDetail(
|
||||||
TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR,
|
TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR,
|
||||||
@ -320,8 +350,13 @@ export class TalerCorebankApiClient {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// FIXME: Corebank should directly return this info!
|
||||||
const infoUrl = new URL(`accounts/${username}`, this.baseUrl);
|
const infoUrl = new URL(`accounts/${username}`, this.baseUrl);
|
||||||
const infoResp = await this.httpLib.fetch(infoUrl.href);
|
const infoResp = await this.httpLib.fetch(infoUrl.href, {
|
||||||
|
headers: {
|
||||||
|
Authorization: makeBasicAuthHeader(username, password),
|
||||||
|
},
|
||||||
|
});
|
||||||
// FIXME: Validate!
|
// FIXME: Validate!
|
||||||
const acctInfo: AccountData = await readSuccessResponseJsonOrThrow(
|
const acctInfo: AccountData = await readSuccessResponseJsonOrThrow(
|
||||||
infoResp,
|
infoResp,
|
||||||
|
@ -143,9 +143,9 @@ export function expandPath(path: string): string {
|
|||||||
export function pathsub(
|
export function pathsub(
|
||||||
x: string,
|
x: string,
|
||||||
lookup: (s: string, depth: number) => string | undefined,
|
lookup: (s: string, depth: number) => string | undefined,
|
||||||
depth = 0,
|
recursionDepth = 0,
|
||||||
): string {
|
): string {
|
||||||
if (depth >= 128) {
|
if (recursionDepth >= 128) {
|
||||||
throw Error("recursion in path substitution");
|
throw Error("recursion in path substitution");
|
||||||
}
|
}
|
||||||
let s = x;
|
let s = x;
|
||||||
@ -201,7 +201,7 @@ export function pathsub(
|
|||||||
} else {
|
} else {
|
||||||
const m = /^[a-zA-Z-_][a-zA-Z0-9-_]*/.exec(s.substring(l + 1));
|
const m = /^[a-zA-Z-_][a-zA-Z0-9-_]*/.exec(s.substring(l + 1));
|
||||||
if (m && m[0]) {
|
if (m && m[0]) {
|
||||||
const r = lookup(m[0], depth + 1);
|
const r = lookup(m[0], recursionDepth + 1);
|
||||||
if (r !== undefined) {
|
if (r !== undefined) {
|
||||||
s = s.substring(0, l) + r + s.substring(l + 1 + m[0].length);
|
s = s.substring(0, l) + r + s.substring(l + 1 + m[0].length);
|
||||||
l = l + r.length;
|
l = l + r.length;
|
||||||
|
@ -73,7 +73,13 @@ import {
|
|||||||
codecForAbsoluteTime,
|
codecForAbsoluteTime,
|
||||||
codecForTimestamp,
|
codecForTimestamp,
|
||||||
} from "./time.js";
|
} from "./time.js";
|
||||||
import { OrderShortInfo, TransactionType } from "./transactions-types.js";
|
import {
|
||||||
|
OrderShortInfo,
|
||||||
|
TransactionMajorState,
|
||||||
|
TransactionMinorState,
|
||||||
|
TransactionState,
|
||||||
|
TransactionType,
|
||||||
|
} from "./transactions-types.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifier for a transaction in the wallet.
|
* Identifier for a transaction in the wallet.
|
||||||
@ -2715,3 +2721,8 @@ export interface WalletContractData {
|
|||||||
maxDepositFee: AmountString;
|
maxDepositFee: AmountString;
|
||||||
minimumAge?: number;
|
minimumAge?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TestingWaitTransactionRequest {
|
||||||
|
transactionId: string;
|
||||||
|
txState: TransactionState;
|
||||||
|
}
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
ConfirmPayResultType,
|
ConfirmPayResultType,
|
||||||
Duration,
|
Duration,
|
||||||
IntegrationTestV2Args,
|
IntegrationTestV2Args,
|
||||||
|
j2s,
|
||||||
Logger,
|
Logger,
|
||||||
NotificationType,
|
NotificationType,
|
||||||
RegisterAccountRequest,
|
RegisterAccountRequest,
|
||||||
@ -31,6 +32,7 @@ import {
|
|||||||
TestPayResult,
|
TestPayResult,
|
||||||
TransactionMajorState,
|
TransactionMajorState,
|
||||||
TransactionMinorState,
|
TransactionMinorState,
|
||||||
|
TransactionState,
|
||||||
TransactionType,
|
TransactionType,
|
||||||
WithdrawTestBalanceRequest,
|
WithdrawTestBalanceRequest,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
@ -494,6 +496,49 @@ async function waitUntilPendingReady(
|
|||||||
cancelNotifs();
|
cancelNotifs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait until a transaction is in a particular state.
|
||||||
|
*/
|
||||||
|
export async function waitTransactionState(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
transactionId: string,
|
||||||
|
txState: TransactionState,
|
||||||
|
): Promise<void> {
|
||||||
|
logger.info(
|
||||||
|
`starting waiting for ${transactionId} to be in ${JSON.stringify(
|
||||||
|
txState,
|
||||||
|
)})`,
|
||||||
|
);
|
||||||
|
ws.ensureTaskLoopRunning();
|
||||||
|
let p: OpenedPromise<void> | undefined = undefined;
|
||||||
|
const cancelNotifs = ws.addNotificationListener((notif) => {
|
||||||
|
if (!p) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (notif.type === NotificationType.TransactionStateTransition) {
|
||||||
|
p.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
while (1) {
|
||||||
|
p = openPromise();
|
||||||
|
const tx = await getTransactionById(ws, {
|
||||||
|
transactionId,
|
||||||
|
});
|
||||||
|
if (
|
||||||
|
tx.txState.major === txState.major &&
|
||||||
|
tx.txState.minor === txState.minor
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Wait until transaction state changed
|
||||||
|
await p.promise;
|
||||||
|
}
|
||||||
|
logger.info(
|
||||||
|
`done waiting for ${transactionId} to be in ${JSON.stringify(txState)}`,
|
||||||
|
);
|
||||||
|
cancelNotifs();
|
||||||
|
}
|
||||||
|
|
||||||
export async function runIntegrationTest2(
|
export async function runIntegrationTest2(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
args: IntegrationTestV2Args,
|
args: IntegrationTestV2Args,
|
||||||
|
@ -104,6 +104,7 @@ import {
|
|||||||
TestPayArgs,
|
TestPayArgs,
|
||||||
TestPayResult,
|
TestPayResult,
|
||||||
TestingSetTimetravelRequest,
|
TestingSetTimetravelRequest,
|
||||||
|
TestingWaitTransactionRequest,
|
||||||
Transaction,
|
Transaction,
|
||||||
TransactionByIdRequest,
|
TransactionByIdRequest,
|
||||||
TransactionsRequest,
|
TransactionsRequest,
|
||||||
@ -214,6 +215,7 @@ export enum WalletApiOperation {
|
|||||||
ValidateIban = "validateIban",
|
ValidateIban = "validateIban",
|
||||||
TestingWaitTransactionsFinal = "testingWaitTransactionsFinal",
|
TestingWaitTransactionsFinal = "testingWaitTransactionsFinal",
|
||||||
TestingWaitRefreshesFinal = "testingWaitRefreshesFinal",
|
TestingWaitRefreshesFinal = "testingWaitRefreshesFinal",
|
||||||
|
TestingWaitTransactionState = "testingWaitTransactionState",
|
||||||
TestingSetTimetravel = "testingSetTimetravel",
|
TestingSetTimetravel = "testingSetTimetravel",
|
||||||
GetScopedCurrencyInfo = "getScopedCurrencyInfo",
|
GetScopedCurrencyInfo = "getScopedCurrencyInfo",
|
||||||
ListStoredBackups = "listStoredBackups",
|
ListStoredBackups = "listStoredBackups",
|
||||||
@ -1004,6 +1006,15 @@ export type TestingWaitRefreshesFinal = {
|
|||||||
response: EmptyObject;
|
response: EmptyObject;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait until a transaction is in a particular state.
|
||||||
|
*/
|
||||||
|
export type TestingWaitTransactionStateOp = {
|
||||||
|
op: WalletApiOperation.TestingWaitTransactionState;
|
||||||
|
request: TestingWaitTransactionRequest;
|
||||||
|
response: EmptyObject;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a coin as (un-)suspended.
|
* Set a coin as (un-)suspended.
|
||||||
* Suspended coins won't be used for payments.
|
* Suspended coins won't be used for payments.
|
||||||
@ -1108,6 +1119,7 @@ export type WalletOperations = {
|
|||||||
[WalletApiOperation.TestingWaitTransactionsFinal]: TestingWaitTransactionsFinal;
|
[WalletApiOperation.TestingWaitTransactionsFinal]: TestingWaitTransactionsFinal;
|
||||||
[WalletApiOperation.TestingWaitRefreshesFinal]: TestingWaitRefreshesFinal;
|
[WalletApiOperation.TestingWaitRefreshesFinal]: TestingWaitRefreshesFinal;
|
||||||
[WalletApiOperation.TestingSetTimetravel]: TestingSetTimetravelOp;
|
[WalletApiOperation.TestingSetTimetravel]: TestingSetTimetravelOp;
|
||||||
|
[WalletApiOperation.TestingWaitTransactionState]: TestingWaitTransactionStateOp;
|
||||||
[WalletApiOperation.GetScopedCurrencyInfo]: GetScopedCurrencyInfoOp;
|
[WalletApiOperation.GetScopedCurrencyInfo]: GetScopedCurrencyInfoOp;
|
||||||
[WalletApiOperation.CreateStoredBackup]: CreateStoredBackupsOp;
|
[WalletApiOperation.CreateStoredBackup]: CreateStoredBackupsOp;
|
||||||
[WalletApiOperation.ListStoredBackups]: ListStoredBackupsOp;
|
[WalletApiOperation.ListStoredBackups]: ListStoredBackupsOp;
|
||||||
|
@ -127,6 +127,7 @@ import {
|
|||||||
codecForRecoverStoredBackupRequest,
|
codecForRecoverStoredBackupRequest,
|
||||||
codecForTestingSetTimetravelRequest,
|
codecForTestingSetTimetravelRequest,
|
||||||
setDangerousTimetravel,
|
setDangerousTimetravel,
|
||||||
|
TestingWaitTransactionRequest,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import type { HttpRequestLibrary } from "@gnu-taler/taler-util/http";
|
import type { HttpRequestLibrary } from "@gnu-taler/taler-util/http";
|
||||||
import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
|
import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
|
||||||
@ -250,6 +251,7 @@ import {
|
|||||||
runIntegrationTest,
|
runIntegrationTest,
|
||||||
runIntegrationTest2,
|
runIntegrationTest2,
|
||||||
testPay,
|
testPay,
|
||||||
|
waitTransactionState,
|
||||||
waitUntilDone,
|
waitUntilDone,
|
||||||
waitUntilRefreshesDone,
|
waitUntilRefreshesDone,
|
||||||
withdrawTestBalance,
|
withdrawTestBalance,
|
||||||
@ -1414,6 +1416,11 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
|
|||||||
const resp = await getBackupRecovery(ws);
|
const resp = await getBackupRecovery(ws);
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
case WalletApiOperation.TestingWaitTransactionState: {
|
||||||
|
const req = payload as TestingWaitTransactionRequest;
|
||||||
|
await waitTransactionState(ws, req.transactionId, req.txState);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
case WalletApiOperation.GetScopedCurrencyInfo: {
|
case WalletApiOperation.GetScopedCurrencyInfo: {
|
||||||
// Ignore result, just validate in this mock implementation
|
// Ignore result, just validate in this mock implementation
|
||||||
codecForGetCurrencyInfoRequest().decode(payload);
|
codecForGetCurrencyInfoRequest().decode(payload);
|
||||||
|
Loading…
Reference in New Issue
Block a user