diff options
Diffstat (limited to 'packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts')
-rw-r--r-- | packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts | 304 |
1 files changed, 304 insertions, 0 deletions
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..8351e5251 --- /dev/null +++ b/packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts @@ -0,0 +1,304 @@ +/* + 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, + Amounts, + Duration, + Logger, + TalerBankConversionApi, + TalerCorebankApiClient, + TransactionType, + WireGatewayApiClient, + WithdrawalType, + j2s, +} from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import * as http from "node:http"; +import { defaultCoinConfig } from "../harness/denomStructures.js"; +import { + BankService, + ExchangeService, + GlobalTestState, + MerchantService, + generateRandomPayto, + setupDb, +} from "../harness/harness.js"; +import { createWalletDaemonWithClient } from "../harness/helpers.js"; + +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: "FOO", + fiat_currency: "BAR", + regional_currency_specification: { + alt_unit_names: {}, + name: "FOO", + num_fractional_input_digits: 2, + num_fractional_normal_digits: 2, + num_fractional_trailing_zero_digits: 2, + }, + fiat_currency_specification: { + alt_unit_names: {}, + name: "BAR", + num_fractional_input_digits: 2, + num_fractional_normal_digits: 2, + num_fractional_trailing_zero_digits: 2, + }, + conversion_rate: { + cashin_fee: "A:1" as AmountString, + cashin_min_amount: "A:0.1" as AmountString, + cashin_ratio: "1", + cashin_rounding_mode: "zero", + cashin_tiny_amount: "A:1" as AmountString, + cashout_fee: "A:1" as AmountString, + cashout_min_amount: "A:0.1" as AmountString, + cashout_ratio: "1", + cashout_rounding_mode: "zero", + cashout_tiny_amount: "A:1" as AmountString, + } + } satisfies TalerBankConversionApi.IntegrationConfig), + ); + } 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 = await walletClient.call( + WalletApiOperation.GetWithdrawalDetailsForAmount, + { + exchangeBaseUrl: exchange.baseUrl, + amount: "TESTKUDOS:20" as AmountString, + }, + ); + + console.log(`withdrawal details: ${j2s(infoRes)}`); + + const checkTransferAmount = infoRes.withdrawalAccountsList[0].transferAmount; + t.assertTrue(checkTransferAmount != null); + t.assertAmountEquals(checkTransferAmount, "FOO:123"); + + 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)}`); + + const acceptedTransferAmount = wres.withdrawalAccountsList[0].transferAmount; + t.assertTrue(acceptedTransferAmount != null); + + t.assertAmountEquals(acceptedTransferAmount, "FOO:123"); + + const txInfo = await walletClient.call( + WalletApiOperation.GetTransactionById, + { + transactionId: wres.transactionId, + }, + ); + + t.assertDeepEqual(txInfo.type, TransactionType.Withdrawal); + t.assertDeepEqual( + txInfo.withdrawalDetails.type, + WithdrawalType.ManualTransfer, + ); + t.assertTrue(!!txInfo.withdrawalDetails.exchangeCreditAccountDetails); + t.assertDeepEqual( + txInfo.withdrawalDetails.exchangeCreditAccountDetails[0].transferAmount, + "FOO:123", + ); + + // 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"]; |