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:
Philipp Kunz 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 { 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 () => {
console.log('Test function started');
// 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
console.log('Testing UTF-16 encoding support...\n');
// Test 1: UTF-16 BE (Big Endian) encoding
console.log('\nTest 1: UTF-16 BE (Big Endian) encoding');
const { result: beResult, metric: beMetric } = await PerformanceTracker.track(
'utf16-be',
async () => {
// Create UTF-16 BE content
const xmlContent = `<?xml version="1.0" encoding="UTF-16BE"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<UBLVersionID>2.1</UBLVersionID>
<ID>UTF16-BE-TEST</ID>
<IssueDate>2025-01-25</IssueDate>
<InvoiceTypeCode>380</InvoiceTypeCode>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
<AccountingSupplierParty>
<Party>
<PartyName>
<Name>UTF-16 BE Test Company</Name>
</PartyName>
</Party>
</AccountingSupplierParty>
<AccountingCustomerParty>
<Party>
<PartyName>
<Name>Test Customer</Name>
</PartyName>
</Party>
</AccountingCustomerParty>
const testUtf16Be = async () => {
// Create UTF-16 BE XML content with proper address fields
const xmlContent = `<?xml version="1.0" encoding="UTF-16BE"?>
<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>UTF16-BE-TEST</cbc:ID>
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<cac:AccountingSupplierParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>UTF-16 BE Test Company</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Test Street</cbc:StreetName>
<cbc:CityName>Test City</cbc:CityName>
<cbc:PostalZone>12345</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
</cac:Party>
</cac:AccountingSupplierParty>
<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>`;
// Convert to UTF-16 BE
const utf16BeBuffer = Buffer.from(xmlContent, 'utf16le').swap16();
let success = false;
let error = null;
try {
// Try to load UTF-16 BE content
const newInvoice = new EInvoice();
await newInvoice.fromXmlString(utf16BeBuffer.toString('utf16le'));
// Check if invoice ID is preserved
success = newInvoice.id === 'UTF16-BE-TEST' ||
newInvoice.invoiceId === 'UTF16-BE-TEST' ||
newInvoice.accountingDocId === 'UTF16-BE-TEST';
} 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 };
// Convert to UTF-16 BE
const utf16BeBuffer = Buffer.from(xmlContent, 'utf16le').swap16();
try {
// Try to load UTF-16 BE content
const invoice = await EInvoice.fromXml(utf16BeBuffer.toString('utf16le'));
return {
success: true,
parsed: invoice.id === 'UTF16-BE-TEST'
};
} catch (error) {
// UTF-16 might not be supported, which is acceptable
return {
success: false,
error: error.message
};
}
);
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
console.log('\nTest 2: UTF-16 LE (Little Endian) encoding');
const { result: leResult, metric: leMetric } = await PerformanceTracker.track(
'utf16-le',
async () => {
// Create UTF-16 LE content
const xmlContent = `<?xml version="1.0" encoding="UTF-16LE"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<UBLVersionID>2.1</UBLVersionID>
<ID>UTF16-LE-TEST</ID>
<IssueDate>2025-01-25</IssueDate>
<InvoiceTypeCode>380</InvoiceTypeCode>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
<AccountingSupplierParty>
<Party>
<PartyName>
<Name>UTF-16 LE Test Company</Name>
</PartyName>
</Party>
</AccountingSupplierParty>
<AccountingCustomerParty>
<Party>
<PartyName>
<Name>Test Customer</Name>
</PartyName>
</Party>
</AccountingCustomerParty>
const testUtf16Le = async () => {
// Create UTF-16 LE XML content
const xmlContent = `<?xml version="1.0" encoding="UTF-16LE"?>
<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>UTF16-LE-TEST</cbc:ID>
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<cac:AccountingSupplierParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>UTF-16 LE Test Company</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Test Street</cbc:StreetName>
<cbc:CityName>Test City</cbc:CityName>
<cbc:PostalZone>12345</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>DE</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
</cac:Party>
</cac:AccountingSupplierParty>
<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>`;
// Convert to UTF-16 LE
const utf16LeBuffer = Buffer.from(xmlContent, 'utf16le');
let success = false;
let error = null;
try {
// Try to load UTF-16 LE content
const newInvoice = new EInvoice();
await newInvoice.fromXmlString(utf16LeBuffer.toString('utf16le'));
// Check if invoice ID is preserved
success = newInvoice.id === 'UTF16-LE-TEST' ||
newInvoice.invoiceId === 'UTF16-LE-TEST' ||
newInvoice.accountingDocId === 'UTF16-LE-TEST';
} catch (e) {
error = e;
// UTF-16 might not be supported, which is acceptable
console.log(' UTF-16 LE not supported:', e.message);
// Convert to UTF-16 LE
const utf16LeBuffer = Buffer.from(xmlContent, 'utf16le');
try {
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
};
}
};
const leResult = await testUtf16Le();
console.log('\nTest 2 - UTF-16 LE (Little Endian):');
console.log(` ${leResult.success ? 'Parsed successfully' : 'Not supported: ' + leResult.error}`);
// Test 3: UTF-16 with BOM
const testUtf16WithBom = async () => {
const einvoice = new EInvoice();
einvoice.id = 'UTF16-BOM-TEST';
einvoice.date = Date.now();
einvoice.currency = 'EUR';
einvoice.subject = 'UTF-16 BOM test';
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 };
}
);
console.log(` UTF-16 LE test completed in ${leMetric.duration}ms`);
// Test 3: UTF-16 auto-detection
console.log('\nTest 3: UTF-16 auto-detection');
const { result: autoResult, metric: autoMetric } = await PerformanceTracker.track(
'utf16-auto',
async () => {
// Create invoice with UTF-16 characters
const einvoice = new EInvoice();
einvoice.id = 'UTF16-AUTO-TEST';
einvoice.issueDate = new Date(2025, 0, 25);
einvoice.invoiceId = 'UTF16-AUTO-TEST';
einvoice.accountingDocId = 'UTF16-AUTO-TEST';
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);
};
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'
}
return { success, error };
}
);
console.log(` UTF-16 auto-detection test completed in ${autoMetric.duration}ms`);
// Test 4: UTF-16 conversion fallback
console.log('\nTest 4: UTF-16 conversion fallback to UTF-8');
const { result: fallbackResult, metric: fallbackMetric } = await PerformanceTracker.track(
'utf16-fallback',
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 = {
type: 'company',
name: 'Fallback Company GmbH',
description: 'Test company for UTF-16 fallback',
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.items = [{
position: 1,
name: 'Test Product',
unitType: 'C62',
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]);
try {
const invoice = await EInvoice.fromXml(withBom.toString());
return {
success: true,
parsed: invoice.id === 'UTF16-BOM-TEST'
};
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'
}
} catch (error) {
return {
success: false,
error: error.message
};
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
console.log('\n=== UTF-16 Encoding Test Summary ===');
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 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'}`);
// 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();

View File

@ -1,240 +1,328 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
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 () => {
// ENC-03: Verify correct handling of ISO-8859-1 encoded XML documents
// This test ensures support for legacy Western European character encoding
// Test 1: Basic ISO-8859-1 encoding
console.log('\nTest 1: Basic ISO-8859-1 encoding');
const { result: basicResult, metric: basicMetric } = await PerformanceTracker.track(
'iso88591-basic',
async () => {
// Create ISO-8859-1 content with Latin-1 specific characters
const xmlContent = `<?xml version="1.0" encoding="ISO-8859-1"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<UBLVersionID>2.1</UBLVersionID>
<ID>ISO88591-TEST</ID>
<IssueDate>2025-01-25</IssueDate>
<Note>ISO-8859-1 Test: àáâãäåæçèéêëìíîïñòóôõöøùúûüý</Note>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
<AccountingSupplierParty>
<Party>
<PartyName>
<Name>Société Générale</Name>
</PartyName>
</Party>
</AccountingSupplierParty>
<AccountingCustomerParty>
<Party>
<PartyName>
<Name>Müller & Associés</Name>
</PartyName>
</Party>
</AccountingCustomerParty>
console.log('Testing ISO-8859-1 (Latin-1) encoding support...\n');
// Test 1: Direct ISO-8859-1 encoding
const testIso88591Direct = async () => {
// Create ISO-8859-1 content with Latin-1 specific characters
const xmlContent = `<?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>ISO88591-TEST</cbc:ID>
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
<cbc:Note>ISO-8859-1 Test: àáâãäåæçèéêëìíîïñòóôõöøùúûüý</cbc:Note>
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
<cac:AccountingSupplierParty>
<cac:Party>
<cac:PartyName>
<cbc:Name>Société Générale</cbc:Name>
</cac:PartyName>
<cac:PostalAddress>
<cbc:StreetName>Rue de la Paix</cbc:StreetName>
<cbc:CityName>Paris</cbc:CityName>
<cbc:PostalZone>75001</cbc:PostalZone>
<cac:Country>
<cbc:IdentificationCode>FR</cbc:IdentificationCode>
</cac:Country>
</cac:PostalAddress>
</cac:Party>
</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>`;
// Convert to ISO-8859-1 buffer
const iso88591Buffer = Buffer.from(xmlContent, 'latin1');
let success = false;
let error = null;
try {
// Try to load ISO-8859-1 content
const newInvoice = new EInvoice();
await newInvoice.fromXmlString(iso88591Buffer.toString('latin1'));
// Check if invoice ID is preserved
success = newInvoice.id === 'ISO88591-TEST' ||
newInvoice.invoiceId === 'ISO88591-TEST' ||
newInvoice.accountingDocId === 'ISO88591-TEST';
} 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 };
// Convert to ISO-8859-1 buffer
const iso88591Buffer = Buffer.from(xmlContent, 'latin1');
try {
// Try to load ISO-8859-1 content
const invoice = await EInvoice.fromXml(iso88591Buffer.toString('latin1'));
return {
success: true,
parsed: invoice.id === 'ISO88591-TEST'
};
} catch (error) {
// ISO-8859-1 might not be supported, which is acceptable
return {
success: false,
error: error.message
};
}
);
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
console.log('\nTest 2: UTF-8 fallback for Latin-1 characters');
const { result: fallbackResult, metric: fallbackMetric } = await PerformanceTracker.track(
'iso88591-fallback',
async () => {
// Create invoice with Latin-1 characters
const einvoice = new EInvoice();
einvoice.id = 'ISO88591-FALLBACK-TEST';
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 = {
type: 'company',
name: 'Société Française S.A.',
description: 'French company with accented characters',
address: {
streetName: 'Rue de la Paix',
houseNumber: '123',
postalCode: '75001',
city: 'Paris',
country: 'FR'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'FR12345678901',
registrationId: 'RCS Paris 123456789',
registrationName: 'Registre du Commerce et des Sociétés'
}
const testUtf8Fallback = async () => {
const einvoice = new EInvoice();
einvoice.id = 'ISO88591-UTF8-TEST';
einvoice.date = Date.now();
einvoice.currency = 'EUR';
einvoice.subject = 'ISO-8859-1 characters: àéïöü';
einvoice.notes = ['French: crème brûlée', 'German: Müller & Söhne'];
einvoice.from = {
type: 'company',
name: 'Société Française S.A.',
description: 'French company with accented characters',
address: {
streetName: 'Rue de la Paix',
houseNumber: '123',
postalCode: '75001',
city: 'Paris',
country: 'FR'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'FR12345678901',
registrationId: 'RCS Paris 123456789',
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'
};
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'
}
} catch (error) {
return {
success: false,
error: error.message
};
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`);
// Test 3: Character range test
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`);
};
const mixedResult = await testMixedEncoding();
console.log('\nTest 4 - Mixed ASCII/Latin-1 encoding:');
console.log(` ${mixedResult.success ? 'Parsed successfully' : 'Not supported: ' + mixedResult.error}`);
// 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(`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
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();