399 lines
13 KiB
TypeScript
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(); |