Bring euFin-based tests to pass.

Note: timetravel-withdraw is now failing for both
pybank and eufin.  That is likely due to the wallet
not refreshing expired denominations.
This commit is contained in:
ms 2021-11-13 12:53:48 +01:00
parent 50b9f2167c
commit 9692f589c6
No known key found for this signature in database
GPG Key ID: 8D526861953F4C0F
25 changed files with 331 additions and 122 deletions

View File

@ -57,6 +57,13 @@ export function parseWithdrawUri(s: string): WithdrawUriResult | undefined {
const host = parts[0].toLowerCase(); const host = parts[0].toLowerCase();
const pathSegments = parts.slice(1, parts.length - 1); const pathSegments = parts.slice(1, parts.length - 1);
/**
* The statement below does not tolerate a slash-ended URI.
* This results in (1) the withdrawalId being passed as the
* empty string, and (2) the bankIntegrationApi ending with the
* actual withdrawal operation ID. That can be fixed by
* trimming the parts-list. FIXME
*/
const withdrawId = parts[parts.length - 1]; const withdrawId = parts[parts.length - 1];
const p = [host, ...pathSegments].join("/"); const p = [host, ...pathSegments].join("/");

View File

@ -65,6 +65,8 @@ import {
EddsaKeyPair, EddsaKeyPair,
encodeCrock, encodeCrock,
getRandomBytes, getRandomBytes,
hash,
stringToBytes
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { CoinConfig } from "./denomStructures.js"; import { CoinConfig } from "./denomStructures.js";
import { LibeufinNexusApi, LibeufinSandboxApi } from "./libeufin-apis.js"; import { LibeufinNexusApi, LibeufinSandboxApi } from "./libeufin-apis.js";
@ -431,8 +433,7 @@ function setCoin(config: Configuration, c: CoinConfig) {
} }
/** /**
* Send an HTTP request until it succeeds or the * Send an HTTP request until it suceeds or the process dies.
* process dies.
*/ */
export async function pingProc( export async function pingProc(
proc: ProcessWrapper | undefined, proc: ProcessWrapper | undefined,
@ -523,22 +524,26 @@ export namespace BankApi {
password: string, password: string,
): Promise<BankUser> { ): Promise<BankUser> {
const url = new URL("testing/register", bank.baseUrl); const url = new URL("testing/register", bank.baseUrl);
await axios.post(url.href, { let resp = await axios.post(url.href, {
username, username,
password, password,
}); });
let paytoUri = `payto://x-taler-bank/localhost/${username}`;
if (process.env.WALLET_HARNESS_WITH_EUFIN) {
paytoUri = resp.data.paytoUri;
}
return { return {
password, password,
username, username,
accountPaytoUri: `payto://x-taler-bank/localhost/${username}`, accountPaytoUri: paytoUri,
}; };
} }
export async function createRandomBankUser( export async function createRandomBankUser(
bank: BankServiceInterface, bank: BankServiceInterface,
): Promise<BankUser> { ): Promise<BankUser> {
const username = "user-" + encodeCrock(getRandomBytes(10)); const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase();
const password = "pw-" + encodeCrock(getRandomBytes(10)); const password = "pw-" + encodeCrock(getRandomBytes(10)).toLowerCase();
return await registerAccount(bank, username, password); return await registerAccount(bank, username, password);
} }
@ -551,9 +556,14 @@ export namespace BankApi {
debitAccountPayto: string; debitAccountPayto: string;
}, },
) { ) {
const url = new URL(
let maybeBaseUrl = bank.baseUrl;
if (process.env.WALLET_HARNESS_WITH_EUFIN) {
maybeBaseUrl = (bank as EufinBankService).baseUrlDemobank;
}
let url = new URL(
`taler-wire-gateway/${params.exchangeBankAccount.accountName}/admin/add-incoming`, `taler-wire-gateway/${params.exchangeBankAccount.accountName}/admin/add-incoming`,
bank.baseUrl, maybeBaseUrl,
); );
await axios.post( await axios.post(
url.href, url.href,
@ -623,28 +633,55 @@ class BankServiceBase {
* Work in progress. The key point is that both Sandbox and Nexus * Work in progress. The key point is that both Sandbox and Nexus
* will be configured and started by this class. * will be configured and started by this class.
*/ */
class LibeufinBankService extends BankServiceBase implements BankService { class EufinBankService extends BankServiceBase implements BankServiceInterface {
sandboxProc: ProcessWrapper | undefined; sandboxProc: ProcessWrapper | undefined;
nexusProc: ProcessWrapper | undefined; nexusProc: ProcessWrapper | undefined;
static async create( static async create(
gc: GlobalTestState, gc: GlobalTestState,
bc: BankConfig, bc: BankConfig,
): Promise<BankService> { ): Promise<EufinBankService> {
return new LibeufinBankService(gc, bc, "foo"); return new EufinBankService(gc, bc, "foo");
} }
get port() { get port() {
return this.bankConfig.httpPort; return this.bankConfig.httpPort;
} }
get nexusPort() {
return this.bankConfig.httpPort + 1000;
}
get nexusDbConn(): string {
return `jdbc:sqlite:${this.globalTestState.testDir}/libeufin-nexus.sqlite3`;
}
get sandboxDbConn(): string {
return `jdbc:sqlite:${this.globalTestState.testDir}/libeufin-sandbox.sqlite3`;
}
get nexusBaseUrl(): string { get nexusBaseUrl(): string {
return `http://localhost:${this.bankConfig.httpPort + 1}`; return `http://localhost:${this.nexusPort}`;
}
get baseUrlDemobank(): string {
let url = new URL("demobanks/default/", this.baseUrlNetloc);
return url.href;
}
get baseUrlAccessApi(): string {
let url = new URL("access-api/", this.baseUrlDemobank);
return url.href;
}
get baseUrlNetloc(): string {
return `http://localhost:${this.bankConfig.httpPort}/`;
} }
get baseUrl(): string { get baseUrl(): string {
return `http://localhost:${this.bankConfig.httpPort}/demobanks/default/access-api`; return this.baseUrlAccessApi;
} }
async setSuggestedExchange( async setSuggestedExchange(
@ -654,7 +691,11 @@ class LibeufinBankService extends BankServiceBase implements BankService {
await sh( await sh(
this.globalTestState, this.globalTestState,
"libeufin-sandbox-set-default-exchange", "libeufin-sandbox-set-default-exchange",
`libeufin-sandbox default-exchange ${exchangePayto}` `libeufin-sandbox default-exchange ${e.baseUrl} ${exchangePayto}`,
{
...process.env,
LIBEUFIN_SANDBOX_DB_CONNECTION: this.sandboxDbConn,
},
); );
} }
@ -663,38 +704,45 @@ class LibeufinBankService extends BankServiceBase implements BankService {
accountName: string, accountName: string,
password: string, password: string,
): Promise<HarnessExchangeBankAccount> { ): Promise<HarnessExchangeBankAccount> {
console.log("Create Exchange account(s)!");
/**
* Many test cases try to create a Exchange account before
* starting the bank; that's because the Pybank did it entirely
* via the configuration file.
*/
await this.start();
await this.pingUntilAvailable();
await LibeufinSandboxApi.createDemobankAccount( await LibeufinSandboxApi.createDemobankAccount(
accountName, accountName,
password, password,
{ baseUrl: this.baseUrl } { baseUrl: this.baseUrlAccessApi }
); );
let bankAccountLabel = `${accountName}-acct` let bankAccountLabel = accountName;
await LibeufinSandboxApi.createDemobankEbicsSubscriber( await LibeufinSandboxApi.createDemobankEbicsSubscriber(
{ {
hostID: "talertest-ebics-host", hostID: "talertestEbicsHost",
userID: "exchange-ebics-user", userID: "exchangeEbicsUser",
partnerID: "exchange-ebics-partner", partnerID: "exchangeEbicsPartner",
}, },
bankAccountLabel, bankAccountLabel,
{ baseUrl: this.baseUrl } { baseUrl: this.baseUrlDemobank }
); );
await LibeufinNexusApi.createUser( await LibeufinNexusApi.createUser(
{ baseUrl: this.nexusBaseUrl }, { baseUrl: this.nexusBaseUrl },
{ {
username: `${accountName}-nexus-username`, username: accountName,
password: `${password}-nexus-password` password: password
} }
); );
await LibeufinNexusApi.createEbicsBankConnection( await LibeufinNexusApi.createEbicsBankConnection(
{ baseUrl: this.nexusBaseUrl }, { baseUrl: this.nexusBaseUrl },
{ {
name: "ebics-connection", // connection name. name: "ebics-connection", // connection name.
ebicsURL: `http://localhost:${this.bankConfig.httpPort}/ebicsweb`, ebicsURL: (new URL("ebicsweb", this.baseUrlNetloc)).href,
hostID: "talertest-ebics-host", hostID: "talertestEbicsHost",
userID: "exchange-ebics-user", userID: "exchangeEbicsUser",
partnerID: "exchange-ebics-partner", partnerID: "exchangeEbicsPartner",
} }
); );
await LibeufinNexusApi.connectBankConnection( await LibeufinNexusApi.connectBankConnection(
@ -706,7 +754,7 @@ class LibeufinBankService extends BankServiceBase implements BankService {
await LibeufinNexusApi.importConnectionAccount( await LibeufinNexusApi.importConnectionAccount(
{ baseUrl: this.nexusBaseUrl }, { baseUrl: this.nexusBaseUrl },
"ebics-connection", // connection name "ebics-connection", // connection name
`${accountName}-acct`, // offered account label accountName, // offered account label
`${accountName}-nexus-label` // bank account label at Nexus `${accountName}-nexus-label` // bank account label at Nexus
); );
await LibeufinNexusApi.createTwgFacade( await LibeufinNexusApi.createTwgFacade(
@ -724,7 +772,7 @@ class LibeufinBankService extends BankServiceBase implements BankService {
{ {
action: "grant", action: "grant",
permission: { permission: {
subjectId: `${accountName}-nexus-username`, subjectId: accountName,
subjectType: "user", subjectType: "user",
resourceType: "facade", resourceType: "facade",
resourceId: "exchange-facade", // facade name resourceId: "exchange-facade", // facade name
@ -737,7 +785,7 @@ class LibeufinBankService extends BankServiceBase implements BankService {
{ {
action: "grant", action: "grant",
permission: { permission: {
subjectId: `${accountName}-nexus-username`, subjectId: accountName,
subjectType: "user", subjectType: "user",
resourceType: "facade", resourceType: "facade",
resourceId: "exchange-facade", // facade name resourceId: "exchange-facade", // facade name
@ -745,12 +793,35 @@ class LibeufinBankService extends BankServiceBase implements BankService {
}, },
} }
); );
// Set fetch task.
await LibeufinNexusApi.postTask(
{ baseUrl: this.nexusBaseUrl },
`${accountName}-nexus-label`,
{
name: "wirewatch-task",
cronspec: "* * *",
type: "fetch",
params: {
level: "all",
rangeType: "all",
},
});
await LibeufinNexusApi.postTask(
{ baseUrl: this.nexusBaseUrl },
`${accountName}-nexus-label`,
{
name: "aggregator-task",
cronspec: "* * *",
type: "submit",
params: {},
}
);
let facadesResp = await LibeufinNexusApi.getAllFacades({ baseUrl: this.nexusBaseUrl }); let facadesResp = await LibeufinNexusApi.getAllFacades({ baseUrl: this.nexusBaseUrl });
let accountInfoResp = await LibeufinSandboxApi.demobankAccountInfo( let accountInfoResp = await LibeufinSandboxApi.demobankAccountInfo(
accountName, // username "admin",
password, "secret",
{ baseUrl: this.nexusBaseUrl }, { baseUrl: this.baseUrlAccessApi },
`${accountName}acct` // bank account label. accountName // bank account label.
); );
return { return {
accountName: accountName, accountName: accountName,
@ -761,15 +832,36 @@ class LibeufinBankService extends BankServiceBase implements BankService {
} }
async start(): Promise<void> { async start(): Promise<void> {
let sandboxDb = `jdbc:sqlite:${this.globalTestState.testDir}/libeufin-sandbox.sqlite3`; /**
let nexusDb = `jdbc:sqlite:${this.globalTestState.testDir}/libeufin-nexus.sqlite3`; * Because many test cases try to create a Exchange bank
* account _before_ starting the bank (Pybank did it only via
* the config), it is possible that at this point Sandbox and
* Nexus are already running. Hence, this method only launches
* them if they weren't launched earlier.
*/
// Only go ahead if BOTH aren't running.
if (this.sandboxProc || this.nexusProc) {
console.log("Nexus or Sandbox already running, not taking any action.");
return;
}
await sh(
this.globalTestState,
"libeufin-sandbox-config-demobank",
`libeufin-sandbox config --currency=${this.bankConfig.currency} default`,
{
...process.env,
LIBEUFIN_SANDBOX_DB_CONNECTION: this.sandboxDbConn,
LIBEUFIN_SANDBOX_ADMIN_PASSWORD: "secret",
},
);
this.sandboxProc = this.globalTestState.spawnService( this.sandboxProc = this.globalTestState.spawnService(
"libeufin-sandbox", "libeufin-sandbox",
["serve", "--port", `${this.port}`], ["serve", "--port", `${this.port}`],
"libeufin-sandbox", "libeufin-sandbox",
{ {
...process.env, ...process.env,
LIBEUFIN_SANDBOX_DB_CONNECTION: sandboxDb, LIBEUFIN_SANDBOX_DB_CONNECTION: this.sandboxDbConn,
LIBEUFIN_SANDBOX_ADMIN_PASSWORD: "secret", LIBEUFIN_SANDBOX_ADMIN_PASSWORD: "secret",
}, },
); );
@ -780,34 +872,48 @@ class LibeufinBankService extends BankServiceBase implements BankService {
["superuser", "admin", "--password", "test"], ["superuser", "admin", "--password", "test"],
{ {
...process.env, ...process.env,
LIBEUFIN_NEXUS_DB_CONNECTION: nexusDb, LIBEUFIN_NEXUS_DB_CONNECTION: this.nexusDbConn,
}, },
); );
this.nexusProc = this.globalTestState.spawnService( this.nexusProc = this.globalTestState.spawnService(
"libeufin-nexus", "libeufin-nexus",
["serve", "--port", `${this.port + 1}`], ["serve", "--port", `${this.nexusPort}`],
"libeufin-nexus", "libeufin-nexus",
{ {
...process.env, ...process.env,
LIBEUFIN_NEXUS_DB_CONNECTION: nexusDb, LIBEUFIN_NEXUS_DB_CONNECTION: this.nexusDbConn,
}, },
); );
// need to wait here, because at this point
// a Ebics host needs to be created (RESTfully)
await this.pingUntilAvailable();
LibeufinSandboxApi.createEbicsHost(
{ baseUrl: this.baseUrlNetloc },
"talertestEbicsHost"
);
} }
async pingUntilAvailable(): Promise<void> { async pingUntilAvailable(): Promise<void> {
await pingProc(this.sandboxProc, this.baseUrl, "libeufin-sandbox"); await pingProc(
await pingProc(this.nexusProc, `${this.baseUrl}config`, "libeufin-nexus"); this.sandboxProc,
`http://localhost:${this.bankConfig.httpPort}`,
"libeufin-sandbox"
);
await pingProc(
this.nexusProc,
`${this.nexusBaseUrl}/config`,
"libeufin-nexus"
);
} }
} }
export class BankService extends BankServiceBase implements BankServiceInterface { class PybankService extends BankServiceBase implements BankServiceInterface {
proc: ProcessWrapper | undefined; proc: ProcessWrapper | undefined;
static async create( static async create(
gc: GlobalTestState, gc: GlobalTestState,
bc: BankConfig, bc: BankConfig,
): Promise<BankService> { ): Promise<PybankService> {
const config = new Configuration(); const config = new Configuration();
setTalerPaths(config, gc.testDir + "/talerhome"); setTalerPaths(config, gc.testDir + "/talerhome");
config.setString("taler", "currency", bc.currency); config.setString("taler", "currency", bc.currency);
@ -835,7 +941,7 @@ export class BankService extends BankServiceBase implements BankServiceInterface
`taler-bank-manage -c '${cfgFilename}' django provide_accounts`, `taler-bank-manage -c '${cfgFilename}' django provide_accounts`,
); );
return new BankService(gc, bc, cfgFilename); return new PybankService(gc, bc, cfgFilename);
} }
setSuggestedExchange(e: ExchangeServiceInterface, exchangePayto: string) { setSuggestedExchange(e: ExchangeServiceInterface, exchangePayto: string) {
@ -870,7 +976,7 @@ export class BankService extends BankServiceBase implements BankServiceInterface
return { return {
accountName: accountName, accountName: accountName,
accountPassword: password, accountPassword: password,
accountPaytoUri: `payto://x-taler-bank/${accountName}`, accountPaytoUri: getPayto(accountName),
wireGatewayApiBaseUrl: `http://localhost:${this.bankConfig.httpPort}/taler-wire-gateway/${accountName}/`, wireGatewayApiBaseUrl: `http://localhost:${this.bankConfig.httpPort}/taler-wire-gateway/${accountName}/`,
}; };
} }
@ -893,11 +999,31 @@ export class BankService extends BankServiceBase implements BankServiceInterface
} }
} }
// Still work in progress..
if (false && process.env.WALLET_HARNESS_WITH_EUFIN) { /**
BankService.create = LibeufinBankService.create; * Return a euFin or a pyBank implementation of
BankService.prototype = Object.create(LibeufinBankService.prototype); * the exported BankService class. This allows
* to "dynamically export" such class depending
* on a particular env variable.
*/
function getBankServiceImpl(): {
prototype: typeof PybankService.prototype,
create: typeof PybankService.create
} {
if (process.env.WALLET_HARNESS_WITH_EUFIN)
return {
prototype: EufinBankService.prototype,
create: EufinBankService.create
} }
return {
prototype: PybankService.prototype,
create: PybankService.create
}
}
export type BankService = PybankService;
export const BankService = getBankServiceImpl();
export class FakeBankService { export class FakeBankService {
proc: ProcessWrapper | undefined; proc: ProcessWrapper | undefined;
@ -1038,6 +1164,10 @@ export class ExchangeService implements ExchangeServiceInterface {
} }
async runWirewatchOnce() { async runWirewatchOnce() {
if (process.env.WALLET_HARNESS_WITH_EUFIN) {
// Not even 2 secods showed to be enough!
await waitMs(4000);
}
await runCommand( await runCommand(
this.globalState, this.globalState,
`exchange-${this.name}-wirewatch-once`, `exchange-${this.name}-wirewatch-once`,
@ -1699,7 +1829,7 @@ export class MerchantService implements MerchantServiceInterface {
return await this.addInstance({ return await this.addInstance({
id: "default", id: "default",
name: "Default Instance", name: "Default Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`], paytoUris: [getPayto("merchant-default")],
auth: { auth: {
method: "external", method: "external",
}, },
@ -1956,3 +2086,46 @@ export class WalletCli {
); );
} }
} }
export function getRandomIban(salt: string | null = null): string {
function getBban(salt: string | null): string {
if (!salt)
return Math.random().toString().substring(2, 6);
let hashed = hash(stringToBytes(salt));
let ret = "";
for (let i = 0; i < hashed.length; i++) {
ret += hashed[i].toString();
}
return ret.substring(0, 4);
}
let cc_no_check = "131400"; // == DE00
let bban = getBban(salt)
let check_digits = (98 - (Number.parseInt(`${bban}${cc_no_check}`) % 97)).toString();
if (check_digits.length == 1) {
check_digits = `0${check_digits}`;
}
return `DE${check_digits}${bban}`;
}
// Only used in one tipping test.
export function getWireMethod(): string {
if (process.env.WALLET_HARNESS_WITH_EUFIN)
return "iban"
return "x-taler-bank"
}
/**
* Generate a payto address, whose authority depends
* on whether the banking is served by euFin or Pybank.
*/
export function getPayto(label: string): string {
if (process.env.WALLET_HARNESS_WITH_EUFIN)
return `payto://iban/SANDBOXX/${getRandomIban(label)}?receiver-name=${label}`
return `payto://x-taler-bank/${label}`
}
function waitMs(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}

View File

@ -50,6 +50,7 @@ import {
MerchantPrivateApi, MerchantPrivateApi,
HarnessExchangeBankAccount, HarnessExchangeBankAccount,
WithAuthorization, WithAuthorization,
getPayto
} from "./harness.js"; } from "./harness.js";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
@ -94,7 +95,7 @@ export async function createSimpleTestkudosEnvironment(
}); });
const exchangeBankAccount = await bank.createExchangeAccount( const exchangeBankAccount = await bank.createExchangeAccount(
"MyExchange", "myexchange",
"x", "x",
); );
exchange.addBankAccount("1", exchangeBankAccount); exchange.addBankAccount("1", exchangeBankAccount);
@ -118,13 +119,13 @@ export async function createSimpleTestkudosEnvironment(
await merchant.addInstance({ await merchant.addInstance({
id: "default", id: "default",
name: "Default Instance", name: "Default Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`], paytoUris: [getPayto("merchant-default")],
}); });
await merchant.addInstance({ await merchant.addInstance({
id: "minst1", id: "minst1",
name: "minst1", name: "minst1",
paytoUris: ["payto://x-taler-bank/minst1"], paytoUris: [getPayto("minst1")],
}); });
console.log("setup done!"); console.log("setup done!");
@ -186,7 +187,7 @@ export async function createFaultInjectedMerchantTestkudosEnvironment(
const faultyExchange = new FaultInjectedExchangeService(t, exchange, 9081); const faultyExchange = new FaultInjectedExchangeService(t, exchange, 9081);
const exchangeBankAccount = await bank.createExchangeAccount( const exchangeBankAccount = await bank.createExchangeAccount(
"MyExchange", "myexchange",
"x", "x",
); );
exchange.addBankAccount("1", exchangeBankAccount); exchange.addBankAccount("1", exchangeBankAccount);
@ -213,13 +214,13 @@ export async function createFaultInjectedMerchantTestkudosEnvironment(
await merchant.addInstance({ await merchant.addInstance({
id: "default", id: "default",
name: "Default Instance", name: "Default Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`], paytoUris: [getPayto("merchant-default")],
}); });
await merchant.addInstance({ await merchant.addInstance({
id: "minst1", id: "minst1",
name: "minst1", name: "minst1",
paytoUris: ["payto://x-taler-bank/minst1"], paytoUris: [getPayto("minst1")],
}); });
console.log("setup done!"); console.log("setup done!");
@ -263,16 +264,19 @@ export async function startWithdrawViaBank(
await wallet.runPending(); await wallet.runPending();
// Confirm it // Withdraw (AKA select)
await BankApi.confirmWithdrawalOperation(bank, user, wop);
// Withdraw
await wallet.client.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, { await wallet.client.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, {
exchangeBaseUrl: exchange.baseUrl, exchangeBaseUrl: exchange.baseUrl,
talerWithdrawUri: wop.taler_withdraw_uri, talerWithdrawUri: wop.taler_withdraw_uri,
}); });
// Confirm it
await BankApi.confirmWithdrawalOperation(bank, user, wop);
await wallet.runPending();
await wallet.runUntilDone();
} }
/** /**

View File

@ -184,7 +184,7 @@ export namespace LibeufinSandboxApi {
libeufinSandboxService: LibeufinSandboxServiceInterface, libeufinSandboxService: LibeufinSandboxServiceInterface,
accountLabel: string accountLabel: string
) { ) {
let url = new URL(`${libeufinSandboxService.baseUrl}/accounts/${accountLabel}`); let url = new URL(`accounts/${accountLabel}`,libeufinSandboxService.baseUrl);
return await axios.get(url.href, { return await axios.get(url.href, {
auth: { auth: {
username: username, username: username,
@ -199,7 +199,7 @@ export namespace LibeufinSandboxApi {
password: string, password: string,
libeufinSandboxService: LibeufinSandboxServiceInterface, libeufinSandboxService: LibeufinSandboxServiceInterface,
) { ) {
let url = new URL(`${libeufinSandboxService.baseUrl}/testing/register`); let url = new URL("testing/register", libeufinSandboxService.baseUrl);
await axios.post(url.href, { await axios.post(url.href, {
username: username, username: username,
password: password password: password
@ -214,11 +214,11 @@ export namespace LibeufinSandboxApi {
password: string = "secret", password: string = "secret",
) { ) {
// baseUrl should already be pointed to one demobank. // baseUrl should already be pointed to one demobank.
let url = new URL(libeufinSandboxService.baseUrl); let url = new URL("ebics/subscribers", libeufinSandboxService.baseUrl);
await axios.post(url.href, { await axios.post(url.href, {
userID: req.userID, userID: req.userID,
hostID: req.hostID, hostID: req.hostID,
partnerID: req.userID, partnerID: req.partnerID,
demobankAccountLabel: demobankAccountLabel, demobankAccountLabel: demobankAccountLabel,
}, { }, {
auth: { auth: {

View File

@ -36,8 +36,8 @@ import {
runCommand, runCommand,
setupDb, setupDb,
sh, sh,
getRandomIban
} from "../harness/harness.js"; } from "../harness/harness.js";
import { import {
LibeufinSandboxApi, LibeufinSandboxApi,
LibeufinNexusApi, LibeufinNexusApi,
@ -183,10 +183,6 @@ export interface LibeufinPreparedPaymentDetails {
nexusBankAccountName: string; nexusBankAccountName: string;
} }
function getRandomIban(countryCode: string): string {
return `${countryCode}715001051796${(Math.random().toString().substring(2, 8))}`
}
export class LibeufinSandboxService implements LibeufinSandboxServiceInterface { export class LibeufinSandboxService implements LibeufinSandboxServiceInterface {
static async create( static async create(
gc: GlobalTestState, gc: GlobalTestState,
@ -405,7 +401,7 @@ export class SandboxUserBundle {
constructor(salt: string) { constructor(salt: string) {
this.ebicsBankAccount = { this.ebicsBankAccount = {
bic: "BELADEBEXXX", bic: "BELADEBEXXX",
iban: getRandomIban("DE"), iban: getRandomIban(),
label: `remote-account-${salt}`, label: `remote-account-${salt}`,
name: `Taler Exchange: ${salt}`, name: `Taler Exchange: ${salt}`,
subscriber: { subscriber: {

View File

@ -27,6 +27,7 @@ import {
BankApi, BankApi,
BankAccessApi, BankAccessApi,
CreditDebitIndicator, CreditDebitIndicator,
getPayto
} from "../harness/harness.js"; } from "../harness/harness.js";
import { createEddsaKeyPair, encodeCrock } from "@gnu-taler/taler-util"; import { createEddsaKeyPair, encodeCrock } from "@gnu-taler/taler-util";
import { defaultCoinConfig } from "../harness/denomStructures"; import { defaultCoinConfig } from "../harness/denomStructures";
@ -61,7 +62,7 @@ export async function runBankApiTest(t: GlobalTestState) {
}); });
const exchangeBankAccount = await bank.createExchangeAccount( const exchangeBankAccount = await bank.createExchangeAccount(
"MyExchange", "myexchange",
"x", "x",
); );
exchange.addBankAccount("1", exchangeBankAccount); exchange.addBankAccount("1", exchangeBankAccount);
@ -85,13 +86,13 @@ export async function runBankApiTest(t: GlobalTestState) {
await merchant.addInstance({ await merchant.addInstance({
id: "minst1", id: "minst1",
name: "minst1", name: "minst1",
paytoUris: ["payto://x-taler-bank/minst1"], paytoUris: [getPayto("minst1")],
}); });
await merchant.addInstance({ await merchant.addInstance({
id: "default", id: "default",
name: "Default Instance", name: "Default Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`], paytoUris: [getPayto("merchant-default")],
}); });
console.log("setup done!"); console.log("setup done!");

View File

@ -18,7 +18,7 @@
* Imports. * Imports.
*/ */
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { GlobalTestState } from "../harness/harness.js"; import { GlobalTestState, getPayto } from "../harness/harness.js";
import { createSimpleTestkudosEnvironment, withdrawViaBank } from "../harness/helpers.js"; import { createSimpleTestkudosEnvironment, withdrawViaBank } from "../harness/helpers.js";
/** /**
@ -44,7 +44,7 @@ export async function runDepositTest(t: GlobalTestState) {
WalletApiOperation.CreateDepositGroup, WalletApiOperation.CreateDepositGroup,
{ {
amount: "TESTKUDOS:10", amount: "TESTKUDOS:10",
depositPaytoUri: "payto://x-taler-bank/localhost/foo", depositPaytoUri: getPayto("foo"),
}, },
); );

View File

@ -26,6 +26,7 @@ import {
MerchantService, MerchantService,
BankApi, BankApi,
BankAccessApi, BankAccessApi,
getPayto
} from "../harness/harness.js"; } from "../harness/harness.js";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { import {
@ -69,7 +70,7 @@ export async function runExchangeManagementTest(t: GlobalTestState) {
}); });
const exchangeBankAccount = await bank.createExchangeAccount( const exchangeBankAccount = await bank.createExchangeAccount(
"MyExchange", "myexchange",
"x", "x",
); );
exchange.addBankAccount("1", exchangeBankAccount); exchange.addBankAccount("1", exchangeBankAccount);
@ -98,13 +99,13 @@ export async function runExchangeManagementTest(t: GlobalTestState) {
await merchant.addInstance({ await merchant.addInstance({
id: "default", id: "default",
name: "Default Instance", name: "Default Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`], paytoUris: [getPayto("merchant-default")],
}); });
await merchant.addInstance({ await merchant.addInstance({
id: "minst1", id: "minst1",
name: "minst1", name: "minst1",
paytoUris: ["payto://x-taler-bank/minst1"], paytoUris: [getPayto("minst1")],
}); });
console.log("setup done!"); console.log("setup done!");

View File

@ -40,6 +40,7 @@ import {
MerchantService, MerchantService,
setupDb, setupDb,
WalletCli, WalletCli,
getPayto
} from "../harness/harness.js"; } from "../harness/harness.js";
import { startWithdrawViaBank, withdrawViaBank } from "../harness/helpers.js"; import { startWithdrawViaBank, withdrawViaBank } from "../harness/helpers.js";
@ -103,7 +104,7 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) {
}); });
const exchangeBankAccount = await bank.createExchangeAccount( const exchangeBankAccount = await bank.createExchangeAccount(
"MyExchange", "myexchange",
"x", "x",
); );
exchange.addBankAccount("1", exchangeBankAccount); exchange.addBankAccount("1", exchangeBankAccount);
@ -127,13 +128,13 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) {
await merchant.addInstance({ await merchant.addInstance({
id: "default", id: "default",
name: "Default Instance", name: "Default Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`], paytoUris: [getPayto("merchant-default")],
}); });
await merchant.addInstance({ await merchant.addInstance({
id: "minst1", id: "minst1",
name: "minst1", name: "minst1",
paytoUris: ["payto://x-taler-bank/minst1"], paytoUris: [getPayto("minst1")],
}); });
console.log("setup done!"); console.log("setup done!");

View File

@ -25,6 +25,7 @@ import {
MerchantService, MerchantService,
setupDb, setupDb,
WalletCli, WalletCli,
getPayto
} from "../harness/harness.js"; } from "../harness/harness.js";
import { import {
withdrawViaBank, withdrawViaBank,
@ -63,7 +64,7 @@ export async function createMyTestkudosEnvironment(
}); });
const exchangeBankAccount = await bank.createExchangeAccount( const exchangeBankAccount = await bank.createExchangeAccount(
"MyExchange", "myexchange",
"x", "x",
); );
exchange.addBankAccount("1", exchangeBankAccount); exchange.addBankAccount("1", exchangeBankAccount);
@ -140,7 +141,7 @@ export async function createMyTestkudosEnvironment(
await merchant.addInstance({ await merchant.addInstance({
id: "minst1", id: "minst1",
name: "minst1", name: "minst1",
paytoUris: ["payto://x-taler-bank/minst1"], paytoUris: [getPayto("minst1")],
}); });
console.log("setup done!"); console.log("setup done!");

View File

@ -25,6 +25,7 @@ import {
MerchantService, MerchantService,
setupDb, setupDb,
WalletCli, WalletCli,
getPayto
} from "../harness/harness.js"; } from "../harness/harness.js";
import { import {
withdrawViaBank, withdrawViaBank,
@ -80,7 +81,7 @@ export async function createConfusedMerchantTestkudosEnvironment(
const faultyExchange = new FaultInjectedExchangeService(t, exchange, 9081); const faultyExchange = new FaultInjectedExchangeService(t, exchange, 9081);
const exchangeBankAccount = await bank.createExchangeAccount( const exchangeBankAccount = await bank.createExchangeAccount(
"MyExchange", "myexchange",
"x", "x",
); );
exchange.addBankAccount("1", exchangeBankAccount); exchange.addBankAccount("1", exchangeBankAccount);
@ -108,13 +109,13 @@ export async function createConfusedMerchantTestkudosEnvironment(
await merchant.addInstance({ await merchant.addInstance({
id: "default", id: "default",
name: "Default Instance", name: "Default Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`], paytoUris: [getPayto("merchant-default")],
}); });
await merchant.addInstance({ await merchant.addInstance({
id: "minst1", id: "minst1",
name: "minst1", name: "minst1",
paytoUris: ["payto://x-taler-bank/minst1"], paytoUris: [getPayto("minst1")]
}); });
console.log("setup done!"); console.log("setup done!");

View File

@ -25,6 +25,7 @@ import {
MerchantApiClient, MerchantApiClient,
MerchantService, MerchantService,
setupDb, setupDb,
getPayto
} from "../harness/harness.js"; } from "../harness/harness.js";
/** /**
@ -74,7 +75,7 @@ export async function runMerchantInstancesDeleteTest(t: GlobalTestState) {
await merchant.addInstance({ await merchant.addInstance({
id: "default", id: "default",
name: "Default Instance", name: "Default Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`], paytoUris: [getPayto("merchant-default")],
auth: { auth: {
method: "external", method: "external",
}, },
@ -84,7 +85,7 @@ export async function runMerchantInstancesDeleteTest(t: GlobalTestState) {
await merchant.addInstance({ await merchant.addInstance({
id: "myinst", id: "myinst",
name: "Second Instance", name: "Second Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`], paytoUris: [getPayto("merchant-default")],
auth: { auth: {
method: "external", method: "external",
}, },

View File

@ -24,6 +24,7 @@ import {
MerchantApiClient, MerchantApiClient,
MerchantService, MerchantService,
setupDb, setupDb,
getPayto
} from "../harness/harness.js"; } from "../harness/harness.js";
/** /**
@ -71,7 +72,7 @@ export async function runMerchantInstancesUrlsTest(t: GlobalTestState) {
default_wire_transfer_delay: { d_ms: 60000 }, default_wire_transfer_delay: { d_ms: 60000 },
jurisdiction: {}, jurisdiction: {},
name: "My Default Instance", name: "My Default Instance",
payto_uris: ["payto://x-taler-bank/foo/bar"], payto_uris: [getPayto("bar")],
auth: { auth: {
method: "token", method: "token",
token: "secret-token:i-am-default", token: "secret-token:i-am-default",
@ -88,7 +89,7 @@ export async function runMerchantInstancesUrlsTest(t: GlobalTestState) {
default_wire_transfer_delay: { d_ms: 60000 }, default_wire_transfer_delay: { d_ms: 60000 },
jurisdiction: {}, jurisdiction: {},
name: "My Second Instance", name: "My Second Instance",
payto_uris: ["payto://x-taler-bank/foo/bar"], payto_uris: [getPayto("bar")],
auth: { auth: {
method: "token", method: "token",
token: "secret-token:i-am-myinst", token: "secret-token:i-am-myinst",

View File

@ -25,6 +25,7 @@ import {
MerchantApiClient, MerchantApiClient,
MerchantService, MerchantService,
setupDb, setupDb,
getPayto
} from "../harness/harness.js"; } from "../harness/harness.js";
/** /**
@ -74,7 +75,7 @@ export async function runMerchantInstancesTest(t: GlobalTestState) {
await merchant.addInstance({ await merchant.addInstance({
id: "default", id: "default",
name: "Default Instance", name: "Default Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`], paytoUris: [getPayto("merchant-default")],
auth: { auth: {
method: "external", method: "external",
}, },
@ -84,7 +85,7 @@ export async function runMerchantInstancesTest(t: GlobalTestState) {
await merchant.addInstance({ await merchant.addInstance({
id: "myinst", id: "myinst",
name: "Second Instance", name: "Second Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`], paytoUris: [getPayto("merchant-default")],
auth: { auth: {
method: "external", method: "external",
}, },

View File

@ -31,6 +31,7 @@ import {
MerchantPrivateApi, MerchantPrivateApi,
BankApi, BankApi,
BankAccessApi, BankAccessApi,
getPayto
} from "../harness/harness.js"; } from "../harness/harness.js";
import { import {
FaultInjectedExchangeService, FaultInjectedExchangeService,
@ -64,7 +65,7 @@ export async function runPaymentFaultTest(t: GlobalTestState) {
}); });
const exchangeBankAccount = await bank.createExchangeAccount( const exchangeBankAccount = await bank.createExchangeAccount(
"MyExchange", "myexchange",
"x", "x",
); );
@ -107,7 +108,7 @@ export async function runPaymentFaultTest(t: GlobalTestState) {
await merchant.addInstance({ await merchant.addInstance({
id: "default", id: "default",
name: "Default Instance", name: "Default Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`], paytoUris: [getPayto("merchant-default")],
}); });
console.log("setup done!"); console.log("setup done!");
@ -131,18 +132,21 @@ export async function runPaymentFaultTest(t: GlobalTestState) {
await wallet.runPending(); await wallet.runPending();
// Confirm it
await BankApi.confirmWithdrawalOperation(bank, user, wop);
// Withdraw // Withdraw
await wallet.client.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, { await wallet.client.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, {
exchangeBaseUrl: faultyExchange.baseUrl, exchangeBaseUrl: faultyExchange.baseUrl,
talerWithdrawUri: wop.taler_withdraw_uri, talerWithdrawUri: wop.taler_withdraw_uri,
}); });
await wallet.runPending();
// Confirm it
await BankApi.confirmWithdrawalOperation(bank, user, wop);
await wallet.runUntilDone(); await wallet.runUntilDone();
// Check balance // Check balance
await wallet.client.call(WalletApiOperation.GetBalances, {}); await wallet.client.call(WalletApiOperation.GetBalances, {});

View File

@ -25,6 +25,7 @@ import {
MerchantService, MerchantService,
WalletCli, WalletCli,
MerchantPrivateApi, MerchantPrivateApi,
getPayto
} from "../harness/harness.js"; } from "../harness/harness.js";
import { withdrawViaBank } from "../harness/helpers.js"; import { withdrawViaBank } from "../harness/helpers.js";
import { coin_ct10, coin_u1 } from "../harness/denomStructures"; import { coin_ct10, coin_u1 } from "../harness/denomStructures";
@ -54,7 +55,7 @@ async function setupTest(
}); });
const exchangeBankAccount = await bank.createExchangeAccount( const exchangeBankAccount = await bank.createExchangeAccount(
"MyExchange", "myexchange",
"x", "x",
); );
@ -86,13 +87,13 @@ async function setupTest(
await merchant.addInstance({ await merchant.addInstance({
id: "default", id: "default",
name: "Default Instance", name: "Default Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`], paytoUris: [getPayto("merchant-default")],
}); });
await merchant.addInstance({ await merchant.addInstance({
id: "minst1", id: "minst1",
name: "minst1", name: "minst1",
paytoUris: ["payto://x-taler-bank/minst1"], paytoUris: [getPayto("minst1")],
}); });
console.log("setup done!"); console.log("setup done!");

View File

@ -27,6 +27,7 @@ import {
setupDb, setupDb,
BankService, BankService,
delayMs, delayMs,
getPayto
} from "../harness/harness.js"; } from "../harness/harness.js";
import { import {
withdrawViaBank, withdrawViaBank,
@ -84,7 +85,7 @@ async function createTestEnvironment(
}); });
const exchangeBankAccount = await bank.createExchangeAccount( const exchangeBankAccount = await bank.createExchangeAccount(
"MyExchange", "myexchange",
"x", "x",
); );
exchange.addBankAccount("1", exchangeBankAccount); exchange.addBankAccount("1", exchangeBankAccount);
@ -121,13 +122,13 @@ async function createTestEnvironment(
await merchant.addInstance({ await merchant.addInstance({
id: "default", id: "default",
name: "Default Instance", name: "Default Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`], paytoUris: [getPayto("merchant-default")],
}); });
await merchant.addInstance({ await merchant.addInstance({
id: "minst1", id: "minst1",
name: "minst1", name: "minst1",
paytoUris: ["payto://x-taler-bank/minst1"], paytoUris: [getPayto("minst1")],
}); });
console.log("setup done!"); console.log("setup done!");

View File

@ -36,6 +36,7 @@ import {
MerchantService, MerchantService,
setupDb, setupDb,
WalletCli, WalletCli,
getPayto
} from "../harness/harness.js"; } from "../harness/harness.js";
import { startWithdrawViaBank, withdrawViaBank } from "../harness/helpers.js"; import { startWithdrawViaBank, withdrawViaBank } from "../harness/helpers.js";
@ -97,7 +98,7 @@ export async function runTimetravelAutorefreshTest(t: GlobalTestState) {
}); });
const exchangeBankAccount = await bank.createExchangeAccount( const exchangeBankAccount = await bank.createExchangeAccount(
"MyExchange", "myexchange",
"x", "x",
); );
exchange.addBankAccount("1", exchangeBankAccount); exchange.addBankAccount("1", exchangeBankAccount);
@ -121,13 +122,13 @@ export async function runTimetravelAutorefreshTest(t: GlobalTestState) {
await merchant.addInstance({ await merchant.addInstance({
id: "default", id: "default",
name: "Default Instance", name: "Default Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`], paytoUris: [getPayto("merchant-default")],
}); });
await merchant.addInstance({ await merchant.addInstance({
id: "minst1", id: "minst1",
name: "minst1", name: "minst1",
paytoUris: ["payto://x-taler-bank/minst1"], paytoUris: [getPayto("minst1")],
}); });
console.log("setup done!"); console.log("setup done!");

View File

@ -18,7 +18,7 @@
* Imports. * Imports.
*/ */
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { GlobalTestState, MerchantPrivateApi, BankApi } from "../harness/harness.js"; import { GlobalTestState, MerchantPrivateApi, BankApi, getWireMethod } from "../harness/harness.js";
import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
/** /**
@ -43,7 +43,7 @@ export async function runTippingTest(t: GlobalTestState) {
{ {
exchange_url: exchange.baseUrl, exchange_url: exchange.baseUrl,
initial_balance: "TESTKUDOS:10", initial_balance: "TESTKUDOS:10",
wire_method: "x-taler-bank", wire_method: getWireMethod(),
}, },
); );

View File

@ -32,6 +32,7 @@ import {
MerchantService, MerchantService,
setupDb, setupDb,
WalletCli, WalletCli,
getPayto
} from "../harness/harness.js"; } from "../harness/harness.js";
import { SimpleTestEnvironment } from "../harness/helpers.js"; import { SimpleTestEnvironment } from "../harness/helpers.js";
@ -69,7 +70,7 @@ export async function createMyEnvironment(
}); });
const exchangeBankAccount = await bank.createExchangeAccount( const exchangeBankAccount = await bank.createExchangeAccount(
"MyExchange", "myexchange",
"x", "x",
); );
exchange.addBankAccount("1", exchangeBankAccount); exchange.addBankAccount("1", exchangeBankAccount);
@ -93,7 +94,7 @@ export async function createMyEnvironment(
await merchant.addInstance({ await merchant.addInstance({
id: "default", id: "default",
name: "Default Instance", name: "Default Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`], paytoUris: [getPayto("merchant-default")],
}); });
console.log("setup done!"); console.log("setup done!");

View File

@ -47,12 +47,18 @@ export async function runWithdrawalAbortBankTest(t: GlobalTestState) {
await wallet.runPending(); await wallet.runPending();
// Confirm it // Abort it
await BankApi.abortWithdrawalOperation(bank, user, wop); await BankApi.abortWithdrawalOperation(bank, user, wop);
// Withdraw // Withdraw
// Difference:
// -> with euFin, the wallet selects
// -> with PyBank, the wallet stops _before_
//
// WHY ?!
//
const e = await t.assertThrowsOperationErrorAsync(async () => { const e = await t.assertThrowsOperationErrorAsync(async () => {
await wallet.client.call( await wallet.client.call(
WalletApiOperation.AcceptBankIntegratedWithdrawal, WalletApiOperation.AcceptBankIntegratedWithdrawal,

View File

@ -47,16 +47,18 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
await wallet.runPending(); await wallet.runPending();
// Confirm it
await BankApi.confirmWithdrawalOperation(bank, user, wop);
// Withdraw // Withdraw
const r2 = await wallet.client.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, { const r2 = await wallet.client.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, {
exchangeBaseUrl: exchange.baseUrl, exchangeBaseUrl: exchange.baseUrl,
talerWithdrawUri: wop.taler_withdraw_uri, talerWithdrawUri: wop.taler_withdraw_uri,
}); });
await wallet.runPending();
// Confirm it
await BankApi.confirmWithdrawalOperation(bank, user, wop);
await wallet.runUntilDone(); await wallet.runUntilDone();
// Check balance // Check balance

View File

@ -50,6 +50,7 @@ export async function runTestWithdrawalManualTest(t: GlobalTestState) {
const reservePub: string = wres.reservePub; const reservePub: string = wres.reservePub;
// Bug.
await BankApi.adminAddIncoming(bank, { await BankApi.adminAddIncoming(bank, {
exchangeBankAccount, exchangeBankAccount,
amount: "TESTKUDOS:10", amount: "TESTKUDOS:10",

View File

@ -175,7 +175,7 @@ export async function getEffectiveDepositAmount(
for (let i = 0; i < pcs.coinPubs.length; i++) { for (let i = 0; i < pcs.coinPubs.length; i++) {
const coin = await tx.coins.get(pcs.coinPubs[i]); const coin = await tx.coins.get(pcs.coinPubs[i]);
if (!coin) { if (!coin) {
throw Error("can't calculate deposit amountt, coin not found"); throw Error("can't calculate deposit amount, coin not found");
} }
const denom = await tx.denominations.get([ const denom = await tx.denominations.get([
coin.exchangeBaseUrl, coin.exchangeBaseUrl,
@ -193,6 +193,9 @@ export async function getEffectiveDepositAmount(
if (!exchangeDetails) { if (!exchangeDetails) {
continue; continue;
} }
// FIXME/NOTE: the line below _likely_ throws exception
// about "find method not found on undefined" when the wireType
// is not supported by the Exchange.
const fee = exchangeDetails.wireInfo.feesForType[wireType].find((x) => { const fee = exchangeDetails.wireInfo.feesForType[wireType].find((x) => {
return timestampIsBetween( return timestampIsBetween(
getTimestampNow(), getTimestampNow(),

View File

@ -174,7 +174,8 @@ async function registerRandomBankUser(
const reqUrl = new URL("testing/register", bankBaseUrl).href; const reqUrl = new URL("testing/register", bankBaseUrl).href;
const randId = makeId(8); const randId = makeId(8);
const bankUser: BankUser = { const bankUser: BankUser = {
username: `testuser-${randId}`, // euFin doesn't allow resource names to have upper case letters.
username: `testuser-${randId.toLowerCase()}`,
password: `testpw-${randId}`, password: `testpw-${randId}`,
}; };