715 lines
21 KiB
TypeScript
715 lines
21 KiB
TypeScript
|
import { tap } from '@git.zone/tstest/tapbundle';
|
||
|
import * as plugins from '../plugins.js';
|
||
|
import { EInvoice } from '../../../ts/index.js';
|
||
|
import { PerformanceTracker } from '../performance.tracker.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 (t) => {
|
||
|
const einvoice = new EInvoice();
|
||
|
|
||
|
// Test 1: Documents with elements from multiple standards
|
||
|
const multiStandardElements = await performanceTracker.measureAsync(
|
||
|
'multi-standard-elements',
|
||
|
async () => {
|
||
|
const mixedXML = `<?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"
|
||
|
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||
|
<!-- UBL elements -->
|
||
|
<ubl:ID>MIXED-001</ubl:ID>
|
||
|
<cbc:IssueDate>2024-01-15</cbc:IssueDate>
|
||
|
|
||
|
<!-- CII elements -->
|
||
|
<cii:ExchangedDocument>
|
||
|
<ram:ID>MIXED-001-CII</ram:ID>
|
||
|
</cii:ExchangedDocument>
|
||
|
|
||
|
<!-- Custom elements -->
|
||
|
<CustomField>Custom Value</CustomField>
|
||
|
|
||
|
<!-- Mix of both -->
|
||
|
<LineItems>
|
||
|
<ubl:InvoiceLine>
|
||
|
<cbc:ID>1</cbc:ID>
|
||
|
</ubl:InvoiceLine>
|
||
|
<cii:SupplyChainTradeLineItem>
|
||
|
<ram:AssociatedDocumentLineDocument>
|
||
|
<ram:LineID>2</ram:LineID>
|
||
|
</ram:AssociatedDocumentLineDocument>
|
||
|
</cii:SupplyChainTradeLineItem>
|
||
|
</LineItems>
|
||
|
</Invoice>`;
|
||
|
|
||
|
try {
|
||
|
const detection = await einvoice.detectFormat(mixedXML);
|
||
|
const parsed = await einvoice.parseDocument(mixedXML);
|
||
|
|
||
|
return {
|
||
|
detected: true,
|
||
|
primaryFormat: detection?.format,
|
||
|
confidence: detection?.confidence,
|
||
|
mixedElements: detection?.mixedElements || [],
|
||
|
standardsFound: detection?.detectedStandards || [],
|
||
|
parsed: !!parsed
|
||
|
};
|
||
|
} catch (error) {
|
||
|
return {
|
||
|
detected: false,
|
||
|
error: error.message
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
|
||
|
t.ok(multiStandardElements.detected, 'Multi-standard document was processed');
|
||
|
t.ok(multiStandardElements.standardsFound?.length > 1, 'Multiple standards detected');
|
||
|
|
||
|
// Test 2: Namespace confusion
|
||
|
const namespaceConfusion = await performanceTracker.measureAsync(
|
||
|
'namespace-confusion',
|
||
|
async () => {
|
||
|
const confusedNamespaces = [
|
||
|
{
|
||
|
name: 'wrong-namespace-binding',
|
||
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
||
|
<!-- Using CII elements in UBL namespace -->
|
||
|
<ExchangedDocument>
|
||
|
<ID>CONFUSED-001</ID>
|
||
|
</ExchangedDocument>
|
||
|
</Invoice>`
|
||
|
},
|
||
|
{
|
||
|
name: 'conflicting-default-namespaces',
|
||
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<root>
|
||
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
||
|
<ID>UBL-001</ID>
|
||
|
</Invoice>
|
||
|
<Invoice xmlns="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100">
|
||
|
<ExchangedDocument>
|
||
|
<ID>CII-001</ID>
|
||
|
</ExchangedDocument>
|
||
|
</Invoice>
|
||
|
</root>`
|
||
|
},
|
||
|
{
|
||
|
name: 'namespace-switching',
|
||
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
||
|
<ID>START-UBL</ID>
|
||
|
<Items xmlns="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100">
|
||
|
<SupplyChainTradeLineItem>
|
||
|
<LineID xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">1</LineID>
|
||
|
</SupplyChainTradeLineItem>
|
||
|
</Items>
|
||
|
</Invoice>`
|
||
|
}
|
||
|
];
|
||
|
|
||
|
const results = [];
|
||
|
|
||
|
for (const test of confusedNamespaces) {
|
||
|
try {
|
||
|
const detection = await einvoice.detectFormat(test.xml);
|
||
|
const parsed = await einvoice.parseDocument(test.xml);
|
||
|
const validation = await einvoice.validate(parsed);
|
||
|
|
||
|
results.push({
|
||
|
scenario: test.name,
|
||
|
detected: true,
|
||
|
format: detection?.format,
|
||
|
hasNamespaceIssues: detection?.namespaceIssues || false,
|
||
|
valid: validation?.isValid || false,
|
||
|
warnings: validation?.warnings || []
|
||
|
});
|
||
|
} catch (error) {
|
||
|
results.push({
|
||
|
scenario: test.name,
|
||
|
detected: false,
|
||
|
error: error.message
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return results;
|
||
|
}
|
||
|
);
|
||
|
|
||
|
namespaceConfusion.forEach(result => {
|
||
|
t.ok(result.detected || result.error, `Namespace confusion ${result.scenario} was handled`);
|
||
|
if (result.detected) {
|
||
|
t.ok(result.hasNamespaceIssues || result.warnings.length > 0,
|
||
|
'Namespace issues should be detected');
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Test 3: Hybrid PDF documents
|
||
|
const hybridPDFDocuments = await performanceTracker.measureAsync(
|
||
|
'hybrid-pdf-documents',
|
||
|
async () => {
|
||
|
const hybridScenarios = [
|
||
|
{
|
||
|
name: 'multiple-xml-attachments',
|
||
|
description: 'PDF with both UBL and CII XML attachments'
|
||
|
},
|
||
|
{
|
||
|
name: 'conflicting-metadata',
|
||
|
description: 'PDF metadata says ZUGFeRD but contains Factur-X'
|
||
|
},
|
||
|
{
|
||
|
name: 'mixed-version-attachments',
|
||
|
description: 'PDF with ZUGFeRD v1 and v2 attachments'
|
||
|
},
|
||
|
{
|
||
|
name: 'non-standard-attachment',
|
||
|
description: 'PDF with standard XML plus custom format'
|
||
|
}
|
||
|
];
|
||
|
|
||
|
const results = [];
|
||
|
|
||
|
for (const scenario of hybridScenarios) {
|
||
|
// Create mock hybrid PDF
|
||
|
const hybridPDF = createHybridPDF(scenario.name);
|
||
|
|
||
|
try {
|
||
|
const extraction = await einvoice.extractFromPDF(hybridPDF);
|
||
|
|
||
|
results.push({
|
||
|
scenario: scenario.name,
|
||
|
extracted: true,
|
||
|
attachmentCount: extraction?.attachments?.length || 0,
|
||
|
formats: extraction?.detectedFormats || [],
|
||
|
primaryFormat: extraction?.primaryFormat,
|
||
|
hasConflicts: extraction?.hasFormatConflicts || false
|
||
|
});
|
||
|
} catch (error) {
|
||
|
results.push({
|
||
|
scenario: scenario.name,
|
||
|
extracted: false,
|
||
|
error: error.message
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return results;
|
||
|
}
|
||
|
);
|
||
|
|
||
|
hybridPDFDocuments.forEach(result => {
|
||
|
t.ok(result.extracted || result.error,
|
||
|
`Hybrid PDF ${result.scenario} was processed`);
|
||
|
});
|
||
|
|
||
|
// Test 4: Schema version mixing
|
||
|
const schemaVersionMixing = await performanceTracker.measureAsync(
|
||
|
'schema-version-mixing',
|
||
|
async () => {
|
||
|
const versionMixes = [
|
||
|
{
|
||
|
name: 'ubl-version-mix',
|
||
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||
|
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
|
||
|
xmlns:cbc1="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-1">
|
||
|
<cbc:UBLVersionID>2.1</cbc:UBLVersionID>
|
||
|
<cbc1:ID>OLD-STYLE-ID</cbc1:ID>
|
||
|
<cbc:IssueDate>2024-01-15</cbc:IssueDate>
|
||
|
</Invoice>`
|
||
|
},
|
||
|
{
|
||
|
name: 'zugferd-version-mix',
|
||
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<rsm:CrossIndustryDocument>
|
||
|
<!-- ZUGFeRD 1.0 structure -->
|
||
|
<rsm:SpecifiedExchangedDocumentContext>
|
||
|
<ram:GuidelineSpecifiedDocumentContextParameter>
|
||
|
<ram:ID>urn:ferd:CrossIndustryDocument:invoice:1p0</ram:ID>
|
||
|
</ram:GuidelineSpecifiedDocumentContextParameter>
|
||
|
</rsm:SpecifiedExchangedDocumentContext>
|
||
|
|
||
|
<!-- ZUGFeRD 2.1 elements -->
|
||
|
<rsm:ExchangedDocument>
|
||
|
<ram:ID>MIXED-VERSION</ram:ID>
|
||
|
</rsm:ExchangedDocument>
|
||
|
</rsm:CrossIndustryDocument>`
|
||
|
}
|
||
|
];
|
||
|
|
||
|
const results = [];
|
||
|
|
||
|
for (const mix of versionMixes) {
|
||
|
try {
|
||
|
const detection = await einvoice.detectFormat(mix.xml);
|
||
|
const parsed = await einvoice.parseDocument(mix.xml);
|
||
|
|
||
|
results.push({
|
||
|
scenario: mix.name,
|
||
|
processed: true,
|
||
|
detectedVersion: detection?.version,
|
||
|
versionConflicts: detection?.versionConflicts || [],
|
||
|
canMigrate: detection?.migrationPath !== undefined
|
||
|
});
|
||
|
} catch (error) {
|
||
|
results.push({
|
||
|
scenario: mix.name,
|
||
|
processed: false,
|
||
|
error: error.message
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return results;
|
||
|
}
|
||
|
);
|
||
|
|
||
|
schemaVersionMixing.forEach(result => {
|
||
|
t.ok(result.processed || result.error,
|
||
|
`Version mix ${result.scenario} was handled`);
|
||
|
});
|
||
|
|
||
|
// Test 5: Invalid format combinations
|
||
|
const invalidFormatCombos = await performanceTracker.measureAsync(
|
||
|
'invalid-format-combinations',
|
||
|
async () => {
|
||
|
const invalidCombos = [
|
||
|
{
|
||
|
name: 'ubl-with-cii-structure',
|
||
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
||
|
<!-- CII structure in UBL namespace -->
|
||
|
<rsm:ExchangedDocumentContext>
|
||
|
<ram:BusinessProcessSpecifiedDocumentContextParameter/>
|
||
|
</rsm:ExchangedDocumentContext>
|
||
|
<rsm:ExchangedDocument>
|
||
|
<ram:ID>INVALID-001</ram:ID>
|
||
|
</rsm:ExchangedDocument>
|
||
|
</Invoice>`
|
||
|
},
|
||
|
{
|
||
|
name: 'html-invoice-hybrid',
|
||
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<html>
|
||
|
<body>
|
||
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
||
|
<ID>HTML-WRAPPED</ID>
|
||
|
</Invoice>
|
||
|
</body>
|
||
|
</html>`
|
||
|
},
|
||
|
{
|
||
|
name: 'json-xml-mix',
|
||
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<Invoice>
|
||
|
<ID>JSON-MIX</ID>
|
||
|
<JSONData>
|
||
|
{"amount": 100, "currency": "EUR"}
|
||
|
</JSONData>
|
||
|
<XMLData>
|
||
|
<Amount>100</Amount>
|
||
|
<Currency>EUR</Currency>
|
||
|
</XMLData>
|
||
|
</Invoice>`
|
||
|
}
|
||
|
];
|
||
|
|
||
|
const results = [];
|
||
|
|
||
|
for (const combo of invalidCombos) {
|
||
|
try {
|
||
|
const detection = await einvoice.detectFormat(combo.xml);
|
||
|
const parsed = await einvoice.parseDocument(combo.xml);
|
||
|
const validation = await einvoice.validate(parsed);
|
||
|
|
||
|
results.push({
|
||
|
combo: combo.name,
|
||
|
detected: !!detection,
|
||
|
format: detection?.format || 'unknown',
|
||
|
valid: validation?.isValid || false,
|
||
|
recoverable: detection?.canRecover || false
|
||
|
});
|
||
|
} catch (error) {
|
||
|
results.push({
|
||
|
combo: combo.name,
|
||
|
detected: false,
|
||
|
error: error.message
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return results;
|
||
|
}
|
||
|
);
|
||
|
|
||
|
invalidFormatCombos.forEach(result => {
|
||
|
t.notOk(result.valid, `Invalid combo ${result.combo} should not validate`);
|
||
|
});
|
||
|
|
||
|
// Test 6: Partial format documents
|
||
|
const partialFormatDocuments = await performanceTracker.measureAsync(
|
||
|
'partial-format-documents',
|
||
|
async () => {
|
||
|
const partials = [
|
||
|
{
|
||
|
name: 'ubl-header-cii-body',
|
||
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
||
|
<cbc:ID>PARTIAL-001</cbc:ID>
|
||
|
<cbc:IssueDate>2024-01-15</cbc:IssueDate>
|
||
|
|
||
|
<!-- Switch to CII for line items -->
|
||
|
<IncludedSupplyChainTradeLineItem>
|
||
|
<ram:AssociatedDocumentLineDocument>
|
||
|
<ram:LineID>1</ram:LineID>
|
||
|
</ram:AssociatedDocumentLineDocument>
|
||
|
</IncludedSupplyChainTradeLineItem>
|
||
|
</Invoice>`
|
||
|
},
|
||
|
{
|
||
|
name: 'incomplete-migration',
|
||
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<Invoice>
|
||
|
<!-- Old format -->
|
||
|
<InvoiceNumber>OLD-001</InvoiceNumber>
|
||
|
|
||
|
<!-- New format -->
|
||
|
<ID>NEW-001</ID>
|
||
|
|
||
|
<!-- Mixed date formats -->
|
||
|
<InvoiceDate>2024-01-15</InvoiceDate>
|
||
|
<IssueDate>2024-01-15T00:00:00</IssueDate>
|
||
|
</Invoice>`
|
||
|
}
|
||
|
];
|
||
|
|
||
|
const results = [];
|
||
|
|
||
|
for (const partial of partials) {
|
||
|
try {
|
||
|
const analysis = await einvoice.analyzeDocument(partial.xml);
|
||
|
|
||
|
results.push({
|
||
|
scenario: partial.name,
|
||
|
analyzed: true,
|
||
|
completeness: analysis?.completeness || 0,
|
||
|
missingElements: analysis?.missingElements || [],
|
||
|
formatConsistency: analysis?.formatConsistency || 0,
|
||
|
migrationNeeded: analysis?.requiresMigration || false
|
||
|
});
|
||
|
} catch (error) {
|
||
|
results.push({
|
||
|
scenario: partial.name,
|
||
|
analyzed: false,
|
||
|
error: error.message
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return results;
|
||
|
}
|
||
|
);
|
||
|
|
||
|
partialFormatDocuments.forEach(result => {
|
||
|
t.ok(result.analyzed || result.error,
|
||
|
`Partial document ${result.scenario} was analyzed`);
|
||
|
if (result.analyzed) {
|
||
|
t.ok(result.formatConsistency < 100,
|
||
|
'Format inconsistency should be detected');
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Test 7: Encoding format conflicts
|
||
|
const encodingFormatConflicts = await performanceTracker.measureAsync(
|
||
|
'encoding-format-conflicts',
|
||
|
async () => {
|
||
|
const encodingConflicts = [
|
||
|
{
|
||
|
name: 'utf8-with-utf16-content',
|
||
|
declared: 'UTF-8',
|
||
|
actual: 'UTF-16',
|
||
|
content: Buffer.from('<?xml version="1.0" encoding="UTF-8"?><Invoice><ID>TEST</ID></Invoice>', 'utf16le')
|
||
|
},
|
||
|
{
|
||
|
name: 'wrong-decimal-separator',
|
||
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<Invoice>
|
||
|
<!-- European format in US-style document -->
|
||
|
<TotalAmount>1.234,56</TotalAmount>
|
||
|
<TaxAmount>234.56</TaxAmount>
|
||
|
</Invoice>`
|
||
|
},
|
||
|
{
|
||
|
name: 'date-format-mixing',
|
||
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<Invoice>
|
||
|
<Dates>
|
||
|
<IssueDate>2024-01-15</IssueDate>
|
||
|
<DueDate>15/01/2024</DueDate>
|
||
|
<DeliveryDate>01-15-2024</DeliveryDate>
|
||
|
<PaymentDate>20240115</PaymentDate>
|
||
|
</Dates>
|
||
|
</Invoice>`
|
||
|
}
|
||
|
];
|
||
|
|
||
|
const results = [];
|
||
|
|
||
|
for (const conflict of encodingConflicts) {
|
||
|
try {
|
||
|
let parseResult;
|
||
|
|
||
|
if (conflict.content) {
|
||
|
parseResult = await einvoice.parseDocument(conflict.content);
|
||
|
} else {
|
||
|
parseResult = await einvoice.parseDocument(conflict.xml);
|
||
|
}
|
||
|
|
||
|
const analysis = await einvoice.analyzeFormatConsistency(parseResult);
|
||
|
|
||
|
results.push({
|
||
|
scenario: conflict.name,
|
||
|
handled: true,
|
||
|
encodingIssues: analysis?.encodingIssues || [],
|
||
|
formatIssues: analysis?.formatIssues || [],
|
||
|
normalized: analysis?.normalized || false
|
||
|
});
|
||
|
} catch (error) {
|
||
|
results.push({
|
||
|
scenario: conflict.name,
|
||
|
handled: false,
|
||
|
error: error.message
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return results;
|
||
|
}
|
||
|
);
|
||
|
|
||
|
encodingFormatConflicts.forEach(result => {
|
||
|
t.ok(result.handled || result.error,
|
||
|
`Encoding conflict ${result.scenario} was handled`);
|
||
|
});
|
||
|
|
||
|
// Test 8: Format autodetection challenges
|
||
|
const autodetectionChallenges = await performanceTracker.measureAsync(
|
||
|
'format-autodetection-challenges',
|
||
|
async () => {
|
||
|
const challenges = [
|
||
|
{
|
||
|
name: 'minimal-structure',
|
||
|
xml: '<Invoice><ID>123</ID></Invoice>'
|
||
|
},
|
||
|
{
|
||
|
name: 'generic-xml',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<Document>
|
||
|
<Header>
|
||
|
<ID>DOC-001</ID>
|
||
|
<Date>2024-01-15</Date>
|
||
|
</Header>
|
||
|
<Items>
|
||
|
<Item>
|
||
|
<Description>Product</Description>
|
||
|
<Amount>100</Amount>
|
||
|
</Item>
|
||
|
</Items>
|
||
|
</Document>`
|
||
|
},
|
||
|
{
|
||
|
name: 'custom-namespace',
|
||
|
xml: `<?xml version="1.0"?>
|
||
|
<inv:Invoice xmlns:inv="http://custom.company.com/invoice">
|
||
|
<inv:Number>INV-001</inv:Number>
|
||
|
<inv:Total>1000</inv:Total>
|
||
|
</inv:Invoice>`
|
||
|
}
|
||
|
];
|
||
|
|
||
|
const results = [];
|
||
|
|
||
|
for (const challenge of challenges) {
|
||
|
const detectionResult = await einvoice.detectFormat(challenge.xml);
|
||
|
|
||
|
results.push({
|
||
|
scenario: challenge.name,
|
||
|
format: detectionResult?.format || 'unknown',
|
||
|
confidence: detectionResult?.confidence || 0,
|
||
|
isGeneric: detectionResult?.isGeneric || false,
|
||
|
suggestedFormats: detectionResult?.possibleFormats || []
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return results;
|
||
|
}
|
||
|
);
|
||
|
|
||
|
autodetectionChallenges.forEach(result => {
|
||
|
t.ok(result.confidence < 100 || result.isGeneric,
|
||
|
`Challenge ${result.scenario} should have detection uncertainty`);
|
||
|
});
|
||
|
|
||
|
// Test 9: Legacy format mixing
|
||
|
const legacyFormatMixing = await performanceTracker.measureAsync(
|
||
|
'legacy-format-mixing',
|
||
|
async () => {
|
||
|
const legacyMixes = [
|
||
|
{
|
||
|
name: 'edifact-xml-hybrid',
|
||
|
content: `UNB+UNOC:3+SENDER+RECEIVER+240115:1200+1++INVOIC'
|
||
|
<?xml version="1.0"?>
|
||
|
<AdditionalData>
|
||
|
<Invoice>
|
||
|
<ID>HYBRID-001</ID>
|
||
|
</Invoice>
|
||
|
</AdditionalData>
|
||
|
UNZ+1+1'`
|
||
|
},
|
||
|
{
|
||
|
name: 'csv-xml-combination',
|
||
|
content: `INVOICE_HEADER
|
||
|
ID,Date,Amount
|
||
|
INV-001,2024-01-15,1000.00
|
||
|
|
||
|
<?xml version="1.0"?>
|
||
|
<InvoiceDetails>
|
||
|
<LineItems>
|
||
|
<Item>Product A</Item>
|
||
|
</LineItems>
|
||
|
</InvoiceDetails>`
|
||
|
}
|
||
|
];
|
||
|
|
||
|
const results = [];
|
||
|
|
||
|
for (const mix of legacyMixes) {
|
||
|
try {
|
||
|
const detection = await einvoice.detectFormat(mix.content);
|
||
|
const extraction = await einvoice.extractStructuredData(mix.content);
|
||
|
|
||
|
results.push({
|
||
|
scenario: mix.name,
|
||
|
processed: true,
|
||
|
formatsFound: detection?.multipleFormats || [],
|
||
|
primaryFormat: detection?.primaryFormat,
|
||
|
dataExtracted: !!extraction?.data
|
||
|
});
|
||
|
} catch (error) {
|
||
|
results.push({
|
||
|
scenario: mix.name,
|
||
|
processed: false,
|
||
|
error: error.message
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return results;
|
||
|
}
|
||
|
);
|
||
|
|
||
|
legacyFormatMixing.forEach(result => {
|
||
|
t.ok(result.processed || result.error,
|
||
|
`Legacy mix ${result.scenario} was handled`);
|
||
|
});
|
||
|
|
||
|
// Test 10: Format conversion conflicts
|
||
|
const formatConversionConflicts = await performanceTracker.measureAsync(
|
||
|
'format-conversion-conflicts',
|
||
|
async () => {
|
||
|
// Create invoice with format-specific features
|
||
|
const sourceInvoice = `<?xml version="1.0" encoding="UTF-8"?>
|
||
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
||
|
<ID>CONVERT-001</ID>
|
||
|
<!-- UBL-specific extension -->
|
||
|
<UBLExtensions>
|
||
|
<UBLExtension>
|
||
|
<ExtensionContent>
|
||
|
<CustomField>UBL-Only-Data</CustomField>
|
||
|
</ExtensionContent>
|
||
|
</UBLExtension>
|
||
|
</UBLExtensions>
|
||
|
|
||
|
<!-- Format-specific calculation -->
|
||
|
<AllowanceCharge>
|
||
|
<ChargeIndicator>false</ChargeIndicator>
|
||
|
<AllowanceChargeReason>Discount</AllowanceChargeReason>
|
||
|
<Amount currencyID="EUR">50.00</Amount>
|
||
|
</AllowanceCharge>
|
||
|
</Invoice>`;
|
||
|
|
||
|
const targetFormats = ['cii', 'xrechnung', 'fatturapa'];
|
||
|
const results = [];
|
||
|
|
||
|
for (const target of targetFormats) {
|
||
|
try {
|
||
|
const converted = await einvoice.convertFormat(sourceInvoice, target);
|
||
|
const analysis = await einvoice.analyzeConversion(sourceInvoice, converted);
|
||
|
|
||
|
results.push({
|
||
|
targetFormat: target,
|
||
|
converted: true,
|
||
|
dataLoss: analysis?.dataLoss || [],
|
||
|
unsupportedFeatures: analysis?.unsupportedFeatures || [],
|
||
|
warnings: analysis?.warnings || []
|
||
|
});
|
||
|
} catch (error) {
|
||
|
results.push({
|
||
|
targetFormat: target,
|
||
|
converted: false,
|
||
|
error: error.message
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return results;
|
||
|
}
|
||
|
);
|
||
|
|
||
|
formatConversionConflicts.forEach(result => {
|
||
|
t.ok(result.converted || result.error,
|
||
|
`Conversion to ${result.targetFormat} was attempted`);
|
||
|
if (result.converted) {
|
||
|
t.ok(result.dataLoss.length > 0 || result.warnings.length > 0,
|
||
|
'Format-specific features should cause warnings');
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Print performance summary
|
||
|
performanceTracker.printSummary();
|
||
|
});
|
||
|
|
||
|
// Helper function to create hybrid PDF
|
||
|
function createHybridPDF(scenario: string): Buffer {
|
||
|
// Simplified mock - in reality would create actual PDF structure
|
||
|
const mockStructure = {
|
||
|
'multiple-xml-attachments': {
|
||
|
attachments: [
|
||
|
{ name: 'invoice.ubl.xml', type: 'application/xml' },
|
||
|
{ name: 'invoice.cii.xml', type: 'application/xml' }
|
||
|
]
|
||
|
},
|
||
|
'conflicting-metadata': {
|
||
|
metadata: { format: 'ZUGFeRD' },
|
||
|
attachments: [{ name: 'facturx.xml', type: 'application/xml' }]
|
||
|
},
|
||
|
'mixed-version-attachments': {
|
||
|
attachments: [
|
||
|
{ name: 'zugferd_v1.xml', version: '1.0' },
|
||
|
{ name: 'zugferd_v2.xml', version: '2.1' }
|
||
|
]
|
||
|
},
|
||
|
'non-standard-attachment': {
|
||
|
attachments: [
|
||
|
{ name: 'invoice.xml', type: 'application/xml' },
|
||
|
{ name: 'custom.json', type: 'application/json' }
|
||
|
]
|
||
|
}
|
||
|
};
|
||
|
|
||
|
return Buffer.from(JSON.stringify(mockStructure[scenario] || {}));
|
||
|
}
|
||
|
|
||
|
// Run the test
|
||
|
tap.start();
|