import { tap, expect } from '@git.zone/tstest/tapbundle'; import * as plugins from '../../../ts/plugins.js'; import { EInvoice } from '../../../ts/index.js'; import { CorpusLoader } from '../../helpers/corpus.loader.js'; import { PerformanceTracker } from '../../helpers/performance.tracker.js'; const testTimeout = 600000; // 10 minutes timeout for performance testing // VAL-12: Validation Performance // Tests validation performance characteristics including speed, memory usage, // and scalability under various load conditions tap.test('VAL-12: Validation Performance - Single Invoice Validation Speed', async (tools) => { const startTime = Date.now(); // Test validation speed for different invoice sizes const performanceTests = [ { name: 'Minimal UBL Invoice', xml: ` MIN-001 2024-01-01 380 EUR 100.00 `, expectedMaxTime: 20 // 20ms max for minimal invoice }, { name: 'Standard UBL Invoice', xml: ` STD-001 2024-01-01 380 EUR Test Supplier Test Street 1 Test City 12345 DE Test Customer Customer Street 1 Customer City 54321 DE 1 1 100.00 Test Item 100.00 19.00 100.00 100.00 119.00 119.00 `, expectedMaxTime: 50 // 50ms max for standard invoice } ]; for (const test of performanceTests) { const times = []; const iterations = 10; for (let i = 0; i < iterations; i++) { const iterationStart = Date.now(); try { const invoice = new EInvoice(); await invoice.fromXmlString(test.xml); const validationResult = await invoice.validate(); const iterationTime = Date.now() - iterationStart; times.push(iterationTime); // Ensure validation actually worked expect(validationResult).toBeTruthy(); } catch (error) { console.log(`Validation failed for ${test.name}: ${error.message}`); throw error; } } const avgTime = times.reduce((a, b) => a + b, 0) / times.length; const minTime = Math.min(...times); const maxTime = Math.max(...times); const p95Time = times.sort((a, b) => a - b)[Math.floor(times.length * 0.95)]; console.log(`${test.name} validation performance:`); console.log(` Average: ${avgTime.toFixed(1)}ms`); console.log(` Min: ${minTime}ms, Max: ${maxTime}ms`); console.log(` P95: ${p95Time}ms`); // Performance expectations expect(avgTime).toBeLessThan(test.expectedMaxTime); expect(p95Time).toBeLessThan(test.expectedMaxTime * 2); // PerformanceTracker.recordMetric(`validation-performance-${test.name.toLowerCase().replace(/\s+/g, '-')}`, avgTime); } const duration = Date.now() - startTime; // PerformanceTracker.recordMetric('validation-performance-single', duration); }); tap.test('VAL-12: Validation Performance - Concurrent Validation', { timeout: testTimeout }, async (tools) => { const startTime = Date.now(); // Test concurrent validation performance const testXml = ` CONCURRENT-001 2024-01-01 380 EUR 100.00 `; const concurrencyLevels = [1, 5, 10, 20]; for (const concurrency of concurrencyLevels) { const concurrentStart = Date.now(); const promises = []; for (let i = 0; i < concurrency; i++) { promises.push((async () => { const invoice = new EInvoice(); await invoice.fromXmlString(testXml); return await invoice.validate(); })()); } try { const results = await Promise.all(promises); const concurrentDuration = Date.now() - concurrentStart; // Verify all validations succeeded for (const result of results) { expect(result).toBeTruthy(); } const avgTimePerValidation = concurrentDuration / concurrency; console.log(`Concurrent validation (${concurrency} parallel):`); console.log(` Total time: ${concurrentDuration}ms`); console.log(` Avg per validation: ${avgTimePerValidation.toFixed(1)}ms`); console.log(` Throughput: ${(1000 / avgTimePerValidation).toFixed(1)} validations/sec`); // Performance expectations expect(avgTimePerValidation).toBeLessThan(100); // 100ms max per validation expect(concurrentDuration).toBeLessThan(5000); // 5 seconds max total // PerformanceTracker.recordMetric(`validation-performance-concurrent-${concurrency}`, avgTimePerValidation); } catch (error) { console.log(`Concurrent validation failed at level ${concurrency}: ${error.message}`); throw error; } } const totalDuration = Date.now() - startTime; // PerformanceTracker.recordMetric('validation-performance-concurrent', totalDuration); }); tap.test('VAL-12: Validation Performance - Large Invoice Handling', { timeout: testTimeout }, async (tools) => { const startTime = Date.now(); // Test performance with large invoices (many line items) const lineCounts = [1, 10, 50, 100]; for (const lineCount of lineCounts) { const largeInvoiceStart = Date.now(); // Generate invoice with multiple lines let invoiceLines = ''; for (let i = 1; i <= lineCount; i++) { invoiceLines += ` ${i} 1 10.00 Item ${i} 10.00 `; } const totalAmount = lineCount * 10; const taxAmount = totalAmount * 0.19; const totalWithTax = totalAmount + taxAmount; const largeInvoiceXml = ` LARGE-${lineCount}-LINES 2024-01-01 380 EUR ${invoiceLines} ${taxAmount.toFixed(2)} ${totalAmount.toFixed(2)} ${totalAmount.toFixed(2)} ${totalWithTax.toFixed(2)} ${totalWithTax.toFixed(2)} `; try { const invoice = new EInvoice(); await invoice.fromXmlString(largeInvoiceXml); const validationResult = await invoice.validate(); const largeInvoiceDuration = Date.now() - largeInvoiceStart; expect(validationResult).toBeTruthy(); const timePerLine = largeInvoiceDuration / lineCount; console.log(`Large invoice validation (${lineCount} lines):`); console.log(` Total time: ${largeInvoiceDuration}ms`); console.log(` Time per line: ${timePerLine.toFixed(2)}ms`); // Performance expectations scale with line count const maxExpectedTime = Math.max(100, lineCount * 2); // 2ms per line minimum expect(largeInvoiceDuration).toBeLessThan(maxExpectedTime); // PerformanceTracker.recordMetric(`validation-performance-large-${lineCount}-lines`, largeInvoiceDuration); } catch (error) { console.log(`Large invoice validation failed (${lineCount} lines): ${error.message}`); throw error; } } const totalDuration = Date.now() - startTime; // PerformanceTracker.recordMetric('validation-performance-large', totalDuration); }); tap.test('VAL-12: Validation Performance - Memory Usage Monitoring', async (tools) => { const startTime = Date.now(); // Monitor memory usage during validation const memoryBefore = process.memoryUsage(); const testXml = ` MEMORY-TEST-001 2024-01-01 380 EUR 100.00 `; // Perform multiple validations and monitor memory const iterations = 100; for (let i = 0; i < iterations; i++) { const invoice = new EInvoice(); await invoice.fromXmlString(testXml); await invoice.validate(); // Force garbage collection periodically if (i % 20 === 0 && global.gc) { global.gc(); } } const memoryAfter = process.memoryUsage(); const heapGrowth = memoryAfter.heapUsed - memoryBefore.heapUsed; const rssGrowth = memoryAfter.rss - memoryBefore.rss; console.log(`Memory usage for ${iterations} validations:`); console.log(` Heap growth: ${(heapGrowth / 1024 / 1024).toFixed(2)} MB`); console.log(` RSS growth: ${(rssGrowth / 1024 / 1024).toFixed(2)} MB`); console.log(` Heap per validation: ${(heapGrowth / iterations / 1024).toFixed(2)} KB`); // Memory expectations const heapPerValidation = heapGrowth / iterations; expect(heapPerValidation).toBeLessThan(200 * 1024); // Less than 200KB per validation const duration = Date.now() - startTime; // PerformanceTracker.recordMetric('validation-performance-memory', duration); }); tap.test('VAL-12: Validation Performance - Corpus Performance Analysis', { timeout: testTimeout }, async (tools) => { const startTime = Date.now(); const performanceResults = []; let totalValidations = 0; let totalTime = 0; try { // Test performance across different corpus categories const categories = ['UBL_XML_RECHNUNG', 'CII_XML_RECHNUNG']; for (const category of categories) { const categoryStart = Date.now(); let categoryValidations = 0; try { const files = await CorpusLoader.getFiles(category); for (const filePath of files.slice(0, 5)) { // Test first 5 files per category const fileStart = Date.now(); try { const invoice = new EInvoice(); await invoice.fromFile(filePath); await invoice.validate(); const fileTime = Date.now() - fileStart; categoryValidations++; totalValidations++; totalTime += fileTime; // Track file size impact on performance const stats = await plugins.fs.stat(filePath); const fileSizeKB = stats.size / 1024; performanceResults.push({ category, file: plugins.path.basename(filePath), time: fileTime, sizeKB: fileSizeKB, timePerKB: fileTime / fileSizeKB }); } catch (error) { console.log(`Failed to process ${plugins.path.basename(filePath)}: ${error.message}`); } } const categoryTime = Date.now() - categoryStart; const avgCategoryTime = categoryValidations > 0 ? categoryTime / categoryValidations : 0; console.log(`${category} performance:`); console.log(` Files processed: ${categoryValidations}`); console.log(` Total time: ${categoryTime}ms`); console.log(` Avg per file: ${avgCategoryTime.toFixed(1)}ms`); } catch (error) { console.log(`Failed to process category ${category}: ${error.message}`); } } // Analyze performance correlations if (performanceResults.length > 0) { const avgTime = totalTime / totalValidations; const avgSize = performanceResults.reduce((sum, r) => sum + r.sizeKB, 0) / performanceResults.length; const avgTimePerKB = performanceResults.reduce((sum, r) => sum + r.timePerKB, 0) / performanceResults.length; console.log(`Overall corpus performance analysis:`); console.log(` Total validations: ${totalValidations}`); console.log(` Average time: ${avgTime.toFixed(1)}ms`); console.log(` Average file size: ${avgSize.toFixed(1)}KB`); console.log(` Average time per KB: ${avgTimePerKB.toFixed(2)}ms/KB`); // Performance expectations expect(avgTime).toBeLessThan(200); // 200ms max average expect(avgTimePerKB).toBeLessThan(10); // 10ms per KB max // Find slowest files const slowestFiles = performanceResults .sort((a, b) => b.time - a.time) .slice(0, 3); console.log(`Slowest files:`); for (const file of slowestFiles) { console.log(` ${file.file}: ${file.time}ms (${file.sizeKB.toFixed(1)}KB)`); } } } catch (error) { console.log(`Corpus performance analysis failed: ${error.message}`); throw error; } const totalDuration = Date.now() - startTime; // PerformanceTracker.recordMetric('validation-performance-corpus', totalDuration); expect(totalDuration).toBeLessThan(300000); // 5 minutes max console.log(`Corpus performance analysis completed in ${totalDuration}ms`); }); tap.test('VAL-12: Validation Performance - Stress Testing', { timeout: testTimeout }, async (tools) => { const startTime = Date.now(); // Stress test with rapid successive validations const stressTestXml = ` STRESS-TEST-001 2024-01-01 380 EUR 100.00 `; const stressIterations = 200; const stressTimes = []; try { for (let i = 0; i < stressIterations; i++) { const iterationStart = Date.now(); const invoice = new EInvoice(); await invoice.fromXmlString(stressTestXml); await invoice.validate(); const iterationTime = Date.now() - iterationStart; stressTimes.push(iterationTime); // Log progress every 50 iterations if ((i + 1) % 50 === 0) { const currentAvg = stressTimes.slice(-50).reduce((a, b) => a + b, 0) / 50; console.log(`Stress test progress: ${i + 1}/${stressIterations}, avg last 50: ${currentAvg.toFixed(1)}ms`); } } // Analyze stress test results const avgStressTime = stressTimes.reduce((a, b) => a + b, 0) / stressTimes.length; const minStressTime = Math.min(...stressTimes); const maxStressTime = Math.max(...stressTimes); const stdDev = Math.sqrt(stressTimes.reduce((sum, time) => sum + Math.pow(time - avgStressTime, 2), 0) / stressTimes.length); // Check for performance degradation over time const firstHalf = stressTimes.slice(0, stressIterations / 2); const secondHalf = stressTimes.slice(stressIterations / 2); const firstHalfAvg = firstHalf.reduce((a, b) => a + b, 0) / firstHalf.length; const secondHalfAvg = secondHalf.reduce((a, b) => a + b, 0) / secondHalf.length; const degradation = ((secondHalfAvg - firstHalfAvg) / firstHalfAvg) * 100; console.log(`Stress test results (${stressIterations} iterations):`); console.log(` Average time: ${avgStressTime.toFixed(1)}ms`); console.log(` Min: ${minStressTime}ms, Max: ${maxStressTime}ms`); console.log(` Standard deviation: ${stdDev.toFixed(1)}ms`); console.log(` Performance degradation: ${degradation.toFixed(1)}%`); // Performance expectations expect(avgStressTime).toBeLessThan(50); // 50ms average max expect(degradation).toBeLessThan(20); // Less than 20% degradation expect(stdDev).toBeLessThan(avgStressTime); // Standard deviation should be reasonable } catch (error) { console.log(`Stress test failed: ${error.message}`); throw error; } const totalDuration = Date.now() - startTime; // PerformanceTracker.recordMetric('validation-performance-stress', totalDuration); console.log(`Stress test completed in ${totalDuration}ms`); }); tap.test('VAL-12: Performance Summary', async (tools) => { const operations = [ 'validation-performance-single', 'validation-performance-concurrent', 'validation-performance-large', 'validation-performance-memory', 'validation-performance-corpus', 'validation-performance-stress' ]; console.log(`\n=== Validation Performance Summary ===`); for (const operation of operations) { const summary = await PerformanceTracker.getSummary(operation); if (summary) { console.log(`${operation}:`); console.log(` avg=${summary.average}ms, min=${summary.min}ms, max=${summary.max}ms, p95=${summary.p95}ms`); } } console.log(`\nValidation performance testing completed successfully.`); }); // Start the test tap.start(); // Export for test runner compatibility export default tap;