add bank API tests

This commit is contained in:
Florian Dold 2020-08-20 13:55:03 +05:30
parent 4c296c9c5f
commit 786976e5a8
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
7 changed files with 299 additions and 95 deletions

View File

@ -66,9 +66,10 @@ import {
TransactionsResponse,
codecForTransactionsResponse,
WithdrawTestBalanceRequest,
AmountString,
} from "taler-wallet-core";
import { URL } from "url";
import axios from "axios";
import axios, { AxiosError } from "axios";
import {
codecForMerchantOrderPrivateStatusResponse,
codecForPostOrderResponse,
@ -276,6 +277,21 @@ export class GlobalTestState {
);
}
async assertThrowsAsync(block: () => Promise<void>): Promise<any> {
try {
await block();
} catch (e) {
return e;
}
throw Error(
`expected exception to be thrown, but block finished without throwing`,
);
}
assertAxiosError(e: any): asserts e is AxiosError {
return e.isAxiosError;
}
assertTrue(b: boolean): asserts b {
if (!b) {
throw Error("test assertion failed");
@ -412,6 +428,7 @@ export interface BankConfig {
httpPort: number;
database: string;
allowRegistrations: boolean;
maxDebt?: string;
}
function setPaths(config: Configuration, home: string) {
@ -474,7 +491,136 @@ export interface ExchangeBankAccount {
wireGatewayApiBaseUrl: string;
}
export class BankService {
export interface BankServiceInterface {
readonly baseUrl: string;
readonly port: number;
}
export enum CreditDebitIndicator {
Credit = "credit",
Debit = "debit",
}
export interface BankAccountBalanceResponse {
balance_amount: AmountString;
credit_debit_indicator: CreditDebitIndicator;
}
export namespace BankAccessApi {
export async function getAccountBalance(
bank: BankServiceInterface,
bankUser: BankUser,
): Promise<BankAccountBalanceResponse> {
const url = new URL(
`accounts/${bankUser.username}/balance`,
bank.baseUrl,
);
const resp = await axios.get(
url.href,
{
auth: bankUser,
},
);
return resp.data;
}
export async function createWithdrawalOperation(
bank: BankServiceInterface,
bankUser: BankUser,
amount: string,
): Promise<WithdrawalOperationInfo> {
const url = new URL(
`accounts/${bankUser.username}/withdrawals`,
bank.baseUrl,
);
const resp = await axios.post(
url.href,
{
amount,
},
{
auth: bankUser,
},
);
return codecForWithdrawalOperationInfo().decode(resp.data);
}
}
export namespace BankApi {
export async function registerAccount(
bank: BankServiceInterface,
username: string,
password: string,
): Promise<BankUser> {
const url = new URL("testing/register", bank.baseUrl);
await axios.post(url.href, {
username,
password,
});
return {
password,
username,
accountPaytoUri: `payto://x-taler-bank/localhost/${username}`,
};
}
export async function createRandomBankUser(
bank: BankServiceInterface,
): Promise<BankUser> {
const username = "user-" + encodeCrock(getRandomBytes(10));
const password = "pw-" + encodeCrock(getRandomBytes(10));
return await registerAccount(bank, username, password);
}
export async function adminAddIncoming(
bank: BankServiceInterface,
params: {
exchangeBankAccount: ExchangeBankAccount;
amount: string;
reservePub: string;
debitAccountPayto: string;
},
) {
const url = new URL(
`taler-wire-gateway/${params.exchangeBankAccount.accountName}/admin/add-incoming`,
bank.baseUrl,
);
await axios.post(
url.href,
{
amount: params.amount,
reserve_pub: params.reservePub,
debit_account: params.debitAccountPayto,
},
{
auth: {
username: params.exchangeBankAccount.accountName,
password: params.exchangeBankAccount.accountPassword,
},
},
);
}
export async function confirmWithdrawalOperation(
bank: BankServiceInterface,
bankUser: BankUser,
wopi: WithdrawalOperationInfo,
): Promise<void> {
const url = new URL(
`accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/confirm`,
bank.baseUrl,
);
await axios.post(
url.href,
{},
{
auth: bankUser,
},
);
}
}
export class BankService implements BankServiceInterface {
proc: ProcessWrapper | undefined;
static fromExistingConfig(gc: GlobalTestState): BankService {
@ -502,6 +648,7 @@ export class BankService {
config.setString("bank", "database", bc.database);
config.setString("bank", "http_port", `${bc.httpPort}`);
config.setString("bank", "max_debt_bank", `${bc.currency}:999999`);
config.setString("bank", "max_debt", bc.maxDebt ?? `${bc.currency}:100`);
config.setString(
"bank",
"allow_registrations",
@ -583,79 +730,6 @@ export class BankService {
const url = `http://localhost:${this.bankConfig.httpPort}/config`;
await pingProc(this.proc, url, "bank");
}
async createAccount(username: string, password: string): Promise<void> {
const url = `http://localhost:${this.bankConfig.httpPort}/testing/register`;
await axios.post(url, {
username,
password,
});
}
async createRandomBankUser(): Promise<BankUser> {
const username = "user-" + encodeCrock(getRandomBytes(10));
const bankUser: BankUser = {
username,
password: "pw-" + encodeCrock(getRandomBytes(10)),
accountPaytoUri: `payto://x-taler-bank/localhost/${username}`,
};
await this.createAccount(bankUser.username, bankUser.password);
return bankUser;
}
async createWithdrawalOperation(
bankUser: BankUser,
amount: string,
): Promise<WithdrawalOperationInfo> {
const url = `http://localhost:${this.bankConfig.httpPort}/accounts/${bankUser.username}/withdrawals`;
const resp = await axios.post(
url,
{
amount,
},
{
auth: bankUser,
},
);
return codecForWithdrawalOperationInfo().decode(resp.data);
}
async adminAddIncoming(params: {
exchangeBankAccount: ExchangeBankAccount;
amount: string;
reservePub: string;
debitAccountPayto: string;
}) {
const url = `http://localhost:${this.bankConfig.httpPort}/taler-wire-gateway/${params.exchangeBankAccount.accountName}/admin/add-incoming`;
await axios.post(
url,
{
amount: params.amount,
reserve_pub: params.reservePub,
debit_account: params.debitAccountPayto,
},
{
auth: {
username: params.exchangeBankAccount.accountName,
password: params.exchangeBankAccount.accountPassword,
},
},
);
}
async confirmWithdrawalOperation(
bankUser: BankUser,
wopi: WithdrawalOperationInfo,
): Promise<void> {
const url = `http://localhost:${this.bankConfig.httpPort}/accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/confirm`;
await axios.post(
url,
{},
{
auth: bankUser,
},
);
}
}
export interface BankUser {
@ -945,11 +1019,12 @@ export namespace MerchantPrivateApi {
export async function giveRefund(
merchantService: MerchantServiceInterface,
r: {
instance: string;
orderId: string;
amount: string;
justification: string;
}): Promise<{ talerRefundUri: string }> {
instance: string;
orderId: string;
amount: string;
justification: string;
},
): Promise<{ talerRefundUri: string }> {
const reqUrl = new URL(
`private/orders/${r.orderId}/refund`,
merchantService.makeInstanceBaseUrl(r.instance),

View File

@ -34,6 +34,8 @@ import {
defaultCoinConfig,
ExchangeBankAccount,
MerchantServiceInterface,
BankApi,
BankAccessApi,
} from "./harness";
import { AmountString } from "taler-wallet-core/lib/types/talerTypes";
import { FaultInjectedMerchantService } from "./faultInjection";
@ -230,8 +232,8 @@ export async function withdrawViaBank(
): Promise<void> {
const { wallet, bank, exchange, amount } = p;
const user = await bank.createRandomBankUser();
const wop = await bank.createWithdrawalOperation(user, amount);
const user = await BankApi.createRandomBankUser(bank);
const wop = await BankAccessApi.createWithdrawalOperation(bank, user, amount);
// Hand it to the wallet
@ -244,7 +246,7 @@ export async function withdrawViaBank(
// Confirm it
await bank.confirmWithdrawalOperation(user, wop);
await BankApi.confirmWithdrawalOperation(bank, user, wop);
// Withdraw
@ -260,3 +262,4 @@ export async function withdrawViaBank(
const balApiResp = await wallet.apiRequest("getBalances", {});
t.assertTrue(balApiResp.type === "response");
}

View File

@ -0,0 +1,122 @@
/*
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 { runTest, GlobalTestState, MerchantPrivateApi, WalletCli, defaultCoinConfig, ExchangeService, setupDb, BankService, MerchantService, BankApi, BankUser, BankAccessApi, CreditDebitIndicator } from "./harness";
import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers";
import { createEddsaKeyPair, encodeCrock } from "taler-wallet-core";
/**
* Run test for basic, bank-integrated withdrawal.
*/
runTest(async (t: GlobalTestState) => {
// Set up test environment
const db = await setupDb(t);
const bank = await BankService.create(t, {
allowRegistrations: true,
currency: "TESTKUDOS",
database: db.connStr,
httpPort: 8082,
});
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 exchangeBankAccount = await bank.createExchangeAccount(
"MyExchange",
"x",
);
exchange.addBankAccount("1", exchangeBankAccount);
bank.setSuggestedExchange(exchange, exchangeBankAccount.accountPaytoUri);
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.addInstance({
id: "minst1",
name: "minst1",
paytoUris: ["payto://x-taler-bank/minst1"],
});
await merchant.addInstance({
id: "default",
name: "Default Instance",
paytoUris: [`payto://x-taler-bank/merchant-default`],
});
console.log("setup done!");
const wallet = new WalletCli(t);
const bankUser = await BankApi.registerAccount(bank, "user1", "pw1");
// Make sure that registering twice results in a 409 Conflict
{
const e = await t.assertThrowsAsync(async () => {
await BankApi.registerAccount(bank, "user1", "pw1");
});
t.assertAxiosError(e);
t.assertTrue(e.response?.status === 409);
}
let bal = await BankAccessApi.getAccountBalance(bank, bankUser);
console.log(bal);
// Check that we got the sign-up bonus.
t.assertAmountEquals(bal.balance_amount, "TESTKUDOS:100");
t.assertTrue(bal.credit_debit_indicator === CreditDebitIndicator.Credit);
const res = createEddsaKeyPair();
await BankApi.adminAddIncoming(bank, {
amount: "TESTKUDOS:115",
debitAccountPayto: bankUser.accountPaytoUri,
exchangeBankAccount: exchangeBankAccount,
reservePub: encodeCrock(res.eddsaPub),
});
bal = await BankAccessApi.getAccountBalance(bank, bankUser);
t.assertAmountEquals(bal.balance_amount, "TESTKUDOS:15");
t.assertTrue(bal.credit_debit_indicator === CreditDebitIndicator.Debit);
});

View File

@ -26,6 +26,8 @@ import {
ExchangeService,
MerchantService,
defaultCoinConfig,
BankApi,
BankAccessApi,
} from "./harness";
import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers";
import {
@ -231,8 +233,8 @@ runTest(async (t: GlobalTestState) => {
// Create withdrawal operation
const user = await bank.createRandomBankUser();
const wop = await bank.createWithdrawalOperation(user, "TESTKUDOS:10");
const user = await BankApi.createRandomBankUser(bank);
const wop = await BankAccessApi.createWithdrawalOperation(bank, user, "TESTKUDOS:10");
// Hand it to the wallet

View File

@ -31,6 +31,8 @@ import {
WalletCli,
defaultCoinConfig,
MerchantPrivateApi,
BankApi,
BankAccessApi,
} from "./harness";
import {
FaultInjectedExchangeService,
@ -114,8 +116,8 @@ runTest(async (t: GlobalTestState) => {
// Create withdrawal operation
const user = await bank.createRandomBankUser();
const wop = await bank.createWithdrawalOperation(user, "TESTKUDOS:20");
const user = await BankApi.createRandomBankUser(bank);
const wop = await BankAccessApi.createWithdrawalOperation(bank, user, "TESTKUDOS:20");
// Hand it to the wallet
@ -128,7 +130,7 @@ runTest(async (t: GlobalTestState) => {
// Confirm it
await bank.confirmWithdrawalOperation(user, wop);
await BankApi.confirmWithdrawalOperation(bank, user, wop);
// Withdraw

View File

@ -17,7 +17,7 @@
/**
* Imports.
*/
import { runTest, GlobalTestState } from "./harness";
import { runTest, GlobalTestState, BankApi, BankAccessApi } from "./harness";
import { createSimpleTestkudosEnvironment } from "./helpers";
import { codecForBalancesResponse } from "taler-wallet-core";
@ -31,8 +31,8 @@ runTest(async (t: GlobalTestState) => {
// Create a withdrawal operation
const user = await bank.createRandomBankUser();
const wop = await bank.createWithdrawalOperation(user, "TESTKUDOS:10");
const user = await BankApi.createRandomBankUser(bank);
const wop = await BankAccessApi.createWithdrawalOperation(bank, user, "TESTKUDOS:10");
// Hand it to the wallet
@ -45,7 +45,7 @@ runTest(async (t: GlobalTestState) => {
// Confirm it
await bank.confirmWithdrawalOperation(user, wop);
await BankApi.confirmWithdrawalOperation(bank, user, wop);
// Withdraw

View File

@ -17,7 +17,7 @@
/**
* Imports.
*/
import { runTest, GlobalTestState } from "./harness";
import { runTest, GlobalTestState, BankApi } from "./harness";
import { createSimpleTestkudosEnvironment } from "./helpers";
import { CoreApiResponse } from "taler-wallet-core/lib/walletCoreApiHandler";
import { codecForBalancesResponse } from "taler-wallet-core";
@ -37,7 +37,7 @@ runTest(async (t: GlobalTestState) => {
// Create a withdrawal operation
const user = await bank.createRandomBankUser();
const user = await BankApi.createRandomBankUser(bank);
let wresp: CoreApiResponse;
@ -56,7 +56,7 @@ runTest(async (t: GlobalTestState) => {
const reservePub: string = (wresp.result as any).reservePub;
await bank.adminAddIncoming({
await BankApi.adminAddIncoming(bank, {
exchangeBankAccount,
amount: "TESTKUDOS:10",
debitAccountPayto: user.accountPaytoUri,