taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit e6b58c34b64a866e71cba09cf5e5dfad0912e318
parent 6ddeb1733f58c7a7dc971f53c7602a55abc9f60a
Author: Sebastian <sebasjm@gmail.com>
Date:   Thu, 10 Apr 2025 08:49:36 -0300

test for #9719

Diffstat:
Apackages/taler-harness/src/integrationtests/test-kyc-merchant-deposit-rewrite.ts | 319+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 4+++-
2 files changed, 322 insertions(+), 1 deletion(-)

diff --git a/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit-rewrite.ts b/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit-rewrite.ts @@ -0,0 +1,319 @@ +/* + This file is part of GNU Taler + (C) 2024 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 { + codecForAccountKycRedirects, + codecForKycProcessClientInformation, + Configuration, + encodeCrock, + failOrThrow, + hashNormalizedPaytoUri, + HttpStatusCode, + j2s, + Logger, + MerchantAccountKycRedirectsResponse, + MerchantAccountKycStatus, + succeedOrThrow, + TalerCoreBankHttpClient, + TalerMerchantApi, + TalerMerchantInstanceHttpClient, + TalerWireGatewayHttpClient, + UserAndToken, +} from "@gnu-taler/taler-util"; +import { + readResponseJsonOrThrow, + readSuccessResponseJsonOrThrow, +} from "@gnu-taler/taler-util/http"; +import { + configureCommonKyc, + createKycTestkudosEnvironment, + postAmlDecisionNoRules, +} from "../harness/environments.js"; +import { + delayMs, + GlobalTestState, + harnessHttpLib, +} from "../harness/harness.js"; + +const logger = new Logger(`test-kyc-merchant-deposit-rewrite.ts`); + +function adjustExchangeConfig(config: Configuration) { + configureCommonKyc(config); + + config.setString("KYC-RULE-R1", "operation_type", "deposit"); + config.setString("KYC-RULE-R1", "enabled", "yes"); + config.setString("KYC-RULE-R1", "exposed", "yes"); + config.setString("KYC-RULE-R1", "is_and_combinator", "yes"); + config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:0"); + config.setString("KYC-RULE-R1", "timeframe", "1d"); + config.setString("KYC-RULE-R1", "next_measures", "M1"); + + config.setString("KYC-MEASURE-M1", "check_name", "C1"); + config.setString("KYC-MEASURE-M1", "context", "{}"); + config.setString("KYC-MEASURE-M1", "program", "P1"); + + config.setString("KYC-MEASURE-FM", "check_name", "SKIP"); + config.setString("KYC-MEASURE-FM", "context", "{}"); + config.setString("KYC-MEASURE-FM", "program", "P1"); + + config.setString("AML-PROGRAM-P1", "command", "/bin/true"); + config.setString("AML-PROGRAM-P1", "enabled", "true"); + config.setString("AML-PROGRAM-P1", "description", "this does nothing"); + config.setString("AML-PROGRAM-P1", "fallback", "FREEZE"); + + config.setString("KYC-CHECK-C1", "type", "INFO"); + config.setString("KYC-CHECK-C1", "description", "my check!"); + config.setString("KYC-CHECK-C1", "fallback", "FREEZE"); +} + +async function retryUntil<T, X extends T>( + fx: () => Promise<T>, + condition: (x: T, i: number) => boolean, + waitMs: number = 500, +): Promise<X> { + let i = 0; + let x = await fx(); + while (!condition(x, i)) { + logger.info( + `retrying since condition was false: ${j2s({ value: x, time: i })}`, + ); + await delayMs(waitMs); + x = await fx(); + i++; + } + return x as X; +} + +export async function runKycMerchantDepositRewriteTest(t: GlobalTestState) { + // Set up test environment + + const { + merchant, + bankClient, + exchange, + exchangeBankAccount, + wireGatewayApiClient, + amlKeypair, + } = await createKycTestkudosEnvironment(t, { + adjustExchangeConfig, + }); + + // let accountPub: string; + + // const instanceUrl = new URL("private", merchant.makeInstanceBaseUrl()); + // { + // const resp = await harnessHttpLib.fetch(instanceUrl.href); + // const parsedResp = await readSuccessResponseJsonOrThrow( + // resp, + // codecForQueryInstancesResponse(), + // ); + // accountPub = parsedResp.merchant_pub; + // } + + // const wireGatewayApiClient = new WireGatewayApiClient( + // exchangeBankAccount.wireGatewayApiBaseUrl, + // { + // auth: { + // username: "admin", + // password: "admin-password", + // }, + // }, + // ); + + const bankApi = new TalerCoreBankHttpClient( + bankClient.baseUrl, + harnessHttpLib, + ); + + const wireGatewayApi = new TalerWireGatewayHttpClient( + bankApi.getWireGatewayAPI(exchangeBankAccount.accountName).href, + exchangeBankAccount.accountName, + harnessHttpLib, + ); + + const merchantApi = new TalerMerchantInstanceHttpClient( + merchant.makeInstanceBaseUrl(), + harnessHttpLib, + ); + + let exchangeWireTarget: string | undefined; + let merchantBankAccount: string | undefined; + { + const kycStatus = await retryUntil( + async () => + succeedOrThrow<MerchantAccountKycRedirectsResponse | void>( + await merchantApi.getCurrentInstanceKycStatus(undefined), + ), + (x) => !!x, + ); + + logger.info(`mechant kyc status: ${j2s(kycStatus)}`); + t.assertTrue(!!kycStatus); + + t.assertTrue( + kycStatus.kyc_data.length > 0, + "no bank account reported by the exchange in the kyc query", + ); + const firstKycStatus = kycStatus.kyc_data[0]; + + t.assertDeepEqual( + firstKycStatus.status, + MerchantAccountKycStatus.KYC_WIRE_REQUIRED, + ); + + t.assertDeepEqual(firstKycStatus.exchange_http_status, 404); + + t.assertTrue( + (firstKycStatus.limits?.length ?? 0) > 0, + "kyc status should contain non-empty limits", + ); + + t.assertTrue( + firstKycStatus.payto_kycauths !== undefined, + "exchange should give information on how to make the auth wire transfer", + ); + t.assertTrue( + firstKycStatus.payto_kycauths.length > 0, + "exchange should at least one bank account to wire", + ); + merchantBankAccount = firstKycStatus.payto_uri; + exchangeWireTarget = firstKycStatus.payto_kycauths[0]; + } + + logger.info("We need to wire some money to ", exchangeWireTarget); + + // // Order creation should fail at this point! + const order = { + summary: "Test", + amount: "TESTKUDOS:5", + fulfillment_url: "taler://fulfillment-success/thx", + } satisfies TalerMerchantApi.Order; + + logger.info("Create an order, it may take time"); + await failOrThrow( + HttpStatusCode.Conflict, // exchange doesn't know the account yet + merchantApi.createOrder(undefined, { order }), + ); + logger.info("Creating an order failed, which is ok "); + + // const adminLogin = succeedOrThrow( + // await bankApi.createAccessTokenBasic("admin", "admin-password", { + // scope: "readwrite", + // }), + // ); + + succeedOrThrow( + await bankApi.createAccount(undefined, { + name: "merchant-default", + password: "merchant-default", + username: "merchant-default", + payto_uri: merchantBankAccount, //this bank user needs to have the same payto that the exchange is asking from + }), + ); + logger.info("Bank account created"); + + + const info = succeedOrThrow( + await merchantApi.getCurrentInstanceDetails(undefined), + ); + + // FIXME: why admin? this should be wired to the exchange bank account + await wireGatewayApiClient.adminAddKycauth({ + amount: "TESTKUDOS:0.1", + debitAccountPayto: merchantBankAccount, + accountPub: info.merchant_pub, + }); + logger.info("Money wired"); + + // wireGatewayApi.addKycAuth(exchangeBankAccount.accountPassword, { + // reserve_pub: info.merchant_pub, + // amount: "TESTKUDOS:0.1", + // debit_account: merchantBankAccount, + // }); + + let merchantAmlAccount; + { + const kycStatus = await retryUntil( + async () => + succeedOrThrow<MerchantAccountKycRedirectsResponse | void>( + await merchantApi.getCurrentInstanceKycStatus(undefined), + ), + (x) => !!x && !x.kyc_data[0].payto_kycauths, + ); + logger.info(`kyc resp 2: ${j2s(kycStatus)}`); + + t.assertTrue(!!kycStatus); + + t.assertTrue( + kycStatus.kyc_data.length > 0, + "no bank account reported by the exchange in the kyc query", + ); + const firstBankAaccount = kycStatus.kyc_data[0]; + t.assertDeepEqual(firstBankAaccount.status, MerchantAccountKycStatus.READY); + + t.assertDeepEqual(firstBankAaccount.exchange_http_status, 200); + + t.assertTrue( + (firstBankAaccount.limits?.length ?? 0) > 0, + "kyc status should contain non-empty limits", + ); + + merchantAmlAccount = encodeCrock( + hashNormalizedPaytoUri(firstBankAaccount.payto_uri), + ); + } + logger.info( + "the aml officer is going to activate the account ", + merchantAmlAccount, + ); + + await postAmlDecisionNoRules(t, { + amlPriv: amlKeypair.priv, + amlPub: amlKeypair.pub, + exchangeBaseUrl: exchange.baseUrl, + paytoHash: merchantAmlAccount, + }); + + { + const kycStatus = await retryUntil( + async () => { + // The mercant is required to signal the backend to update the information + // from the exchange + logger.info("Create an order, it may take time"); + await failOrThrow( + HttpStatusCode.Conflict, // exchange doesn't know the account yet + merchantApi.createOrder(undefined, { order }), + ); + logger.info("Creating an order failed but it's ok"); + // Now we can check the status + return succeedOrThrow<MerchantAccountKycRedirectsResponse | void>( + await merchantApi.getCurrentInstanceKycStatus(undefined), + ); + }, + (x, i) => { + return (x?.kyc_data[0].limits?.length ?? 0) === 0; + }, + ); + t.assertTrue(!!kycStatus); + logger.info(`kyc resp 3: ${j2s(kycStatus)}`); + } + +} + +runKycMerchantDepositRewriteTest.suites = ["wallet", "merchant", "kyc"]; diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -65,7 +65,6 @@ import { runKycExchangeWalletTest } from "./test-kyc-exchange-wallet.js"; import { runKycFormWithdrawalTest } from "./test-kyc-form-withdrawal.js"; import { runKycMerchantActivateBankAccountTest } from "./test-kyc-merchant-activate-bank-account.js"; import { runKycMerchantAggregateTest } from "./test-kyc-merchant-aggregate.js"; -import { runKycMerchantDepositTest } from "./test-kyc-merchant-deposit.js"; import { runKycNewMeasureTest } from "./test-kyc-new-measure.js"; import { runKycNewMeasuresProgTest } from "./test-kyc-new-measures-prog.js"; import { runKycPeerPullTest } from "./test-kyc-peer-pull.js"; @@ -160,6 +159,8 @@ import { runWithdrawalHugeTest } from "./test-withdrawal-huge.js"; import { runWithdrawalIdempotentTest } from "./test-withdrawal-idempotent.js"; import { runWithdrawalManualTest } from "./test-withdrawal-manual.js"; import { runWithdrawalPrepareTest } from "./test-withdrawal-prepare.js"; +import { runKycMerchantDepositRewriteTest } from "./test-kyc-merchant-deposit-rewrite.js"; +import { runKycMerchantDepositTest } from "./test-kyc-merchant-deposit.js"; /** * Test runner. @@ -285,6 +286,7 @@ const allTests: TestMainFunction[] = [ runKycTwoFormsTest, runKycDepositDepositTest, runKycMerchantDepositTest, + runKycMerchantDepositRewriteTest, runKycMerchantAggregateTest, runKycDepositDepositKyctransferTest, runWithdrawalPrepareTest,