2025-05-25 19:45:37 +00:00
|
|
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
2025-05-30 04:29:13 +00:00
|
|
|
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';
|
2025-05-25 19:45:37 +00:00
|
|
|
|
|
|
|
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: `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
|
|
<ID>MIN-001</ID>
|
|
|
|
<IssueDate>2024-01-01</IssueDate>
|
|
|
|
<InvoiceTypeCode>380</InvoiceTypeCode>
|
|
|
|
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
|
|
|
|
<LegalMonetaryTotal>
|
|
|
|
<PayableAmount currencyID="EUR">100.00</PayableAmount>
|
|
|
|
</LegalMonetaryTotal>
|
|
|
|
</Invoice>`,
|
|
|
|
expectedMaxTime: 20 // 20ms max for minimal invoice
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'Standard UBL Invoice',
|
|
|
|
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
|
|
<ID>STD-001</ID>
|
|
|
|
<IssueDate>2024-01-01</IssueDate>
|
|
|
|
<InvoiceTypeCode>380</InvoiceTypeCode>
|
|
|
|
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
|
|
|
|
<AccountingSupplierParty>
|
|
|
|
<Party>
|
|
|
|
<PartyName><Name>Test Supplier</Name></PartyName>
|
|
|
|
<PostalAddress>
|
|
|
|
<StreetName>Test Street 1</StreetName>
|
|
|
|
<CityName>Test City</CityName>
|
|
|
|
<PostalZone>12345</PostalZone>
|
|
|
|
<Country><IdentificationCode>DE</IdentificationCode></Country>
|
|
|
|
</PostalAddress>
|
|
|
|
</Party>
|
|
|
|
</AccountingSupplierParty>
|
|
|
|
<AccountingCustomerParty>
|
|
|
|
<Party>
|
|
|
|
<PartyName><Name>Test Customer</Name></PartyName>
|
|
|
|
<PostalAddress>
|
|
|
|
<StreetName>Customer Street 1</StreetName>
|
|
|
|
<CityName>Customer City</CityName>
|
|
|
|
<PostalZone>54321</PostalZone>
|
|
|
|
<Country><IdentificationCode>DE</IdentificationCode></Country>
|
|
|
|
</PostalAddress>
|
|
|
|
</Party>
|
|
|
|
</AccountingCustomerParty>
|
|
|
|
<InvoiceLine>
|
|
|
|
<ID>1</ID>
|
|
|
|
<InvoicedQuantity unitCode="C62">1</InvoicedQuantity>
|
|
|
|
<LineExtensionAmount currencyID="EUR">100.00</LineExtensionAmount>
|
|
|
|
<Item><Name>Test Item</Name></Item>
|
|
|
|
<Price><PriceAmount currencyID="EUR">100.00</PriceAmount></Price>
|
|
|
|
</InvoiceLine>
|
|
|
|
<TaxTotal>
|
|
|
|
<TaxAmount currencyID="EUR">19.00</TaxAmount>
|
|
|
|
</TaxTotal>
|
|
|
|
<LegalMonetaryTotal>
|
|
|
|
<LineExtensionAmount currencyID="EUR">100.00</LineExtensionAmount>
|
|
|
|
<TaxExclusiveAmount currencyID="EUR">100.00</TaxExclusiveAmount>
|
|
|
|
<TaxInclusiveAmount currencyID="EUR">119.00</TaxInclusiveAmount>
|
|
|
|
<PayableAmount currencyID="EUR">119.00</PayableAmount>
|
|
|
|
</LegalMonetaryTotal>
|
|
|
|
</Invoice>`,
|
|
|
|
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) {
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(`Validation failed for ${test.name}: ${error.message}`);
|
2025-05-25 19:45:37 +00:00
|
|
|
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)];
|
|
|
|
|
2025-05-30 04:29:13 +00:00
|
|
|
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`);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
|
|
|
// Performance expectations
|
|
|
|
expect(avgTime).toBeLessThan(test.expectedMaxTime);
|
|
|
|
expect(p95Time).toBeLessThan(test.expectedMaxTime * 2);
|
|
|
|
|
2025-05-30 04:29:13 +00:00
|
|
|
// PerformanceTracker.recordMetric(`validation-performance-${test.name.toLowerCase().replace(/\s+/g, '-')}`, avgTime);
|
2025-05-25 19:45:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const duration = Date.now() - startTime;
|
2025-05-30 04:29:13 +00:00
|
|
|
// PerformanceTracker.recordMetric('validation-performance-single', duration);
|
2025-05-25 19:45:37 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
tap.test('VAL-12: Validation Performance - Concurrent Validation', { timeout: testTimeout }, async (tools) => {
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
|
|
|
// Test concurrent validation performance
|
|
|
|
const testXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
|
|
<ID>CONCURRENT-001</ID>
|
|
|
|
<IssueDate>2024-01-01</IssueDate>
|
|
|
|
<InvoiceTypeCode>380</InvoiceTypeCode>
|
|
|
|
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
|
|
|
|
<LegalMonetaryTotal>
|
|
|
|
<PayableAmount currencyID="EUR">100.00</PayableAmount>
|
|
|
|
</LegalMonetaryTotal>
|
|
|
|
</Invoice>`;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2025-05-30 04:29:13 +00:00
|
|
|
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`);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
|
|
|
// Performance expectations
|
|
|
|
expect(avgTimePerValidation).toBeLessThan(100); // 100ms max per validation
|
|
|
|
expect(concurrentDuration).toBeLessThan(5000); // 5 seconds max total
|
|
|
|
|
2025-05-30 04:29:13 +00:00
|
|
|
// PerformanceTracker.recordMetric(`validation-performance-concurrent-${concurrency}`, avgTimePerValidation);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
|
|
|
} catch (error) {
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(`Concurrent validation failed at level ${concurrency}: ${error.message}`);
|
2025-05-25 19:45:37 +00:00
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const totalDuration = Date.now() - startTime;
|
2025-05-30 04:29:13 +00:00
|
|
|
// PerformanceTracker.recordMetric('validation-performance-concurrent', totalDuration);
|
2025-05-25 19:45:37 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
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 += `
|
|
|
|
<InvoiceLine>
|
|
|
|
<ID>${i}</ID>
|
|
|
|
<InvoicedQuantity unitCode="C62">1</InvoicedQuantity>
|
|
|
|
<LineExtensionAmount currencyID="EUR">10.00</LineExtensionAmount>
|
|
|
|
<Item><Name>Item ${i}</Name></Item>
|
|
|
|
<Price><PriceAmount currencyID="EUR">10.00</PriceAmount></Price>
|
|
|
|
</InvoiceLine>`;
|
|
|
|
}
|
|
|
|
|
|
|
|
const totalAmount = lineCount * 10;
|
|
|
|
const taxAmount = totalAmount * 0.19;
|
|
|
|
const totalWithTax = totalAmount + taxAmount;
|
|
|
|
|
|
|
|
const largeInvoiceXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
|
|
<ID>LARGE-${lineCount}-LINES</ID>
|
|
|
|
<IssueDate>2024-01-01</IssueDate>
|
|
|
|
<InvoiceTypeCode>380</InvoiceTypeCode>
|
|
|
|
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
|
|
|
|
${invoiceLines}
|
|
|
|
<TaxTotal>
|
|
|
|
<TaxAmount currencyID="EUR">${taxAmount.toFixed(2)}</TaxAmount>
|
|
|
|
</TaxTotal>
|
|
|
|
<LegalMonetaryTotal>
|
|
|
|
<LineExtensionAmount currencyID="EUR">${totalAmount.toFixed(2)}</LineExtensionAmount>
|
|
|
|
<TaxExclusiveAmount currencyID="EUR">${totalAmount.toFixed(2)}</TaxExclusiveAmount>
|
|
|
|
<TaxInclusiveAmount currencyID="EUR">${totalWithTax.toFixed(2)}</TaxInclusiveAmount>
|
|
|
|
<PayableAmount currencyID="EUR">${totalWithTax.toFixed(2)}</PayableAmount>
|
|
|
|
</LegalMonetaryTotal>
|
|
|
|
</Invoice>`;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(`Large invoice validation (${lineCount} lines):`);
|
|
|
|
console.log(` Total time: ${largeInvoiceDuration}ms`);
|
|
|
|
console.log(` Time per line: ${timePerLine.toFixed(2)}ms`);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
|
|
|
// Performance expectations scale with line count
|
|
|
|
const maxExpectedTime = Math.max(100, lineCount * 2); // 2ms per line minimum
|
|
|
|
expect(largeInvoiceDuration).toBeLessThan(maxExpectedTime);
|
|
|
|
|
2025-05-30 04:29:13 +00:00
|
|
|
// PerformanceTracker.recordMetric(`validation-performance-large-${lineCount}-lines`, largeInvoiceDuration);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
|
|
|
} catch (error) {
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(`Large invoice validation failed (${lineCount} lines): ${error.message}`);
|
2025-05-25 19:45:37 +00:00
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const totalDuration = Date.now() - startTime;
|
2025-05-30 04:29:13 +00:00
|
|
|
// PerformanceTracker.recordMetric('validation-performance-large', totalDuration);
|
2025-05-25 19:45:37 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
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 = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
|
|
<ID>MEMORY-TEST-001</ID>
|
|
|
|
<IssueDate>2024-01-01</IssueDate>
|
|
|
|
<InvoiceTypeCode>380</InvoiceTypeCode>
|
|
|
|
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
|
|
|
|
<LegalMonetaryTotal>
|
|
|
|
<PayableAmount currencyID="EUR">100.00</PayableAmount>
|
|
|
|
</LegalMonetaryTotal>
|
|
|
|
</Invoice>`;
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2025-05-30 04:29:13 +00:00
|
|
|
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`);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
|
|
|
// Memory expectations
|
|
|
|
const heapPerValidation = heapGrowth / iterations;
|
2025-05-30 18:08:27 +00:00
|
|
|
expect(heapPerValidation).toBeLessThan(200 * 1024); // Less than 200KB per validation
|
2025-05-25 19:45:37 +00:00
|
|
|
|
|
|
|
const duration = Date.now() - startTime;
|
2025-05-30 04:29:13 +00:00
|
|
|
// PerformanceTracker.recordMetric('validation-performance-memory', duration);
|
2025-05-25 19:45:37 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
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) {
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(`Failed to process ${plugins.path.basename(filePath)}: ${error.message}`);
|
2025-05-25 19:45:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const categoryTime = Date.now() - categoryStart;
|
|
|
|
const avgCategoryTime = categoryValidations > 0 ? categoryTime / categoryValidations : 0;
|
|
|
|
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(`${category} performance:`);
|
|
|
|
console.log(` Files processed: ${categoryValidations}`);
|
|
|
|
console.log(` Total time: ${categoryTime}ms`);
|
|
|
|
console.log(` Avg per file: ${avgCategoryTime.toFixed(1)}ms`);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
|
|
|
} catch (error) {
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(`Failed to process category ${category}: ${error.message}`);
|
2025-05-25 19:45:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2025-05-30 04:29:13 +00:00
|
|
|
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`);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(`Slowest files:`);
|
2025-05-25 19:45:37 +00:00
|
|
|
for (const file of slowestFiles) {
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(` ${file.file}: ${file.time}ms (${file.sizeKB.toFixed(1)}KB)`);
|
2025-05-25 19:45:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(`Corpus performance analysis failed: ${error.message}`);
|
2025-05-25 19:45:37 +00:00
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
|
|
|
|
const totalDuration = Date.now() - startTime;
|
2025-05-30 04:29:13 +00:00
|
|
|
// PerformanceTracker.recordMetric('validation-performance-corpus', totalDuration);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
|
|
|
expect(totalDuration).toBeLessThan(300000); // 5 minutes max
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(`Corpus performance analysis completed in ${totalDuration}ms`);
|
2025-05-25 19:45:37 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
tap.test('VAL-12: Validation Performance - Stress Testing', { timeout: testTimeout }, async (tools) => {
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
|
|
|
// Stress test with rapid successive validations
|
|
|
|
const stressTestXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
|
|
<ID>STRESS-TEST-001</ID>
|
|
|
|
<IssueDate>2024-01-01</IssueDate>
|
|
|
|
<InvoiceTypeCode>380</InvoiceTypeCode>
|
|
|
|
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
|
|
|
|
<LegalMonetaryTotal>
|
|
|
|
<PayableAmount currencyID="EUR">100.00</PayableAmount>
|
|
|
|
</LegalMonetaryTotal>
|
|
|
|
</Invoice>`;
|
|
|
|
|
|
|
|
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;
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(`Stress test progress: ${i + 1}/${stressIterations}, avg last 50: ${currentAvg.toFixed(1)}ms`);
|
2025-05-25 19:45:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2025-05-30 04:29:13 +00:00
|
|
|
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)}%`);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
|
|
|
// 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) {
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(`Stress test failed: ${error.message}`);
|
2025-05-25 19:45:37 +00:00
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
|
|
|
|
const totalDuration = Date.now() - startTime;
|
2025-05-30 04:29:13 +00:00
|
|
|
// PerformanceTracker.recordMetric('validation-performance-stress', totalDuration);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(`Stress test completed in ${totalDuration}ms`);
|
2025-05-25 19:45:37 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
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'
|
|
|
|
];
|
|
|
|
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(`\n=== Validation Performance Summary ===`);
|
2025-05-25 19:45:37 +00:00
|
|
|
|
|
|
|
for (const operation of operations) {
|
|
|
|
const summary = await PerformanceTracker.getSummary(operation);
|
|
|
|
if (summary) {
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(`${operation}:`);
|
|
|
|
console.log(` avg=${summary.average}ms, min=${summary.min}ms, max=${summary.max}ms, p95=${summary.p95}ms`);
|
2025-05-25 19:45:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-30 04:29:13 +00:00
|
|
|
console.log(`\nValidation performance testing completed successfully.`);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Start the test
|
|
|
|
tap.start();
|
|
|
|
|
|
|
|
// Export for test runner compatibility
|
|
|
|
export default tap;
|