import { expect, tap } from '@git.zone/tstest/tapbundle'; import { EInvoice } from '../../../ts/index.js'; tap.test('EDGE-04: Unusual Character Sets - should handle unusual and exotic character encodings', async () => { console.log('Testing unusual character sets in e-invoices...\n'); // Test 1: Unicode edge cases with real invoice data const testUnicodeEdgeCases = async () => { const testCases = [ { name: 'zero-width-characters', text: 'Invoice\u200B\u200C\u200D\uFEFFNumber', description: 'Zero-width spaces and joiners' }, { name: 'right-to-left', text: 'مرحبا INV-001 שלום', description: 'RTL Arabic and Hebrew mixed with LTR' }, { name: 'surrogate-pairs', text: '𝐇𝐞𝐥𝐥𝐨 😀 🎉 Invoice', description: 'Mathematical bold text and emojis' }, { name: 'combining-characters', text: 'Ińvȯíçë̃ Nüm̈bër̊', description: 'Combining diacritical marks' }, { name: 'control-characters', text: 'Invoice Test', // Remove actual control chars as they break XML description: 'Control characters (removed for XML safety)' }, { name: 'bidi-override', text: '\u202Eتسا Invoice 123\u202C', description: 'Bidirectional override characters' } ]; const results = []; for (const testCase of testCases) { try { const einvoice = new EInvoice(); einvoice.issueDate = new Date(2024, 0, 1); einvoice.id = testCase.text; einvoice.subject = testCase.description; // Set required fields for EN16931 compliance einvoice.from = { type: 'company', name: 'Test Unicode Company', description: testCase.description, address: { streetName: 'Test Street', houseNumber: '1', postalCode: '12345', city: 'Test City', country: 'DE' }, status: 'active', foundedDate: { year: 2020, month: 1, day: 1 }, registrationDetails: { vatId: 'DE123456789', registrationId: 'HRB 12345', registrationName: 'Commercial Register' } }; einvoice.to = { type: 'person', name: 'Test', surname: 'Customer', salutation: 'Mr' as const, sex: 'male' as const, title: 'Doctor' as const, description: 'Test customer', address: { streetName: 'Customer Street', houseNumber: '2', postalCode: '54321', city: 'Customer City', country: 'DE' } }; // Add test item einvoice.items = [{ position: 1, name: `Item with ${testCase.name}`, articleNumber: 'ART-001', unitType: 'EA', unitQuantity: 1, unitNetPrice: 100, vatPercentage: 19 }]; // Export to UBL format const ublString = await einvoice.toXmlString('ubl'); // Check if special characters are preserved const preserved = ublString.includes(testCase.text); // Try to import it back const newInvoice = new EInvoice(); await newInvoice.fromXmlString(ublString); const roundTripPreserved = (newInvoice.id === testCase.text || newInvoice.invoiceId === testCase.text || newInvoice.accountingDocId === testCase.text); console.log(`Test 1.${testCase.name}:`); console.log(` Unicode preserved in XML: ${preserved ? 'Yes' : 'No'}`); console.log(` Round-trip successful: ${roundTripPreserved ? 'Yes' : 'No'}`); results.push({ name: testCase.name, preserved, roundTripPreserved }); } catch (error) { console.log(`Test 1.${testCase.name}:`); console.log(` Error: ${error.message}`); results.push({ name: testCase.name, preserved: false, roundTripPreserved: false, error: error.message }); } } return results; }; // Test 2: Various character encodings in invoice content const testVariousEncodings = async () => { const encodingTests = [ { encoding: 'UTF-8', text: 'Übung macht den Meister - äöüß' }, { encoding: 'Latin', text: 'Ñoño español - ¡Hola!' }, { encoding: 'Cyrillic', text: 'Счёт-фактура № 2024' }, { encoding: 'Greek', text: 'Τιμολόγιο: ΜΜΚΔ' }, { encoding: 'Chinese', text: '發票編號:貳零貳肆' } ]; const results = []; for (const test of encodingTests) { try { const einvoice = new EInvoice(); einvoice.issueDate = new Date(2024, 0, 1); einvoice.id = `ENC-${test.encoding}`; einvoice.subject = test.text; einvoice.from = { type: 'company', name: test.text, description: `Company using ${test.encoding}`, address: { streetName: 'Test Street', houseNumber: '1', postalCode: '12345', city: 'Test City', country: 'DE' }, status: 'active', foundedDate: { year: 2020, month: 1, day: 1 }, registrationDetails: { vatId: 'DE123456789', registrationId: 'HRB 12345', registrationName: 'Commercial Register' } }; einvoice.to = { type: 'person', name: 'Test', surname: 'Customer', salutation: 'Mr' as const, sex: 'male' as const, title: 'Doctor' as const, description: 'Test customer', address: { streetName: 'Customer Street', houseNumber: '2', postalCode: '54321', city: 'Customer City', country: 'DE' } }; einvoice.items = [{ position: 1, name: test.text, articleNumber: 'ENC-001', unitType: 'EA', unitQuantity: 1, unitNetPrice: 100, vatPercentage: 19 }]; // Test both UBL and CII formats const ublString = await einvoice.toXmlString('ubl'); const ciiString = await einvoice.toXmlString('cii'); // Check preservation in both formats const ublPreserved = ublString.includes(test.text); const ciiPreserved = ciiString.includes(test.text); // Test round-trip for both formats const ublInvoice = new EInvoice(); await ublInvoice.fromXmlString(ublString); const ciiInvoice = new EInvoice(); await ciiInvoice.fromXmlString(ciiString); const ublRoundTrip = ublInvoice.from?.name?.includes(test.text.substring(0, 10)) || false; const ciiRoundTrip = ciiInvoice.from?.name?.includes(test.text.substring(0, 10)) || false; console.log(`\nTest 2.${test.encoding}:`); console.log(` UBL preserves encoding: ${ublPreserved ? 'Yes' : 'No'}`); console.log(` CII preserves encoding: ${ciiPreserved ? 'Yes' : 'No'}`); console.log(` UBL round-trip: ${ublRoundTrip ? 'Yes' : 'No'}`); console.log(` CII round-trip: ${ciiRoundTrip ? 'Yes' : 'No'}`); results.push({ encoding: test.encoding, ublPreserved, ciiPreserved, ublRoundTrip, ciiRoundTrip }); } catch (error) { console.log(`\nTest 2.${test.encoding}:`); console.log(` Error: ${error.message}`); results.push({ encoding: test.encoding, ublPreserved: false, ciiPreserved: false, ublRoundTrip: false, ciiRoundTrip: false, error: error.message }); } } return results; }; // Test 3: Extremely unusual characters const testExtremelyUnusualChars = async () => { const extremeTests = [ { name: 'ancient-scripts', text: '𐀀𐀁𐀂 Invoice 𓀀𓀁𓀂', description: 'Linear B and Egyptian hieroglyphs' }, { name: 'musical-symbols', text: '♪♫♪ Invoice ♫♪♫', description: 'Musical notation symbols' }, { name: 'math-symbols', text: '∫∂ Invoice ∆∇', description: 'Mathematical operators' }, { name: 'private-use', text: '\uE000\uE001 Invoice \uE002\uE003', description: 'Private use area characters' } ]; const results = []; for (const test of extremeTests) { try { const einvoice = new EInvoice(); einvoice.id = `EXTREME-${test.name}`; einvoice.issueDate = new Date(2024, 0, 1); einvoice.subject = test.description; einvoice.from = { type: 'company', name: `Company ${test.text}`, description: test.description, address: { streetName: 'Test Street', houseNumber: '1', postalCode: '12345', city: 'Test City', country: 'DE' }, status: 'active', foundedDate: { year: 2020, month: 1, day: 1 }, registrationDetails: { vatId: 'DE123456789', registrationId: 'HRB 12345', registrationName: 'Commercial Register' } }; einvoice.to = { type: 'person', name: 'Test', surname: 'Customer', salutation: 'Mr' as const, sex: 'male' as const, title: 'Doctor' as const, description: 'Test customer', address: { streetName: 'Customer Street', houseNumber: '2', postalCode: '54321', city: 'Customer City', country: 'DE' } }; einvoice.items = [{ position: 1, name: `Product ${test.text}`, articleNumber: 'EXT-001', unitType: 'EA', unitQuantity: 1, unitNetPrice: 100, vatPercentage: 19 }]; const xmlString = await einvoice.toXmlString('ubl'); const preserved = xmlString.includes(test.text); const newInvoice = new EInvoice(); await newInvoice.fromXmlString(xmlString); const roundTrip = newInvoice.from?.name?.includes(test.text) || false; console.log(`\nTest 3.${test.name}:`); console.log(` Extreme chars preserved: ${preserved ? 'Yes' : 'No'}`); console.log(` Round-trip successful: ${roundTrip ? 'Yes' : 'No'}`); results.push({ name: test.name, preserved, roundTrip }); } catch (error) { console.log(`\nTest 3.${test.name}:`); console.log(` Error: ${error.message}`); results.push({ name: test.name, preserved: false, roundTrip: false, error: error.message }); } } return results; }; // Test 4: Normalization issues const testNormalizationIssues = async () => { const normalizationTests = [ { name: 'nfc-nfd', nfc: 'é', // NFC: single character nfd: 'é', // NFD: e + combining acute description: 'NFC vs NFD normalization' }, { name: 'ligatures', text: 'ff Invoice ffi', // ff and ffi ligatures description: 'Unicode ligatures' } ]; const results = []; for (const test of normalizationTests) { try { const einvoice = new EInvoice(); einvoice.id = `NORM-${test.name}`; einvoice.issueDate = new Date(2024, 0, 1); einvoice.subject = test.description; // Use the test text in company name const testText = test.text || test.nfc; einvoice.from = { type: 'company', name: `Company ${testText}`, description: test.description, address: { streetName: 'Test Street', houseNumber: '1', postalCode: '12345', city: 'Test City', country: 'DE' }, status: 'active', foundedDate: { year: 2020, month: 1, day: 1 }, registrationDetails: { vatId: 'DE123456789', registrationId: 'HRB 12345', registrationName: 'Commercial Register' } }; einvoice.to = { type: 'person', name: 'Test', surname: 'Customer', salutation: 'Mr' as const, sex: 'male' as const, title: 'Doctor' as const, description: 'Test customer', address: { streetName: 'Customer Street', houseNumber: '2', postalCode: '54321', city: 'Customer City', country: 'DE' } }; einvoice.items = [{ position: 1, name: `Product ${testText}`, articleNumber: 'NORM-001', unitType: 'EA', unitQuantity: 1, unitNetPrice: 100, vatPercentage: 19 }]; const xmlString = await einvoice.toXmlString('ubl'); const preserved = xmlString.includes(testText); const newInvoice = new EInvoice(); await newInvoice.fromXmlString(xmlString); const roundTrip = newInvoice.from?.name?.includes(testText) || false; console.log(`\nTest 4.${test.name}:`); console.log(` Normalization preserved: ${preserved ? 'Yes' : 'No'}`); console.log(` Round-trip successful: ${roundTrip ? 'Yes' : 'No'}`); results.push({ name: test.name, preserved, roundTrip }); } catch (error) { console.log(`\nTest 4.${test.name}:`); console.log(` Error: ${error.message}`); results.push({ name: test.name, preserved: false, roundTrip: false, error: error.message }); } } return results; }; // Run all tests const unicodeResults = await testUnicodeEdgeCases(); const encodingResults = await testVariousEncodings(); const extremeResults = await testExtremelyUnusualChars(); const normalizationResults = await testNormalizationIssues(); console.log(`\n=== Unusual Character Sets Test Summary ===`); // Count successful tests const unicodeSuccess = unicodeResults.filter(r => r.roundTripPreserved).length; const encodingSuccess = encodingResults.filter(r => r.ublRoundTrip || r.ciiRoundTrip).length; const extremeSuccess = extremeResults.filter(r => r.roundTrip).length; const normalizationSuccess = normalizationResults.filter(r => r.roundTrip).length; console.log(`Unicode edge cases: ${unicodeSuccess}/${unicodeResults.length} successful`); console.log(`Various encodings: ${encodingSuccess}/${encodingResults.length} successful`); console.log(`Extreme characters: ${extremeSuccess}/${extremeResults.length} successful`); console.log(`Normalization tests: ${normalizationSuccess}/${normalizationResults.length} successful`); // Test passes if at least basic Unicode handling works const basicUnicodeWorks = unicodeResults.some(r => r.roundTripPreserved); const basicEncodingWorks = encodingResults.some(r => r.ublRoundTrip || r.ciiRoundTrip); expect(basicUnicodeWorks).toBeTrue(); expect(basicEncodingWorks).toBeTrue(); }); // Run the test tap.start();