340 lines
13 KiB
TypeScript
340 lines
13 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
|
|
tap.test('ENC-10: Cross-Format Encoding - should handle encoding across different invoice formats', async () => {
|
|
console.log('Testing cross-format encoding consistency...\n');
|
|
|
|
// Test 1: UBL to CII encoding consistency
|
|
const testUblToCiiEncoding = async () => {
|
|
const einvoice = new EInvoice();
|
|
einvoice.id = 'CROSS-FORMAT-TEST';
|
|
einvoice.issueDate = new Date(2025, 0, 25);
|
|
einvoice.subject = 'Cross-format test with special chars: éñüß';
|
|
|
|
einvoice.from = {
|
|
type: 'company',
|
|
name: 'Test Company éñüß',
|
|
description: 'Testing cross-format encoding: €£¥',
|
|
address: {
|
|
streetName: 'Straße with ümlaut',
|
|
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: 'Commercial Register'
|
|
}
|
|
};
|
|
|
|
einvoice.to = {
|
|
type: 'person',
|
|
name: 'José',
|
|
surname: 'Müller',
|
|
salutation: 'Mr' as const,
|
|
sex: 'male' as const,
|
|
title: 'Doctor' as const,
|
|
description: 'Customer with spëcial chars',
|
|
address: {
|
|
streetName: 'Côte d\'Azur',
|
|
houseNumber: '2',
|
|
postalCode: '54321',
|
|
city: 'São Paulo',
|
|
country: 'BR'
|
|
}
|
|
};
|
|
|
|
einvoice.items = [{
|
|
position: 1,
|
|
name: 'Product with éñü symbols',
|
|
articleNumber: 'CROSS-001',
|
|
unitType: 'EA',
|
|
unitQuantity: 1,
|
|
unitNetPrice: 100,
|
|
vatPercentage: 19
|
|
}];
|
|
|
|
try {
|
|
// Export as UBL
|
|
const ublXml = await einvoice.toXmlString('ubl');
|
|
|
|
// Export as CII
|
|
const ciiXml = await einvoice.toXmlString('cii');
|
|
|
|
// Verify both formats preserve special characters
|
|
const ublHasSpecialChars = ublXml.includes('éñüß') && ublXml.includes('München') && ublXml.includes('José');
|
|
const ciiHasSpecialChars = ciiXml.includes('éñüß') && ciiXml.includes('München') && ciiXml.includes('José');
|
|
|
|
// Test round-trip for both formats
|
|
const ublInvoice = new EInvoice();
|
|
await ublInvoice.fromXmlString(ublXml);
|
|
|
|
const ciiInvoice = new EInvoice();
|
|
await ciiInvoice.fromXmlString(ciiXml);
|
|
|
|
const ublRoundTrip = ublInvoice.from?.name?.includes('éñüß') && ublInvoice.to?.name?.includes('José');
|
|
const ciiRoundTrip = ciiInvoice.from?.name?.includes('éñüß') && ciiInvoice.to?.name?.includes('José');
|
|
|
|
console.log(`Test 1 - UBL to CII encoding:`);
|
|
console.log(` UBL preserves special chars: ${ublHasSpecialChars ? 'Yes' : 'No'}`);
|
|
console.log(` CII preserves special chars: ${ciiHasSpecialChars ? 'Yes' : 'No'}`);
|
|
console.log(` UBL round-trip successful: ${ublRoundTrip ? 'Yes' : 'No'}`);
|
|
console.log(` CII round-trip successful: ${ciiRoundTrip ? 'Yes' : 'No'}`);
|
|
|
|
return { ublHasSpecialChars, ciiHasSpecialChars, ublRoundTrip, ciiRoundTrip };
|
|
} catch (error) {
|
|
console.log(`Test 1 - UBL to CII encoding:`);
|
|
console.log(` Cross-format encoding failed: ${error.message}`);
|
|
|
|
return { ublHasSpecialChars: false, ciiHasSpecialChars: false, ublRoundTrip: false, ciiRoundTrip: false };
|
|
}
|
|
};
|
|
|
|
// Test 2: Different encoding declarations consistency
|
|
const testEncodingDeclarations = async () => {
|
|
const ublWithUnicodeXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<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:UBLVersionID>2.1</cbc:UBLVersionID>
|
|
<cbc:ID>ENCODING-CONSISTENCY-TEST</cbc:ID>
|
|
<cbc:IssueDate>2025-01-25</cbc:IssueDate>
|
|
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
|
<cac:AccountingSupplierParty>
|
|
<cac:Party>
|
|
<cac:PartyName>
|
|
<cbc:Name>Ünîcödë Company €éñ</cbc:Name>
|
|
</cac:PartyName>
|
|
</cac:Party>
|
|
</cac:AccountingSupplierParty>
|
|
<cac:InvoiceLine>
|
|
<cbc:ID>1</cbc:ID>
|
|
<cbc:InvoicedQuantity unitCode="EA">1</cbc:InvoicedQuantity>
|
|
<cac:Item>
|
|
<cbc:Name>Product with spëcîãl chars</cbc:Name>
|
|
</cac:Item>
|
|
</cac:InvoiceLine>
|
|
</Invoice>`;
|
|
|
|
try {
|
|
// Parse UBL with Unicode content
|
|
const ublInvoice = new EInvoice();
|
|
await ublInvoice.fromXmlString(ublWithUnicodeXml);
|
|
|
|
// Convert to CII and back to UBL
|
|
const ciiXml = await ublInvoice.toXmlString('cii');
|
|
const ublFromCii = new EInvoice();
|
|
await ublFromCii.fromXmlString(ciiXml);
|
|
|
|
// Check if special characters survive format conversion
|
|
const originalHasUnicode = ublInvoice.from?.name?.includes('Ünîcödë') &&
|
|
ublInvoice.from?.name?.includes('€éñ');
|
|
|
|
const ciiPreservesUnicode = ciiXml.includes('Ünîcödë') && ciiXml.includes('€éñ');
|
|
|
|
const roundTripPreservesUnicode = ublFromCii.from?.name?.includes('Ünîcödë') &&
|
|
ublFromCii.from?.name?.includes('€éñ');
|
|
|
|
console.log(`\nTest 2 - Encoding declaration consistency:`);
|
|
console.log(` Original UBL has Unicode: ${originalHasUnicode ? 'Yes' : 'No'}`);
|
|
console.log(` CII conversion preserves Unicode: ${ciiPreservesUnicode ? 'Yes' : 'No'}`);
|
|
console.log(` Round-trip preserves Unicode: ${roundTripPreservesUnicode ? 'Yes' : 'No'}`);
|
|
|
|
return { originalHasUnicode, ciiPreservesUnicode, roundTripPreservesUnicode };
|
|
} catch (error) {
|
|
console.log(`\nTest 2 - Encoding declaration consistency:`);
|
|
console.log(` Encoding consistency test failed: ${error.message}`);
|
|
|
|
return { originalHasUnicode: false, ciiPreservesUnicode: false, roundTripPreservesUnicode: false };
|
|
}
|
|
};
|
|
|
|
// Test 3: Mixed format documents
|
|
const testMixedFormatSupport = async () => {
|
|
try {
|
|
const einvoice = new EInvoice();
|
|
einvoice.id = 'MIXED-FORMAT-TEST';
|
|
einvoice.issueDate = new Date(2025, 0, 25);
|
|
einvoice.subject = 'Mixed format test';
|
|
|
|
einvoice.from = {
|
|
type: 'company',
|
|
name: 'Mixed Format Tëst Co.',
|
|
description: 'Testing mixed formats with €áàâ',
|
|
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 Product',
|
|
articleNumber: 'MIXED-001',
|
|
unitType: 'EA',
|
|
unitQuantity: 1,
|
|
unitNetPrice: 100,
|
|
vatPercentage: 19
|
|
}];
|
|
|
|
// Test multiple format exports and verify encoding consistency
|
|
const ublXml = await einvoice.toXmlString('ubl');
|
|
const ciiXml = await einvoice.toXmlString('cii');
|
|
|
|
// All formats should have proper UTF-8 encoding declaration
|
|
const ublHasUtf8 = ublXml.includes('encoding="UTF-8"') || !ublXml.includes('encoding=');
|
|
const ciiHasUtf8 = ciiXml.includes('encoding="UTF-8"') || !ciiXml.includes('encoding=');
|
|
|
|
// Check if special characters are preserved across formats
|
|
const ublPreservesChars = ublXml.includes('Tëst') && ublXml.includes('€áàâ');
|
|
const ciiPreservesChars = ciiXml.includes('Tëst') && ciiXml.includes('€áàâ');
|
|
|
|
console.log(`\nTest 3 - Mixed format support:`);
|
|
console.log(` UBL has UTF-8 encoding: ${ublHasUtf8 ? 'Yes' : 'No'}`);
|
|
console.log(` CII has UTF-8 encoding: ${ciiHasUtf8 ? 'Yes' : 'No'}`);
|
|
console.log(` UBL preserves special chars: ${ublPreservesChars ? 'Yes' : 'No'}`);
|
|
console.log(` CII preserves special chars: ${ciiPreservesChars ? 'Yes' : 'No'}`);
|
|
|
|
return { ublHasUtf8, ciiHasUtf8, ublPreservesChars, ciiPreservesChars };
|
|
} catch (error) {
|
|
console.log(`\nTest 3 - Mixed format support:`);
|
|
console.log(` Mixed format test failed: ${error.message}`);
|
|
|
|
return { ublHasUtf8: false, ciiHasUtf8: false, ublPreservesChars: false, ciiPreservesChars: false };
|
|
}
|
|
};
|
|
|
|
// Test 4: Encoding header consistency
|
|
const testEncodingHeaders = async () => {
|
|
try {
|
|
const einvoice = new EInvoice();
|
|
einvoice.id = 'HEADER-TEST';
|
|
einvoice.issueDate = new Date(2025, 0, 25);
|
|
einvoice.subject = 'Encoding header test';
|
|
|
|
einvoice.from = {
|
|
type: 'company',
|
|
name: 'Header Test Company',
|
|
description: 'Testing encoding headers',
|
|
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 Product',
|
|
articleNumber: 'HEADER-001',
|
|
unitType: 'EA',
|
|
unitQuantity: 1,
|
|
unitNetPrice: 100,
|
|
vatPercentage: 19
|
|
}];
|
|
|
|
// Generate both formats and check XML headers
|
|
const ublXml = await einvoice.toXmlString('ubl');
|
|
const ciiXml = await einvoice.toXmlString('cii');
|
|
|
|
// Check if both start with proper XML declaration
|
|
const ublHasXmlDecl = ublXml.startsWith('<?xml version="1.0"');
|
|
const ciiHasXmlDecl = ciiXml.startsWith('<?xml version="1.0"');
|
|
|
|
// Check if encoding is consistent
|
|
const ublConsistentEncoding = !ublXml.includes('encoding=') || ublXml.includes('encoding="UTF-8"');
|
|
const ciiConsistentEncoding = !ciiXml.includes('encoding=') || ciiXml.includes('encoding="UTF-8"');
|
|
|
|
console.log(`\nTest 4 - Encoding header consistency:`);
|
|
console.log(` UBL has XML declaration: ${ublHasXmlDecl ? 'Yes' : 'No'}`);
|
|
console.log(` CII has XML declaration: ${ciiHasXmlDecl ? 'Yes' : 'No'}`);
|
|
console.log(` UBL encoding consistent: ${ublConsistentEncoding ? 'Yes' : 'No'}`);
|
|
console.log(` CII encoding consistent: ${ciiConsistentEncoding ? 'Yes' : 'No'}`);
|
|
|
|
return { ublHasXmlDecl, ciiHasXmlDecl, ublConsistentEncoding, ciiConsistentEncoding };
|
|
} catch (error) {
|
|
console.log(`\nTest 4 - Encoding header consistency:`);
|
|
console.log(` Header consistency test failed: ${error.message}`);
|
|
|
|
return { ublHasXmlDecl: false, ciiHasXmlDecl: false, ublConsistentEncoding: false, ciiConsistentEncoding: false };
|
|
}
|
|
};
|
|
|
|
// Run all tests
|
|
const crossFormatResult = await testUblToCiiEncoding();
|
|
const encodingDeclResult = await testEncodingDeclarations();
|
|
const mixedFormatResult = await testMixedFormatSupport();
|
|
const headerResult = await testEncodingHeaders();
|
|
|
|
console.log(`\n=== Cross-Format Encoding Test Summary ===`);
|
|
console.log(`UBL-CII encoding consistency: ${crossFormatResult.ublRoundTrip && crossFormatResult.ciiRoundTrip ? 'Working' : 'Issues'}`);
|
|
console.log(`Format conversion encoding: ${encodingDeclResult.roundTripPreservesUnicode ? 'Working' : 'Issues'}`);
|
|
console.log(`Mixed format support: ${mixedFormatResult.ublPreservesChars && mixedFormatResult.ciiPreservesChars ? 'Working' : 'Issues'}`);
|
|
console.log(`Encoding header consistency: ${headerResult.ublConsistentEncoding && headerResult.ciiConsistentEncoding ? 'Working' : 'Issues'}`);
|
|
console.log(`Cross-format round-trip: ${crossFormatResult.ublRoundTrip && crossFormatResult.ciiRoundTrip ? 'Working' : 'Issues'}`);
|
|
|
|
// Test passes if basic cross-format consistency works
|
|
expect(crossFormatResult.ublRoundTrip || crossFormatResult.ciiRoundTrip).toBeTrue();
|
|
expect(headerResult.ublHasXmlDecl && headerResult.ciiHasXmlDecl).toBeTrue();
|
|
});
|
|
|
|
// Run the test
|
|
tap.start();
|