From 786976e5a8e10f6a3eab50cacbefe98d8b2364f5 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 20 Aug 2020 13:55:03 +0530 Subject: add bank API tests --- packages/taler-integrationtests/src/harness.ts | 235 ++++++++++++++------- packages/taler-integrationtests/src/helpers.ts | 9 +- .../taler-integrationtests/src/test-bank-api.ts | 122 +++++++++++ .../src/test-exchange-management.ts | 6 +- .../src/test-payment-fault.ts | 8 +- .../src/test-withdrawal-bank-integrated.ts | 8 +- .../src/test-withdrawal-manual.ts | 6 +- 7 files changed, 299 insertions(+), 95 deletions(-) create mode 100644 packages/taler-integrationtests/src/test-bank-api.ts (limited to 'packages/taler-integrationtests/src') diff --git a/packages/taler-integrationtests/src/harness.ts b/packages/taler-integrationtests/src/harness.ts index 77914af66..6897b4b5c 100644 --- a/packages/taler-integrationtests/src/harness.ts +++ b/packages/taler-integrationtests/src/harness.ts @@ -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): Promise { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { - const url = `http://localhost:${this.bankConfig.httpPort}/testing/register`; - await axios.post(url, { - username, - password, - }); - } - - async createRandomBankUser(): Promise { - 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 { - 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 { - 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), diff --git a/packages/taler-integrationtests/src/helpers.ts b/packages/taler-integrationtests/src/helpers.ts index 86f530e09..d33aaff4e 100644 --- a/packages/taler-integrationtests/src/helpers.ts +++ b/packages/taler-integrationtests/src/helpers.ts @@ -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 { 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"); } + diff --git a/packages/taler-integrationtests/src/test-bank-api.ts b/packages/taler-integrationtests/src/test-bank-api.ts new file mode 100644 index 000000000..1c233a55c --- /dev/null +++ b/packages/taler-integrationtests/src/test-bank-api.ts @@ -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 + */ + +/** + * 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); +}); diff --git a/packages/taler-integrationtests/src/test-exchange-management.ts b/packages/taler-integrationtests/src/test-exchange-management.ts index 9199c183e..4ca86b341 100644 --- a/packages/taler-integrationtests/src/test-exchange-management.ts +++ b/packages/taler-integrationtests/src/test-exchange-management.ts @@ -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 diff --git a/packages/taler-integrationtests/src/test-payment-fault.ts b/packages/taler-integrationtests/src/test-payment-fault.ts index 8065b430c..a7f44706b 100644 --- a/packages/taler-integrationtests/src/test-payment-fault.ts +++ b/packages/taler-integrationtests/src/test-payment-fault.ts @@ -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 diff --git a/packages/taler-integrationtests/src/test-withdrawal-bank-integrated.ts b/packages/taler-integrationtests/src/test-withdrawal-bank-integrated.ts index 46ccdaaed..f2593c802 100644 --- a/packages/taler-integrationtests/src/test-withdrawal-bank-integrated.ts +++ b/packages/taler-integrationtests/src/test-withdrawal-bank-integrated.ts @@ -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 diff --git a/packages/taler-integrationtests/src/test-withdrawal-manual.ts b/packages/taler-integrationtests/src/test-withdrawal-manual.ts index 0d09c3e5c..ee844d9f0 100644 --- a/packages/taler-integrationtests/src/test-withdrawal-manual.ts +++ b/packages/taler-integrationtests/src/test-withdrawal-manual.ts @@ -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, -- cgit v1.2.3