import { tap, expect } from '@push.rocks/tapbundle'; import { EInvoice } from '../../../ts/index.js'; import { InvoiceFormat } from '../../../ts/interfaces/common.js'; import { FormatDetector } from '../../../ts/formats/utils/format.detector.js'; import { CorpusLoader, PerformanceTracker } from '../../helpers/test-utils.js'; /** * Test ID: FD-01 * Test Description: UBL Format Detection * Priority: High * * This test validates the accurate detection of UBL (Universal Business Language) format * from XML invoice files across different UBL versions and implementations. */ tap.test('FD-01: UBL Format Detection - Corpus files', async (t) => { // Load UBL test files from corpus const ublFiles = await CorpusLoader.loadCategory('UBL_XMLRECHNUNG'); const peppolFiles = await CorpusLoader.loadCategory('PEPPOL'); const en16931UblFiles = await CorpusLoader.loadCategory('EN16931_UBL_EXAMPLES'); const allUblFiles = [...ublFiles, ...peppolFiles, ...en16931UblFiles]; console.log(`Testing ${allUblFiles.length} UBL files for format detection`); let successCount = 0; let failureCount = 0; const detectionTimes: number[] = []; for (const file of allUblFiles) { try { const xmlBuffer = await CorpusLoader.loadFile(file.path); const xmlString = xmlBuffer.toString('utf-8'); // Track performance const { result: detectedFormat, metric } = await PerformanceTracker.track( 'format-detection', async () => FormatDetector.detectFormat(xmlString), { file: file.path, size: file.size } ); detectionTimes.push(metric.duration); // UBL files can be detected as UBL or XRechnung (which is UBL-based) const validFormats = [InvoiceFormat.UBL, InvoiceFormat.XRECHNUNG]; if (validFormats.includes(detectedFormat)) { successCount++; t.pass(`✓ ${path.basename(file.path)}: Correctly detected as ${detectedFormat}`); } else { failureCount++; t.fail(`✗ ${path.basename(file.path)}: Detected as ${detectedFormat}, expected UBL or XRechnung`); } } catch (error) { failureCount++; t.fail(`✗ ${path.basename(file.path)}: Detection failed - ${error.message}`); } } // Calculate statistics const avgTime = detectionTimes.length > 0 ? detectionTimes.reduce((a, b) => a + b, 0) / detectionTimes.length : 0; console.log(`\nUBL Detection Summary:`); console.log(`- Files tested: ${allUblFiles.length}`); console.log(`- Successful detections: ${successCount} (${(successCount / allUblFiles.length * 100).toFixed(1)}%)`); console.log(`- Failed detections: ${failureCount}`); console.log(`- Average detection time: ${avgTime.toFixed(2)}ms`); // Performance assertion t.ok(avgTime < 10, 'Average detection time should be under 10ms'); // Success rate assertion (allow some flexibility for edge cases) const successRate = successCount / allUblFiles.length; t.ok(successRate > 0.9, 'Success rate should be above 90%'); }); tap.test('FD-01: UBL Format Detection - Specific UBL elements', async (t) => { // Test specific UBL invoice const ublInvoice = ` INV-001 2024-01-01 Test Supplier `; const format = FormatDetector.detectFormat(ublInvoice); t.equal(format, InvoiceFormat.UBL, 'Should detect standard UBL invoice'); // Test UBL credit note const ublCreditNote = ` CN-001 2024-01-01 `; const creditNoteFormat = FormatDetector.detectFormat(ublCreditNote); t.equal(creditNoteFormat, InvoiceFormat.UBL, 'Should detect UBL credit note'); }); tap.test('FD-01: UBL Format Detection - PEPPOL BIS', async (t) => { // Test PEPPOL BIS 3.0 (which is UBL-based) const peppolInvoice = ` urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 Peppol-001 `; const format = FormatDetector.detectFormat(peppolInvoice); t.ok( [InvoiceFormat.UBL, InvoiceFormat.XRECHNUNG].includes(format), 'Should detect PEPPOL BIS as UBL or specialized format' ); }); tap.test('FD-01: UBL Format Detection - Edge cases', async (t) => { // Test with minimal UBL const minimalUBL = ''; const minimalFormat = FormatDetector.detectFormat(minimalUBL); t.equal(minimalFormat, InvoiceFormat.UBL, 'Should detect minimal UBL invoice'); // Test with different namespace prefix const differentPrefix = ` 123 `; const prefixFormat = FormatDetector.detectFormat(differentPrefix); t.equal(prefixFormat, InvoiceFormat.UBL, 'Should detect UBL with different namespace prefix'); // Test without XML declaration const noDeclaration = ` 456 `; const noDecFormat = FormatDetector.detectFormat(noDeclaration); t.equal(noDecFormat, InvoiceFormat.UBL, 'Should detect UBL without XML declaration'); }); tap.test('FD-01: UBL Format Detection - Performance benchmarks', async (t) => { // Test detection speed with various file sizes const testCases = [ { name: 'Small UBL', size: 1000, content: generateUBLInvoice(5) }, { name: 'Medium UBL', size: 10000, content: generateUBLInvoice(50) }, { name: 'Large UBL', size: 100000, content: generateUBLInvoice(500) } ]; for (const testCase of testCases) { const times: number[] = []; // Run multiple iterations for accuracy for (let i = 0; i < 100; i++) { const start = performance.now(); FormatDetector.detectFormat(testCase.content); times.push(performance.now() - start); } const avgTime = times.reduce((a, b) => a + b, 0) / times.length; console.log(`${testCase.name} (${testCase.content.length} bytes): avg ${avgTime.toFixed(3)}ms`); t.ok(avgTime < 5, `${testCase.name} detection should be under 5ms`); } }); // Helper function to generate UBL invoice with specified number of line items function generateUBLInvoice(lineItems: number): string { let invoice = ` TEST-${Date.now()} 2024-01-01`; for (let i = 1; i <= lineItems; i++) { invoice += ` ${i} ${i} ${i * 100} `; } invoice += '\n'; return invoice; } // Generate performance report at the end tap.teardown(async () => { const stats = PerformanceTracker.getStats('format-detection'); if (stats) { console.log('\nPerformance Summary:'); console.log(`- Total detections: ${stats.count}`); console.log(`- Average time: ${stats.avg.toFixed(2)}ms`); console.log(`- Min/Max: ${stats.min.toFixed(2)}ms / ${stats.max.toFixed(2)}ms`); console.log(`- P95: ${stats.p95.toFixed(2)}ms`); } }); // Import path for basename import * as path from 'path'; tap.start();