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:
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 {