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(`broken`); } 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();