taler-typescript-core

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

commit d495a1b4103735520e113d8e7f4c4de08bf7ce20
parent d8b695258e97411d1921f03b83703419aa0bfd88
Author: Florian Dold <florian@dold.me>
Date:   Tue, 12 May 2026 12:15:42 +0200

harness: fix kyc-two-forms test, add new test

The kyc-two-forms test simply had some bad test assertions. The new
(failing!) kyc-form-validation test shows that the exchange currently
does not properly validate form IDs / allows submitting any form at any
time.

Diffstat:
Mpackages/taler-harness/src/index.ts | 2+-
Apackages/taler-harness/src/integrationtests/test-kyc-form-validation.ts | 196+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/integrationtests/test-kyc-two-forms.ts | 97++++++++++++++++++++-----------------------------------------------------------
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 2++
4 files changed, 223 insertions(+), 74 deletions(-)

diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts @@ -80,7 +80,6 @@ import { import { deepStrictEqual } from "assert"; import { AML_PROGRAM_FAIL_RECOVER } from "integrationtests/test-kyc-fail-recover-simple.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"; import { execSync } from "node:child_process"; import fs from "node:fs"; import os from "node:os"; @@ -103,6 +102,7 @@ import { waitMs, } from "./harness/harness.js"; import { AML_PROGRAM_TEST_KYC_NEW_MEASURES_PROG } from "./integrationtests/test-kyc-new-measures-prog.js"; +import { AML_PROGRAM_NEXT_MEASURE_FORM } from "./integrationtests/test-kyc-two-forms.js"; import { getTestInfo, runTests } from "./integrationtests/testrunner.js"; import { lintExchangeDeployment, lintExchangeUrl } from "./lint.js"; diff --git a/packages/taler-harness/src/integrationtests/test-kyc-form-validation.ts b/packages/taler-harness/src/integrationtests/test-kyc-form-validation.ts @@ -0,0 +1,196 @@ +/* + 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 { + AccessToken, + alternativeOrThrow, + AmountString, + bufferFromAmount, + buildSigPS, + Configuration, + createNewWalletKycAccount, + eddsaSign, + encodeCrock, + HttpStatusCode, + j2s, + KycRequirementInformationId, + Logger, + succeedOrThrow, + TalerSignaturePurpose, + WalletKycRequest, +} from "@gnu-taler/taler-util"; +import { + configureCommonKyc, + createKycTestkudosEnvironmentFull, +} from "../harness/environments.js"; +import { GlobalTestState } from "../harness/harness.js"; + +const logger = new Logger("test-kyc-form-validation.ts"); + +function adjustExchangeConfig(config: Configuration) { + configureCommonKyc(config); + + 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", "NONE"); + + config.setString("KYC-MEASURE-M3", "check_name", "C3"); + config.setString("KYC-MEASURE-M3", "context", "{}"); + config.setString("KYC-MEASURE-M3", "program", "NONE"); + + config.setString("KYC-MEASURE-M4", "check_name", "C4"); + config.setString("KYC-MEASURE-M4", "context", "{}"); + config.setString("KYC-MEASURE-M4", "program", "NONE"); + + config.setString( + "AML-PROGRAM-P1", + "command", + "taler-harness aml-program run-program --name no-rules", + ); + 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", "FREEZE"); + + 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", "FULL_NAME DATE_OF_BIRTH"); + config.setString("KYC-CHECK-C1", "fallback", "FREEZE"); + + 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", "FREEZE"); + + 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", "FREEZE"); + + 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", "FREEZE"); +} + +export async function runKycFormValidationTest(t: GlobalTestState) { + // Set up test environment + + const { exchange, amlKeypair, exchangeApi } = + await createKycTestkudosEnvironmentFull(t, { + adjustExchangeConfig, + onWalletNotification: () => {}, + }); + + // Withdraw digital cash into the wallet. + let kycPaytoHash: string; + let accessToken: AccessToken; + let latestFormId: KycRequirementInformationId; + + const account = await createNewWalletKycAccount(new Uint8Array()); + { + t.logStep("Check balance to trigger AML"); + + const balance: AmountString = "TESTKUDOS:20"; + const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_ACCOUNT_SETUP) + .put(bufferFromAmount(balance)) + .build(); + const body: WalletKycRequest = { + balance, + reserve_pub: account.id, + reserve_sig: encodeCrock(eddsaSign(sigBlob, account.signingKey)), + }; + + const infoResp = await exchangeApi.notifyKycBalanceLimit(body); + const clientInfo = alternativeOrThrow( + infoResp, + HttpStatusCode.UnavailableForLegalReasons, + ); + + t.assertTrue(clientInfo?.h_payto !== undefined); + kycPaytoHash = clientInfo?.h_payto; + } + + { + t.logStep("Get account access token"); + const sigBlob = buildSigPS(TalerSignaturePurpose.KYC_AUTH).build(); + const accountPriv = encodeCrock(eddsaSign(sigBlob, account.signingKey)); + const infoResp = await exchangeApi.checkKycStatus({ + paytoHash: kycPaytoHash, + accountPub: account.id, + accountSig: accountPriv, + }); + const clientInfo = alternativeOrThrow(infoResp, HttpStatusCode.Accepted); + t.assertTrue(clientInfo.access_token !== undefined); + accessToken = clientInfo.access_token; + } + + { + t.logStep("check KYC info, should be waiting for the first form"); + const infoResp = await exchangeApi.checkKycInfo(accessToken); + const clientInfo = succeedOrThrow(infoResp); + + console.log(j2s(clientInfo)); + t.assertDeepEqual(clientInfo?.requirements.length, 1); + t.assertDeepEqual(clientInfo?.requirements[0].form, "firstform"); + t.assertTrue(!!clientInfo?.requirements[0].id); + latestFormId = clientInfo?.requirements[0].id; + } + + { + t.logStep("complete form"); + // We're using a non-existing form ID here, this should fail. + const res = await exchangeApi.uploadKycForm(latestFormId, { + FORM_ID: "does-not-exist-123", + FULL_NAME: "who", + DATE_OF_BIRTH: "2000-01-01", + FORM_VERSION: 1, + }); + console.log(j2s(res)); + t.assertTrue(res.type !== "ok"); + } +} + +runKycFormValidationTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-kyc-two-forms.ts b/packages/taler-harness/src/integrationtests/test-kyc-two-forms.ts @@ -19,32 +19,28 @@ */ import { AccessToken, + alternativeOrThrow, AmountString, bufferFromAmount, buildSigPS, - codecForAccountKycStatus, - codecForKycProcessClientInformation, - codecForLegitimizationNeededResponse, - codecOptional, Configuration, createNewWalletKycAccount, eddsaSign, encodeCrock, - ExchangeKycUploadFormRequest, HttpStatusCode, j2s, KycRequirementInformationId, Logger, + succeedOrThrow, TalerKycAml, TalerSignaturePurpose, WalletKycRequest, } from "@gnu-taler/taler-util"; -import { readResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; import { configureCommonKyc, createKycTestkudosEnvironmentFull, } from "../harness/environments.js"; -import { GlobalTestState, harnessHttpLib, waitMs } from "../harness/harness.js"; +import { GlobalTestState, waitMs } from "../harness/harness.js"; const logger = new Logger("test-kyc-two-forms.ts"); @@ -143,9 +139,6 @@ function adjustExchangeConfig(config: Configuration) { config.setString("KYC-CHECK-C4", "fallback", "FREEZE"); } -/** - * Test setting a `new_measure` as the AML officer. - */ export async function runKycTwoFormsTest(t: GlobalTestState) { // Set up test environment @@ -175,18 +168,17 @@ export async function runKycTwoFormsTest(t: GlobalTestState) { }; const infoResp = await exchangeApi.notifyKycBalanceLimit(body); - const clientInfo = - infoResp.type === "fail" && - infoResp.case === HttpStatusCode.UnavailableForLegalReasons - ? infoResp.body - : undefined; + const clientInfo = alternativeOrThrow( + infoResp, + HttpStatusCode.UnavailableForLegalReasons, + ); t.assertTrue(clientInfo?.h_payto !== undefined); kycPaytoHash = clientInfo?.h_payto; } { - logger.info("step 2) Get account access token"); + t.logStep("Get account access token"); const sigBlob = buildSigPS(TalerSignaturePurpose.KYC_AUTH).build(); const accountPriv = encodeCrock(eddsaSign(sigBlob, account.signingKey)); const infoResp = await exchangeApi.checkKycStatus({ @@ -194,20 +186,15 @@ export async function runKycTwoFormsTest(t: GlobalTestState) { accountPub: account.id, accountSig: accountPriv, }); - - t.assertTrue( - infoResp.type === "fail" && infoResp.case === HttpStatusCode.Accepted, - ); - const clientInfo = infoResp.body; - t.assertTrue(clientInfo?.access_token !== undefined); - accessToken = clientInfo?.access_token; + const clientInfo = alternativeOrThrow(infoResp, HttpStatusCode.Accepted); + t.assertTrue(clientInfo.access_token !== undefined); + accessToken = clientInfo.access_token; } { - logger.info("step 3) Check KYC info, should be waiting for the first form"); + t.logStep("Check KYC info, should be waiting for the first form"); const infoResp = await exchangeApi.checkKycInfo(accessToken); - t.assertTrue(infoResp.type === "ok"); - const clientInfo = infoResp.body; + const clientInfo = succeedOrThrow(infoResp); console.log(j2s(clientInfo)); t.assertDeepEqual(clientInfo?.requirements.length, 1); @@ -217,58 +204,24 @@ export async function runKycTwoFormsTest(t: GlobalTestState) { } { - logger.info("step 4) Complete form"); - const infoResp = await exchangeApi.uploadKycForm(latestFormId, { - FORM_ID: "test", - NAME: "who", - FORM_VERSION: 1, - } as ExchangeKycUploadFormRequest); - - t.assertTrue( - infoResp.type === "fail" && - infoResp.case === HttpStatusCode.InternalServerError, + logger.info("complete first form"); + + succeedOrThrow( + await exchangeApi.uploadKycForm(latestFormId, { + FORM_ID: "firstform", + NAME: "who", + FORM_VERSION: 1, + }), ); } - // { - // 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", - ); + t.logStep("Check KYC info again"); // doing a second request shouldn't fail const infoResp = await exchangeApi.checkKycInfo(accessToken); + console.log(j2s(infoResp)); t.assertTrue(infoResp.type === "ok"); const clientInfo = infoResp.body; - - console.log(j2s(clientInfo)); t.assertDeepEqual(clientInfo?.requirements.length, 1); t.assertDeepEqual(clientInfo?.requirements[0].form, "secondform"); } @@ -276,9 +229,7 @@ export async function runKycTwoFormsTest(t: GlobalTestState) { await waitMs(2000); { - logger.info( - "step 6) Check KYC info again after some time, here the exchange fails", - ); + logger.info("Check KYC info again after some time"); const infoResp = await exchangeApi.checkKycInfo(accessToken); t.assertTrue(infoResp.type === "ok"); const clientInfo = infoResp.body; diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -87,6 +87,7 @@ import { runKycExchangeWalletTest } from "./test-kyc-exchange-wallet.js"; import { runKycFailRecoverSimpleTest } from "./test-kyc-fail-recover-simple.js"; import { runKycFormBadMeasureTest } from "./test-kyc-form-bad-measure.js"; import { runKycFormCompressionTest } from "./test-kyc-form-compression.js"; +import { runKycFormValidationTest } from "./test-kyc-form-validation.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"; @@ -434,6 +435,7 @@ const allTests: TestMainFunction[] = [ runPreparedTransferTest, runPaivanaTest, runPaivanaRepurchaseTest, + runKycFormValidationTest, ]; export interface TestRunSpec {