/** * @file test.perf-04.conversion-throughput.ts * @description Performance tests for format conversion throughput */ import { expect, tap } from '@git.zone/tstest/tapbundle'; import { EInvoice } from '../../../ts/index.js'; // Simple performance tracking class SimplePerformanceTracker { private measurements: Map = new Map(); private name: string; constructor(name: string) { this.name = name; } addMeasurement(key: string, time: number): void { if (!this.measurements.has(key)) { this.measurements.set(key, []); } this.measurements.get(key)!.push(time); } getStats(key: string) { const times = this.measurements.get(key) || []; if (times.length === 0) return null; const sorted = [...times].sort((a, b) => a - b); return { avg: times.reduce((a, b) => a + b, 0) / times.length, min: sorted[0], max: sorted[sorted.length - 1], p95: sorted[Math.floor(sorted.length * 0.95)] }; } printSummary(): void { console.log(`\n${this.name} - Performance Summary:`); for (const [key, times] of this.measurements) { const stats = this.getStats(key); if (stats) { console.log(` ${key}: avg=${stats.avg.toFixed(2)}ms, min=${stats.min.toFixed(2)}ms, max=${stats.max.toFixed(2)}ms, p95=${stats.p95.toFixed(2)}ms`); } } } } const performanceTracker = new SimplePerformanceTracker('PERF-04: Conversion Throughput'); // Helper to create test invoices function createUblInvoice(id: string, lineItems: number = 10): string { const lines = Array(lineItems).fill(null).map((_, i) => ` ${i + 1} 1 100.00 Product ${i + 1} 100.00 `).join(''); return ` ${id} 2025-01-25 380 EUR Test Supplier Berlin 10115 DE Test Customer Munich 80331 DE ${100 * lineItems}.00 ${lines} `; } tap.test('PERF-04: UBL to CII conversion throughput', async () => { const testCases = [ { name: 'Small invoice', lineItems: 5 }, { name: 'Medium invoice', lineItems: 20 }, { name: 'Large invoice', lineItems: 100 } ]; const iterations = 30; for (const testCase of testCases) { const ublXml = createUblInvoice(`CONV-${testCase.name}`, testCase.lineItems); const times: number[] = []; let convertedXml: string = ''; for (let i = 0; i < iterations; i++) { const einvoice = await EInvoice.fromXml(ublXml); const startTime = performance.now(); convertedXml = await einvoice.toXmlString('cii'); const endTime = performance.now(); const duration = endTime - startTime; times.push(duration); performanceTracker.addMeasurement(`ubl-to-cii-${testCase.name}`, duration); } // Verify conversion worked expect(convertedXml).toContain('CrossIndustryInvoice'); const avg = times.reduce((a, b) => a + b, 0) / times.length; const throughput = (ublXml.length / 1024) / (avg / 1000); // KB/s console.log(`${testCase.name} (${testCase.lineItems} items): avg=${avg.toFixed(3)}ms, throughput=${throughput.toFixed(2)} KB/s`); // Performance expectations expect(avg).toBeLessThan(testCase.lineItems * 2 + 50); // Allow 2ms per line item + 50ms base } }); tap.test('PERF-04: CII to UBL conversion throughput', async () => { // First create a CII invoice by converting from UBL const ublXml = createUblInvoice('CII-SOURCE', 20); const sourceInvoice = await EInvoice.fromXml(ublXml); const ciiXml = await sourceInvoice.toXmlString('cii'); const iterations = 30; const times: number[] = []; let convertedXml: string = ''; for (let i = 0; i < iterations; i++) { const einvoice = await EInvoice.fromXml(ciiXml); const startTime = performance.now(); convertedXml = await einvoice.toXmlString('ubl'); const endTime = performance.now(); const duration = endTime - startTime; times.push(duration); performanceTracker.addMeasurement('cii-to-ubl', duration); } // Verify conversion worked expect(convertedXml).toContain('urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'); const avg = times.reduce((a, b) => a + b, 0) / times.length; console.log(`CII to UBL conversion: avg=${avg.toFixed(3)}ms`); // CII to UBL should be reasonably fast expect(avg).toBeLessThan(100); }); tap.test('PERF-04: Round-trip conversion performance', async () => { const originalUbl = createUblInvoice('ROUND-TRIP', 10); const iterations = 20; const times: number[] = []; for (let i = 0; i < iterations; i++) { const startTime = performance.now(); // UBL -> CII -> UBL const invoice1 = await EInvoice.fromXml(originalUbl); const ciiXml = await invoice1.toXmlString('cii'); const invoice2 = await EInvoice.fromXml(ciiXml); const finalUbl = await invoice2.toXmlString('ubl'); const endTime = performance.now(); const duration = endTime - startTime; times.push(duration); performanceTracker.addMeasurement('round-trip', duration); if (i === 0) { // Verify data integrity expect(finalUbl).toContain('ROUND-TRIP'); } } const avg = times.reduce((a, b) => a + b, 0) / times.length; console.log(`Round-trip conversion: avg=${avg.toFixed(3)}ms`); // Round-trip should complete in reasonable time expect(avg).toBeLessThan(150); }); tap.test('PERF-04: Batch conversion throughput', async () => { const batchSizes = [5, 10, 20]; for (const batchSize of batchSizes) { // Create batch of invoices const invoices = Array(batchSize).fill(null).map((_, i) => createUblInvoice(`BATCH-${i}`, 10) ); const startTime = performance.now(); // Convert all invoices const conversions = await Promise.all( invoices.map(async (xml) => { const einvoice = await EInvoice.fromXml(xml); return einvoice.toXmlString('cii'); }) ); const endTime = performance.now(); const totalTime = endTime - startTime; const avgTimePerInvoice = totalTime / batchSize; console.log(`Batch of ${batchSize}: total=${totalTime.toFixed(2)}ms, avg per invoice=${avgTimePerInvoice.toFixed(2)}ms`); performanceTracker.addMeasurement(`batch-${batchSize}`, avgTimePerInvoice); // Verify all conversions succeeded expect(conversions.every(xml => xml.includes('CrossIndustryInvoice'))).toEqual(true); // Batch processing should be efficient expect(avgTimePerInvoice).toBeLessThan(50); } }); tap.test('PERF-04: Format-specific optimizations', async () => { const formats = ['ubl', 'cii', 'facturx', 'zugferd'] as const; const ublSource = createUblInvoice('FORMAT-TEST', 20); for (const targetFormat of formats) { try { const times: number[] = []; for (let i = 0; i < 20; i++) { const einvoice = await EInvoice.fromXml(ublSource); const startTime = performance.now(); await einvoice.toXmlString(targetFormat); const endTime = performance.now(); times.push(endTime - startTime); } const avg = times.reduce((a, b) => a + b, 0) / times.length; console.log(`UBL to ${targetFormat}: avg=${avg.toFixed(3)}ms`); performanceTracker.addMeasurement(`ubl-to-${targetFormat}`, avg); // All conversions should be reasonably fast expect(avg).toBeLessThan(100); } catch (error) { // Some formats might not be supported for all conversions console.log(`UBL to ${targetFormat}: Not supported`); } } }); tap.test('PERF-04: Memory efficiency during conversion', async () => { const largeInvoice = createUblInvoice('MEMORY-TEST', 500); // Very large invoice const initialMemory = process.memoryUsage(); // Perform multiple conversions for (let i = 0; i < 10; i++) { const einvoice = await EInvoice.fromXml(largeInvoice); await einvoice.toXmlString('cii'); } const finalMemory = process.memoryUsage(); const memoryIncrease = (finalMemory.heapUsed - initialMemory.heapUsed) / 1024 / 1024; console.log(`Memory increase after 10 large conversions: ${memoryIncrease.toFixed(2)} MB`); // Memory usage should be reasonable expect(memoryIncrease).toBeLessThan(100); }); tap.test('PERF-04: Performance Summary', async () => { performanceTracker.printSummary(); // Check overall performance const ublToCiiStats = performanceTracker.getStats('ubl-to-cii-Small invoice'); if (ublToCiiStats) { console.log(`\nSmall invoice UBL to CII conversion: avg=${ublToCiiStats.avg.toFixed(2)}ms`); expect(ublToCiiStats.avg).toBeLessThan(30); // Small invoices should convert very quickly } console.log('\nConversion throughput performance tests completed successfully'); }); tap.start();