commit 9b16e959286bc24810185d6569ffe13855df30b3
parent 87793376701af498e0ad366adc00bdd5ccca5de7
Author: Christian Grothoff <christian@grothoff.org>
Date: Sat, 6 Dec 2025 17:39:50 +0100
expand PDF generation test (#10666), lots of FIXMEs for Dold
Diffstat:
1 file changed, 835 insertions(+), 5 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-tops-aml-pdf.ts b/packages/taler-harness/src/integrationtests/test-tops-aml-pdf.ts
@@ -25,6 +25,62 @@ import { setupMeasuresTestEnvironment } from "../harness/tops.js";
export const logger = new Logger("test-tops-aml-measures.ts");
/**
+ * Helper function to create a mock PDF file attachment.
+ */
+function createPdfAttachment(content: string): string {
+ // Escape parentheses for PDF literal strings
+ const contentEscaped = content
+ .replace(/\(/g, "\\(")
+ .replace(/\)/g, "\\)");
+
+ // Minimal PDF
+ const pdf = `%PDF-1.1
+1 0 obj
+<< /Type /Catalog /Pages 2 0 R>>
+endobj
+2 0 obj
+<< /Type /Pages /Kids[3 0 R]/Count 1>>
+endobj
+3 0 obj
+<< /Type /Page /Parent 2 0 R /MediaBox[0 0 200 50]
+/Resources<< /Font<< /F<< /Type/Font /Subtype/Type3 /FontBBox[0 0 0 0]
+/FontMatrix[0.001 0 0 0.001 0 0] /CharProcs<< /H 4 0 R >> /Encoding<< /Type/Encoding/MissingWidth 1 /Differences[0/H] >>
+/FirstChar 0 /LastChar 0 /Widths[1000] >> >> /Contents 5 0 R >>
+endobj
+4 0 obj
+<< /Length 8>>
+stream
+0 0 m
+endstream
+endobj
+5 0 obj
+<< /Length ${35 + contentEscaped.length - "Hello World".length}>>
+stream
+BT /F 12 Tf 10 20 Td (${contentEscaped}) Tj ET
+endstream
+endobj
+xref
+0 6
+0000000000 65535 f
+0000000009 00000 n
+0000000056 00000 n
+0000000107 00000 n
+0000000334 00000 n
+0000000409 00000 n
+trailer<< /Size 6 /Root 1 0 R>>
+startxref
+475
+%%EOF`;
+
+ // FIXME: Dold: which encoding are we using? Base64 or Crockford?
+ // Also: this doesn't yet wrap in "FILE". "File" data type seems
+ // unspecified in tops.rst!
+ return Buffer.from(pdf, "utf8").toString("base64");
+}
+
+
+
+/**
* Test that invokes all measures defined for the TOPS deployment.
*/
export async function runTopsAmlPdfTest(t: GlobalTestState) {
@@ -40,6 +96,7 @@ export async function runTopsAmlPdfTest(t: GlobalTestState) {
accountPaytoHash,
} = await setupMeasuresTestEnvironment(t);
+ // Test vqf_902_1_customer form - LEGAL_ENTITY variant
{
await decideMeasure("kyx");
await expectNoInvestigate();
@@ -47,9 +104,113 @@ export async function runTopsAmlPdfTest(t: GlobalTestState) {
FORM_ID: "vqf_902_1_customer",
FORM_VERSION: 1,
CUSTOMER_TYPE: "LEGAL_ENTITY",
+ COMPANY_NAME: "Acme Corporation",
+ REGISTERED_OFFICE_ADDRESS: "Castle St. 1\nWondertown\n8000 Zurich",
+ CONTACT_PERSON_NAME: "Bob Smith",
+ CONTACT_PHONE: "+41123456789",
+ CONTACT_EMAIL: "bob@acme.com",
+ LEGAL_ENTITY_IDENTIFICATION_DOCUMENT_COPY: createPdfAttachment("company-id-doc"),
+ ESTABLISHER_LIST: [
+ {
+ FULL_NAME: "Alice Johnson",
+ DOMICILE_ADDRESS: "Main St. 10\n8001 Zurich",
+ DATE_OF_BIRTH: "1980-05-15",
+ NATIONALITY: "CH",
+ PERSONAL_IDENTIFICATION_DOCUMENT_COPY: createPdfAttachment("alice-id"),
+ SIGNING_AUTHORITY_TYPE: "SINGLE",
+ SIGNING_AUTHORITY_EVIDENCE_TYPE: "CR",
+ SIGNING_AUTHORITY_EVIDENCE_DOCUMENT_COPY: createPdfAttachment("alice-cr"),
+ },
+ {
+ FULL_NAME: "Charlie Brown",
+ DOMICILE_ADDRESS: "Park Ave. 20\n8002 Zurich",
+ DATE_OF_BIRTH: "1975-08-22",
+ NATIONALITY: "DE",
+ PERSONAL_IDENTIFICATION_DOCUMENT_COPY: createPdfAttachment("charlie-id"),
+ SIGNING_AUTHORITY_TYPE: "COLLECTIVE_TWO",
+ SIGNING_AUTHORITY_EVIDENCE_TYPE: "MANDATE",
+ SIGNING_AUTHORITY_EVIDENCE_DOCUMENT_COPY: createPdfAttachment("charlie-mandate"),
+ },
+ {
+ FULL_NAME: "Diana Prince",
+ DOMICILE_ADDRESS: "Lake Road 30\n8003 Zurich",
+ DATE_OF_BIRTH: "1985-12-01",
+ NATIONALITY: "US",
+ PERSONAL_IDENTIFICATION_DOCUMENT_COPY: createPdfAttachment("diana-id"),
+ SIGNING_AUTHORITY_TYPE: "OTHER",
+ SIGNING_AUTHORITY_EVIDENCE_TYPE: "OTHER",
+ SIGNING_AUTHORITY_EVIDENCE_OTHER: "Board Resolution",
+ SIGNING_AUTHORITY_EVIDENCE_DOCUMENT_COPY: createPdfAttachment("diana-resolution"),
+ },
+ ],
+ CORRESPONDENCE_LANGUAGE: "en",
+ CUSTOMER_TYPE_VQF: "OPERATIONAL",
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ console.log(`got ${res.byteLength} bytes`);
+ const f = t.testDir + `/aml-form-vqf_902_1_customer-legal.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_1_customer form - NATURAL_PERSON variant (without sole proprietor)
+ {
+ await decideMeasure("kyx");
+ await expectNoInvestigate();
+ await submitForm("vqf_902_1_customer", {
+ FORM_ID: "vqf_902_1_customer",
+ FORM_VERSION: 1,
+ CUSTOMER_TYPE: "NATURAL_PERSON",
+ FULL_NAME: "John Doe",
+ DOMICILE_ADDRESS: "Elm Street 5\n8010 Zurich",
+ CONTACT_PHONE: "+41987654321",
+ CONTACT_EMAIL: "john.doe@example.com",
+ DATE_OF_BIRTH: "1990-03-12",
+ NATIONALITY: "CH",
+ PERSONAL_IDENTIFICATION_DOCUMENT_COPY: createPdfAttachment("john-passport"),
+ CUSTOMER_IS_SOLE_PROPRIETOR: false,
+ CORRESPONDENCE_LANGUAGE: "de",
+ CUSTOMER_TYPE_VQF: "NATURAL",
+ });
+ await expectInvestigate();
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-vqf_902_1_customer-natural.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_1_customer form - NATURAL_PERSON variant (with sole proprietor)
+ {
+ await decideMeasure("kyx");
+ await submitForm("vqf_902_1_customer", {
+ FORM_ID: "vqf_902_1_customer",
+ FORM_VERSION: 1,
+ CUSTOMER_TYPE: "NATURAL_PERSON",
+ FULL_NAME: "Jane Smith",
+ DOMICILE_ADDRESS: "Oak Avenue 12\n8020 Zurich",
+ CONTACT_PHONE: "+41555123456",
+ CONTACT_EMAIL: "jane@smithconsulting.ch",
+ DATE_OF_BIRTH: "1985-07-20",
+ NATIONALITY: "FR",
+ PERSONAL_IDENTIFICATION_DOCUMENT_COPY: createPdfAttachment("jane-id"),
+ CUSTOMER_IS_SOLE_PROPRIETOR: true,
+ COMPANY_NAME: "Smith Consulting GmbH",
+ REGISTERED_OFFICE_ADDRESS: "Business Park 3\n8021 Zurich",
+ LEGAL_ENTITY_IDENTIFICATION_DOCUMENT_COPY: createPdfAttachment("smith-biz-reg"),
+ CORRESPONDENCE_LANGUAGE: "fr",
CUSTOMER_TYPE_VQF: "OTHER",
- FULL_NAME: "Alice A",
- DOMICILE_ADDRESS: "Castle St. 1\nWondertown",
});
await expectInvestigate();
@@ -59,12 +220,680 @@ export async function runTopsAmlPdfTest(t: GlobalTestState) {
accountPaytoHash,
),
);
- console.log(`got ${res.byteLength} bytes`);
- const f = t.testDir + `/aml-form-vqf_902_1_customer.pdf`;
+ const f = t.testDir + `/aml-form-vqf_902_1_customer-sole-proprietor.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_1_officer form
+ if (false) // FIXME: Dold
+ {
+ await decideMeasure("kyx");
+ await submitForm("vqf_902_1_officer", {
+ FORM_ID: "vqf_902_1_officer",
+ FORM_VERSION: 1,
+ ACCEPTANCE_DATE: "2025-12-01",
+ ACCEPTANCE_METHOD: "FACE_TO_FACE",
+ ACCEPTANCE_FURTHER_INFO: "Meeting at branch office with ID verification",
+ EMBARGO_TERRORISM_CHECK_RESULT: "NOT_LISTED",
+ EMBARGO_TERRORISM_CHECK_DATE: "2025-12-01",
+ SUPPLEMENTAL_FILES_LIST: [
+ {
+ FILE: createPdfAttachment("meeting-notes"),
+ DESCRIPTION: "Meeting notes from customer onboarding",
+ },
+ {
+ FILE: createPdfAttachment("id-verification"),
+ DESCRIPTION: "ID verification documentation",
+ },
+ {
+ FILE: createPdfAttachment("background-check"),
+ DESCRIPTION: "Background check results",
+ },
+ ],
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-vqf_902_1_officer.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_1_officer form with LISTED terrorism check
+ if (false) // FIXME: dold
+ {
+ await decideMeasure("kyx");
+ await submitForm("vqf_902_1_officer", {
+ FORM_ID: "vqf_902_1_officer",
+ FORM_VERSION: 1,
+ ACCEPTANCE_DATE: "2025-12-02",
+ ACCEPTANCE_METHOD: "AUTHENTICATED_COPY",
+ ACCEPTANCE_FURTHER_INFO: "Authenticated copy received via postal service",
+ EMBARGO_TERRORISM_CHECK_RESULT: "LISTED",
+ EMBARGO_TERRORISM_CHECK_DATE: "2025-12-02",
+ EMBARGO_TERRORISM_INFO: "Name match found on sanctions list - requires further investigation",
+ SUPPLEMENTAL_FILES_LIST: [
+ {
+ FILE: createPdfAttachment("sanctions-report"),
+ DESCRIPTION: "Detailed sanctions screening report",
+ },
+ {
+ FILE: createPdfAttachment("compliance-review"),
+ DESCRIPTION: "Compliance team review",
+ },
+ {
+ FILE: createPdfAttachment("escalation-doc"),
+ DESCRIPTION: "Escalation to senior management",
+ },
+ ],
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-vqf_902_1_officer-listed.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_4 form - without PEP or high risk
+ if (false) // FIXME: dold
+ {
+ await decideMeasure("kyx"); // FIXME: which measure?
+ await submitForm("vqf_902_4", {
+ FORM_ID: "vqf_902_4",
+ FORM_VERSION: 1,
+ PEP_FOREIGN: false,
+ PEP_DOMESTIC: false,
+ PEP_INTERNATIONAL_ORGANIZATION: false,
+ HIGH_RISK_COUNTRY: false,
+ COUNTRY_RISK_NATIONALITY_TYPE: ["NATIONALITY_CUSTOMER", "DOMICILE_CUSTOMER"],
+ COUNTRY_RISK_NATIONALITY_LEVEL: "LOW",
+ COUNTRY_RISK_BUSINESS_TYPE: ["CUSTOMER"],
+ COUNTRY_RISK_BUSINESS_LEVEL: "LOW",
+ COUNTRY_RISK_PAYMENTS_LEVEL: "LOW",
+ INDUSTRY_RISK_TYPE: "CUSTOMER",
+ INDUSTRY_RISK_LEVEL: "TRANSPARENT",
+ CONTACT_RISK_LEVEL: "LOW",
+ RISK_RATIONALE: "Customer is well-known local business with transparent operations",
+ RISK_CLASSIFICATION_LEVEL: "NO_HIGH_RISK",
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-vqf_902_4-low-risk.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_4 form - with PEP and high risk
+ if (false) // FIXME: dold
+ {
+ await decideMeasure("kyx");
+ await submitForm("vqf_902_4", {
+ FORM_ID: "vqf_902_4",
+ FORM_VERSION: 1,
+ PEP_FOREIGN: true,
+ PEP_DOMESTIC: true,
+ PEP_INTERNATIONAL_ORGANIZATION: false,
+ PEP_HIGH_RISK: true,
+ PEP_ACCEPTANCE_DATE: "2025-11-15",
+ HIGH_RISK_COUNTRY: true,
+ HIGH_RISK_ACCEPTANCE_DATE: "2025-11-15",
+ COUNTRY_RISK_NATIONALITY_TYPE: [
+ "NATIONALITY_CUSTOMER",
+ "NATIONALITY_OWNER",
+ "DOMICILE_CUSTOMER",
+ ],
+ COUNTRY_RISK_NATIONALITY_LEVEL: "HIGH",
+ COUNTRY_RISK_BUSINESS_TYPE: ["CUSTOMER", "OWNER"],
+ COUNTRY_RISK_BUSINESS_LEVEL: "HIGH",
+ COUNTRY_RISK_PAYMENTS_LEVEL: "MEDIUM",
+ INDUSTRY_RISK_TYPE: "CUSTOMER",
+ INDUSTRY_RISK_LEVEL: "HIGH_RISK_TRADE",
+ CONTACT_RISK_LEVEL: "HIGH",
+ RISK_RATIONALE: "Customer is foreign PEP with connections to high-risk jurisdictions. Enhanced due diligence applied.",
+ RISK_CLASSIFICATION_LEVEL: "HIGH_RISK",
+ });
+ await expectInvestigate();
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-vqf_902_4-high-risk.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_5 form - with SAVINGS origin
+ if (false) // FIXME: dold
+ {
+ await decideMeasure("kyx");
+ await expectNoInvestigate();
+ await submitForm("vqf_902_5", {
+ FORM_ID: "vqf_902_5",
+ FORM_VERSION: 1,
+ BIZREL_PROFESSION: "Software Engineer at Tech Company\nPreviously worked as IT Consultant\n15 years experience",
+ BIZREL_FINANCIAL_CIRCUMSTANCES: "Annual income: CHF 120,000\nTotal assets: CHF 500,000\nNo significant liabilities",
+ BIZREL_ORIGIN_NATURE: "Cash deposits and investment returns\nAmount: CHF 200,000\nCurrency: CHF",
+ BIZREL_ORIGIN_CATEGORY: ["SAVINGS", "OWN_BUSINESS"],
+ BIZREL_ORIGIN_DETAIL: "Accumulated savings from employment over 10 years. Additional income from freelance software consulting projects.",
+ BIZREL_PURPOSE: "Investment account for long-term wealth management and retirement planning",
+ BIZREL_DEVELOPMENT: "Expect regular monthly deposits of CHF 3,000-5,000. Occasional larger deposits from consulting projects.",
+ BIZREL_FINANCIAL_VOLUME: "Average monthly volume: CHF 4,000. Expected annual growth of 10-15%.",
+ BIZREL_FINANCIAL_BENEFICIARIES_FULL_NAME: "Self (account holder)",
+ BIZREL_THIRDPARTY_RELATIONSHIP: "No third parties involved. Account holder is sole beneficial owner.",
+ BIZREL_THIRDPARTY_AMLA_FILES: "None",
+ BIZREL_THIRDPARTY_REFERENCES: "Introduced by existing client Marcus Weber, Account #CH-12345",
+ BIZREL_FURTHER_INFO: "Customer has been transparent about income sources. All documentation verified.",
+ });
+ await expectInvestigate();
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-vqf_902_5-savings.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_5 form - with INHERITANCE and OTHER origin
+ if (false) // FIXME: dold
+ {
+ await decideMeasure("kyx");
+ await expectNoInvestigate();
+ await submitForm("vqf_902_5", {
+ FORM_ID: "vqf_902_5",
+ FORM_VERSION: 1,
+ BIZREL_PROFESSION: "Retired business owner\nFormer CEO of manufacturing company\nRetired since 2020",
+ BIZREL_FINANCIAL_CIRCUMSTANCES: "Pension income: CHF 80,000 per year\nTotal net worth: CHF 2,500,000\nNo debt",
+ BIZREL_ORIGIN_NATURE: "Inheritance and business sale proceeds\nAmount: CHF 1,500,000\nCurrency: CHF and EUR",
+ BIZREL_ORIGIN_CATEGORY: ["INHERITANCE", "OTHER"],
+ BIZREL_ORIGIN_CATEGORY_OTHER: "Sale of family business in 2020",
+ BIZREL_ORIGIN_DETAIL: "CHF 800,000 from inheritance after parents' estate settlement in 2019. CHF 700,000 from sale of 60% stake in family manufacturing business to private equity firm.",
+ BIZREL_PURPOSE: "Wealth preservation and estate planning for children",
+ BIZREL_DEVELOPMENT: "Minimal transaction activity expected. Quarterly dividend reinvestment.",
+ BIZREL_FINANCIAL_VOLUME: "Low volume - primarily passive investment portfolio",
+ BIZREL_FINANCIAL_BENEFICIARIES_FULL_NAME: "Account holder and designated heirs: Maria Schmidt, Thomas Schmidt",
+ BIZREL_THIRDPARTY_RELATIONSHIP: "Trust arrangement for benefit of children listed above",
+ BIZREL_THIRDPARTY_AMLA_FILES: "Linked to estate file #EST-2019-445",
+ BIZREL_THIRDPARTY_REFERENCES: "Legal advisor: Dr. Hans Mueller, Zurich Law Firm",
+ BIZREL_FURTHER_INFO: "Estate documentation and business sale agreement on file. All sources verified through notarized documents.",
+ });
+ await expectInvestigate();
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-vqf_902_5-inheritance.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_9_customer form
+ if (false) // FIXME: dold
+ {
+ await decideMeasure("kyx");
+ await expectNoInvestigate();
+ await submitForm("vqf_902_9_customer", {
+ FORM_ID: "vqf_902_9_customer",
+ FORM_VERSION: 1,
+ IDENTITY_CONTRACTING_PARTNER: "Global Trading Ltd.\nCommerce Street 45\n8050 Zurich",
+ IDENTITY_LIST: [
+ {
+ FULL_NAME: "Michael Anderson",
+ DATE_OF_BIRTH: "1970-04-10",
+ DOMICILE_ADDRESS: "Riverside Drive 88\n8051 Zurich",
+ NATIONALITY: "GB",
+ },
+ {
+ FULL_NAME: "Sarah Johnson",
+ DATE_OF_BIRTH: "1975-09-25",
+ DOMICILE_ADDRESS: "Mountain View 22\n8052 Zurich",
+ NATIONALITY: "US",
+ },
+ {
+ FULL_NAME: "Roberto Martinez",
+ DATE_OF_BIRTH: "1968-11-30",
+ DOMICILE_ADDRESS: "Garden Plaza 7\n8053 Zurich",
+ NATIONALITY: "ES",
+ },
+ ],
+ SIGNATURE: "Michael Anderson",
+ SIGN_DATE: "2025-12-05",
+ });
+ await expectInvestigate();
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-vqf_902_9_customer.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_9_officer form
+ if (false) // FIXME: dold
+ {
+ await decideMeasure("kyx");
+ await submitForm("vqf_902_9_officer", {
+ FORM_ID: "vqf_902_9_officer",
+ FORM_VERSION: 1,
+ IDENTITY_CONTRACTING_PARTNER: "Tech Innovations AG\nInnovation Park 12\n8060 Zurich",
+ IDENTITY_LIST: [
+ {
+ FULL_NAME: "Emma Thompson",
+ DATE_OF_BIRTH: "1982-06-18",
+ DOMICILE_ADDRESS: "Sunset Boulevard 15\n8061 Zurich",
+ NATIONALITY: "CH",
+ },
+ {
+ FULL_NAME: "David Chen",
+ DATE_OF_BIRTH: "1978-03-05",
+ DOMICILE_ADDRESS: "Tech Valley 99\n8062 Zurich",
+ NATIONALITY: "SG",
+ },
+ {
+ FULL_NAME: "Isabella Rossi",
+ DATE_OF_BIRTH: "1985-12-22",
+ DOMICILE_ADDRESS: "Lake View 44\n8063 Zurich",
+ NATIONALITY: "IT",
+ },
+ ],
+ ATTACHMENT_SIGNED_DOCUMENT: createPdfAttachment("signed-form-902-9"),
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-vqf_902_9_officer.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_11_customer form - HAS_25_MORE_RIGHTS variant, without third party
+ if (false) // FIXME: dold
+ {
+ await decideMeasure("kyx");
+ await submitForm("vqf_902_11_customer", {
+ FORM_ID: "vqf_902_11_customer",
+ FORM_VERSION: 1,
+ IDENTITY_CONTRACTING_PARTNER: "Manufacturing Solutions Ltd.\nIndustrial Zone 78\n8070 Zurich",
+ CONTROL_REASON: "HAS_25_MORE_RIGHTS",
+ IDENTITY_LIST: [
+ {
+ FULL_NAME: "William Brown",
+ DOMICILE_ADDRESS: "Highland Road 33\n8071 Zurich",
+ },
+ {
+ FULL_NAME: "Olivia Davis",
+ DOMICILE_ADDRESS: "Valley Street 56\n8072 Zurich",
+ },
+ {
+ FULL_NAME: "James Wilson",
+ DOMICILE_ADDRESS: "Forest Avenue 11\n8073 Zurich",
+ },
+ ],
+ THIRD_PARTY_OWNERSHIP: false,
+ SIGNATURE: "William Brown",
+ SIGN_DATE: "2025-12-04",
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-vqf_902_11_customer-25percent.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_11_customer form - OTHER_WAY variant, with third party
+ if (false) // FIXME: dold
+ {
+ await decideMeasure("kyx");
+ await submitForm("vqf_902_11_customer", {
+ FORM_ID: "vqf_902_11_customer",
+ FORM_VERSION: 1,
+ IDENTITY_CONTRACTING_PARTNER: "Service Holdings AG\nService Center 90\n8080 Zurich",
+ CONTROL_REASON: "OTHER_WAY",
+ IDENTITY_LIST: [
+ {
+ FULL_NAME: "Sophia Martinez",
+ DOMICILE_ADDRESS: "Central Plaza 77\n8081 Zurich",
+ },
+ {
+ FULL_NAME: "Lucas Garcia",
+ DOMICILE_ADDRESS: "North Street 24\n8082 Zurich",
+ },
+ {
+ FULL_NAME: "Mia Rodriguez",
+ DOMICILE_ADDRESS: "South Boulevard 13\n8083 Zurich",
+ },
+ ],
+ THIRD_PARTY_OWNERSHIP: true,
+ SIGNATURE: "Sophia Martinez",
+ SIGN_DATE: "2025-12-03",
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-vqf_902_11_customer-other-way.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_11_customer form - DIRECTOR variant
+ if (false) // FIXME: dold
+ {
+ await decideMeasure("kyx");
+ await submitForm("vqf_902_11_customer", {
+ FORM_ID: "vqf_902_11_customer",
+ FORM_VERSION: 1,
+ IDENTITY_CONTRACTING_PARTNER: "Consulting Partners GmbH\nConsulting Tower 5\n8090 Zurich",
+ CONTROL_REASON: "DIRECTOR",
+ IDENTITY_LIST: [
+ {
+ FULL_NAME: "Alexander Mueller",
+ DOMICILE_ADDRESS: "Executive Lane 66\n8091 Zurich",
+ },
+ {
+ FULL_NAME: "Charlotte Weber",
+ DOMICILE_ADDRESS: "Management Street 88\n8092 Zurich",
+ },
+ {
+ FULL_NAME: "Benjamin Schneider",
+ DOMICILE_ADDRESS: "Director Avenue 99\n8093 Zurich",
+ },
+ ],
+ THIRD_PARTY_OWNERSHIP: false,
+ SIGNATURE: "Alexander Mueller",
+ SIGN_DATE: "2025-12-02",
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-vqf_902_11_customer-director.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_11_officer form - HAS_25_MORE_RIGHTS variant
+ if (false) // FIXME: dold
+ {
+ await decideMeasure("kyx");
+ await submitForm("vqf_902_11_officer", {
+ FORM_ID: "vqf_902_11_officer",
+ FORM_VERSION: 1,
+ IDENTITY_CONTRACTING_PARTNER: "Investment Group SA\nFinance District 101\n8100 Zurich",
+ CONTROL_REASON: "HAS_25_MORE_RIGHTS",
+ IDENTITY_LIST: [
+ {
+ FULL_NAME: "Victoria Fischer",
+ DOMICILE: "Investor Road 55\n8101 Zurich",
+ },
+ {
+ FULL_NAME: "Daniel Hoffmann",
+ DOMICILE: "Capital Street 77\n8102 Zurich",
+ },
+ {
+ FULL_NAME: "Sophie Zimmermann",
+ DOMICILE: "Fund Avenue 22\n8103 Zurich",
+ },
+ ],
+ THIRD_PARTY_OWNERSHIP: true,
+ ATTACHMENT_SIGNED_DOCUMENT: createPdfAttachment("signed-form-902-11"),
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-vqf_902_11_officer.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_14 form - NO_SUSPICION result
+ if (false) // FIXME: dold
+ {
+ await decideMeasure("kyx");
+ await submitForm("vqf_902_14", {
+ FORM_ID: "vqf_902_14",
+ FORM_VERSION: 1,
+ INCRISK_REASON: "Large cash deposit of CHF 50,000 triggered monitoring alert. Customer explanation requested.",
+ INCRISK_MEANS: "GATHERING",
+ INCRISK_SUMMARY: "Customer provided proof of property sale. Documentation includes notarized sales agreement and bank transfer records. All information is consistent and plausible.",
+ INCRISK_DOCUMENTS: "Property sales agreement (notarized), Bank transfer confirmation, Tax declaration",
+ INCRISK_RESULT: "NO_SUSPICION",
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-vqf_902_14-no-suspicion.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_14 form - SUBSTANTIATED_SUSPICION result
+ if (false) // FIXME: dold
+ {
+ await decideMeasure("kyx");
+ await expectNoInvestigate();
+ await submitForm("vqf_902_14", {
+ FORM_ID: "vqf_902_14",
+ FORM_VERSION: 1,
+ INCRISK_REASON: "Multiple transactions to high-risk jurisdictions. Inconsistent explanations provided. Transaction patterns suggest possible structuring.",
+ INCRISK_MEANS: "CONSULTATION",
+ INCRISK_SUMMARY: "Customer's stated business activity does not align with transaction patterns. Multiple red flags identified including: (1) transactions just below reporting threshold, (2) payments to shell companies in offshore jurisdictions, (3) inconsistent business documentation.",
+ INCRISK_DOCUMENTS: "Transaction logs, Customer interviews, Public records search, Third-party database checks",
+ INCRISK_RESULT: "SUBSTANTIATED_SUSPICION",
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-vqf_902_14-substantiated.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test vqf_902_14 form - OTHER result with explanation
+ if (false) // FIXME: dold
+ {
+ await decideMeasure("kyx");
+ await submitForm("vqf_902_14", {
+ FORM_ID: "vqf_902_14",
+ FORM_VERSION: 1,
+ INCRISK_REASON: "Unusual activity pattern detected. Customer requested clarification on enhanced monitoring procedures.",
+ INCRISK_MEANS: "OTHER",
+ INCRISK_MEANS_OTHER: "Video conference with customer and legal counsel, review of corporate governance documents, consultation with external compliance specialist",
+ INCRISK_SUMMARY: "Customer is undergoing corporate restructuring. Increased transaction volume is temporary and related to merger activity. All parties have been identified and verified.",
+ INCRISK_DOCUMENTS: "Merger agreement, Board resolutions, Legal opinions, Updated beneficial ownership structure",
+ INCRISK_RESULT: "OTHER",
+ INCRISK_RESULT_OTHER: "Enhanced monitoring period established - 6-month review cycle with quarterly reporting requirements",
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-vqf_902_14-other.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test generic_note form
+ if (false) // FIXME: dold
+ {
+ await decideMeasure("kyx");
+ await submitForm("generic_note", {
+ FORM_ID: "generic_note",
+ FORM_VERSION: 1,
+ NOTE_TEXT: "Follow-up meeting held with customer regarding recent transaction patterns. Customer provided satisfactory explanations and supporting documentation. No further concerns at this time.",
+ SUPPLEMENTAL_FILES_LIST: [
+ {
+ DESCRIPTION: "Meeting minutes from customer interview",
+ FILE: createPdfAttachment("meeting-minutes-20251205"),
+ },
+ {
+ DESCRIPTION: "Supporting bank statements",
+ FILE: createPdfAttachment("bank-statements-q4-2025"),
+ },
+ {
+ DESCRIPTION: "Customer signed acknowledgment",
+ FILE: createPdfAttachment("customer-acknowledgment"),
+ },
+ ],
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-generic_note.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test generic_upload form
+ if (false) // FIXME: dold
+ {
+ await decideMeasure("kyx");
+ await submitForm("generic_upload", {
+ FORM_ID: "generic_upload",
+ FORM_VERSION: 1,
+ NOTE_TEXT: "Customer requested to upload current business license and updated company registry extract as part of periodic review.",
+ FILE: createPdfAttachment("business-license-2025"),
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-generic_upload.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test accept-tos form
+ {
+ await decideMeasure("accept-tos");
+ await submitForm("accept-tos", {
+ FORM_ID: "accept-tos",
+ FORM_VERSION: 1,
+ ACCEPTED_TERMS_OF_SERVICE: "v1",
+ DOWNLOADED_TERMS_OF_SERVICE: true,
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-accept-tos.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test challenger-postal form
+ if (false) // FIXME: Dold: This is not exactly a 'form'. How to submit?
+ {
+ await decideMeasure("postal-registration");
+ await submitForm("challenger-postal", {
+ FORM_ID: "challenger-postal",
+ FORM_VERSION: 1,
+ CONTACT_NAME: "Hans Müller",
+ ADDRESS_LINES: "Bahnhofstrasse 123\n8001 Zürich",
+ ADDRESS_COUNTRY: "CH",
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-challenger-postal.pdf`;
+ fs.writeFileSync(f, Buffer.from(res));
+ console.log(`written to ${f}`);
+ }
+
+ // Test challenger-sms form
+ if (false) // FIXME: Dold: This is not exactly a 'form'. How to submit?
+ {
+ await decideMeasure("sms-registration");
+ await submitForm("challenger-sms", {
+ FORM_ID: "challenger-sms",
+ FORM_VERSION: 1,
+ CONTACT_PHONE: "+41791234567",
+ });
+
+ const res = succeedOrThrow(
+ await exchangeClient.getAmlAttributesForAccountAsPdf(
+ officerAcc,
+ accountPaytoHash,
+ ),
+ );
+ const f = t.testDir + `/aml-form-challenger-sms.pdf`;
fs.writeFileSync(f, Buffer.from(res));
console.log(`written to ${f}`);
}
+ // Export accounts in different formats
{
const res = succeedOrThrow(
await exchangeClient.getAmlAccountsAsOtherFormat(officerAcc, "text/csv"),
@@ -99,4 +928,4 @@ export async function runTopsAmlPdfTest(t: GlobalTestState) {
}
}
-runTopsAmlPdfTest.suites = ["wallet"];
+runTopsAmlPdfTest.suites = ["wallet"];
+\ No newline at end of file