taler-typescript-core

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

commit 5e8bd098b1e1aac592ae893e2798d69aaac6d78c
parent 0b9ea45bfa604bebb76b4655dd81a7552007d6d9
Author: Sebastian <sebasjm@gmail.com>
Date:   Tue,  3 Dec 2024 11:13:02 -0300

test using multiple form in kyc measures

Diffstat:
Mpackages/taler-harness/src/harness/environments.ts | 13+++++++++----
Mpackages/taler-harness/src/index.ts | 8++++++++
Apackages/taler-harness/src/integrationtests/test-kyc-two-forms.ts | 399+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 2++
4 files changed, 418 insertions(+), 4 deletions(-)

diff --git a/packages/taler-harness/src/harness/environments.ts b/packages/taler-harness/src/harness/environments.ts @@ -1101,9 +1101,14 @@ export async function postAmlDecision( t.assertDeepEqual(resp.status, HttpStatusCode.NoContent); } +function defaultOnNotification(n: WalletNotification):void { + console.log("wallet-core notification", n); +} + + export interface KycEnvOptions { coinConfig?: CoinConfig[]; - + onWalletNotification?: (n: WalletNotification) => void; adjustExchangeConfig?(config: Configuration): void; } @@ -1118,6 +1123,7 @@ export interface KycTestEnv { merchant: MerchantService; } + export async function createKycTestkudosEnvironment( t: GlobalTestState, opts: KycEnvOptions = {}, @@ -1206,12 +1212,11 @@ export async function createKycTestkudosEnvironment( await walletService.start(); await walletService.pingUntilAvailable(); + const walletClient = new WalletClient({ name: "wallet", unixPath: walletService.socketPath, - onNotification(n) { - console.log("wallet-core notification", n); - }, + onNotification: opts.onWalletNotification ?? defaultOnNotification, }); await walletClient.connect(); await walletClient.client.call(WalletApiOperation.InitWallet, { diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts @@ -83,6 +83,7 @@ import { import { getTestInfo, runTests } from "./integrationtests/testrunner.js"; import { lintExchangeDeployment } from "./lint.js"; import { AML_PROGRAM_FROM_ATTRIBUTES_TO_CONTEXT } from "integrationtests/test-kyc-skip-expiration.js"; +import { AML_PROGRAM_NEXT_MEASURE_FORM } from "integrationtests/test-kyc-two-forms.js"; const logger = new Logger("taler-harness:index.ts"); @@ -1451,12 +1452,14 @@ const allAmlPrograms: TalerKycAml.AmlProgramDefinition[] = [ requiredContext: [], }, AML_PROGRAM_FROM_ATTRIBUTES_TO_CONTEXT, + AML_PROGRAM_NEXT_MEASURE_FORM, ]; amlProgramCli .subcommand("run", "run-program") .requiredOption("name", ["-n", "--name"], clk.STRING) .flag("requires", ["-r"]) + .flag("inputs", ["-i"]) .flag("attributes", ["-a"]) .maybeOption("config", ["-c", "--config"], clk.STRING) .action(async (args) => { @@ -1473,6 +1476,11 @@ amlProgramCli console.log(found.requiredContext.join("\n")); return; } + if (args.run.inputs) { + logger.info("Reporting requirements"); + console.log(found.requiredInputs.join("\n")); + return; + } if (args.run.attributes) { logger.info("reporting attributes"); diff --git a/packages/taler-harness/src/integrationtests/test-kyc-two-forms.ts b/packages/taler-harness/src/integrationtests/test-kyc-two-forms.ts @@ -0,0 +1,399 @@ +/* + 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 { + AbsoluteTime, + AmountString, + amountToBuffer, + buildSigPS, + codecForAccountKycStatus, + codecForAny, + codecForKycProcessClientInformation, + codecForLegitimizationNeededResponse, + codecOptional, + Configuration, + createNewWalletKycAccount, + decodeCrock, + eddsaSign, + encodeCrock, + j2s, + Logger, + signAmlQuery, + TalerKycAml, + TalerProtocolTimestamp, + TalerSignaturePurpose, + TransactionIdStr, + TransactionMajorState, + TransactionMinorState, + WalletKycRequest, +} from "@gnu-taler/taler-util"; +import { readResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { + createKycTestkudosEnvironment, + postAmlDecision, + withdrawViaBankV3, +} from "../harness/environments.js"; +import { GlobalTestState, harnessHttpLib, waitMs } from "../harness/harness.js"; + +const logger = new Logger("test-kyc-two-forms.ts"); + +export const AML_PROGRAM_NEXT_MEASURE_FORM: TalerKycAml.AmlProgramDefinition = { + name: "TWO_FORMS", + logic: (input, config) => { + const outcome: TalerKycAml.AmlOutcome = { + to_investigate: false, + properties: {}, + events: [], + new_rules: { + expiration_time: { t_s: 1 }, + rules: [], + successor_measure: "M2", + custom_measures: {}, + }, + }; + logger.info("aml program TWO FORMS outcome", j2s(outcome)); + + return outcome; + }, + requiredAttributes: [], + requiredInputs: [], + requiredContext: [], +}; + +function adjustExchangeConfig(config: Configuration) { + config.setString("exchange", "enable_kyc", "yes"); + + // config.setString("KYC-RULE-R1", "operation_type", "withdraw"); + // config.setString("KYC-RULE-R1", "enabled", "yes"); + // config.setString("KYC-RULE-R1", "exposed", "yes"); + // config.setString("KYC-RULE-R1", "is_and_combinator", "no"); + // config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5"); + // config.setString("KYC-RULE-R1", "timeframe", "1d"); + // config.setString("KYC-RULE-R1", "next_measures", "M1"); + + config.setString("KYC-RULE-R1", "operation_type", "balance"); + config.setString("KYC-RULE-R1", "enabled", "yes"); + config.setString("KYC-RULE-R1", "exposed", "yes"); + config.setString("KYC-RULE-R1", "is_and_combinator", "no"); + config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5"); + config.setString("KYC-RULE-R1", "timeframe", "forever"); + 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-M2", "check_name", "C2"); + config.setString("KYC-MEASURE-M2", "context", "{}"); + config.setString("KYC-MEASURE-M2", "program", "P2"); + + config.setString("KYC-MEASURE-M3", "check_name", "C3"); + config.setString("KYC-MEASURE-M3", "context", "{}"); + config.setString("KYC-MEASURE-M3", "program", "NOP"); + + config.setString("KYC-MEASURE-M4", "check_name", "C4"); + config.setString("KYC-MEASURE-M4", "context", "{}"); + config.setString("KYC-MEASURE-M4", "program", "NOP"); + + config.setString("KYC-MEASURE-MF", "check_name", "SKIP"); + config.setString("KYC-MEASURE-MF", "context", "{}"); + config.setString("KYC-MEASURE-MF", "program", "NOP"); + + config.setString( + "AML-PROGRAM-P1", + "command", + "taler-harness aml-program run-program --name TWO_FORMS", + ); + config.setString("AML-PROGRAM-P1", "enabled", "true"); + config.setString("AML-PROGRAM-P1", "description", "remove all rules"); + config.setString("AML-PROGRAM-P1", "description_i18n", "{}"); + config.setString("AML-PROGRAM-P1", "fallback", "M1"); + + config.setString("AML-PROGRAM-P2", "command", "/bin/true"); + config.setString("AML-PROGRAM-P2", "enabled", "true"); + config.setString("AML-PROGRAM-P2", "description", "does nothing"); + config.setString("AML-PROGRAM-P2", "description_i18n", "{}"); + config.setString("AML-PROGRAM-P2", "fallback", "M1"); + + config.setString("AML-PROGRAM-NOP", "command", "/bin/true"); + config.setString("AML-PROGRAM-NOP", "enabled", "true"); + config.setString( + "AML-PROGRAM-NOP", + "description", + "does nothing (never used)", + ); + config.setString("AML-PROGRAM-NOP", "description_i18n", "{}"); + config.setString("AML-PROGRAM-NOP", "fallback", "MF"); + + config.setString("KYC-CHECK-C1", "type", "FORM"); + config.setString("KYC-CHECK-C1", "form_name", "firstForm"); + config.setString("KYC-CHECK-C1", "description", "starting check!"); + config.setString("KYC-CHECK-C1", "description_i18n", "{}"); + config.setString("KYC-CHECK-C1", "outputs", "NAME"); + config.setString("KYC-CHECK-C1", "fallback", "MF"); + + config.setString("KYC-CHECK-C2", "type", "FORM"); + config.setString("KYC-CHECK-C2", "form_name", "secondForm"); + config.setString("KYC-CHECK-C2", "description", "final check!"); + config.setString("KYC-CHECK-C2", "description_i18n", "{}"); + config.setString("KYC-CHECK-C2", "outputs", "FINAL"); + config.setString("KYC-CHECK-C2", "fallback", "MF"); + + config.setString("KYC-CHECK-C3", "type", "FORM"); + config.setString("KYC-CHECK-C3", "form_name", "thirdForm"); + config.setString( + "KYC-CHECK-C3", + "description", + "this is check c3 (never used)", + ); + config.setString("KYC-CHECK-C3", "description_i18n", "{}"); + config.setString("KYC-CHECK-C3", "fallback", "MF"); + + config.setString("KYC-CHECK-C4", "type", "FORM"); + config.setString("KYC-CHECK-C4", "form_name", "fourthForm"); + config.setString( + "KYC-CHECK-C4", + "description", + "this is check c4 (never used)", + ); + config.setString("KYC-CHECK-C4", "description_i18n", "{}"); + config.setString("KYC-CHECK-C4", "fallback", "MF"); +} + +/** + * Test setting a `new_measure` as the AML officer. + */ +export async function runKycTwoFormsTest(t: GlobalTestState) { + // Set up test environment + + const { exchange, amlKeypair } = await createKycTestkudosEnvironment(t, { + adjustExchangeConfig, + onWalletNotification: () => {}, + }); + + // Withdraw digital cash into the wallet. + let kycPaytoHash: string; + let accessToken: string; + let latestFormId: string; + + // { + // logger.info("step 1) Withdraw to trigger AML and 2) get access token") + // const wres = await withdrawViaBankV3(t, { + // amount: "TESTKUDOS:20", + // bankClient, + // exchange, + // walletClient, + // }); + + // await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + // transactionId: wres.transactionId as TransactionIdStr, + // txState: { + // major: TransactionMajorState.Pending, + // minor: TransactionMinorState.KycRequired, + // }, + // }); + + // const txDetails = await walletClient.call( + // WalletApiOperation.GetTransactionById, + // { + // transactionId: wres.transactionId, + // }, + // ); + + // accessToken = txDetails.kycAccessToken; + // kycPaytoHash = txDetails.kycPaytoHash; + // firstTransaction = wres.transactionId; + // } + const account = await createNewWalletKycAccount(new Uint8Array()); + { + logger.info("step 1) Check balance to trigger AML"); + + const balance: AmountString = "TESTKUDOS:20"; + const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_ACCOUNT_SETUP) + .put(amountToBuffer(balance)) + .build(); + const body: WalletKycRequest = { + balance, + reserve_pub: account.id, + reserve_sig: encodeCrock(eddsaSign(sigBlob, account.signingKey)), + }; + const infoResp = await harnessHttpLib.fetch( + new URL(`kyc-wallet`, exchange.baseUrl).href, + { + method: "POST", + body, + }, + ); + + t.assertDeepEqual(infoResp.status, 451); + const clientInfo = await readResponseJsonOrThrow( + infoResp, + codecOptional(codecForLegitimizationNeededResponse()), + ); + + t.assertTrue(clientInfo?.h_payto !== undefined); + kycPaytoHash = clientInfo?.h_payto; + } + + { + logger.info("step 2) Get account access token"); + const sigBlob = buildSigPS(TalerSignaturePurpose.KYC_AUTH).build(); + + const infoResp = await harnessHttpLib.fetch( + new URL(`kyc-check/${kycPaytoHash}`, exchange.baseUrl).href, + { + headers: { + "Account-Owner-Signature": encodeCrock( + eddsaSign(sigBlob, account.signingKey), + ), + }, + }, + ); + + t.assertDeepEqual(infoResp.status, 202); + const clientInfo = await readResponseJsonOrThrow( + infoResp, + codecOptional(codecForAccountKycStatus()), + ); + t.assertTrue(clientInfo?.access_token !== undefined); + accessToken = clientInfo?.access_token; + } + + // { + // // step 2) Check KYC info + // const infoResp = await harnessHttpLib.fetch( + // new URL(`kyc-info/${accessToken}`, exchange.baseUrl).href, + // ); + + // const clientInfo = await readResponseJsonOrThrow( + // infoResp, + // codecOptional(codecForKycProcessClientInformation()), + // ); + + // console.log(j2s(clientInfo)); + // t.assertDeepEqual(infoResp.status, 200); + // t.assertDeepEqual(clientInfo?.requirements.length, 1); + // t.assertDeepEqual(clientInfo?.requirements[0].form, "firstForm"); + // } + + { + logger.info("step 3) Check KYC info, should be waiting for the first form"); + const infoResp = await harnessHttpLib.fetch( + new URL(`kyc-info/${accessToken}?timeout_ms=1000`, exchange.baseUrl).href, + ); + const clientInfo = await readResponseJsonOrThrow( + infoResp, + codecOptional(codecForKycProcessClientInformation()), + ); + + console.log(j2s(clientInfo)); + t.assertDeepEqual(infoResp.status, 200); + t.assertDeepEqual(clientInfo?.requirements.length, 1); + t.assertDeepEqual(clientInfo?.requirements[0].form, "firstForm"); + t.assertTrue(!!clientInfo?.requirements[0].id); + latestFormId = clientInfo?.requirements[0].id; + } + + { + logger.info("step 4) Complete form"); + const infoResp = await harnessHttpLib.fetch( + new URL(`kyc-upload/${latestFormId}`, exchange.baseUrl).href, + { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: "NAME=who", + }, + ); + + t.assertDeepEqual(infoResp.status, 204); + } + + { + logger.info( + "step 5) Check KYC info again, should see the second form but this time is too fast", + ); + + /////////////////////////////////////////// + // if the request is too early id result in garbage + // can't be ignored because browser see this + /////////////////////////////////////////// + { + const infoResp = await harnessHttpLib.fetch( + new URL(`kyc-info/${accessToken}?timeout_ms=1000`, exchange.baseUrl) + .href, + ); + { + const clientInfo = await readResponseJsonOrThrow( + infoResp, + codecOptional(codecForKycProcessClientInformation()), + ); + + console.log(j2s(clientInfo)); + // t.assertDeepEqual(infoResp.status, 200); + // t.assertDeepEqual(clientInfo?.requirements.length, 1); + // t.assertDeepEqual(clientInfo?.requirements[0].form, "secondForm"); + + } + } + } + + await waitMs(2000); + { + logger.info("step 6) Check KYC info again after some time, should see the second form"); + //doing a second request shouldnt fail + const infoResp = await harnessHttpLib.fetch( + new URL(`kyc-info/${accessToken}?timeout_ms=1000`, exchange.baseUrl) + .href, + ); + + const clientInfo = await readResponseJsonOrThrow( + infoResp, + codecOptional(codecForKycProcessClientInformation()), + ); + + console.log(j2s(clientInfo)); + t.assertDeepEqual(infoResp.status, 200); + t.assertDeepEqual(clientInfo?.requirements.length, 1); + t.assertDeepEqual(clientInfo?.requirements[0].form, "secondForm"); + } + + await waitMs(2000); + + { + logger.info("step 6) Check KYC info again after some time, here the exchange fails"); + const infoResp = await harnessHttpLib.fetch( + new URL(`kyc-info/${accessToken}?timeout_ms=1000`, exchange.baseUrl).href, + ); + + const clientInfo = await readResponseJsonOrThrow( + infoResp, + codecOptional(codecForKycProcessClientInformation()), + ); + + console.log(j2s(clientInfo)); + t.assertDeepEqual(infoResp.status, 200); + t.assertDeepEqual(clientInfo?.requirements.length, 1); + t.assertDeepEqual(clientInfo?.requirements[0].form, "secondForm"); + } +} + +runKycTwoFormsTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -148,6 +148,7 @@ 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 { runKycTwoFormsTest } from "./test-kyc-two-forms.js"; /** * Test runner. @@ -274,6 +275,7 @@ const allTests: TestMainFunction[] = [ runKycBalanceWithdrawalTest, runKycNewMeasureTest, runKycSkipExpirationTest, + runKycTwoFormsTest, runKycDepositDepositTest, runKycMerchantDepositTest, runKycMerchantAggregateTest,