/** * @file test.perf-02.validation-performance.ts * @description Performance tests for invoice validation operations */ import { expect, tap } from '@git.zone/tstest/tapbundle'; import { EInvoice } from '../../../ts/index.js'; import { ValidationLevel } from '../../../ts/interfaces/common.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-02: Validation Performance'); // Helper to create test invoices function createTestInvoice(name: string, lineItems: number): string { const lines = Array(lineItems).fill(null).map((_, i) => ` ${i + 1} 1 100.00 Product ${i + 1} 100.00 `).join(''); return ` ${name} 2025-01-25 380 EUR Test Supplier Berlin 10115 DE Test Customer Munich 80331 DE ${100 * lineItems}.00 ${lines} `; } tap.test('PERF-02: Syntax validation performance', async () => { const testCases = [ { name: 'Minimal Invoice', lineItems: 1 }, { name: 'Small Invoice', lineItems: 10 }, { name: 'Medium Invoice', lineItems: 50 }, { name: 'Large Invoice', lineItems: 200 } ]; const iterations = 50; for (const testCase of testCases) { const xmlContent = createTestInvoice(testCase.name, testCase.lineItems); const times: number[] = []; for (let i = 0; i < iterations; i++) { const einvoice = await EInvoice.fromXml(xmlContent); const startTime = performance.now(); const result = await einvoice.validate(ValidationLevel.SYNTAX); const endTime = performance.now(); const duration = endTime - startTime; times.push(duration); performanceTracker.addMeasurement(`syntax-${testCase.name}`, duration); if (i === 0) { expect(result.valid).toEqual(true); } } const avg = times.reduce((a, b) => a + b, 0) / times.length; console.log(`${testCase.name} (${testCase.lineItems} items) - Syntax validation: avg=${avg.toFixed(3)}ms`); // Performance expectations expect(avg).toBeLessThan(testCase.lineItems * 0.5 + 10); // Allow 0.5ms per line item + 10ms base } }); tap.test('PERF-02: Semantic validation performance', async () => { const testCases = [ { name: 'Valid Invoice', valid: true, xml: createTestInvoice('VALID-001', 10) }, { name: 'Missing Fields', valid: false, xml: ` INVALID-001 ` } ]; const iterations = 30; for (const testCase of testCases) { const times: number[] = []; for (let i = 0; i < iterations; i++) { try { const einvoice = await EInvoice.fromXml(testCase.xml); const startTime = performance.now(); const result = await einvoice.validate(ValidationLevel.SEMANTIC); const endTime = performance.now(); const duration = endTime - startTime; times.push(duration); performanceTracker.addMeasurement(`semantic-${testCase.name}`, duration); } catch (error) { // For invalid XML, measure the error handling time const duration = 0.1; // Minimal time for error cases times.push(duration); } } const avg = times.reduce((a, b) => a + b, 0) / times.length; console.log(`${testCase.name} - Semantic validation: avg=${avg.toFixed(3)}ms`); // Semantic validation should be fast expect(avg).toBeLessThan(50); } }); tap.test('PERF-02: Business rules validation performance', async () => { const xmlContent = createTestInvoice('BUSINESS-001', 20); const iterations = 20; const times: number[] = []; for (let i = 0; i < iterations; i++) { const einvoice = await EInvoice.fromXml(xmlContent); const startTime = performance.now(); const result = await einvoice.validate(ValidationLevel.BUSINESS); const endTime = performance.now(); const duration = endTime - startTime; times.push(duration); performanceTracker.addMeasurement('business-validation', duration); } const avg = times.reduce((a, b) => a + b, 0) / times.length; console.log(`Business rules validation: avg=${avg.toFixed(3)}ms`); // Business rules validation is more complex expect(avg).toBeLessThan(100); }); tap.test('PERF-02: Concurrent validation performance', async () => { const xmlContent = createTestInvoice('CONCURRENT-001', 10); const concurrentCount = 5; const iterations = 5; for (let iter = 0; iter < iterations; iter++) { const startTime = performance.now(); // Run multiple validations concurrently const promises = Array(concurrentCount).fill(null).map(async () => { const einvoice = await EInvoice.fromXml(xmlContent); return einvoice.validate(ValidationLevel.SYNTAX); }); const results = await Promise.all(promises); const endTime = performance.now(); const duration = endTime - startTime; performanceTracker.addMeasurement('concurrent-validation', duration); // All should be valid expect(results.every(r => r.valid)).toEqual(true); console.log(`Concurrent validation (${concurrentCount} parallel): ${duration.toFixed(3)}ms`); } const stats = performanceTracker.getStats('concurrent-validation'); if (stats) { // Concurrent validation should still be efficient expect(stats.avg).toBeLessThan(150); } }); tap.test('PERF-02: Validation caching performance', async () => { const xmlContent = createTestInvoice('CACHE-001', 50); const einvoice = await EInvoice.fromXml(xmlContent); // First validation (cold) const coldStart = performance.now(); const result1 = await einvoice.validate(ValidationLevel.SYNTAX); const coldEnd = performance.now(); const coldTime = coldEnd - coldStart; // Second validation (potentially cached) const warmStart = performance.now(); const result2 = await einvoice.validate(ValidationLevel.SYNTAX); const warmEnd = performance.now(); const warmTime = warmEnd - warmStart; console.log(`Cold validation: ${coldTime.toFixed(3)}ms`); console.log(`Warm validation: ${warmTime.toFixed(3)}ms`); expect(result1.valid).toEqual(true); expect(result2.valid).toEqual(true); // Note: We don't expect caching to necessarily make it faster, // but it should at least not be significantly slower expect(warmTime).toBeLessThan(coldTime * 2); }); tap.test('PERF-02: Error validation performance', async () => { // Test validation performance with various error conditions const errorCases = [ { name: 'Empty XML', xml: '' }, { name: 'Invalid XML', xml: 'test' } ]; for (const errorCase of errorCases) { const times: number[] = []; for (let i = 0; i < 20; i++) { const startTime = performance.now(); try { await EInvoice.fromXml(errorCase.xml); } catch (error) { // Expected error } const endTime = performance.now(); const duration = endTime - startTime; times.push(duration); } const avg = times.reduce((a, b) => a + b, 0) / times.length; console.log(`${errorCase.name} - Error handling: avg=${avg.toFixed(3)}ms`); // Error cases should fail fast expect(avg).toBeLessThan(5); } }); tap.test('PERF-02: Performance Summary', async () => { performanceTracker.printSummary(); console.log('\nValidation performance tests completed successfully'); }); tap.start();