fix(tests): Fixed ENC-01, ENC-02, and ENC-03 encoding tests

- Fixed UTF-8 encoding test (ENC-01) to accept multiple encoding declarations
- Fixed UTF-16 encoding test (ENC-02) by rewriting with correct API usage
- Fixed ISO-8859-1 encoding test (ENC-03) with proper address fields and methods
- All three encoding tests now pass successfully
- Updated edge-cases tests (EDGE-02 through EDGE-07) with new test structure
This commit is contained in:
2025-05-28 12:52:08 +00:00
parent a5b2d435d4
commit 784a50bc7f
6 changed files with 2069 additions and 3267 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,311 +1,325 @@
import { expect, tap } from '@git.zone/tstest/tapbundle'; import { expect, tap } from '@git.zone/tstest/tapbundle';
import { EInvoice } from '../../../ts/index.js'; import { EInvoice } from '../../../ts/index.js';
import { PerformanceTracker } from '../performance.tracker.js';
console.log('Starting ENC-02 UTF-16 encoding test...');
tap.test('ENC-02: UTF-16 Encoding - should handle UTF-16 encoded documents correctly', async () => { tap.test('ENC-02: UTF-16 Encoding - should handle UTF-16 encoded documents correctly', async () => {
console.log('Test function started'); console.log('Testing UTF-16 encoding support...\n');
// ENC-02: Verify correct handling of UTF-16 encoded XML documents (both BE and LE)
// This test ensures proper support for UTF-16 encoding variants
// Test 1: UTF-16 BE (Big Endian) encoding // Test 1: UTF-16 BE (Big Endian) encoding
console.log('\nTest 1: UTF-16 BE (Big Endian) encoding'); const testUtf16Be = async () => {
const { result: beResult, metric: beMetric } = await PerformanceTracker.track( // Create UTF-16 BE XML content with proper address fields
'utf16-be', const xmlContent = `<?xml version="1.0" encoding="UTF-16BE"?>
async () => { <Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
// Create UTF-16 BE content xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
const xmlContent = `<?xml version="1.0" encoding="UTF-16BE"?> xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"> <cbc:ID>UTF16-BE-TEST</cbc:ID>
<UBLVersionID>2.1</UBLVersionID> <cbc:IssueDate>2025-01-25</cbc:IssueDate>
<ID>UTF16-BE-TEST</ID> <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<IssueDate>2025-01-25</IssueDate> <cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<InvoiceTypeCode>380</InvoiceTypeCode> <cac:AccountingSupplierParty>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode> <cac:Party>
<AccountingSupplierParty> <cac:PartyName>
<Party> <cbc:Name>UTF-16 BE Test Company</cbc:Name>
<PartyName> </cac:PartyName>
<Name>UTF-16 BE Test Company</Name> <cac:PostalAddress>
</PartyName> <cbc:StreetName>Test Street</cbc:StreetName>
</Party> <cbc:CityName>Test City</cbc:CityName>
</AccountingSupplierParty> <cbc:PostalZone>12345</cbc:PostalZone>
<AccountingCustomerParty> <cac:Country>
<Party> <cbc:IdentificationCode>DE</cbc:IdentificationCode>
<PartyName> </cac:Country>
<Name>Test Customer</Name> </cac:PostalAddress>
</PartyName> </cac:Party>
</Party> </cac:AccountingSupplierParty>
</AccountingCustomerParty> <cac:AccountingCustomerParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Test Customer</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Customer Street</cbc:StreetName>
<cbc:CityName>Customer City</cbc:CityName>
<cbc:PostalZone>54321</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
</cac:Party>
</cac:AccountingCustomerParty>
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="C62">1</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Test Item</cbc:Name>
</cac:Item>
</cac:InvoiceLine>
</Invoice>`; </Invoice>`;
// Convert to UTF-16 BE // Convert to UTF-16 BE
const utf16BeBuffer = Buffer.from(xmlContent, 'utf16le').swap16(); const utf16BeBuffer = Buffer.from(xmlContent, 'utf16le').swap16();
let success = false; try {
let error = null; // Try to load UTF-16 BE content
const invoice = await EInvoice.fromXml(utf16BeBuffer.toString('utf16le'));
try { return {
// Try to load UTF-16 BE content success: true,
const newInvoice = new EInvoice(); parsed: invoice.id === 'UTF16-BE-TEST'
await newInvoice.fromXmlString(utf16BeBuffer.toString('utf16le')); };
} catch (error) {
// Check if invoice ID is preserved // UTF-16 might not be supported, which is acceptable
success = newInvoice.id === 'UTF16-BE-TEST' || return {
newInvoice.invoiceId === 'UTF16-BE-TEST' || success: false,
newInvoice.accountingDocId === 'UTF16-BE-TEST'; error: error.message
} catch (e) { };
error = e;
// UTF-16 might not be supported, which is acceptable
console.log(' UTF-16 BE not supported:', e.message);
}
return { success, error };
} }
); };
console.log(` UTF-16 BE test completed in ${beMetric.duration}ms`); const beResult = await testUtf16Be();
console.log('Test 1 - UTF-16 BE (Big Endian):');
console.log(` ${beResult.success ? 'Parsed successfully' : 'Not supported: ' + beResult.error}`);
// Test 2: UTF-16 LE (Little Endian) encoding // Test 2: UTF-16 LE (Little Endian) encoding
console.log('\nTest 2: UTF-16 LE (Little Endian) encoding'); const testUtf16Le = async () => {
const { result: leResult, metric: leMetric } = await PerformanceTracker.track( // Create UTF-16 LE XML content
'utf16-le', const xmlContent = `<?xml version="1.0" encoding="UTF-16LE"?>
async () => { <Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
// Create UTF-16 LE content xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
const xmlContent = `<?xml version="1.0" encoding="UTF-16LE"?> xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"> <cbc:ID>UTF16-LE-TEST</cbc:ID>
<UBLVersionID>2.1</UBLVersionID> <cbc:IssueDate>2025-01-25</cbc:IssueDate>
<ID>UTF16-LE-TEST</ID> <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<IssueDate>2025-01-25</IssueDate> <cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<InvoiceTypeCode>380</InvoiceTypeCode> <cac:AccountingSupplierParty>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode> <cac:Party>
<AccountingSupplierParty> <cac:PartyName>
<Party> <cbc:Name>UTF-16 LE Test Company</cbc:Name>
<PartyName> </cac:PartyName>
<Name>UTF-16 LE Test Company</Name> <cac:PostalAddress>
</PartyName> <cbc:StreetName>Test Street</cbc:StreetName>
</Party> <cbc:CityName>Test City</cbc:CityName>
</AccountingSupplierParty> <cbc:PostalZone>12345</cbc:PostalZone>
<AccountingCustomerParty> <cac:Country>
<Party> <cbc:IdentificationCode>DE</cbc:IdentificationCode>
<PartyName> </cac:Country>
<Name>Test Customer</Name> </cac:PostalAddress>
</PartyName> </cac:Party>
</Party> </cac:AccountingSupplierParty>
</AccountingCustomerParty> <cac:AccountingCustomerParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Test Customer</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Customer Street</cbc:StreetName>
<cbc:CityName>Customer City</cbc:CityName>
<cbc:PostalZone>54321</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
</cac:Party>
</cac:AccountingCustomerParty>
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="C62">1</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Test Item</cbc:Name>
</cac:Item>
</cac:InvoiceLine>
</Invoice>`; </Invoice>`;
// Convert to UTF-16 LE // Convert to UTF-16 LE
const utf16LeBuffer = Buffer.from(xmlContent, 'utf16le'); const utf16LeBuffer = Buffer.from(xmlContent, 'utf16le');
let success = false; try {
let error = null; const invoice = await EInvoice.fromXml(utf16LeBuffer.toString('utf16le'));
return {
success: true,
parsed: invoice.id === 'UTF16-LE-TEST'
};
} catch (error) {
return {
success: false,
error: error.message
};
}
};
try { const leResult = await testUtf16Le();
// Try to load UTF-16 LE content console.log('\nTest 2 - UTF-16 LE (Little Endian):');
const newInvoice = new EInvoice(); console.log(` ${leResult.success ? 'Parsed successfully' : 'Not supported: ' + leResult.error}`);
await newInvoice.fromXmlString(utf16LeBuffer.toString('utf16le'));
// Check if invoice ID is preserved // Test 3: UTF-16 with BOM
success = newInvoice.id === 'UTF16-LE-TEST' || const testUtf16WithBom = async () => {
newInvoice.invoiceId === 'UTF16-LE-TEST' || const einvoice = new EInvoice();
newInvoice.accountingDocId === 'UTF16-LE-TEST'; einvoice.id = 'UTF16-BOM-TEST';
} catch (e) { einvoice.date = Date.now();
error = e; einvoice.currency = 'EUR';
// UTF-16 might not be supported, which is acceptable einvoice.subject = 'UTF-16 BOM test';
console.log(' UTF-16 LE not supported:', e.message);
einvoice.from = {
type: 'company',
name: 'BOM Test Company',
description: 'Test company',
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'
} }
};
return { success, error }; einvoice.to = {
} type: 'company',
); name: 'Customer Inc',
description: 'Test customer',
console.log(` UTF-16 LE test completed in ${leMetric.duration}ms`); address: {
streetName: 'Customer St',
// Test 3: UTF-16 auto-detection houseNumber: '2',
console.log('\nTest 3: UTF-16 auto-detection'); postalCode: '54321',
const { result: autoResult, metric: autoMetric } = await PerformanceTracker.track( city: 'Customer City',
'utf16-auto', country: 'US'
async () => { },
// Create invoice with UTF-16 characters status: 'active',
const einvoice = new EInvoice(); foundedDate: { year: 2020, month: 1, day: 1 },
einvoice.id = 'UTF16-AUTO-TEST'; registrationDetails: {
einvoice.issueDate = new Date(2025, 0, 25); vatId: 'US987654321',
einvoice.invoiceId = 'UTF16-AUTO-TEST'; registrationId: 'EIN 12-3456789',
einvoice.accountingDocId = 'UTF16-AUTO-TEST'; registrationName: 'IRS Registration'
einvoice.subject = 'UTF-16 auto-detection test';
einvoice.from = {
type: 'company',
name: 'Auto-detect Company',
description: 'Test company for UTF-16 auto-detection',
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: 'company',
name: 'Customer Inc',
description: 'Test customer',
address: {
streetName: 'Customer St',
houseNumber: '2',
postalCode: '54321',
city: 'Customer City',
country: 'US'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'US987654321',
registrationId: 'EIN 12-3456789',
registrationName: 'IRS Registration'
}
};
einvoice.items = [{
position: 1,
name: 'Test Product',
articleNumber: 'UTF16-001',
unitType: 'EA',
unitQuantity: 1,
unitNetPrice: 100,
vatPercentage: 19
}];
// Export to XML
const xmlString = await einvoice.toXmlString('ubl');
// Create UTF-16 with BOM
const utf16Bom = Buffer.from([0xFE, 0xFF]); // UTF-16 BE BOM
const utf16Content = Buffer.from(xmlString, 'utf16le').swap16();
const withBom = Buffer.concat([utf16Bom, utf16Content]);
let success = false;
let error = null;
try {
// Try to load with BOM
const newInvoice = new EInvoice();
await newInvoice.fromXmlString(withBom.toString());
success = newInvoice.id === 'UTF16-AUTO-TEST' ||
newInvoice.invoiceId === 'UTF16-AUTO-TEST' ||
newInvoice.accountingDocId === 'UTF16-AUTO-TEST';
} catch (e) {
error = e;
console.log(' UTF-16 auto-detection not supported:', e.message);
} }
};
return { success, error }; einvoice.items = [{
} position: 1,
); name: 'Test Product',
unitType: 'C62',
unitQuantity: 1,
unitNetPrice: 100,
vatPercentage: 19
}];
console.log(` UTF-16 auto-detection test completed in ${autoMetric.duration}ms`); // Export to XML
const xmlString = await einvoice.toXmlString('ubl');
// Test 4: UTF-16 conversion fallback // Create UTF-16 with BOM
console.log('\nTest 4: UTF-16 conversion fallback to UTF-8'); const utf16Bom = Buffer.from([0xFE, 0xFF]); // UTF-16 BE BOM
const { result: fallbackResult, metric: fallbackMetric } = await PerformanceTracker.track( const utf16Content = Buffer.from(xmlString, 'utf16le').swap16();
'utf16-fallback', const withBom = Buffer.concat([utf16Bom, utf16Content]);
async () => {
// Since UTF-16 might not be fully supported, test fallback to UTF-8
const einvoice = new EInvoice();
einvoice.id = 'UTF16-FALLBACK-TEST';
einvoice.issueDate = new Date(2025, 0, 25);
einvoice.invoiceId = 'UTF16-FALLBACK-TEST';
einvoice.accountingDocId = 'UTF16-FALLBACK-TEST';
einvoice.subject = 'UTF-16 fallback test: €£¥';
einvoice.from = { try {
type: 'company', const invoice = await EInvoice.fromXml(withBom.toString());
name: 'Fallback Company GmbH', return {
description: 'Test company for UTF-16 fallback', success: true,
address: { parsed: invoice.id === 'UTF16-BOM-TEST'
streetName: 'Hauptstraße',
houseNumber: '42',
postalCode: '80331',
city: 'München',
country: 'DE'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'DE234567890',
registrationId: 'HRB 23456',
registrationName: 'Handelsregister München'
}
}; };
} catch (error) {
einvoice.to = { return {
type: 'company', success: false,
name: 'Customer España S.L.', error: error.message
description: 'Spanish test customer',
address: {
streetName: 'Calle Mayor',
houseNumber: '10',
postalCode: '28001',
city: 'Madrid',
country: 'ES'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'ES876543210',
registrationId: 'B-87654321',
registrationName: 'Registro Mercantil de Madrid'
}
}; };
einvoice.items = [{
position: 1,
name: 'Product with special chars: äöü',
articleNumber: 'UTF16-FALLBACK-001',
unitType: 'EA',
unitQuantity: 1,
unitNetPrice: 100,
vatPercentage: 19
}];
// Export as UTF-8 (our default)
const utf8Xml = await einvoice.toXmlString('ubl');
// Verify UTF-8 works correctly
const newInvoice = new EInvoice();
await newInvoice.fromXmlString(utf8Xml);
const success = newInvoice.id === 'UTF16-FALLBACK-TEST' ||
newInvoice.invoiceId === 'UTF16-FALLBACK-TEST' ||
newInvoice.accountingDocId === 'UTF16-FALLBACK-TEST';
console.log(` UTF-8 fallback works: ${success}`);
return { success };
} }
); };
console.log(` UTF-16 fallback test completed in ${fallbackMetric.duration}ms`); const bomResult = await testUtf16WithBom();
console.log('\nTest 3 - UTF-16 with BOM:');
console.log(` ${bomResult.success ? 'Parsed successfully' : 'Not supported: ' + bomResult.error}`);
// Test 4: UTF-8 fallback (should always work)
const testUtf8Fallback = async () => {
const einvoice = new EInvoice();
einvoice.id = 'UTF8-FALLBACK-TEST';
einvoice.date = Date.now();
einvoice.currency = 'EUR';
einvoice.subject = 'UTF-8 fallback test: €£¥';
einvoice.from = {
type: 'company',
name: 'Fallback Company GmbH',
description: 'Test company for UTF-8',
address: {
streetName: 'Hauptstraße',
houseNumber: '42',
postalCode: '80331',
city: 'München',
country: 'DE'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'DE234567890',
registrationId: 'HRB 23456',
registrationName: 'Handelsregister München'
}
};
einvoice.to = {
type: 'company',
name: 'Customer España S.L.',
description: 'Spanish test customer',
address: {
streetName: 'Calle Mayor',
houseNumber: '10',
postalCode: '28001',
city: 'Madrid',
country: 'ES'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'ES876543210',
registrationId: 'B-87654321',
registrationName: 'Registro Mercantil de Madrid'
}
};
einvoice.items = [{
position: 1,
name: 'Product with special chars: äöü',
unitType: 'C62',
unitQuantity: 1,
unitNetPrice: 100,
vatPercentage: 19
}];
// Export as UTF-8 (our default)
const utf8Xml = await einvoice.toXmlString('ubl');
// Verify UTF-8 works correctly
const newInvoice = await EInvoice.fromXml(utf8Xml);
const success = newInvoice.id === 'UTF8-FALLBACK-TEST';
const charsPreserved = newInvoice.from?.name === 'Fallback Company GmbH' &&
newInvoice.from?.address?.city === 'München';
return { success, charsPreserved };
};
const fallbackResult = await testUtf8Fallback();
console.log('\nTest 4 - UTF-8 fallback:');
console.log(` Invoice parsed: ${fallbackResult.success ? 'Yes' : 'No'}`);
console.log(` Special chars preserved: ${fallbackResult.charsPreserved ? 'Yes' : 'No'}`);
// Summary // Summary
console.log('\n=== UTF-16 Encoding Test Summary ==='); console.log('\n=== UTF-16 Encoding Test Summary ===');
console.log(`UTF-16 BE: ${beResult.success ? 'Supported' : 'Not supported (acceptable)'}`); console.log(`UTF-16 BE: ${beResult.success ? 'Supported' : 'Not supported (acceptable)'}`);
console.log(`UTF-16 LE: ${leResult.success ? 'Supported' : 'Not supported (acceptable)'}`); console.log(`UTF-16 LE: ${leResult.success ? 'Supported' : 'Not supported (acceptable)'}`);
console.log(`UTF-16 Auto-detection: ${autoResult.success ? 'Supported' : 'Not supported (acceptable)'}`); console.log(`UTF-16 with BOM: ${bomResult.success ? 'Supported' : 'Not supported (acceptable)'}`);
console.log(`UTF-8 Fallback: ${fallbackResult.success ? 'Working' : 'Failed'}`); console.log(`UTF-8 Fallback: ${fallbackResult.success ? 'Working' : 'Failed'}`);
// The test passes if UTF-8 fallback works, since UTF-16 support is optional // The test passes if UTF-8 fallback works, since UTF-16 support is optional
expect(fallbackResult.success).toBeTrue(); expect(fallbackResult.success).toEqual(true);
expect(fallbackResult.charsPreserved).toEqual(true);
console.log('\n✓ UTF-16 encoding test completed');
}); });
// Run the test
tap.start(); tap.start();

View File

@@ -1,240 +1,328 @@
import { expect, tap } from '@git.zone/tstest/tapbundle'; import { expect, tap } from '@git.zone/tstest/tapbundle';
import { EInvoice } from '../../../ts/index.js'; import { EInvoice } from '../../../ts/index.js';
import { PerformanceTracker } from '../performance.tracker.js';
tap.test('ENC-03: ISO-8859-1 Encoding - should handle ISO-8859-1 (Latin-1) encoded documents', async () => { tap.test('ENC-03: ISO-8859-1 Encoding - should handle ISO-8859-1 (Latin-1) encoded documents', async () => {
// ENC-03: Verify correct handling of ISO-8859-1 encoded XML documents console.log('Testing ISO-8859-1 (Latin-1) encoding support...\n');
// This test ensures support for legacy Western European character encoding
// Test 1: Basic ISO-8859-1 encoding // Test 1: Direct ISO-8859-1 encoding
console.log('\nTest 1: Basic ISO-8859-1 encoding'); const testIso88591Direct = async () => {
const { result: basicResult, metric: basicMetric } = await PerformanceTracker.track( // Create ISO-8859-1 content with Latin-1 specific characters
'iso88591-basic', const xmlContent = `<?xml version="1.0" encoding="ISO-8859-1"?>
async () => { <Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
// Create ISO-8859-1 content with Latin-1 specific characters xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
const xmlContent = `<?xml version="1.0" encoding="ISO-8859-1"?> xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"> <cbc:ID>ISO88591-TEST</cbc:ID>
<UBLVersionID>2.1</UBLVersionID> <cbc:IssueDate>2025-01-25</cbc:IssueDate>
<ID>ISO88591-TEST</ID> <cbc:Note>ISO-8859-1 Test: àáâãäåæçèéêëìíîïñòóôõöøùúûüý</cbc:Note>
<IssueDate>2025-01-25</IssueDate> <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<Note>ISO-8859-1 Test: àáâãäåæçèéêëìíîïñòóôõöøùúûüý</Note> <cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode> <cac:AccountingSupplierParty>
<AccountingSupplierParty> <cac:Party>
<Party> <cac:PartyName>
<PartyName> <cbc:Name>Société Générale</cbc:Name>
<Name>Société Générale</Name> </cac:PartyName>
</PartyName> <cac:PostalAddress>
</Party> <cbc:StreetName>Rue de la Paix</cbc:StreetName>
</AccountingSupplierParty> <cbc:CityName>Paris</cbc:CityName>
<AccountingCustomerParty> <cbc:PostalZone>75001</cbc:PostalZone>
<Party> <cac:Country>
<PartyName> <cbc:IdentificationCode>FR</cbc:IdentificationCode>
<Name>Müller & Associés</Name> </cac:Country>
</PartyName> </cac:PostalAddress>
</Party> </cac:Party>
</AccountingCustomerParty> </cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Müller &amp; Associés</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Königstraße</cbc:StreetName>
<cbc:CityName>München</cbc:CityName>
<cbc:PostalZone>80331</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
</cac:Party>
</cac:AccountingCustomerParty>
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="C62">1</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Test Item</cbc:Name>
</cac:Item>
</cac:InvoiceLine>
</Invoice>`; </Invoice>`;
// Convert to ISO-8859-1 buffer // Convert to ISO-8859-1 buffer
const iso88591Buffer = Buffer.from(xmlContent, 'latin1'); const iso88591Buffer = Buffer.from(xmlContent, 'latin1');
let success = false; try {
let error = null; // Try to load ISO-8859-1 content
const invoice = await EInvoice.fromXml(iso88591Buffer.toString('latin1'));
try { return {
// Try to load ISO-8859-1 content success: true,
const newInvoice = new EInvoice(); parsed: invoice.id === 'ISO88591-TEST'
await newInvoice.fromXmlString(iso88591Buffer.toString('latin1')); };
} catch (error) {
// Check if invoice ID is preserved // ISO-8859-1 might not be supported, which is acceptable
success = newInvoice.id === 'ISO88591-TEST' || return {
newInvoice.invoiceId === 'ISO88591-TEST' || success: false,
newInvoice.accountingDocId === 'ISO88591-TEST'; error: error.message
} catch (e) { };
error = e;
// ISO-8859-1 might not be supported, which is acceptable
console.log(' ISO-8859-1 not supported:', e.message);
}
return { success, error };
} }
); };
console.log(` ISO-8859-1 basic test completed in ${basicMetric.duration}ms`); const directResult = await testIso88591Direct();
console.log('Test 1 - Direct ISO-8859-1 encoding:');
console.log(` ${directResult.success ? 'Parsed successfully' : 'Not supported: ' + directResult.error}`);
// Test 2: UTF-8 fallback for Latin-1 characters // Test 2: UTF-8 fallback for Latin-1 characters
console.log('\nTest 2: UTF-8 fallback for Latin-1 characters'); const testUtf8Fallback = async () => {
const { result: fallbackResult, metric: fallbackMetric } = await PerformanceTracker.track( const einvoice = new EInvoice();
'iso88591-fallback', einvoice.id = 'ISO88591-UTF8-TEST';
async () => { einvoice.date = Date.now();
// Create invoice with Latin-1 characters einvoice.currency = 'EUR';
const einvoice = new EInvoice(); einvoice.subject = 'ISO-8859-1 characters: àéïöü';
einvoice.id = 'ISO88591-FALLBACK-TEST'; einvoice.notes = ['French: crème brûlée', 'German: Müller & Söhne'];
einvoice.issueDate = new Date(2025, 0, 25);
einvoice.invoiceId = 'ISO88591-FALLBACK-TEST';
einvoice.accountingDocId = 'ISO88591-FALLBACK-TEST';
einvoice.subject = 'ISO-8859-1 characters: àéïöü';
einvoice.from = { einvoice.from = {
type: 'company', type: 'company',
name: 'Société Française S.A.', name: 'Société Française S.A.',
description: 'French company with accented characters', description: 'French company with accented characters',
address: { address: {
streetName: 'Rue de la Paix', streetName: 'Rue de la Paix',
houseNumber: '123', houseNumber: '123',
postalCode: '75001', postalCode: '75001',
city: 'Paris', city: 'Paris',
country: 'FR' country: 'FR'
}, },
status: 'active', status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 }, foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: { registrationDetails: {
vatId: 'FR12345678901', vatId: 'FR12345678901',
registrationId: 'RCS Paris 123456789', registrationId: 'RCS Paris 123456789',
registrationName: 'Registre du Commerce et des Sociétés' registrationName: 'Registre du Commerce et des Sociétés'
} }
};
einvoice.to = {
type: 'company',
name: 'Müller & Söhne GmbH',
description: 'German company with umlauts',
address: {
streetName: 'Königstraße',
houseNumber: '45',
postalCode: '80331',
city: 'München',
country: 'DE'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'DE987654321',
registrationId: 'HRB 98765',
registrationName: 'Handelsregister München'
}
};
einvoice.items = [{
position: 1,
name: 'Spécialité française: crème brûlée',
unitType: 'C62',
unitQuantity: 10,
unitNetPrice: 5.50,
vatPercentage: 19
}];
// Export as UTF-8 (our default)
const utf8Xml = await einvoice.toXmlString('ubl');
// Verify UTF-8 works correctly with Latin-1 characters
const newInvoice = await EInvoice.fromXml(utf8Xml);
const success = newInvoice.id === 'ISO88591-UTF8-TEST';
const charactersPreserved =
utf8Xml.includes('Société Française') &&
utf8Xml.includes('Müller &amp; Söhne') &&
utf8Xml.includes('crème brûlée') &&
utf8Xml.includes('München') &&
utf8Xml.includes('Königstraße');
return { success, charactersPreserved };
};
const fallbackResult = await testUtf8Fallback();
console.log('\nTest 2 - UTF-8 fallback for Latin-1 characters:');
console.log(` Invoice parsed: ${fallbackResult.success ? 'Yes' : 'No'}`);
console.log(` Latin-1 chars preserved: ${fallbackResult.charactersPreserved ? 'Yes' : 'No'}`);
// Test 3: Extended Latin-1 character range
const testExtendedRange = async () => {
const einvoice = new EInvoice();
// Test high Latin-1 characters (0x80-0xFF)
const highChars = '¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ';
einvoice.id = 'ISO88591-RANGE-TEST';
einvoice.date = Date.now();
einvoice.currency = 'EUR';
einvoice.subject = `Latin-1 range test: ${highChars}`;
einvoice.notes = [`Testing characters: ${highChars}`];
einvoice.from = {
type: 'company',
name: 'Test Company',
description: 'Testing ISO-8859-1 character range',
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 with symbols: ${highChars.substring(0, 10)}`,
unitType: 'C62',
unitQuantity: 1,
unitNetPrice: 100,
vatPercentage: 19
}];
const xmlString = await einvoice.toXmlString('ubl');
// Check if characters are preserved (either directly or as entities)
const preserved = highChars.split('').filter(char => {
const charCode = char.charCodeAt(0);
return xmlString.includes(char) ||
xmlString.includes(`&#${charCode};`) ||
xmlString.includes(`&#x${charCode.toString(16).toUpperCase()};`);
}).length;
const percentage = (preserved / highChars.length) * 100;
return {
preserved,
total: highChars.length,
percentage,
success: percentage > 50 // At least 50% should be preserved
};
};
const rangeResult = await testExtendedRange();
console.log('\nTest 3 - Extended Latin-1 character range (0x80-0xFF):');
console.log(` Characters preserved: ${rangeResult.preserved}/${rangeResult.total} (${rangeResult.percentage.toFixed(1)}%)`);
// Test 4: Mixed encoding scenario
const testMixedEncoding = async () => {
// Test with a document that mixes ASCII and Latin-1
const mixedXml = `<?xml version="1.0" encoding="ISO-8859-1"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
<cbc:ID>MIXED-TEST</cbc:ID>
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
<cbc:Note>Mixed ASCII and Latin-1: café, naïve, résumé</cbc:Note>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<cac:AccountingSupplierParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>ASCII Company</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Main Street</cbc:StreetName>
<cbc:CityName>New York</cbc:CityName>
<cbc:PostalZone>10001</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>US</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
</cac:Party>
</cac:AccountingSupplierParty>
<cac:AccountingCustomerParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Café Société</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Avenue des Champs-Élysées</cbc:StreetName>
<cbc:CityName>Paris</cbc:CityName>
<cbc:PostalZone>75008</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>FR</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
</cac:Party>
</cac:AccountingCustomerParty>
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="C62">1</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Café au lait</cbc:Name>
</cac:Item>
</cac:InvoiceLine>
</Invoice>`;
try {
const invoice = await EInvoice.fromXml(mixedXml);
return {
success: true,
parsed: invoice.id === 'MIXED-TEST'
}; };
} catch (error) {
einvoice.to = { return {
type: 'company', success: false,
name: 'Müller & Söhne GmbH', error: error.message
description: 'German company with umlauts',
address: {
streetName: 'Königstraße',
houseNumber: '45',
postalCode: '80331',
city: 'München',
country: 'DE'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'DE987654321',
registrationId: 'HRB 98765',
registrationName: 'Handelsregister München'
}
}; };
einvoice.items = [{
position: 1,
name: 'Spécialité française: crème brûlée',
articleNumber: 'ISO88591-001',
unitType: 'EA',
unitQuantity: 10,
unitNetPrice: 5.50,
vatPercentage: 19
}];
// Export as UTF-8 (our default)
const utf8Xml = await einvoice.toXmlString('ubl');
// Verify UTF-8 works correctly with Latin-1 characters
const newInvoice = new EInvoice();
await newInvoice.fromXmlString(utf8Xml);
const success = (newInvoice.id === 'ISO88591-FALLBACK-TEST' ||
newInvoice.invoiceId === 'ISO88591-FALLBACK-TEST' ||
newInvoice.accountingDocId === 'ISO88591-FALLBACK-TEST') &&
utf8Xml.includes('Société Française') &&
utf8Xml.includes('Müller &amp; Söhne') &&
utf8Xml.includes('crème brûlée');
console.log(` UTF-8 fallback works: ${success}`);
console.log(` Latin-1 chars preserved: ${utf8Xml.includes('àéïöü') || utf8Xml.includes('crème brûlée')}`);
return { success };
} }
); };
console.log(` ISO-8859-1 fallback test completed in ${fallbackMetric.duration}ms`); const mixedResult = await testMixedEncoding();
console.log('\nTest 4 - Mixed ASCII/Latin-1 encoding:');
// Test 3: Character range test console.log(` ${mixedResult.success ? 'Parsed successfully' : 'Not supported: ' + mixedResult.error}`);
console.log('\nTest 3: ISO-8859-1 character range (0x80-0xFF)');
const { result: rangeResult, metric: rangeMetric } = await PerformanceTracker.track(
'iso88591-range',
async () => {
const einvoice = new EInvoice();
// Test high Latin-1 characters (0x80-0xFF)
const highChars = '¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ';
einvoice.id = 'ISO88591-RANGE-TEST';
einvoice.issueDate = new Date(2025, 0, 25);
einvoice.invoiceId = 'ISO88591-RANGE-TEST';
einvoice.accountingDocId = 'ISO88591-RANGE-TEST';
einvoice.subject = `Latin-1 range test: ${highChars}`;
einvoice.notes = [`Testing characters: ${highChars}`];
einvoice.from = {
type: 'company',
name: 'Test Company',
description: 'Testing ISO-8859-1 character range',
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 with symbols: ${highChars.substring(0, 10)}`,
articleNumber: 'ISO88591-RANGE-001',
unitType: 'EA',
unitQuantity: 1,
unitNetPrice: 100,
vatPercentage: 19
}];
const xmlString = await einvoice.toXmlString('ubl');
// Check if some characters are preserved
const preserved = highChars.split('').filter(char => xmlString.includes(char)).length;
const percentage = (preserved / highChars.length) * 100;
console.log(` Characters preserved: ${preserved}/${highChars.length} (${percentage.toFixed(1)}%)`);
return { success: percentage > 50 }; // At least 50% should be preserved
}
);
console.log(` ISO-8859-1 range test completed in ${rangeMetric.duration}ms`);
// Summary // Summary
console.log('\n=== ISO-8859-1 Encoding Test Summary ==='); console.log('\n=== ISO-8859-1 Encoding Test Summary ===');
console.log(`ISO-8859-1 Direct: ${basicResult.success ? 'Supported' : 'Not supported (acceptable)'}`); console.log(`ISO-8859-1 Direct: ${directResult.success ? 'Supported' : 'Not supported (acceptable)'}`);
console.log(`UTF-8 Fallback: ${fallbackResult.success ? 'Working' : 'Failed'}`); console.log(`UTF-8 Fallback: ${fallbackResult.success ? 'Working' : 'Failed'}`);
console.log(`Character Range: ${rangeResult.success ? 'Good coverage' : 'Limited coverage'}`); console.log(`Character Range: ${rangeResult.success ? 'Good coverage' : 'Limited coverage'}`);
console.log(`Mixed Encoding: ${mixedResult.success ? 'Supported' : 'Not supported (acceptable)'}`);
// The test passes if UTF-8 fallback works, since ISO-8859-1 support is optional // The test passes if UTF-8 fallback works, since ISO-8859-1 support is optional
expect(fallbackResult.success).toBeTrue(); expect(fallbackResult.success).toEqual(true);
expect(fallbackResult.charactersPreserved).toEqual(true);
console.log('\n✓ ISO-8859-1 encoding test completed');
}); });
// Run the test
tap.start(); tap.start();