einvoice/test/suite/einvoice_error-handling/test.err-05.memory-errors.ts

399 lines
13 KiB
TypeScript

import { expect, tap } from '@git.zone/tstest/tapbundle';
import { EInvoice } from '../../../ts/index.js';
tap.test('ERR-05: Memory Errors - should handle memory constraints', 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';
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
});
}
// 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: 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',
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 = [{
position: 1,
name: 'Cleanup Test Item',
articleNumber: 'CLEANUP-001',
unitType: 'EA',
unitQuantity: 1,
unitNetPrice: 100,
vatPercentage: 19
}];
const xml = await einvoice.toXmlString('ubl');
canRecover = xml.includes('CLEANUP-TEST');
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;
expect(largeDataHandling).toBeTrue(); // Must handle large invoices or large fields
expect(memoryManagement).toBeTrue(); // Must manage memory efficiently
});
tap.start();