import { expect, tap } from '@git.zone/tstest/tapbundle'; import { EInvoice } from '../../../ts/index.js'; tap.test('ENC-07: Attribute Encoding - should handle character encoding in XML attributes', async () => { console.log('Testing XML attribute character encoding...\n'); // Test 1: Special characters in XML attributes const testSpecialCharacters = async () => { const einvoice = new EInvoice(); einvoice.id = 'ATTR-SPECIAL-TEST'; einvoice.issueDate = new Date(2025, 0, 25); einvoice.subject = 'Attribute encoding test with special characters'; // Create invoice with special characters that need escaping in attributes einvoice.from = { type: 'company', name: 'Company & Co. "Special" Ltd', description: 'Testing chars & "quotes"', address: { streetName: 'Street & "Quote" ', 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: 'John & "Test"', surname: 'Customer', salutation: 'Mr' as const, sex: 'male' as const, title: 'Doctor' as const, description: 'Customer with & "chars"', address: { streetName: 'Customer & Street', houseNumber: '2', postalCode: '54321', city: 'Customer "City"', country: 'DE' } }; einvoice.items = [{ position: 1, name: 'Product & "Special" ', articleNumber: 'ATTR&001', unitType: 'EA', unitQuantity: 1, unitNetPrice: 100, vatPercentage: 19 }]; // Export and verify attributes are properly encoded const xmlString = await einvoice.toXmlString('ubl'); // Check that special characters are properly escaped in the XML const hasEscapedAmpersand = xmlString.includes('&'); const hasEscapedQuotes = xmlString.includes('"'); const hasEscapedLt = xmlString.includes('<'); const hasEscapedGt = xmlString.includes('>'); // Verify the XML can be parsed back const newInvoice = new EInvoice(); await newInvoice.fromXmlString(xmlString); const roundTripSuccess = (newInvoice.id === 'ATTR-SPECIAL-TEST' || newInvoice.invoiceId === 'ATTR-SPECIAL-TEST' || newInvoice.accountingDocId === 'ATTR-SPECIAL-TEST') && newInvoice.from?.name?.includes('&') && newInvoice.from?.name?.includes('"'); console.log(`Test 1 - Special characters in attributes:`); console.log(` Ampersand escaped: ${hasEscapedAmpersand ? 'Yes' : 'No'}`); console.log(` Quotes escaped: ${hasEscapedQuotes ? 'Yes' : 'No'}`); console.log(` Less-than escaped: ${hasEscapedLt ? 'Yes' : 'No'}`); console.log(` Greater-than escaped: ${hasEscapedGt ? 'Yes' : 'No'}`); console.log(` Round-trip successful: ${roundTripSuccess ? 'Yes' : 'No'}`); return { hasEscapedAmpersand, hasEscapedQuotes, hasEscapedLt, hasEscapedGt, roundTripSuccess }; }; // Test 2: Unicode characters in attributes const testUnicodeCharacters = async () => { const einvoice = new EInvoice(); einvoice.id = 'ATTR-UNICODE-TEST'; einvoice.issueDate = new Date(2025, 0, 25); einvoice.subject = 'Unicode attribute test: €äöüßñç'; einvoice.from = { type: 'company', name: 'Företag AB (€äöüß)', description: 'Testing Unicode: ∑∏∆ €£¥₹', address: { streetName: 'Straße Åäöü', houseNumber: '1', postalCode: '12345', city: 'München', country: 'DE' }, status: 'active', foundedDate: { year: 2020, month: 1, day: 1 }, registrationDetails: { vatId: 'DE123456789', registrationId: 'HRB 12345', registrationName: 'Handelsregister' } }; einvoice.to = { type: 'person', name: 'José', surname: 'Müller-Øst', salutation: 'Mr' as const, sex: 'male' as const, title: 'Doctor' as const, description: 'Unicode customer: café résumé naïve', address: { streetName: 'Côte d\'Azur', houseNumber: '2', postalCode: '54321', city: 'São Paulo', country: 'BR' } }; einvoice.items = [{ position: 1, name: 'Café Spécial (™)', articleNumber: 'UNI-001', unitType: 'EA', unitQuantity: 1, unitNetPrice: 100, vatPercentage: 19 }]; const xmlString = await einvoice.toXmlString('ubl'); // Verify Unicode characters are preserved const hasUnicodePreserved = xmlString.includes('Företag') && xmlString.includes('München') && xmlString.includes('José') && xmlString.includes('Müller') && xmlString.includes('Café'); // Test round-trip const newInvoice = new EInvoice(); await newInvoice.fromXmlString(xmlString); const unicodeRoundTrip = newInvoice.from?.name?.includes('Företag') && newInvoice.to?.name?.includes('José') && newInvoice.items?.[0]?.name?.includes('Café'); console.log(`\nTest 2 - Unicode characters in attributes:`); console.log(` Unicode preserved in XML: ${hasUnicodePreserved ? 'Yes' : 'No'}`); console.log(` Unicode round-trip successful: ${unicodeRoundTrip ? 'Yes' : 'No'}`); return { hasUnicodePreserved, unicodeRoundTrip }; }; // Test 3: XML predefined entities in attributes const testXmlEntities = async () => { const testXml = ` 2.1 ATTR-ENTITY-TEST 2025-01-25 EUR Company & Co. "Special" <Ltd> `; try { const einvoice = new EInvoice(); await einvoice.fromXmlString(testXml); const entitySuccess = einvoice.from?.name?.includes('&') && einvoice.from?.name?.includes('"') && einvoice.from?.name?.includes('<') && einvoice.from?.name?.includes('>'); console.log(`\nTest 3 - XML entity parsing:`); console.log(` Entities correctly parsed: ${entitySuccess ? 'Yes' : 'No'}`); return { entitySuccess }; } catch (error) { console.log(`\nTest 3 - XML entity parsing:`); console.log(` Entity parsing failed: ${error.message}`); return { entitySuccess: false }; } }; // Test 4: Attribute value normalization const testAttributeNormalization = async () => { const testXml = ` 2.1 ATTR-NORM-TEST 2025-01-25 EUR Normalized Spaces Test `; try { const einvoice = new EInvoice(); await einvoice.fromXmlString(testXml); // Check if whitespace normalization occurs appropriately const hasNormalization = einvoice.from?.name?.trim() === 'Normalized Spaces Test'; console.log(`\nTest 4 - Attribute value normalization:`); console.log(` Normalization handling: ${hasNormalization ? 'Correct' : 'Needs review'}`); return { hasNormalization }; } catch (error) { console.log(`\nTest 4 - Attribute value normalization:`); console.log(` Normalization test failed: ${error.message}`); return { hasNormalization: false }; } }; // Run all tests const specialCharsResult = await testSpecialCharacters(); const unicodeResult = await testUnicodeCharacters(); const entitiesResult = await testXmlEntities(); const normalizationResult = await testAttributeNormalization(); console.log(`\n=== XML Attribute Encoding Test Summary ===`); console.log(`Special character escaping: ${specialCharsResult.hasEscapedAmpersand && specialCharsResult.hasEscapedQuotes ? 'Working' : 'Issues'}`); console.log(`Unicode character support: ${unicodeResult.hasUnicodePreserved ? 'Working' : 'Issues'}`); console.log(`XML entity parsing: ${entitiesResult.entitySuccess ? 'Working' : 'Issues'}`); console.log(`Attribute normalization: ${normalizationResult.hasNormalization ? 'Working' : 'Issues'}`); console.log(`Round-trip consistency: ${specialCharsResult.roundTripSuccess && unicodeResult.unicodeRoundTrip ? 'Working' : 'Issues'}`); // Test passes if basic XML character escaping and Unicode support work expect(specialCharsResult.hasEscapedAmpersand || specialCharsResult.roundTripSuccess).toBeTrue(); expect(unicodeResult.hasUnicodePreserved || unicodeResult.unicodeRoundTrip).toBeTrue(); }); // Run the test tap.start();