import { tap } from '@git.zone/tstest/tapbundle'; import * as path from 'path'; import { InvoiceFormat } from '../../../ts/interfaces/common.js'; import { FormatDetector } from '../../../ts/formats/utils/format.detector.js'; import { CorpusLoader } from '../../helpers/corpus.loader.js'; import { PerformanceTracker } from '../../helpers/performance.tracker.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 () => { // 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++; console.log(`✓ ${path.basename(file.path)}: Correctly detected as ${detectedFormat}`); } else { failureCount++; console.log(`✗ ${path.basename(file.path)}: Detected as ${detectedFormat}, expected UBL or XRechnung`); } } catch (error) { failureCount++; console.log(`✗ ${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 const performanceOk = avgTime < 10; console.log(`Performance check (avg < 10ms): ${performanceOk ? 'PASS' : 'FAIL'} (${avgTime.toFixed(2)}ms)`); // Success rate assertion (allow some flexibility for edge cases) const successRate = successCount / allUblFiles.length; const successRateOk = successRate > 0.9; console.log(`Success rate check (> 90%): ${successRateOk ? 'PASS' : 'FAIL'} (${(successRate * 100).toFixed(1)}%)`); }); tap.test('FD-01: UBL Format Detection - Specific UBL elements', async () => { // Test specific UBL invoice const ublInvoice = ` INV-001 2024-01-01 Test Supplier `; const format = FormatDetector.detectFormat(ublInvoice); const isUbl = format === InvoiceFormat.UBL; console.log(`Standard UBL invoice detection: ${isUbl ? 'PASS' : 'FAIL'} (detected as ${format})`); // Test UBL credit note const ublCreditNote = ` CN-001 2024-01-01 `; const creditNoteFormat = FormatDetector.detectFormat(ublCreditNote); const isCreditNoteUbl = creditNoteFormat === InvoiceFormat.UBL; console.log(`UBL credit note detection: ${isCreditNoteUbl ? 'PASS' : 'FAIL'} (detected as ${creditNoteFormat})`); }); tap.test('FD-01: UBL Format Detection - PEPPOL BIS', async () => { // 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); const isPeppolValid = [InvoiceFormat.UBL, InvoiceFormat.XRECHNUNG].includes(format); console.log(`PEPPOL BIS detection: ${isPeppolValid ? 'PASS' : 'FAIL'} (detected as ${format})`); }); tap.test('FD-01: UBL Format Detection - Edge cases', async () => { // Test with minimal UBL const minimalUBL = ''; const minimalFormat = FormatDetector.detectFormat(minimalUBL); const isMinimalUbl = minimalFormat === InvoiceFormat.UBL; console.log(`Minimal UBL invoice detection: ${isMinimalUbl ? 'PASS' : 'FAIL'} (detected as ${minimalFormat})`); // Test with different namespace prefix const differentPrefix = ` 123 `; const prefixFormat = FormatDetector.detectFormat(differentPrefix); const isPrefixUbl = prefixFormat === InvoiceFormat.UBL; console.log(`UBL with different namespace prefix: ${isPrefixUbl ? 'PASS' : 'FAIL'} (detected as ${prefixFormat})`); // Test without XML declaration const noDeclaration = ` 456 `; const noDecFormat = FormatDetector.detectFormat(noDeclaration); const isNoDecUbl = noDecFormat === InvoiceFormat.UBL; console.log(`UBL without XML declaration: ${isNoDecUbl ? 'PASS' : 'FAIL'} (detected as ${noDecFormat})`); }); tap.test('FD-01: UBL Format Detection - Performance benchmarks', async () => { // 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; const isPerformanceOk = avgTime < 5; console.log(`${testCase.name} (${testCase.content.length} bytes): avg ${avgTime.toFixed(3)}ms - ${isPerformanceOk ? 'PASS' : 'FAIL'}`); } }); // 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 // Note: tap.teardown is not available in this version // Performance summary can be shown in the last test or externally tap.start();