- 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
378 lines
12 KiB
TypeScript
378 lines
12 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
import { PerformanceTracker } from '../performance.tracker.js';
|
|
import { FormatDetector } from '../../../ts/formats/utils/format.detector.js';
|
|
|
|
const performanceTracker = new PerformanceTracker('EDGE-08: Mixed Format Documents');
|
|
|
|
tap.test('EDGE-08: Mixed Format Documents - should handle documents with mixed or ambiguous formats', async () => {
|
|
console.log('Testing mixed format document handling...\n');
|
|
|
|
// Test 1: Invalid XML with mixed namespaces
|
|
const invalidMixedTest = await performanceTracker.measureAsync(
|
|
'invalid-mixed-xml',
|
|
async () => {
|
|
const invalidMixedXML = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
|
xmlns:cii="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100">
|
|
<!-- Mixing UBL and CII elements incorrectly -->
|
|
<ubl:ID>MIXED-001</ubl:ID>
|
|
<cii:ExchangedDocument>
|
|
<ram:ID>MIXED-001</ram:ID>
|
|
</cii:ExchangedDocument>
|
|
</Invoice>`;
|
|
|
|
try {
|
|
const format = FormatDetector.detectFormat(invalidMixedXML);
|
|
return {
|
|
detected: true,
|
|
format: format,
|
|
error: null
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
detected: false,
|
|
format: null,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
);
|
|
|
|
console.log(`Test 1 - Invalid mixed XML: ${invalidMixedTest.detected ? 'Handled' : 'Failed'}`);
|
|
if (invalidMixedTest.format) {
|
|
console.log(` Detected format: ${invalidMixedTest.format}`);
|
|
}
|
|
|
|
// Test 2: Valid UBL with unusual namespace declarations
|
|
const unusualNamespacesTest = await performanceTracker.measureAsync(
|
|
'unusual-namespaces',
|
|
async () => {
|
|
const unusualUBL = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<ns0:Invoice xmlns:ns0="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
|
xmlns:ns1="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
|
|
xmlns:ns2="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
|
<ns2:ID>UNUSUAL-001</ns2:ID>
|
|
<ns2:IssueDate>2024-01-15</ns2:IssueDate>
|
|
<ns2:InvoiceTypeCode>380</ns2:InvoiceTypeCode>
|
|
<ns2:DocumentCurrencyCode>EUR</ns2:DocumentCurrencyCode>
|
|
<ns1:AccountingSupplierParty>
|
|
<ns1:Party>
|
|
<ns1:PartyName>
|
|
<ns2:Name>Test Supplier</ns2:Name>
|
|
</ns1:PartyName>
|
|
<ns1:PostalAddress>
|
|
<ns2:StreetName>Test Street</ns2:StreetName>
|
|
<ns2:CityName>Test City</ns2:CityName>
|
|
<ns2:PostalZone>12345</ns2:PostalZone>
|
|
<ns1:Country>
|
|
<ns2:IdentificationCode>DE</ns2:IdentificationCode>
|
|
</ns1:Country>
|
|
</ns1:PostalAddress>
|
|
</ns1:Party>
|
|
</ns1:AccountingSupplierParty>
|
|
<ns1:AccountingCustomerParty>
|
|
<ns1:Party>
|
|
<ns1:PartyName>
|
|
<ns2:Name>Test Customer</ns2:Name>
|
|
</ns1:PartyName>
|
|
<ns1:PostalAddress>
|
|
<ns2:StreetName>Customer Street</ns2:StreetName>
|
|
<ns2:CityName>Customer City</ns2:CityName>
|
|
<ns2:PostalZone>54321</ns2:PostalZone>
|
|
<ns1:Country>
|
|
<ns2:IdentificationCode>DE</ns2:IdentificationCode>
|
|
</ns1:Country>
|
|
</ns1:PostalAddress>
|
|
</ns1:Party>
|
|
</ns1:AccountingCustomerParty>
|
|
<ns1:LegalMonetaryTotal>
|
|
<ns2:PayableAmount currencyID="EUR">100.00</ns2:PayableAmount>
|
|
</ns1:LegalMonetaryTotal>
|
|
<ns1:InvoiceLine>
|
|
<ns2:ID>1</ns2:ID>
|
|
<ns2:InvoicedQuantity unitCode="EA">1</ns2:InvoicedQuantity>
|
|
<ns2:LineExtensionAmount currencyID="EUR">100.00</ns2:LineExtensionAmount>
|
|
<ns1:Item>
|
|
<ns2:Name>Test Item</ns2:Name>
|
|
</ns1:Item>
|
|
</ns1:InvoiceLine>
|
|
</ns0:Invoice>`;
|
|
|
|
try {
|
|
const einvoice = new EInvoice();
|
|
await einvoice.loadXml(unusualUBL);
|
|
const exported = await einvoice.toXmlString('ubl');
|
|
|
|
return {
|
|
success: true,
|
|
invoiceId: einvoice.id,
|
|
hasValidStructure: exported.includes('Invoice'),
|
|
preservedData: exported.includes('UNUSUAL-001')
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
);
|
|
|
|
console.log(`\nTest 2 - Unusual namespaces: ${unusualNamespacesTest.success ? 'Success' : 'Failed'}`);
|
|
if (unusualNamespacesTest.success) {
|
|
console.log(` Invoice ID: ${unusualNamespacesTest.invoiceId}`);
|
|
console.log(` Valid structure: ${unusualNamespacesTest.hasValidStructure}`);
|
|
console.log(` Data preserved: ${unusualNamespacesTest.preservedData}`);
|
|
}
|
|
expect(unusualNamespacesTest.success).toEqual(true);
|
|
|
|
// Test 3: Malformed but recoverable XML
|
|
const malformedRecoverableTest = await performanceTracker.measureAsync(
|
|
'malformed-recoverable',
|
|
async () => {
|
|
const malformedXML = `<?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:ID>MALFORMED-001</cbc:ID>
|
|
<cbc:IssueDate>2024-01-15</cbc:IssueDate>
|
|
<cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
|
|
<cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
|
|
<!-- Extra characters that shouldn't be here --> &
|
|
<cac:AccountingSupplierParty>
|
|
<cac:Party>
|
|
<cac:PartyName>
|
|
<cbc:Name>Test & Supplier</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:LegalMonetaryTotal>
|
|
<cbc:PayableAmount currencyID="EUR">100.00</cbc:PayableAmount>
|
|
</cac:LegalMonetaryTotal>
|
|
<cac:InvoiceLine>
|
|
<cbc:ID>1</cbc:ID>
|
|
<cbc:InvoicedQuantity unitCode="EA">1</cbc:InvoicedQuantity>
|
|
<cbc:LineExtensionAmount currencyID="EUR">100.00</cbc:LineExtensionAmount>
|
|
<cac:Item>
|
|
<cbc:Name>Test Item</cbc:Name>
|
|
</cac:Item>
|
|
</cac:InvoiceLine>
|
|
</Invoice>`;
|
|
|
|
try {
|
|
const einvoice = new EInvoice();
|
|
await einvoice.loadXml(malformedXML);
|
|
|
|
return {
|
|
parsed: true,
|
|
invoiceId: einvoice.id,
|
|
supplierName: einvoice.from?.name,
|
|
preserved: einvoice.from?.name?.includes('&')
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
parsed: false,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
);
|
|
|
|
console.log(`\nTest 3 - Malformed but recoverable: ${malformedRecoverableTest.parsed ? 'Parsed' : 'Failed'}`);
|
|
if (malformedRecoverableTest.parsed) {
|
|
console.log(` Invoice ID: ${malformedRecoverableTest.invoiceId}`);
|
|
console.log(` Supplier name: ${malformedRecoverableTest.supplierName}`);
|
|
console.log(` Special chars preserved: ${malformedRecoverableTest.preserved}`);
|
|
}
|
|
|
|
// Test 4: Format detection edge cases
|
|
const formatDetectionTest = await performanceTracker.measureAsync(
|
|
'format-detection-edge',
|
|
async () => {
|
|
const testCases = [
|
|
{
|
|
name: 'Empty namespace',
|
|
xml: `<?xml version="1.0" encoding="UTF-8"?><Invoice><ID>TEST</ID></Invoice>`
|
|
},
|
|
{
|
|
name: 'Wrong root element',
|
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
|
<Document xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
<cbc:ID>TEST</cbc:ID>
|
|
</Document>`
|
|
},
|
|
{
|
|
name: 'Mixed case namespace',
|
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
|
<Invoice xmlns="urn:oasis:names:specification:UBL:schema:xsd:Invoice-2">
|
|
<ID>TEST</ID>
|
|
</Invoice>`
|
|
}
|
|
];
|
|
|
|
const results = [];
|
|
|
|
for (const testCase of testCases) {
|
|
try {
|
|
const format = FormatDetector.detectFormat(testCase.xml);
|
|
results.push({
|
|
name: testCase.name,
|
|
detected: true,
|
|
format: format
|
|
});
|
|
} catch (error) {
|
|
results.push({
|
|
name: testCase.name,
|
|
detected: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
);
|
|
|
|
console.log('\nTest 4 - Format detection edge cases:');
|
|
formatDetectionTest.forEach(result => {
|
|
console.log(` ${result.name}: ${result.detected ? `Detected as ${result.format}` : 'Failed'}`);
|
|
});
|
|
|
|
// Test 5: Round-trip with format confusion
|
|
const roundTripTest = await performanceTracker.measureAsync(
|
|
'round-trip-confusion',
|
|
async () => {
|
|
try {
|
|
// Start with a simple invoice
|
|
const einvoice = new EInvoice();
|
|
einvoice.id = 'ROUNDTRIP-001';
|
|
einvoice.date = Date.now();
|
|
einvoice.currency = 'EUR';
|
|
|
|
einvoice.from = {
|
|
type: 'company',
|
|
name: 'Round Trip Supplier',
|
|
description: 'Test supplier',
|
|
address: {
|
|
streetName: 'Test Street',
|
|
houseNumber: '1',
|
|
postalCode: '12345',
|
|
city: 'Test City',
|
|
country: 'DE'
|
|
},
|
|
status: 'active',
|
|
registrationDetails: {
|
|
vatId: 'DE123456789',
|
|
registrationId: 'HRB 12345',
|
|
registrationName: 'Test Registry'
|
|
},
|
|
foundedDate: { year: 2020, month: 1, day: 1 }
|
|
};
|
|
|
|
einvoice.to = {
|
|
type: 'company',
|
|
name: 'Round Trip Customer',
|
|
description: 'Test customer',
|
|
address: {
|
|
streetName: 'Customer Street',
|
|
houseNumber: '2',
|
|
postalCode: '54321',
|
|
city: 'Customer City',
|
|
country: 'DE'
|
|
},
|
|
status: 'active',
|
|
registrationDetails: {
|
|
vatId: 'FR987654321',
|
|
registrationId: 'RCS 54321',
|
|
registrationName: 'Customer Registry'
|
|
},
|
|
foundedDate: { year: 2021, month: 6, day: 15 }
|
|
};
|
|
|
|
einvoice.items = [{
|
|
position: 1,
|
|
name: 'Test Product',
|
|
unitType: 'EA',
|
|
unitQuantity: 1,
|
|
unitNetPrice: 100,
|
|
vatPercentage: 19
|
|
}];
|
|
|
|
// Convert to UBL
|
|
const ublXml = await einvoice.toXmlString('ubl');
|
|
|
|
// Load it back
|
|
const reloaded = new EInvoice();
|
|
await reloaded.loadXml(ublXml);
|
|
|
|
// Convert to CII
|
|
const ciiXml = await reloaded.toXmlString('cii');
|
|
|
|
// Load it back again
|
|
const finalInvoice = new EInvoice();
|
|
await finalInvoice.loadXml(ciiXml);
|
|
|
|
return {
|
|
success: true,
|
|
originalId: einvoice.id,
|
|
finalId: finalInvoice.id,
|
|
preservedSupplier: finalInvoice.from?.name === einvoice.from?.name,
|
|
preservedCustomer: finalInvoice.to?.name === einvoice.to?.name,
|
|
itemCount: finalInvoice.items?.length
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
);
|
|
|
|
console.log(`\nTest 5 - Round-trip with format changes: ${roundTripTest.success ? 'Success' : 'Failed'}`);
|
|
if (roundTripTest.success) {
|
|
console.log(` ID preserved: ${roundTripTest.originalId === roundTripTest.finalId}`);
|
|
console.log(` Supplier preserved: ${roundTripTest.preservedSupplier}`);
|
|
console.log(` Customer preserved: ${roundTripTest.preservedCustomer}`);
|
|
console.log(` Items preserved: ${roundTripTest.itemCount} item(s)`);
|
|
}
|
|
expect(roundTripTest.success).toEqual(true);
|
|
|
|
// Print performance summary
|
|
console.log('\n' + performanceTracker.getSummary());
|
|
|
|
// Verify at least some tests succeeded
|
|
const successfulTests = [
|
|
unusualNamespacesTest.success,
|
|
roundTripTest.success
|
|
].filter(Boolean).length;
|
|
|
|
expect(successfulTests).toBeGreaterThan(0);
|
|
console.log(`\n✓ ${successfulTests} out of 5 edge case tests handled successfully`);
|
|
});
|
|
|
|
tap.start(); |