aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-11-21 15:00:14 +0100
committerFlorian Dold <florian@dold.me>2023-11-21 15:00:24 +0100
commit6000a55d583832a71335310514688f1f6faed722 (patch)
treec9b5ae2ca9f2511c75e3ed375ae5bbeade44ac66
parent77eb29cb7caf50e870ada861a4ef83712a31a1cb (diff)
downloadwallet-core-6000a55d583832a71335310514688f1f6faed722.tar.xz
towards a currency conversion integration test
-rw-r--r--packages/taler-harness/src/harness/harness.ts6
-rw-r--r--packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts247
-rw-r--r--packages/taler-harness/src/integrationtests/testrunner.ts2
-rw-r--r--packages/taler-util/src/merchant-api-types.ts8
-rw-r--r--packages/taler-util/src/taler-types.ts48
-rw-r--r--packages/taler-util/src/wallet-types.ts20
-rw-r--r--packages/taler-wallet-core/src/operations/exchanges.ts16
7 files changed, 282 insertions, 65 deletions
diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts
index 1253e3dd5..37e0b02a7 100644
--- a/packages/taler-harness/src/harness/harness.ts
+++ b/packages/taler-harness/src/harness/harness.ts
@@ -45,6 +45,7 @@ import {
j2s,
parsePaytoUri,
stringToBytes,
+ AccountRestriction,
} from "@gnu-taler/taler-util";
import {
HttpRequestLibrary,
@@ -589,6 +590,11 @@ export interface HarnessExchangeBankAccount {
accountPassword: string;
accountPaytoUri: string;
wireGatewayApiBaseUrl: string;
+
+ conversionUrl?: string;
+
+ debitRestrictions?: AccountRestriction[];
+ creditRestrictions?: AccountRestriction[];
}
/**
diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts
new file mode 100644
index 000000000..2a9dd5800
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts
@@ -0,0 +1,247 @@
+/*
+ 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 {
+ AbsoluteTime,
+ AmountString,
+ Duration,
+ Logger,
+ TalerCorebankApiClient,
+ WireGatewayApiClient,
+ j2s,
+} from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import {
+ BankService,
+ ExchangeService,
+ GlobalTestState,
+ MerchantService,
+ generateRandomPayto,
+ setupDb,
+} from "../harness/harness.js";
+import { createWalletDaemonWithClient } from "../harness/helpers.js";
+import { defaultCoinConfig } from "../harness/denomStructures.js";
+import * as http from "node:http";
+
+const logger = new Logger("test-withdrawal-conversion.ts");
+
+interface TestfakeConversionService {
+ stop: () => void;
+}
+
+function splitInTwoAt(s: string, separator: string): [string, string] {
+ const idx = s.indexOf(separator);
+ if (idx === -1) {
+ return [s, ""];
+ }
+ return [s.slice(0, idx), s.slice(idx + 1)];
+}
+
+/**
+ * Testfake for the kyc service that the exchange talks to.
+ */
+async function runTestfakeConversionService(): Promise<TestfakeConversionService> {
+ const server = http.createServer((req, res) => {
+ const requestUrl = req.url!;
+ logger.info(`kyc: got ${req.method} request, ${requestUrl}`);
+
+ const [path, query] = splitInTwoAt(requestUrl, "?");
+
+ const qp = new URLSearchParams(query);
+
+ if (path === "/config") {
+ res.writeHead(200, { "Content-Type": "application/json" });
+ res.end(
+ JSON.stringify({
+ version: "0:0:0",
+ name: "taler-conversion-info",
+ regional_currency: {},
+ fiat_currency: {},
+ }),
+ );
+ } else if (path === "/cashin-rate") {
+ res.writeHead(200, { "Content-Type": "application/json" });
+ res.end(
+ JSON.stringify({
+ amount_debit: "FOO:123",
+ amount_credit: "BAR:123",
+ }),
+ );
+ } else {
+ res.writeHead(400, { "Content-Type": "application/json" });
+ res.end(JSON.stringify({ code: 1, message: "bad request" }));
+ }
+ });
+ await new Promise<void>((resolve, reject) => {
+ server.listen(8071, () => resolve());
+ });
+ return {
+ stop() {
+ server.close();
+ },
+ };
+}
+
+/**
+ * Test for currency conversion during manual withdrawal.
+ */
+export async function runWithdrawalConversionTest(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",
+ );
+ exchangeBankAccount.conversionUrl = "http://localhost:8071/";
+ await exchange.addBankAccount("1", exchangeBankAccount);
+
+ 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")],
+ defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ });
+
+ await merchant.addInstanceWithWireAccount({
+ id: "minst1",
+ name: "minst1",
+ paytoUris: [generateRandomPayto("minst1")],
+ defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ });
+
+ const { walletClient, walletService } = await createWalletDaemonWithClient(
+ t,
+ { name: "wallet" },
+ );
+
+ await runTestfakeConversionService();
+
+ // Create a withdrawal operation
+
+ const bankAccessApiClient = new TalerCorebankApiClient(
+ bank.corebankApiBaseUrl,
+ );
+
+ const user = await bankAccessApiClient.createRandomBankUser();
+
+ await walletClient.call(WalletApiOperation.AddExchange, {
+ exchangeBaseUrl: exchange.baseUrl,
+ });
+
+ const infoRes = walletClient.call(
+ WalletApiOperation.GetWithdrawalDetailsForAmount,
+ {
+ exchangeBaseUrl: exchange.baseUrl,
+ amount: "EXTCOIN:20" as AmountString,
+ },
+ );
+
+ console.log(`withdrawal details: ${j2s(infoRes)}`);
+
+ const tStart = AbsoluteTime.now();
+
+ logger.info("starting AcceptManualWithdrawal request");
+ // We expect this to return immediately.
+
+ const wres = await walletClient.call(
+ WalletApiOperation.AcceptManualWithdrawal,
+ {
+ exchangeBaseUrl: exchange.baseUrl,
+ amount: "TESTKUDOS:10" as AmountString,
+ },
+ );
+
+ logger.info("AcceptManualWithdrawal finished");
+ logger.info(`result: ${j2s(wres)}`);
+
+ // Check that the request did not go into long-polling.
+ const duration = AbsoluteTime.difference(tStart, AbsoluteTime.now());
+ if (typeof duration.d_ms !== "number" || duration.d_ms > 5 * 1000) {
+ throw Error("withdrawal took too long (longpolling issue)");
+ }
+
+ const reservePub: string = wres.reservePub;
+
+ const wireGatewayApiClient = new WireGatewayApiClient(
+ exchangeBankAccount.wireGatewayApiBaseUrl,
+ {
+ auth: {
+ username: exchangeBankAccount.accountName,
+ password: exchangeBankAccount.accountPassword,
+ },
+ },
+ );
+
+ await wireGatewayApiClient.adminAddIncoming({
+ amount: "TESTKUDOS:10",
+ debitAccountPayto: user.accountPaytoUri,
+ reservePub: reservePub,
+ });
+
+ await exchange.runWirewatchOnce();
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
+
+ // Check balance
+
+ const balResp = await walletClient.call(WalletApiOperation.GetBalances, {});
+ t.assertAmountEquals("TESTKUDOS:9.72", balResp.balances[0].available);
+}
+
+runWithdrawalConversionTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
index 25bce5712..b363e58a9 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -95,6 +95,7 @@ import { runWalletGenDbTest } from "./test-wallet-gendb.js";
import { runLibeufinBankTest } from "./test-libeufin-bank.js";
import { runMultiExchangeTest } from "./test-multiexchange.js";
import { runAgeRestrictionsDepositTest } from "./test-age-restrictions-deposit.js";
+import { runWithdrawalConversionTest } from "./test-withdrawal-conversion.js";
/**
* Test runner.
@@ -173,6 +174,7 @@ const allTests: TestMainFunction[] = [
runWithdrawalBankIntegratedTest,
runWithdrawalFakebankTest,
runWithdrawalFeesTest,
+ runWithdrawalConversionTest,
runWithdrawalHugeTest,
runTermOfServiceFormatTest,
runStoredBackupsTest,
diff --git a/packages/taler-util/src/merchant-api-types.ts b/packages/taler-util/src/merchant-api-types.ts
index 9a7740088..999246597 100644
--- a/packages/taler-util/src/merchant-api-types.ts
+++ b/packages/taler-util/src/merchant-api-types.ts
@@ -44,8 +44,8 @@ import {
TalerProtocolDuration,
codecForTimestamp,
TalerProtocolTimestamp,
- WireAccount,
- codecForWireAccount,
+ ExchangeWireAccount,
+ codecForExchangeWireAccount,
codecForList,
FacadeCredentials,
} from "@gnu-taler/taler-util";
@@ -376,13 +376,13 @@ export interface MerchantReserveCreateConfirmation {
reserve_pub: EddsaPublicKeyString;
// Wire accounts of the exchange where to transfer the funds.
- accounts: WireAccount[];
+ accounts: ExchangeWireAccount[];
}
export const codecForMerchantReserveCreateConfirmation =
(): Codec<MerchantReserveCreateConfirmation> =>
buildCodecForObject<MerchantReserveCreateConfirmation>()
- .property("accounts", codecForList(codecForWireAccount()))
+ .property("accounts", codecForList(codecForExchangeWireAccount()))
.property("reserve_pub", codecForString())
.build("MerchantReserveCreateConfirmation");
diff --git a/packages/taler-util/src/taler-types.ts b/packages/taler-util/src/taler-types.ts
index c0c8cc17d..5774f09f7 100644
--- a/packages/taler-util/src/taler-types.ts
+++ b/packages/taler-util/src/taler-types.ts
@@ -761,7 +761,7 @@ export class ExchangeKeysJson {
global_fees: GlobalFees[];
- accounts: AccountInfo[];
+ accounts: ExchangeWireAccount[];
wire_fees: { [methodName: string]: WireFeesJson[] };
@@ -939,20 +939,6 @@ export class WireFeesJson {
end_date: TalerProtocolTimestamp;
}
-export interface AccountInfo {
- payto_uri: string;
- master_sig: string;
- // Will become mandatory in later protocol versions
- conversion_url?: string;
- credit_restrictions?: any;
- debit_restrictions?: any;
-}
-
-/**
- * @deprecated
- */
-export interface ExchangeWireJson { }
-
/**
* Proposal returned from the contract URL.
*/
@@ -1516,7 +1502,7 @@ export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> =>
.property("version", codecForString())
.property("reserve_closing_delay", codecForDuration)
.property("global_fees", codecForList(codecForGlobalFees()))
- .property("accounts", codecForList(codecForAccountInfo()))
+ .property("accounts", codecForList(codecForExchangeWireAccount()))
.property("wire_fees", codecForMap(codecForList(codecForWireFeesJson())))
.property("denominations", codecForList(codecForNgDenominations))
.build("ExchangeKeysJson");
@@ -1530,15 +1516,6 @@ export const codecForWireFeesJson = (): Codec<WireFeesJson> =>
.property("end_date", codecForTimestamp)
.build("WireFeesJson");
-export const codecForAccountInfo = (): Codec<AccountInfo> =>
- buildCodecForObject<AccountInfo>()
- .property("payto_uri", codecForString())
- .property("master_sig", codecForString())
- .property("conversion_url", codecOptional(codecForString()))
- .property("credit_restrictions", codecForAny())
- .property("debit_restrictions", codecForAny())
- .build("AccountInfo");
-
export const codecForProposal = (): Codec<Proposal> =>
buildCodecForObject<Proposal>()
.property("contract_terms", codecForAny())
@@ -1568,13 +1545,14 @@ export const codecForWithdrawOperationStatusResponse =
.property("wire_types", codecForList(codecForString()))
.build("WithdrawOperationStatusResponse");
-export const codecForRewardPickupGetResponse = (): Codec<RewardPickupGetResponse> =>
- buildCodecForObject<RewardPickupGetResponse>()
- .property("reward_amount", codecForString())
- .property("exchange_url", codecForString())
- .property("next_url", codecOptional(codecForString()))
- .property("expiration", codecForTimestamp)
- .build("TipPickupGetResponse");
+export const codecForRewardPickupGetResponse =
+ (): Codec<RewardPickupGetResponse> =>
+ buildCodecForObject<RewardPickupGetResponse>()
+ .property("reward_amount", codecForString())
+ .property("exchange_url", codecForString())
+ .property("next_url", codecOptional(codecForString()))
+ .property("expiration", codecForTimestamp)
+ .build("TipPickupGetResponse");
export const codecForRecoupConfirmation = (): Codec<RecoupConfirmation> =>
buildCodecForObject<RecoupConfirmation>()
@@ -2376,7 +2354,7 @@ export interface RegexAccountRestriction {
human_hint_i18n?: InternationalizedString;
}
-export interface WireAccount {
+export interface ExchangeWireAccount {
// payto:// URI identifying the account and wire method
payto_uri: string;
@@ -2401,8 +2379,8 @@ export interface WireAccount {
master_sig: EddsaSignatureString;
}
-export const codecForWireAccount = (): Codec<WireAccount> =>
- buildCodecForObject<WireAccount>()
+export const codecForExchangeWireAccount = (): Codec<ExchangeWireAccount> =>
+ buildCodecForObject<ExchangeWireAccount>()
.property("conversion_url", codecOptional(codecForString()))
.property("credit_restrictions", codecForList(codecForAny()))
.property("debit_restrictions", codecForList(codecForAny()))
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index 7a4ad91e8..148117673 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -58,11 +58,13 @@ import {
DenomKeyType,
DenominationPubKey,
ExchangeAuditor,
+ ExchangeWireAccount,
InternationalizedString,
MerchantContractTerms,
MerchantInfo,
PeerContractTerms,
UnblindedSignature,
+ codecForExchangeWireAccount,
codecForMerchantContractTerms,
codecForPeerContractTerms,
} from "./taler-types.js";
@@ -1123,19 +1125,11 @@ export interface WireFee {
sig: string;
}
-/**
- * Information about one of the exchange's bank accounts.
- */
-export interface ExchangeAccount {
- payto_uri: string;
- master_sig: string;
-}
-
export type WireFeeMap = { [wireMethod: string]: WireFee[] };
export interface WireInfo {
feesForType: WireFeeMap;
- accounts: ExchangeAccount[];
+ accounts: ExchangeWireAccount[];
}
export interface ExchangeGlobalFees {
@@ -1154,12 +1148,6 @@ export interface ExchangeGlobalFees {
signature: string;
}
-const codecForExchangeAccount = (): Codec<ExchangeAccount> =>
- buildCodecForObject<ExchangeAccount>()
- .property("payto_uri", codecForString())
- .property("master_sig", codecForString())
- .build("codecForExchangeAccount");
-
const codecForWireFee = (): Codec<WireFee> =>
buildCodecForObject<WireFee>()
.property("sig", codecForString())
@@ -1172,7 +1160,7 @@ const codecForWireFee = (): Codec<WireFee> =>
const codecForWireInfo = (): Codec<WireInfo> =>
buildCodecForObject<WireInfo>()
.property("feesForType", codecForMap(codecForList(codecForWireFee())))
- .property("accounts", codecForList(codecForExchangeAccount()))
+ .property("accounts", codecForList(codecForExchangeWireAccount()))
.build("codecForWireInfo");
export interface DenominationInfo {
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts
index 82d7b42bf..622f04bd3 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -19,23 +19,19 @@
*/
import {
AbsoluteTime,
- AccountInfo,
Amounts,
CancellationToken,
canonicalizeBaseUrl,
codecForExchangeKeysJson,
- DenomGroup,
DenominationPubKey,
DenomKeyType,
Duration,
durationFromSpec,
encodeCrock,
ExchangeAuditor,
- ExchangeDenomination,
- ExchangeEntryStatus,
ExchangeGlobalFees,
ExchangeSignKeyJson,
- ExchangeWireJson,
+ ExchangeWireAccount,
GlobalFees,
hashDenomPub,
j2s,
@@ -58,10 +54,10 @@ import {
WireInfo,
} from "@gnu-taler/taler-util";
import {
+ getExpiry,
HttpRequestLibrary,
- readSuccessResponseTextOrThrow,
readSuccessResponseJsonOrThrow,
- getExpiry,
+ readSuccessResponseTextOrThrow,
} from "@gnu-taler/taler-util/http";
import {
DenominationRecord,
@@ -79,7 +75,7 @@ import {
timestampProtocolToDb,
WalletDbReadWriteTransaction,
} from "../index.js";
-import { InternalWalletState, TrustInfo } from "../internal-wallet-state.js";
+import { InternalWalletState } from "../internal-wallet-state.js";
import { checkDbInvariant } from "../util/invariants.js";
import {
DbAccess,
@@ -88,10 +84,10 @@ import {
} from "../util/query.js";
import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "../versions.js";
import {
- TaskRunResultType,
runTaskWithErrorReporting,
TaskIdentifiers,
TaskRunResult,
+ TaskRunResultType,
} from "./common.js";
const logger = new Logger("exchanges.ts");
@@ -380,7 +376,7 @@ interface ExchangeKeysDownloadResult {
recoup: Recoup[];
listIssueDate: TalerProtocolTimestamp;
globalFees: GlobalFees[];
- accounts: AccountInfo[];
+ accounts: ExchangeWireAccount[];
wireFees: { [methodName: string]: WireFeesJson[] };
}