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 = `
MIXED-001
2024-01-15
MIXED-001-CII
Custom Value
1
2
`;
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: `
CONFUSED-001
`
},
{
name: 'conflicting-default-namespaces',
xml: `
UBL-001
CII-001
`
},
{
name: 'namespace-switching',
xml: `
START-UBL
1
`
}
];
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: `
2.1
OLD-STYLE-ID
2024-01-15
`
},
{
name: 'zugferd-version-mix',
xml: `
urn:ferd:CrossIndustryDocument:invoice:1p0
MIXED-VERSION
`
}
];
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: `
INVALID-001
`
},
{
name: 'html-invoice-hybrid',
xml: `
HTML-WRAPPED
`
},
{
name: 'json-xml-mix',
xml: `
JSON-MIX
{"amount": 100, "currency": "EUR"}
100
EUR
`
}
];
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: `
PARTIAL-001
2024-01-15
1
`
},
{
name: 'incomplete-migration',
xml: `
OLD-001
NEW-001
2024-01-15
2024-01-15T00:00:00
`
}
];
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('TEST', 'utf16le')
},
{
name: 'wrong-decimal-separator',
xml: `
1.234,56
234.56
`
},
{
name: 'date-format-mixing',
xml: `
2024-01-15
15/01/2024
01-15-2024
20240115
`
}
];
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: '123'
},
{
name: 'generic-xml',
xml: `
-
Product
100
`
},
{
name: 'custom-namespace',
xml: `
INV-001
1000
`
}
];
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'
HYBRID-001
UNZ+1+1'`
},
{
name: 'csv-xml-combination',
content: `INVOICE_HEADER
ID,Date,Amount
INV-001,2024-01-15,1000.00
- Product A
`
}
];
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 = `
CONVERT-001
UBL-Only-Data
false
Discount
50.00
`;
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();