aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2020-08-12 19:45:34 +0530
committerFlorian Dold <florian.dold@gmail.com>2020-08-12 19:45:34 +0530
commitc5ec34136874be87210711fd65eea1c1a6feec50 (patch)
treeb5795aeec939769e8c14ac7ecc2cfc4abf273282
parent11fa3397053c16cfcbf594c1389a75eaad94a40e (diff)
add exchange management test casev0.7.1-dev.20
-rw-r--r--packages/taler-integrationtests/src/faultInjection.ts2
-rw-r--r--packages/taler-integrationtests/src/harness.ts51
-rw-r--r--packages/taler-integrationtests/src/test-exchange-management.ts232
-rw-r--r--packages/taler-wallet-core/src/index.ts2
-rw-r--r--packages/taler-wallet-core/src/operations/exchanges.ts2
-rw-r--r--packages/taler-wallet-core/src/types/talerTypes.ts9
-rw-r--r--packages/taler-wallet-core/src/types/walletTypes.ts12
7 files changed, 306 insertions, 4 deletions
diff --git a/packages/taler-integrationtests/src/faultInjection.ts b/packages/taler-integrationtests/src/faultInjection.ts
index 26bfeee53..46ab1c5fe 100644
--- a/packages/taler-integrationtests/src/faultInjection.ts
+++ b/packages/taler-integrationtests/src/faultInjection.ts
@@ -182,7 +182,7 @@ export class FaultProxy {
this.currentFaultSpecs.push(f);
}
- clearFault() {
+ clearAllFaults() {
this.currentFaultSpecs = [];
}
}
diff --git a/packages/taler-integrationtests/src/harness.ts b/packages/taler-integrationtests/src/harness.ts
index e8a0941d2..0cf769163 100644
--- a/packages/taler-integrationtests/src/harness.ts
+++ b/packages/taler-integrationtests/src/harness.ts
@@ -44,6 +44,12 @@ import {
codecForPreparePayResultPaymentPossible,
codecForPreparePayResult,
OperationFailedError,
+ AddExchangeRequest,
+ ExchangesListRespose,
+ codecForExchangesListResponse,
+ GetWithdrawalDetailsForUriRequest,
+ WithdrawUriInfoResponse,
+ codecForWithdrawUriInfoResponse,
} from "taler-wallet-core";
import { URL } from "url";
import axios from "axios";
@@ -60,6 +66,7 @@ import {
eddsaGetPublic,
createEddsaKeyPair,
} from "taler-wallet-core/lib/crypto/talerCrypto";
+import { WithdrawalDetails } from "taler-wallet-core/lib/types/transactions";
const exec = util.promisify(require("child_process").exec);
@@ -244,6 +251,22 @@ export class GlobalTestState {
process.on("uncaughtException", () => this.shutdownSync());
}
+ async assertThrowsOperationErrorAsync(
+ block: () => Promise<void>,
+ ): Promise<OperationFailedError> {
+ try {
+ await block();
+ } catch (e) {
+ if (e instanceof OperationFailedError) {
+ return e;
+ }
+ throw Error(`expected OperationFailedError to be thrown, but got ${e}`);
+ }
+ throw Error(
+ `expected OperationFailedError to be thrown, but block finished without throwing`,
+ );
+ }
+
assertTrue(b: boolean): asserts b {
if (!b) {
throw Error("test assertion failed");
@@ -488,7 +511,7 @@ export class BankService {
return new BankService(gc, bc, cfgFilename);
}
- setSuggestedExchange(e: ExchangeService, exchangePayto: string) {
+ setSuggestedExchange(e: ExchangeServiceInterface, exchangePayto: string) {
const config = Configuration.load(this.configFile);
config.setString("bank", "suggested_exchange", e.baseUrl);
config.setString("bank", "suggested_exchange_payto", exchangePayto);
@@ -1153,4 +1176,30 @@ export class WalletCli {
}
throw new OperationFailedError(resp.error);
}
+
+ async addExchange(req: AddExchangeRequest): Promise<void> {
+ const resp = await this.apiRequest("addExchange", req);
+ if (resp.type === "response") {
+ return;
+ }
+ throw new OperationFailedError(resp.error);
+ }
+
+ async listExchanges(): Promise<ExchangesListRespose> {
+ const resp = await this.apiRequest("listExchanges", {});
+ if (resp.type === "response") {
+ return codecForExchangesListResponse().decode(resp.result);
+ }
+ throw new OperationFailedError(resp.error);
+ }
+
+ async getWithdrawalDetailsForUri(
+ req: GetWithdrawalDetailsForUriRequest,
+ ): Promise<WithdrawUriInfoResponse> {
+ const resp = await this.apiRequest("getWithdrawalDetailsForUri", req);
+ if (resp.type === "response") {
+ return codecForWithdrawUriInfoResponse().decode(resp.result);
+ }
+ throw new OperationFailedError(resp.error);
+ }
}
diff --git a/packages/taler-integrationtests/src/test-exchange-management.ts b/packages/taler-integrationtests/src/test-exchange-management.ts
new file mode 100644
index 000000000..990a6fa4e
--- /dev/null
+++ b/packages/taler-integrationtests/src/test-exchange-management.ts
@@ -0,0 +1,232 @@
+/*
+ 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,
+ WalletCli,
+ setupDb,
+ BankService,
+ ExchangeService,
+ MerchantService,
+ defaultCoinConfig,
+} from "./harness";
+import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers";
+import {
+ PreparePayResultType,
+ ExchangesListRespose,
+ URL,
+} from "taler-wallet-core";
+import {
+ FaultInjectedExchangeService,
+ FaultInjectionResponseContext,
+} from "./faultInjection";
+
+/**
+ * Test if the wallet handles outdated exchange versions correct.y
+ */
+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);
+
+ const faultyExchange = new FaultInjectedExchangeService(t, exchange, 8091);
+
+ bank.setSuggestedExchange(
+ faultyExchange,
+ 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!");
+
+ /*
+ * =========================================================================
+ * Check that the exchange can be added to the wallet
+ * (without any faults active).
+ * =========================================================================
+ */
+
+ const wallet = new WalletCli(t);
+
+ let exchangesList: ExchangesListRespose;
+
+ exchangesList = await wallet.listExchanges();
+ t.assertTrue(exchangesList.exchanges.length === 0);
+
+ // Try before fault is injected
+ await wallet.addExchange({
+ exchangeBaseUrl: faultyExchange.baseUrl,
+ });
+
+ exchangesList = await wallet.listExchanges();
+ t.assertTrue(exchangesList.exchanges.length === 1);
+
+ await wallet.addExchange({
+ exchangeBaseUrl: faultyExchange.baseUrl,
+ });
+
+ console.log("listing exchanges");
+
+ exchangesList = await wallet.listExchanges();
+ t.assertTrue(exchangesList.exchanges.length === 1);
+
+ console.log("got list", exchangesList);
+
+ /*
+ * =========================================================================
+ * Check what happens if the exchange returns something totally
+ * bogus for /keys.
+ * =========================================================================
+ */
+
+ wallet.deleteDatabase();
+
+ exchangesList = await wallet.listExchanges();
+ t.assertTrue(exchangesList.exchanges.length === 0);
+
+ faultyExchange.faultProxy.addFault({
+ modifyResponse(ctx: FaultInjectionResponseContext) {
+ const url = new URL(ctx.request.requestUrl);
+ if (url.pathname === "/keys") {
+ const body = {
+ version: "whaaat",
+ };
+ ctx.responseBody = Buffer.from(JSON.stringify(body), "utf-8");
+ }
+ },
+ });
+
+ await t.assertThrowsOperationErrorAsync(async () => {
+ await wallet.addExchange({
+ exchangeBaseUrl: faultyExchange.baseUrl,
+ });
+ });
+
+ exchangesList = await wallet.listExchanges();
+ t.assertTrue(exchangesList.exchanges.length === 0);
+
+ /*
+ * =========================================================================
+ * Check what happens if the exchange returns an old, unsupported
+ * version for /keys
+ * =========================================================================
+ */
+
+ wallet.deleteDatabase();
+ faultyExchange.faultProxy.clearAllFaults();
+
+ faultyExchange.faultProxy.addFault({
+ modifyResponse(ctx: FaultInjectionResponseContext) {
+ const url = new URL(ctx.request.requestUrl);
+ if (url.pathname === "/keys") {
+ const keys = ctx.responseBody?.toString("utf-8");
+ t.assertTrue(keys != null);
+ const keysJson = JSON.parse(keys);
+ keysJson["version"] = "2:0:0";
+ ctx.responseBody = Buffer.from(JSON.stringify(keysJson), "utf-8");
+ }
+ },
+ });
+
+ await t.assertThrowsOperationErrorAsync(async () => {
+ await wallet.addExchange({
+ exchangeBaseUrl: faultyExchange.baseUrl,
+ });
+ });
+
+ exchangesList = await wallet.listExchanges();
+ t.assertTrue(exchangesList.exchanges.length === 0);
+
+ /*
+ * =========================================================================
+ * Check that the exchange version is also checked when
+ * the exchange is implicitly added via the suggested
+ * exchange of a bank-integrated withdrawal.
+ * =========================================================================
+ */
+
+ // Fault from above is still active!
+
+ // Create withdrawal operation
+
+ const user = await bank.createRandomBankUser();
+ const wop = await bank.createWithdrawalOperation(user, "TESTKUDOS:10");
+
+ // Hand it to the wallet
+
+ const wd = await wallet.getWithdrawalDetailsForUri({
+ talerWithdrawUri: wop.taler_withdraw_uri,
+ });
+
+ // Make sure the faulty exchange isn't used for the suggestion.
+ t.assertTrue(wd.possibleExchanges.length === 0);
+});
diff --git a/packages/taler-wallet-core/src/index.ts b/packages/taler-wallet-core/src/index.ts
index 954798e46..784c9d63b 100644
--- a/packages/taler-wallet-core/src/index.ts
+++ b/packages/taler-wallet-core/src/index.ts
@@ -75,3 +75,5 @@ export * from "./crypto/workers/nodeThreadWorker";
export * from "./types/notifications";
export { Configuration } from "./util/talerconfig";
+
+export { URL } from "./util/url"; \ No newline at end of file
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts
index 8967173ca..a7771f6d2 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -88,7 +88,7 @@ async function setExchangeError(
baseUrl: string,
err: OperationErrorDetails,
): Promise<void> {
- console.log(`last error for exchange ${baseUrl}:`, err);
+ logger.warn(`last error for exchange ${baseUrl}:`, err);
const mut = (exchange: ExchangeRecord): ExchangeRecord => {
exchange.lastError = err;
return exchange;
diff --git a/packages/taler-wallet-core/src/types/talerTypes.ts b/packages/taler-wallet-core/src/types/talerTypes.ts
index bb0118a78..99f44ea25 100644
--- a/packages/taler-wallet-core/src/types/talerTypes.ts
+++ b/packages/taler-wallet-core/src/types/talerTypes.ts
@@ -47,7 +47,7 @@ import {
Duration,
codecForDuration,
} from "../util/time";
-import { ExchangeListItem } from "./walletTypes";
+import { ExchangeListItem, codecForExchangeListItem } from "./walletTypes";
import { codecForAmountString } from "../util/amounts";
/**
@@ -940,6 +940,13 @@ export interface WithdrawUriInfoResponse {
possibleExchanges: ExchangeListItem[];
}
+export const codecForWithdrawUriInfoResponse = (): Codec<WithdrawUriInfoResponse> =>
+ buildCodecForObject<WithdrawUriInfoResponse>()
+ .property("amount", codecForAmountString())
+ .property("defaultExchangeBaseUrl", codecOptional(codecForString()))
+ .property("possibleExchanges", codecForList(codecForExchangeListItem()))
+ .build("WithdrawUriInfoResponse");
+
/**
* Response body for the following endpoint:
*
diff --git a/packages/taler-wallet-core/src/types/walletTypes.ts b/packages/taler-wallet-core/src/types/walletTypes.ts
index ec57e7d2a..6f6340520 100644
--- a/packages/taler-wallet-core/src/types/walletTypes.ts
+++ b/packages/taler-wallet-core/src/types/walletTypes.ts
@@ -554,6 +554,18 @@ export interface ExchangeListItem {
paytoUris: string[];
}
+export const codecForExchangeListItem = (): Codec<ExchangeListItem> =>
+ buildCodecForObject<ExchangeListItem>()
+ .property("currency", codecForString())
+ .property("exchangeBaseUrl", codecForString())
+ .property("paytoUris", codecForList(codecForString()))
+ .build("ExchangeListItem");
+
+export const codecForExchangesListResponse = (): Codec<ExchangesListRespose> =>
+ buildCodecForObject<ExchangesListRespose>()
+ .property("exchanges", codecForList(codecForExchangeListItem()))
+ .build("ExchangesListRespose");
+
export interface AcceptManualWithdrawalResult {
/**
* Payto URIs that can be used to fund the withdrawal.