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: `
DOC-001 2024-01-15
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();