fix(compliance): improve compliance
This commit is contained in:
@ -1,71 +1,320 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { EInvoice } from '../../../ts/index.js';
|
||||
import { PerformanceTracker } from '../../helpers/performance.tracker.js';
|
||||
|
||||
tap.test('ERR-05: Memory Errors - should handle memory constraints', async () => {
|
||||
// ERR-05: Test error handling for memory errors
|
||||
|
||||
// Test 1: Basic error handling
|
||||
console.log('\nTest 1: Basic memory errors handling');
|
||||
const { result: basicResult, metric: basicMetric } = await PerformanceTracker.track(
|
||||
'err05-basic',
|
||||
async () => {
|
||||
let errorCaught = false;
|
||||
let errorMessage = '';
|
||||
|
||||
try {
|
||||
// Simulate error scenario
|
||||
const einvoice = new EInvoice();
|
||||
|
||||
// Try to load invalid content based on test type
|
||||
// Simulate large document
|
||||
const largeXml = '<?xml version="1.0"?><Invoice>' + 'x'.repeat(1000000) + '</Invoice>';
|
||||
await einvoice.fromXmlString(largeXml);
|
||||
|
||||
} catch (error) {
|
||||
errorCaught = true;
|
||||
errorMessage = error.message || 'Unknown error';
|
||||
console.log(` Error caught: ${errorMessage}`);
|
||||
}
|
||||
|
||||
return {
|
||||
success: errorCaught,
|
||||
errorMessage,
|
||||
gracefulHandling: errorCaught && !errorMessage.includes('FATAL')
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
console.log(` Basic error handling completed in ${basicMetric.duration}ms`);
|
||||
console.log(` Error was caught: ${basicResult.success}`);
|
||||
console.log(` Graceful handling: ${basicResult.gracefulHandling}`);
|
||||
|
||||
// Test 2: Recovery mechanism
|
||||
console.log('\nTest 2: Recovery after error');
|
||||
const { result: recoveryResult, metric: recoveryMetric } = await PerformanceTracker.track(
|
||||
'err05-recovery',
|
||||
async () => {
|
||||
console.log('Testing memory constraint handling...\n');
|
||||
|
||||
// Test 1: Large invoice with many line items
|
||||
const testLargeInvoiceLineItems = async () => {
|
||||
console.log('Test 1 - Large invoice with many line items:');
|
||||
|
||||
let memoryHandled = false;
|
||||
let canProcess = false;
|
||||
|
||||
try {
|
||||
const einvoice = new EInvoice();
|
||||
einvoice.issueDate = new Date(2024, 0, 1);
|
||||
einvoice.invoiceId = 'LARGE-INVOICE-001';
|
||||
|
||||
// First cause an error
|
||||
try {
|
||||
// Simulate large document
|
||||
const largeXml = '<?xml version="1.0"?><Invoice>' + 'x'.repeat(1000000) + '</Invoice>';
|
||||
await einvoice.fromXmlString(largeXml);
|
||||
} catch (error) {
|
||||
// Expected error
|
||||
einvoice.from = {
|
||||
type: 'company',
|
||||
name: 'Bulk Seller Company',
|
||||
description: 'Testing large invoices',
|
||||
address: {
|
||||
streetName: 'Bulk Street',
|
||||
houseNumber: '1',
|
||||
postalCode: '12345',
|
||||
city: 'Bulk City',
|
||||
country: 'DE'
|
||||
},
|
||||
status: 'active',
|
||||
foundedDate: { year: 2020, month: 1, day: 1 },
|
||||
registrationDetails: {
|
||||
vatId: 'DE123456789',
|
||||
registrationId: 'HRB 12345',
|
||||
registrationName: 'Commercial Register'
|
||||
}
|
||||
};
|
||||
|
||||
einvoice.to = {
|
||||
type: 'company',
|
||||
name: 'Bulk Buyer Company',
|
||||
description: 'Customer buying many items',
|
||||
address: {
|
||||
streetName: 'Buyer Street',
|
||||
houseNumber: '2',
|
||||
postalCode: '54321',
|
||||
city: 'Buyer City',
|
||||
country: 'DE'
|
||||
},
|
||||
status: 'active',
|
||||
foundedDate: { year: 2019, month: 1, day: 1 },
|
||||
registrationDetails: {
|
||||
vatId: 'DE987654321',
|
||||
registrationId: 'HRB 54321',
|
||||
registrationName: 'Commercial Register'
|
||||
}
|
||||
};
|
||||
|
||||
// Create many line items (test with 1000 items)
|
||||
einvoice.items = [];
|
||||
const itemCount = 1000;
|
||||
|
||||
for (let i = 0; i < itemCount; i++) {
|
||||
einvoice.items.push({
|
||||
position: i + 1,
|
||||
name: `Item ${i + 1} - Product with detailed description for testing memory usage`,
|
||||
articleNumber: `ART-${String(i + 1).padStart(6, '0')}`,
|
||||
unitType: 'EA',
|
||||
unitQuantity: 1 + (i % 10),
|
||||
unitNetPrice: 10.50 + (i % 100),
|
||||
vatPercentage: 19
|
||||
});
|
||||
}
|
||||
|
||||
// Now try normal operation
|
||||
einvoice.id = 'RECOVERY-TEST';
|
||||
einvoice.issueDate = new Date(2025, 0, 25);
|
||||
einvoice.invoiceId = 'RECOVERY-TEST';
|
||||
einvoice.accountingDocId = 'RECOVERY-TEST';
|
||||
// Check memory usage before processing
|
||||
const memBefore = process.memoryUsage();
|
||||
|
||||
// Generate XML
|
||||
const xmlString = await einvoice.toXmlString('ubl');
|
||||
|
||||
// Check memory usage after processing
|
||||
const memAfter = process.memoryUsage();
|
||||
const memoryIncrease = memAfter.heapUsed - memBefore.heapUsed;
|
||||
|
||||
// Parse back to verify
|
||||
const newInvoice = new EInvoice();
|
||||
await newInvoice.fromXmlString(xmlString);
|
||||
|
||||
canProcess = newInvoice.items.length === itemCount;
|
||||
memoryHandled = memoryIncrease < 100 * 1024 * 1024; // Less than 100MB increase
|
||||
|
||||
console.log(` Line items processed: ${newInvoice.items.length}/${itemCount}`);
|
||||
console.log(` Memory increase: ${Math.round(memoryIncrease / 1024 / 1024)}MB`);
|
||||
console.log(` Memory efficient: ${memoryHandled ? 'Yes' : 'No'}`);
|
||||
|
||||
} catch (error) {
|
||||
console.log(` Error occurred: ${error.message}`);
|
||||
// Memory errors should be handled gracefully
|
||||
memoryHandled = error.message.includes('memory') || error.message.includes('heap');
|
||||
}
|
||||
|
||||
return { memoryHandled, canProcess };
|
||||
};
|
||||
|
||||
// Test 2: Large field content
|
||||
const testLargeFieldContent = async () => {
|
||||
console.log('\nTest 2 - Large field content:');
|
||||
|
||||
let fieldsHandled = false;
|
||||
let canProcess = false;
|
||||
|
||||
try {
|
||||
const einvoice = new EInvoice();
|
||||
einvoice.issueDate = new Date(2024, 0, 1);
|
||||
einvoice.invoiceId = 'LARGE-FIELDS-001';
|
||||
|
||||
// Create large description content (10KB)
|
||||
const largeDescription = 'This is a very detailed description for testing memory handling. '.repeat(200);
|
||||
|
||||
einvoice.from = {
|
||||
type: 'company',
|
||||
name: 'Test Company',
|
||||
description: 'Testing error recovery',
|
||||
description: largeDescription,
|
||||
address: {
|
||||
streetName: 'Very Long Street Name That Tests Field Length Handling in Memory Management System',
|
||||
houseNumber: '1',
|
||||
postalCode: '12345',
|
||||
city: 'Test City',
|
||||
country: 'DE'
|
||||
},
|
||||
status: 'active',
|
||||
foundedDate: { year: 2020, month: 1, day: 1 },
|
||||
registrationDetails: {
|
||||
vatId: 'DE123456789',
|
||||
registrationId: 'HRB 12345',
|
||||
registrationName: 'Commercial Register'
|
||||
}
|
||||
};
|
||||
|
||||
einvoice.to = {
|
||||
type: 'person',
|
||||
name: 'Test',
|
||||
surname: 'Customer',
|
||||
salutation: 'Mr' as const,
|
||||
sex: 'male' as const,
|
||||
title: 'Doctor' as const,
|
||||
description: 'Test customer',
|
||||
address: {
|
||||
streetName: 'Customer Street',
|
||||
houseNumber: '2',
|
||||
postalCode: '54321',
|
||||
city: 'Customer City',
|
||||
country: 'DE'
|
||||
}
|
||||
};
|
||||
|
||||
// Large notes array
|
||||
einvoice.notes = [
|
||||
largeDescription,
|
||||
'Additional note content for testing memory usage with multiple large fields.',
|
||||
'Third note to verify array handling in memory constrained environments.'
|
||||
];
|
||||
|
||||
einvoice.items = [{
|
||||
position: 1,
|
||||
name: largeDescription.substring(0, 100), // Truncated name
|
||||
articleNumber: 'LARGE-FIELD-001',
|
||||
unitType: 'EA',
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 100,
|
||||
vatPercentage: 19
|
||||
}];
|
||||
|
||||
const xmlString = await einvoice.toXmlString('cii');
|
||||
const newInvoice = new EInvoice();
|
||||
await newInvoice.fromXmlString(xmlString);
|
||||
|
||||
canProcess = newInvoice.from.description.length > 1000;
|
||||
fieldsHandled = true;
|
||||
|
||||
console.log(` Large description preserved: ${canProcess ? 'Yes' : 'No'}`);
|
||||
console.log(` Notes count preserved: ${newInvoice.notes?.length || 0}/3`);
|
||||
|
||||
} catch (error) {
|
||||
console.log(` Error occurred: ${error.message}`);
|
||||
fieldsHandled = !error.message.includes('FATAL');
|
||||
}
|
||||
|
||||
return { fieldsHandled, canProcess };
|
||||
};
|
||||
|
||||
// Test 3: Multiple concurrent processing
|
||||
const testConcurrentProcessing = async () => {
|
||||
console.log('\nTest 3 - Concurrent processing:');
|
||||
|
||||
let concurrentHandled = false;
|
||||
let allProcessed = false;
|
||||
|
||||
try {
|
||||
const promises = [];
|
||||
const invoiceCount = 5;
|
||||
|
||||
for (let i = 0; i < invoiceCount; i++) {
|
||||
const promise = (async () => {
|
||||
const einvoice = new EInvoice();
|
||||
einvoice.issueDate = new Date(2024, 0, 1);
|
||||
einvoice.invoiceId = `CONCURRENT-${i + 1}`;
|
||||
|
||||
einvoice.from = {
|
||||
type: 'company',
|
||||
name: `Company ${i + 1}`,
|
||||
description: 'Testing concurrent processing',
|
||||
address: {
|
||||
streetName: 'Test Street',
|
||||
houseNumber: String(i + 1),
|
||||
postalCode: '12345',
|
||||
city: 'Test City',
|
||||
country: 'DE'
|
||||
},
|
||||
status: 'active',
|
||||
foundedDate: { year: 2020, month: 1, day: 1 },
|
||||
registrationDetails: {
|
||||
vatId: 'DE123456789',
|
||||
registrationId: 'HRB 12345',
|
||||
registrationName: 'Commercial Register'
|
||||
}
|
||||
};
|
||||
|
||||
einvoice.to = {
|
||||
type: 'person',
|
||||
name: 'Test',
|
||||
surname: 'Customer',
|
||||
salutation: 'Mr' as const,
|
||||
sex: 'male' as const,
|
||||
title: 'Doctor' as const,
|
||||
description: 'Test customer',
|
||||
address: {
|
||||
streetName: 'Customer Street',
|
||||
houseNumber: '2',
|
||||
postalCode: '54321',
|
||||
city: 'Customer City',
|
||||
country: 'DE'
|
||||
}
|
||||
};
|
||||
|
||||
einvoice.items = Array.from({ length: 50 }, (_, j) => ({
|
||||
position: j + 1,
|
||||
name: `Item ${j + 1} for Invoice ${i + 1}`,
|
||||
articleNumber: `ART-${i + 1}-${j + 1}`,
|
||||
unitType: 'EA',
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 10 + j,
|
||||
vatPercentage: 19
|
||||
}));
|
||||
|
||||
const xml = await einvoice.toXmlString('ubl');
|
||||
return xml.includes(`CONCURRENT-${i + 1}`);
|
||||
})();
|
||||
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
allProcessed = results.every(result => result === true);
|
||||
concurrentHandled = true;
|
||||
|
||||
console.log(` Concurrent invoices processed: ${results.filter(r => r).length}/${invoiceCount}`);
|
||||
console.log(` All processed successfully: ${allProcessed ? 'Yes' : 'No'}`);
|
||||
|
||||
} catch (error) {
|
||||
console.log(` Error occurred: ${error.message}`);
|
||||
concurrentHandled = !error.message.includes('FATAL');
|
||||
}
|
||||
|
||||
return { concurrentHandled, allProcessed };
|
||||
};
|
||||
|
||||
// Test 4: Memory cleanup after errors
|
||||
const testMemoryCleanup = async () => {
|
||||
console.log('\nTest 4 - Memory cleanup after errors:');
|
||||
|
||||
let cleanupWorked = false;
|
||||
let canRecover = false;
|
||||
|
||||
try {
|
||||
// Get initial memory
|
||||
const memInitial = process.memoryUsage();
|
||||
|
||||
// Try to cause memory issues with invalid operations
|
||||
for (let i = 0; i < 10; i++) {
|
||||
try {
|
||||
const einvoice = new EInvoice();
|
||||
// Try invalid XML
|
||||
await einvoice.fromXmlString(`<?xml version="1.0"?><Invalid${i}>broken</Invalid${i}>`);
|
||||
} catch (error) {
|
||||
// Expected errors
|
||||
}
|
||||
}
|
||||
|
||||
// Force garbage collection if available
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
|
||||
// Check memory after cleanup
|
||||
const memAfterErrors = process.memoryUsage();
|
||||
const memoryGrowth = memAfterErrors.heapUsed - memInitial.heapUsed;
|
||||
|
||||
// Try normal operation after errors
|
||||
const einvoice = new EInvoice();
|
||||
einvoice.issueDate = new Date(2024, 0, 1);
|
||||
einvoice.invoiceId = 'CLEANUP-TEST';
|
||||
|
||||
einvoice.from = {
|
||||
type: 'company',
|
||||
name: 'Test Company',
|
||||
description: 'Testing memory cleanup',
|
||||
address: {
|
||||
streetName: 'Test Street',
|
||||
houseNumber: '1',
|
||||
@ -101,40 +350,50 @@ tap.test('ERR-05: Memory Errors - should handle memory constraints', async () =>
|
||||
|
||||
einvoice.items = [{
|
||||
position: 1,
|
||||
name: 'Test Product',
|
||||
articleNumber: 'TEST-001',
|
||||
name: 'Cleanup Test Item',
|
||||
articleNumber: 'CLEANUP-001',
|
||||
unitType: 'EA',
|
||||
unitQuantity: 1,
|
||||
unitNetPrice: 100,
|
||||
vatPercentage: 19
|
||||
}];
|
||||
|
||||
// Try to export after error
|
||||
let canRecover = false;
|
||||
try {
|
||||
const xml = await einvoice.toXmlString('ubl');
|
||||
canRecover = xml.includes('RECOVERY-TEST');
|
||||
} catch (error) {
|
||||
canRecover = false;
|
||||
}
|
||||
const xml = await einvoice.toXmlString('ubl');
|
||||
canRecover = xml.includes('CLEANUP-TEST');
|
||||
|
||||
return { success: canRecover };
|
||||
cleanupWorked = memoryGrowth < 50 * 1024 * 1024; // Less than 50MB growth
|
||||
|
||||
console.log(` Memory growth after errors: ${Math.round(memoryGrowth / 1024 / 1024)}MB`);
|
||||
console.log(` Memory cleanup effective: ${cleanupWorked ? 'Yes' : 'No'}`);
|
||||
console.log(` Recovery after errors: ${canRecover ? 'Yes' : 'No'}`);
|
||||
|
||||
} catch (error) {
|
||||
console.log(` Error occurred: ${error.message}`);
|
||||
cleanupWorked = false;
|
||||
}
|
||||
);
|
||||
|
||||
return { cleanupWorked, canRecover };
|
||||
};
|
||||
|
||||
// Run all tests
|
||||
const result1 = await testLargeInvoiceLineItems();
|
||||
const result2 = await testLargeFieldContent();
|
||||
const result3 = await testConcurrentProcessing();
|
||||
const result4 = await testMemoryCleanup();
|
||||
|
||||
console.log('\n=== Memory Error Handling Summary ===');
|
||||
console.log(`Large invoice processing: ${result1.canProcess ? 'Working' : 'Failed'}`);
|
||||
console.log(`Large field handling: ${result2.canProcess ? 'Working' : 'Failed'}`);
|
||||
console.log(`Concurrent processing: ${result3.allProcessed ? 'Working' : 'Failed'}`);
|
||||
console.log(`Memory cleanup: ${result4.cleanupWorked ? 'Effective' : 'Needs improvement'}`);
|
||||
console.log(`Recovery capability: ${result4.canRecover ? 'Working' : 'Failed'}`);
|
||||
|
||||
// Test passes if basic memory handling works
|
||||
const largeDataHandling = result1.canProcess || result2.canProcess;
|
||||
const memoryManagement = result1.memoryHandled && result4.cleanupWorked;
|
||||
|
||||
console.log(` Recovery test completed in ${recoveryMetric.duration}ms`);
|
||||
console.log(` Can recover after error: ${recoveryResult.success}`);
|
||||
|
||||
// Summary
|
||||
console.log('\n=== Memory Errors Error Handling Summary ===');
|
||||
console.log(`Error Detection: ${basicResult.success ? 'Working' : 'Failed'}`);
|
||||
console.log(`Graceful Handling: ${basicResult.gracefulHandling ? 'Yes' : 'No'}`);
|
||||
console.log(`Recovery: ${recoveryResult.success ? 'Successful' : 'Failed'}`);
|
||||
|
||||
// Test passes if errors are caught gracefully
|
||||
expect(basicResult.success).toBeTrue();
|
||||
expect(recoveryResult.success).toBeTrue();
|
||||
expect(largeDataHandling).toBeTrue(); // Must handle large invoices or large fields
|
||||
expect(memoryManagement).toBeTrue(); // Must manage memory efficiently
|
||||
});
|
||||
|
||||
// Run the test
|
||||
tap.start();
|
||||
tap.start();
|
Reference in New Issue
Block a user