taler-typescript-core

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

commit a011a780d8002ce54e5e94afccde5cf8194a3f9b
parent 39fd544b71f538613a96c299076e0864d46abfd5
Author: Florian Dold <florian@dold.me>
Date:   Wed, 28 Jan 2026 00:02:25 +0100

aml: also test events, fix import cycle

Diffstat:
Apackages/taler-util/src/aml/events.test.ts | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-util/src/aml/events.ts | 76++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mpackages/taler-util/src/aml/properties.test.ts | 29+++++++++++++++++++++++++----
Mpackages/taler-util/src/aml/reporting.ts | 8++++++--
Mpackages/taler-util/src/errors.ts | 29++++++++++++++---------------
Mpackages/taler-util/src/http-common.ts | 11++++-------
Mpackages/taler-util/src/index.ts | 16++++++++--------
7 files changed, 219 insertions(+), 60 deletions(-)

diff --git a/packages/taler-util/src/aml/events.test.ts b/packages/taler-util/src/aml/events.test.ts @@ -0,0 +1,110 @@ +/* + This file is part of GNU Taler + (C) 2026 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/> + */ + +import test from "ava"; +import { deriveTopsAmlEvents } from "./events.js"; + +test("AML event derivation for TOPS, unknown form", (t) => { + const events = deriveTopsAmlEvents("some_unknown_form", {}, {}, {}); + t.deepEqual([...events], []); +}); + +test("AML event derivation for TOPS, vqf_902_1_officer", (t) => { + { + const events = deriveTopsAmlEvents( + "vqf_902_1_officer", + {}, + {}, + { + ACCOUNT_OPEN: true, + }, + ); + t.deepEqual([...events], ["INCR_ACCOUNT_OPEN"]); + } + + { + // Already open, no event + const events = deriveTopsAmlEvents( + "vqf_902_1_officer", + {}, + { + ACCOUNT_OPEN: true, + }, + { + ACCOUNT_OPEN: true, + }, + ); + t.deepEqual([...events], []); + } + + { + const events = deriveTopsAmlEvents( + "vqf_902_1_officer", + {}, + {}, + { + ACCOUNT_OPEN: true, + PEP_FOREIGN: true, + }, + ); + t.deepEqual( + [...events], + ["INCR_ACCOUNT_OPEN", "INCR_PEP", "INCR_PEP_FOREIGN"], + ); + } +}); + +test("AML event derivation for TOPS, vqf_902_4", (t) => { + { + // Account not open => no event + const events = deriveTopsAmlEvents( + "vqf_902_4", + {}, + {}, + { + PEP_FOREIGN: true, + }, + ); + t.deepEqual([...events], []); + } + { + const events = deriveTopsAmlEvents( + "vqf_902_4", + {}, + { + ACCOUNT_OPEN: true, + }, + { + PEP_FOREIGN: true, + }, + ); + t.deepEqual([...events], ["INCR_PEP", "INCR_PEP_FOREIGN"]); + } + { + const events = deriveTopsAmlEvents( + "vqf_902_4", + {}, + { + ACCOUNT_OPEN: true, + PEP_FOREIGN: true, + }, + { + PEP_FOREIGN: false, + }, + ); + t.deepEqual([...events], ["DECR_PEP", "DECR_PEP_FOREIGN"]); + } +}); diff --git a/packages/taler-util/src/aml/events.ts b/packages/taler-util/src/aml/events.ts @@ -1,7 +1,17 @@ import { Amounts } from "../amounts.js"; -import { AccountProperties, KycRule, LimitOperationType } from "../types-taler-exchange.js"; import { TalerAmlProperties } from "../taler-account-properties.js"; -import { isOneOf } from "./properties.js"; +import { + AccountProperties, + KycRule, + LimitOperationType, +} from "../types-taler-exchange.js"; +import { + AmlFormAttributesMap, + isOneOf, + TopsAccountPropertiesMap, +} from "./properties.js"; + +// FIXME: We really ought to have a GANA registry for AML event names /** * List of events triggered by TOPS @@ -68,8 +78,6 @@ export type EventMapInfo<T> = { */ shouldBeTriggered: ( formId: string, - prevLimits: KycRule[] | undefined, - nextLimits: KycRule[] | undefined, prevState: AccountProperties | undefined, nextState: AccountProperties | undefined, newAttributes: Record<string, unknown>, @@ -123,7 +131,7 @@ function isAnyKindOfPep(state: AccountProperties): boolean { */ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { INCR_ACCOUNT_OPEN: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { if (nextState === undefined) { return false; } @@ -142,12 +150,12 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { }, }, DECR_ACCOUNT_OPEN: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { return false; }, }, INCR_HIGH_RISK_CUSTOMER: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { if (nextState === undefined) { return false; } @@ -181,7 +189,7 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { }, }, DECR_HIGH_RISK_CUSTOMER: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { if (nextState === undefined) { return false; } @@ -204,7 +212,7 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { }, }, INCR_HIGH_RISK_COUNTRY: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { if (nextState === undefined) { return false; } @@ -238,7 +246,7 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { }, }, DECR_HIGH_RISK_COUNTRY: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { if (nextState === undefined) { return false; } @@ -261,7 +269,7 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { }, }, INCR_PEP: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { if (nextState === undefined) { return false; } @@ -294,7 +302,7 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { }, }, DECR_PEP: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { if (nextState === undefined) { return false; } @@ -316,7 +324,7 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { }, }, INCR_PEP_FOREIGN: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { if (nextState === undefined) { return false; } @@ -346,7 +354,7 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { }, }, DECR_PEP_FOREIGN: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { if (nextState === undefined) { return false; } @@ -365,7 +373,7 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { }, }, INCR_PEP_DOMESTIC: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { if (nextState === undefined) { return false; } @@ -395,7 +403,7 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { }, }, DECR_PEP_DOMESTIC: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { if (nextState === undefined) { return false; } @@ -414,7 +422,7 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { }, }, INCR_PEP_INTERNATIONAL_ORGANIZATION: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { if (nextState === undefined) { return false; } @@ -448,7 +456,7 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { }, }, DECR_PEP_INTERNATIONAL_ORGANIZATION: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { if (nextState === undefined) { return false; } @@ -471,7 +479,7 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { }, }, MROS_REPORTED_SUSPICION_SIMPLE: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { if (prevState === undefined || nextState === undefined) { return false; } @@ -492,7 +500,7 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { }, }, MROS_REPORTED_SUSPICION_SUBSTANTIATED: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { if (prevState === undefined || nextState === undefined) { return false; } @@ -513,7 +521,7 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { }, }, INCR_INVESTIGATION_CONCLUDED: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { if (prevState === undefined || nextState === undefined) { return false; } @@ -538,7 +546,7 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { }, }, DECR_INVESTIGATION_CONCLUDED: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { return false; }, }, @@ -546,13 +554,33 @@ export const EventsDerivation_TOPS: EventMapInfo<typeof TOPS_AmlEventsName> = { export const GLS_AML_EVENTS: EventMapInfo<typeof GLS_AmlEventsName> = { ACCOUNT_OPENED: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { return false; }, }, ACCOUNT_CLOSED: { - shouldBeTriggered(formId, pL, nL, prevState, nextState, attr) { + shouldBeTriggered(formId, prevState, nextState, attr) { return false; }, }, }; + +const topsEventNames = Object.values(TOPS_AmlEventsName); + +export function deriveTopsAmlEvents( + formId: string, + newAttributes: AmlFormAttributesMap, + oldProperties: TopsAccountPropertiesMap, + newProperties: TopsAccountPropertiesMap, +): Set<TOPS_AmlEventsName> { + const events: Set<TOPS_AmlEventsName> = new Set(); + for (const evt of topsEventNames) { + const h = EventsDerivation_TOPS[evt]; + if ( + h.shouldBeTriggered(formId, oldProperties, newProperties, newAttributes) + ) { + events.add(evt); + } + } + return events; +} diff --git a/packages/taler-util/src/aml/properties.test.ts b/packages/taler-util/src/aml/properties.test.ts @@ -19,27 +19,48 @@ import { TalerAmlProperties } from "../taler-account-properties.js"; import { TalerFormAttributes } from "../taler-form-attributes.js"; import { deriveTopsAmlProperties } from "./properties.js"; -test("prop derivation for TOPS", (t) => { +test("AML prop derivation for TOPS, unknown form", (t) => { { const props = deriveTopsAmlProperties("custom_does_not_exist132", {}, {}); t.deepEqual(props, {}); } { - const props = deriveTopsAmlProperties("vqf_902_1_officer", {}, {}); + const props = deriveTopsAmlProperties( + "vqf_902_4", + { + [TalerFormAttributes.RISK_CLASSIFICATION_LEVEL]: "HIGH_RISK", + }, + {}, + ); t.deepEqual(props, { - [TalerAmlProperties.ACCOUNT_OPEN]: true, + [TalerAmlProperties.HIGH_RISK_CUSTOMER]: true, }); } +}); + +test("AML prop derivation for TOPS, vqf_902_4", (t) => { { const props = deriveTopsAmlProperties( "vqf_902_4", { [TalerFormAttributes.RISK_CLASSIFICATION_LEVEL]: "HIGH_RISK", }, - {}, + { + PEP_FOREIGN: true, + }, ); t.deepEqual(props, { [TalerAmlProperties.HIGH_RISK_CUSTOMER]: true, + [TalerAmlProperties.PEP_FOREIGN]: true, + }); + } +}); + +test("AML prop derivation for TOPS, vqf_902_1_officer", (t) => { + { + const props = deriveTopsAmlProperties("vqf_902_1_officer", {}, {}); + t.deepEqual(props, { + [TalerAmlProperties.ACCOUNT_OPEN]: true, }); } }); diff --git a/packages/taler-util/src/aml/reporting.ts b/packages/taler-util/src/aml/reporting.ts @@ -364,7 +364,9 @@ export async function fetchTopsInfoFromServer( const resultMap = allResponses.reduce((prev, event) => { prev[event.key] = - event.response.type === "ok" ? event.response.body.statistics[0].counter : undefined; + event.response.type === "ok" + ? event.response.body.statistics[0].counter + : undefined; return prev; }, {} as CounterResultByEventName<EventType>); @@ -435,7 +437,9 @@ export async function fetchVqfInfoFromServer( const resultMap = allResponses.reduce((prev, event) => { prev[event.key] = - event.response.type === "ok" ? event.response.body.statistics[0].counter : undefined; + event.response.type === "ok" + ? event.response.body.statistics[0].counter + : undefined; return prev; }, {} as CounterResultByEventName<EventType>); diff --git a/packages/taler-util/src/errors.ts b/packages/taler-util/src/errors.ts @@ -14,25 +14,24 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { CancellationToken } from "./CancellationToken.js"; +import { TalerErrorCode } from "./taler-error-codes.js"; +import { AbsoluteTime } from "./time.js"; +import { + TransactionState, + TransactionType, +} from "./types-taler-wallet-transactions.js"; +import { + PaymentInsufficientBalanceDetails, + TalerErrorDetail, +} from "./types-taler-wallet.js"; + /** * Classes and helpers for error handling specific to wallet operations. * * @author Florian Dold <dold@taler.net> */ -/** - * Imports. - */ -import { - AbsoluteTime, - CancellationToken, - PaymentInsufficientBalanceDetails, - TalerErrorCode, - TalerErrorDetail, - TransactionState, - TransactionType, -} from "@gnu-taler/taler-util"; - type empty = Record<string, never>; export interface DetailsMap { @@ -366,8 +365,8 @@ export function getErrorDetailFromException(e: any): TalerErrorDetail { /** * This function should not be called at runtime. * Useful on switch/case to detect that all path has been contemplated. - * - * @param x + * + * @param x */ export function assertUnreachable(x: never): never { throw new Error("Didn't expect to get here"); diff --git a/packages/taler-util/src/http-common.ts b/packages/taler-util/src/http-common.ts @@ -16,16 +16,13 @@ SPDX-License-Identifier: AGPL3.0-or-later */ -import { CancellationToken } from "@gnu-taler/taler-util"; +import { base64FromArrayBuffer } from "./base64.js"; +import { CancellationToken } from "./CancellationToken.js"; import { Codec } from "./codec.js"; +import { makeErrorDetail, TalerError } from "./errors.js"; import { j2s } from "./helpers.js"; -import { - TalerError, - base64FromArrayBuffer, - makeErrorDetail, - stringToBytes, -} from "./index.js"; import { Logger } from "./logging.js"; +import { stringToBytes } from "./taler-crypto.js"; import { TalerErrorCode } from "./taler-error-codes.js"; import { AbsoluteTime, Duration } from "./time.js"; import { TalerErrorDetail } from "./types-taler-wallet.js"; diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts @@ -1,8 +1,8 @@ export * from "./amounts.js"; export * from "./bank-api-client.js"; export * from "./base64.js"; -export * from "./bitcoin.js"; export * from "./bech32.js"; +export * from "./bitcoin.js"; export * from "./CancellationToken.js"; export * from "./codec.js"; export * from "./contract-terms.js"; @@ -19,8 +19,8 @@ export * from "./http-client/bank-wire.js"; export * from "./http-client/challenger.js"; export * from "./http-client/donau-client.js"; export * from "./http-client/exchange-client.js"; -export * from "./http-client/merchant.js"; export * from "./http-client/mailbox.js"; +export * from "./http-client/merchant.js"; export * from "./http-client/officer-account.js"; export { @@ -64,12 +64,12 @@ export * from "./transaction-test-data.js"; export * from "./url.js"; // FIXME: remove all this, needs refactor +export * from "./types-donau.js"; export * from "./types-taler-bank-conversion.js"; export * from "./types-taler-bank-integration.js"; export * from "./types-taler-exchange.js"; export * from "./types-taler-mailbox.js"; export * from "./types-taler-merchant.js"; -export * from "./types-donau.js"; // end export * from "./types-taler-common.js"; @@ -88,17 +88,17 @@ export * as TalerMerchantApi from "./types-taler-merchant.js"; export * as TalerRevenueApi from "./types-taler-revenue.js"; export * as TalerWireGatewayApi from "./types-taler-wire-gateway.js"; +export * from "./taler-account-properties.js"; +export * from "./taler-form-attributes.js"; + export * from "./taler-signatures.js"; export * from "./account-restrictions.js"; -export * from "./aml/events.js"; export * from "./aml/properties.js"; -export * from "./taler-account-properties.js"; -export * from "./taler-form-attributes.js"; +export * from "./aml/events.js"; +export * from "./aml/reporting.js"; export * from "./iso-3166.js"; export * from "./iso-4217.js"; export * from "./iso-639.js"; - -export * from "./aml/reporting.js";