UNPKG

@taprsvp/types

Version:

TypeScript types and interfaces for the Transaction Authorization Protocol (TAP)

618 lines 30.2 kB
"use strict"; /** * @fileoverview Tests for TAIP-12 Name Hashing Implementation * * These tests verify the name normalization and hashing functions comply with * the TAIP-12 specification and produce the correct hash values for interoperability * with VerifyVASP and GTR networks. * * Enhanced with fast-check property-based testing and IVMS101 integration tests. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const vitest_1 = require("vitest"); const fc = __importStar(require("fast-check")); const ivms101_1 = require("ivms101"); const nameHash_1 = require("./nameHash"); (0, vitest_1.describe)('normalizeForHashing', () => { (0, vitest_1.it)('should remove all whitespace and convert to uppercase', () => { (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('Alice Lee')).toBe('ALICELEE'); (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('Bob Smith')).toBe('BOBSMITH'); }); (0, vitest_1.it)('should handle multiple spaces and different whitespace types', () => { (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)(' Alice Lee ')).toBe('ALICELEE'); (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('Bob\tSmith')).toBe('BOBSMITH'); (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('John\nDoe')).toBe('JOHNDOE'); (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('Jane\r\nSmith')).toBe('JANESMITH'); }); (0, vitest_1.it)('should preserve non-ASCII characters', () => { (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('José María')).toBe('JOSÉMARÍA'); (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('François Müller')).toBe('FRANÇOISMÜLLER'); }); (0, vitest_1.it)('should handle non-Western scripts correctly', () => { // Arabic names (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('محمد علي')).toBe('محمدعلي'); (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)(' أحمد محمد ')).toBe('أحمدمحمد'); // Chinese names (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('李 小明')).toBe('李小明'); (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('王小华')).toBe('王小华'); // Korean names (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('김 철수')).toBe('김철수'); (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('박지민')).toBe('박지민'); // Japanese names (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('田中 太郎')).toBe('田中太郎'); (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('佐藤花子')).toBe('佐藤花子'); }); (0, vitest_1.it)('should handle edge cases', () => { (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('')).toBe(''); (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)(' ')).toBe(''); (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('A')).toBe('A'); (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('a')).toBe('A'); }); (0, vitest_1.it)('should handle names with middle names and initials', () => { (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('Mary Jane Watson')).toBe('MARYJANEWATSON'); (0, vitest_1.expect)((0, nameHash_1.normalizeForHashing)('John F. Kennedy')).toBe('JOHNF.KENNEDY'); }); }); (0, vitest_1.describe)('generateNameHash', () => { (0, vitest_1.it)('should generate correct hash for TAIP-12 test vector "Alice Lee"', async () => { const hash = await (0, nameHash_1.generateNameHash)('Alice Lee'); (0, vitest_1.expect)(hash).toBe('b117f44426c9670da91b563db728cd0bc8bafa7d1a6bb5e764d1aad2ca25032e'); }); (0, vitest_1.it)('should generate correct hash for TAIP-12 test vector "Bob Smith"', async () => { const hash = await (0, nameHash_1.generateNameHash)('Bob Smith'); (0, vitest_1.expect)(hash).toBe('5432e86b4d4a3a2b4be57b713b12c5c576c88459fe1cfdd760fd6c99a0e06686'); }); (0, vitest_1.it)('should return consistent hashes for the same input', async () => { const hash1 = await (0, nameHash_1.generateNameHash)('Alice Lee'); const hash2 = await (0, nameHash_1.generateNameHash)('Alice Lee'); (0, vitest_1.expect)(hash1).toBe(hash2); }); (0, vitest_1.it)('should produce different hashes for different names', async () => { const hash1 = await (0, nameHash_1.generateNameHash)('Alice Lee'); const hash2 = await (0, nameHash_1.generateNameHash)('Bob Smith'); (0, vitest_1.expect)(hash1).not.toBe(hash2); }); (0, vitest_1.it)('should handle whitespace variations consistently', async () => { const hash1 = await (0, nameHash_1.generateNameHash)('Alice Lee'); const hash2 = await (0, nameHash_1.generateNameHash)(' Alice Lee '); const hash3 = await (0, nameHash_1.generateNameHash)('Alice\tLee'); (0, vitest_1.expect)(hash1).toBe(hash2); (0, vitest_1.expect)(hash1).toBe(hash3); }); (0, vitest_1.it)('should handle case variations consistently', async () => { const hash1 = await (0, nameHash_1.generateNameHash)('Alice Lee'); const hash2 = await (0, nameHash_1.generateNameHash)('alice lee'); const hash3 = await (0, nameHash_1.generateNameHash)('ALICE LEE'); (0, vitest_1.expect)(hash1).toBe(hash2); (0, vitest_1.expect)(hash1).toBe(hash3); }); (0, vitest_1.it)('should always return 64-character hex string', async () => { const testNames = [ 'A', 'Alice Lee', 'Very Long Name With Multiple Words', 'José María García-González' ]; for (const name of testNames) { const hash = await (0, nameHash_1.generateNameHash)(name); (0, vitest_1.expect)(hash).toMatch(/^[0-9a-f]{64}$/); (0, vitest_1.expect)(hash.length).toBe(64); } }); (0, vitest_1.it)('should handle empty string', async () => { const hash = await (0, nameHash_1.generateNameHash)(''); (0, vitest_1.expect)(hash).toMatch(/^[0-9a-f]{64}$/); (0, vitest_1.expect)(hash.length).toBe(64); }); (0, vitest_1.it)('should handle non-ASCII characters', async () => { const hash1 = await (0, nameHash_1.generateNameHash)('José María'); const hash2 = await (0, nameHash_1.generateNameHash)('François Müller'); (0, vitest_1.expect)(hash1).toMatch(/^[0-9a-f]{64}$/); (0, vitest_1.expect)(hash2).toMatch(/^[0-9a-f]{64}$/); (0, vitest_1.expect)(hash1).not.toBe(hash2); }); (0, vitest_1.it)('should handle non-Western names correctly', async () => { const testCases = [ { name: 'محمد علي', description: 'Arabic name' }, { name: '李小明', description: 'Chinese name' }, { name: '김철수', description: 'Korean name' }, { name: '田中太郎', description: 'Japanese name' }, { name: 'أحمد محمد', description: 'Another Arabic name' }, { name: '王小华', description: 'Another Chinese name' }, { name: '박지민', description: 'Another Korean name' }, { name: '佐藤花子', description: 'Another Japanese name' } ]; const hashes = new Set(); for (const { name, description } of testCases) { const hash = await (0, nameHash_1.generateNameHash)(name); // Verify hash format (0, vitest_1.expect)(hash).toMatch(/^[0-9a-f]{64}$/); (0, vitest_1.expect)(hash.length).toBe(64); // Verify uniqueness (no collisions in our test set) (0, vitest_1.expect)(hashes.has(hash)).toBe(false); hashes.add(hash); // Test that normalization works consistently const hashWithSpaces = await (0, nameHash_1.generateNameHash)(` ${name} `); const hashLowerCase = await (0, nameHash_1.generateNameHash)(name.toLowerCase()); (0, vitest_1.expect)(hash).toBe(hashWithSpaces); // Note: toLowerCase() behavior varies for non-Latin scripts, // but our normalization uses toUpperCase() so we test that const hashUpperCase = await (0, nameHash_1.generateNameHash)(name.toUpperCase()); (0, vitest_1.expect)(hash).toBe(hashUpperCase); } }); (0, vitest_1.it)('should handle special characters and punctuation', async () => { const hash1 = await (0, nameHash_1.generateNameHash)("O'Connor"); const hash2 = await (0, nameHash_1.generateNameHash)('Smith-Jones'); const hash3 = await (0, nameHash_1.generateNameHash)('Dr. Watson'); (0, vitest_1.expect)(hash1).toMatch(/^[0-9a-f]{64}$/); (0, vitest_1.expect)(hash2).toMatch(/^[0-9a-f]{64}$/); (0, vitest_1.expect)(hash3).toMatch(/^[0-9a-f]{64}$/); }); }); (0, vitest_1.describe)('TAIP-12 compliance', () => { (0, vitest_1.it)('should produce hashes compatible with VerifyVASP specification', async () => { // These test vectors should match the exact hashing algorithm used by VerifyVASP const testCases = [ { name: 'Alice Lee', expected: 'b117f44426c9670da91b563db728cd0bc8bafa7d1a6bb5e764d1aad2ca25032e' }, { name: 'Bob Smith', expected: '5432e86b4d4a3a2b4be57b713b12c5c576c88459fe1cfdd760fd6c99a0e06686' } ]; for (const { name, expected } of testCases) { const hash = await (0, nameHash_1.generateNameHash)(name); (0, vitest_1.expect)(hash).toBe(expected); } }); (0, vitest_1.it)('should handle normalization exactly as specified in TAIP-12', async () => { // Test the specific normalization requirements from TAIP-12 const variations = [ 'Alice Lee', ' Alice Lee ', ' Alice Lee ', 'alice lee', 'ALICE LEE' ]; const expectedHash = 'b117f44426c9670da91b563db728cd0bc8bafa7d1a6bb5e764d1aad2ca25032e'; for (const variation of variations) { const hash = await (0, nameHash_1.generateNameHash)(variation); (0, vitest_1.expect)(hash).toBe(expectedHash); } }); }); (0, vitest_1.describe)('IVMS101 Integration', () => { (0, vitest_1.describe)('generateNameHash with IVMS101 2020 originators', () => { (0, vitest_1.it)('should handle natural person originators', async () => { const originator = { originatorPerson: [ { naturalPerson: { name: { nameIdentifier: [ { primaryIdentifier: "Lee", secondaryIdentifier: "Alice", naturalPersonNameIdentifierType: "LEGL" } ] } } } ] }; const hash = await (0, nameHash_1.generateNameHash)(originator); const expectedHash = await (0, nameHash_1.generateNameHash)("Alice Lee"); (0, vitest_1.expect)(hash).toBe(expectedHash); }); (0, vitest_1.it)('should handle legal person originators', async () => { const originator = { originatorPerson: [ { legalPerson: { name: { nameIdentifier: [ { legalPersonName: "Acme Corporation", legalPersonNameIdentifierType: "LEGL" } ] } } } ] }; const hash = await (0, nameHash_1.generateNameHash)(originator); const expectedHash = await (0, nameHash_1.generateNameHash)("Acme Corporation"); (0, vitest_1.expect)(hash).toBe(expectedHash); }); (0, vitest_1.it)('should handle multiple persons in originator', async () => { const originator = { originatorPerson: [ { naturalPerson: { name: { nameIdentifier: [ { primaryIdentifier: "Smith", secondaryIdentifier: "John", naturalPersonNameIdentifierType: "LEGL" } ] } } }, { legalPerson: { name: { nameIdentifier: [ { legalPersonName: "Tech Corp", legalPersonNameIdentifierType: "LEGL" } ] } } } ] }; const hash = await (0, nameHash_1.generateNameHash)(originator); const expectedHash = await (0, nameHash_1.generateNameHash)("John Smith Tech Corp"); (0, vitest_1.expect)(hash).toBe(expectedHash); }); (0, vitest_1.it)('should prefer legal names over other name types', async () => { const originator = { originatorPerson: [ { naturalPerson: { name: { nameIdentifier: [ { primaryIdentifier: "Doe", secondaryIdentifier: "Johnny", naturalPersonNameIdentifierType: "ALIA" }, { primaryIdentifier: "Doe", secondaryIdentifier: "John", naturalPersonNameIdentifierType: "LEGL" } ] } } } ] }; const hash = await (0, nameHash_1.generateNameHash)(originator); const expectedHash = await (0, nameHash_1.generateNameHash)("John Doe"); (0, vitest_1.expect)(hash).toBe(expectedHash); }); }); (0, vitest_1.describe)('generateNameHash with IVMS101 2020 beneficiaries', () => { (0, vitest_1.it)('should handle natural person beneficiaries', async () => { const beneficiary = { beneficiaryPerson: [ { naturalPerson: { name: { nameIdentifier: [ { primaryIdentifier: "Smith", secondaryIdentifier: "Bob", naturalPersonNameIdentifierType: "LEGL" } ] } } } ] }; const hash = await (0, nameHash_1.generateNameHash)(beneficiary); const expectedHash = await (0, nameHash_1.generateNameHash)("Bob Smith"); (0, vitest_1.expect)(hash).toBe(expectedHash); }); (0, vitest_1.it)('should handle legal person beneficiaries', async () => { const beneficiary = { beneficiaryPerson: [ { legalPerson: { name: { nameIdentifier: [ { legalPersonName: "Global Bank Ltd", legalPersonNameIdentifierType: "LEGL" } ] } } } ] }; const hash = await (0, nameHash_1.generateNameHash)(beneficiary); const expectedHash = await (0, nameHash_1.generateNameHash)("Global Bank Ltd"); (0, vitest_1.expect)(hash).toBe(expectedHash); }); }); (0, vitest_1.describe)('generateNameHash with IVMS101 2023 structures', () => { (0, vitest_1.it)('should handle 2023 originator format', async () => { const originator = { originatorPerson: [ { naturalPerson: { name: { nameIdentifier: [ { primaryIdentifier: "Williams", secondaryIdentifier: "Sarah", naturalPersonNameIdentifierType: "LEGL" } ] } } } ] }; const hash = await (0, nameHash_1.generateNameHash)(originator); const expectedHash = await (0, nameHash_1.generateNameHash)("Sarah Williams"); (0, vitest_1.expect)(hash).toBe(expectedHash); }); (0, vitest_1.it)('should handle 2023 beneficiary format', async () => { const beneficiary = { beneficiaryPerson: [ { legalPerson: { name: { nameIdentifier: [ { legalPersonName: "Future Finance Inc", legalPersonNameIdentifierType: "LEGL" } ] } } } ] }; const hash = await (0, nameHash_1.generateNameHash)(beneficiary); const expectedHash = await (0, nameHash_1.generateNameHash)("Future Finance Inc"); (0, vitest_1.expect)(hash).toBe(expectedHash); }); }); (0, vitest_1.describe)('edge cases', () => { (0, vitest_1.it)('should handle empty name arrays', async () => { const originator = { originatorPerson: [ { naturalPerson: { name: { nameIdentifier: [] } } } ] }; const hash = await (0, nameHash_1.generateNameHash)(originator); const expectedHash = await (0, nameHash_1.generateNameHash)(""); // Empty string hash (0, vitest_1.expect)(hash).toBe(expectedHash); }); (0, vitest_1.it)('should handle missing name identifiers', async () => { const beneficiary = { beneficiaryPerson: [ { naturalPerson: { name: { nameIdentifier: [ { primaryIdentifier: "OnlyPrimary", naturalPersonNameIdentifierType: "LEGL" } ] } } } ] }; const hash = await (0, nameHash_1.generateNameHash)(beneficiary); const expectedHash = await (0, nameHash_1.generateNameHash)("OnlyPrimary"); (0, vitest_1.expect)(hash).toBe(expectedHash); }); (0, vitest_1.it)('should throw error for invalid input types', async () => { await (0, vitest_1.expect)((0, nameHash_1.generateNameHash)({})).rejects.toThrow('Invalid input type. Expected string, IVMS101 Originator, or IVMS101 Beneficiary'); }); }); }); (0, vitest_1.describe)('Property-Based Testing with IVMS101', () => { (0, vitest_1.it)('should always produce 64-character hex strings for generated originators', () => { fc.assert(fc.asyncProperty(ivms101_1.arbitraries.originator2023(), async (originator) => { const hash = await (0, nameHash_1.generateNameHash)(originator); (0, vitest_1.expect)(hash).toMatch(/^[0-9a-f]{64}$/); (0, vitest_1.expect)(hash.length).toBe(64); })); }); (0, vitest_1.it)('should always produce 64-character hex strings for generated beneficiaries', () => { fc.assert(fc.asyncProperty(ivms101_1.arbitraries.beneficiary2023(), async (beneficiary) => { const hash = await (0, nameHash_1.generateNameHash)(beneficiary); (0, vitest_1.expect)(hash).toMatch(/^[0-9a-f]{64}$/); (0, vitest_1.expect)(hash.length).toBe(64); })); }); (0, vitest_1.it)('should always produce 64-character hex strings for IVMS101 2023 originators', () => { fc.assert(fc.asyncProperty(ivms101_1.arbitraries.originator2023(), async (originator) => { const hash = await (0, nameHash_1.generateNameHash)(originator); (0, vitest_1.expect)(hash).toMatch(/^[0-9a-f]{64}$/); (0, vitest_1.expect)(hash.length).toBe(64); })); }); (0, vitest_1.it)('should always produce 64-character hex strings for IVMS101 2023 beneficiaries', () => { fc.assert(fc.asyncProperty(ivms101_1.arbitraries.beneficiary2023(), async (beneficiary) => { const hash = await (0, nameHash_1.generateNameHash)(beneficiary); (0, vitest_1.expect)(hash).toMatch(/^[0-9a-f]{64}$/); (0, vitest_1.expect)(hash.length).toBe(64); })); }); (0, vitest_1.it)('should produce consistent hashes for equivalent name strings', () => { fc.assert(fc.asyncProperty(ivms101_1.arbitraries.naturalPersonNameId2023(), async (nameId) => { // Build name string manually const parts = []; if (nameId.secondaryIdentifier) { parts.push(nameId.secondaryIdentifier); } parts.push(nameId.primaryIdentifier); const nameString = parts.join(' '); // Create IVMS101 structure with same name const originator = { originatorPerson: [ { naturalPerson: { name: { nameIdentifier: [nameId] } } } ] }; const stringHash = await (0, nameHash_1.generateNameHash)(nameString); const structureHash = await (0, nameHash_1.generateNameHash)(originator); (0, vitest_1.expect)(stringHash).toBe(structureHash); })); }); (0, vitest_1.it)('should handle all natural person name types correctly', () => { fc.assert(fc.asyncProperty(ivms101_1.arbitraries.naturalPerson2023(), async (naturalPerson) => { const originator = { originatorPerson: [{ naturalPerson }] }; const hash = await (0, nameHash_1.generateNameHash)(originator); // Should always be a valid hash (0, vitest_1.expect)(hash).toMatch(/^[0-9a-f]{64}$/); (0, vitest_1.expect)(hash.length).toBe(64); // Should be deterministic const hash2 = await (0, nameHash_1.generateNameHash)(originator); (0, vitest_1.expect)(hash).toBe(hash2); })); }); (0, vitest_1.it)('should handle all legal person name types correctly', () => { fc.assert(fc.asyncProperty(ivms101_1.arbitraries.legalPerson2023(), async (legalPerson) => { const beneficiary = { beneficiaryPerson: [{ legalPerson }] }; const hash = await (0, nameHash_1.generateNameHash)(beneficiary); // Should always be a valid hash (0, vitest_1.expect)(hash).toMatch(/^[0-9a-f]{64}$/); (0, vitest_1.expect)(hash.length).toBe(64); // Should be deterministic const hash2 = await (0, nameHash_1.generateNameHash)(beneficiary); (0, vitest_1.expect)(hash).toBe(hash2); })); }); (0, vitest_1.it)('should normalize names consistently across input types', () => { fc.assert(fc.asyncProperty(fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0), async (nameString) => { // Create an IVMS101 structure with the same name const originator = { originatorPerson: [ { naturalPerson: { name: { nameIdentifier: [ { primaryIdentifier: nameString, naturalPersonNameIdentifierType: "LEGL" } ] } } } ] }; const stringHash = await (0, nameHash_1.generateNameHash)(nameString); const structureHash = await (0, nameHash_1.generateNameHash)(originator); (0, vitest_1.expect)(stringHash).toBe(structureHash); })); }); }); (0, vitest_1.describe)('Performance and Edge Cases', () => { (0, vitest_1.it)('should handle large numbers of persons efficiently', async () => { // Create an originator with many persons const manyPersons = Array.from({ length: 100 }, (_, i) => ({ naturalPerson: { name: { nameIdentifier: [ { primaryIdentifier: `Person${i}`, naturalPersonNameIdentifierType: "LEGL" } ] } } })); const originator = { originatorPerson: manyPersons }; const start = Date.now(); const hash = await (0, nameHash_1.generateNameHash)(originator); const duration = Date.now() - start; (0, vitest_1.expect)(hash).toMatch(/^[0-9a-f]{64}$/); (0, vitest_1.expect)(duration).toBeLessThan(1000); // Should complete within 1 second }); (0, vitest_1.it)('should handle Unicode names correctly', async () => { const unicodeNames = [ "李小明", // Chinese "محمد علي", // Arabic "José María", // Spanish "Владимир", // Russian "田中太郎" // Japanese ]; for (const name of unicodeNames) { const originator = { originatorPerson: [ { naturalPerson: { name: { nameIdentifier: [ { primaryIdentifier: name, naturalPersonNameIdentifierType: "LEGL" } ] } } } ] }; const stringHash = await (0, nameHash_1.generateNameHash)(name); const structureHash = await (0, nameHash_1.generateNameHash)(originator); (0, vitest_1.expect)(stringHash).toBe(structureHash); (0, vitest_1.expect)(stringHash).toMatch(/^[0-9a-f]{64}$/); } }); }); //# sourceMappingURL=nameHash.test.js.map