import { tap } from '@git.zone/tstest/tapbundle'; import * as plugins from '../plugins.js'; import { EInvoice } from '../../../ts/index.js'; import { PerformanceTracker } from '../performance.tracker.js'; const performanceTracker = new PerformanceTracker('EDGE-03: Deeply Nested XML Structures'); tap.test('EDGE-03: Deeply Nested XML Structures - should handle extremely nested XML', async (t) => { const einvoice = new EInvoice(); // Test 1: Linear deep nesting const linearDeepNesting = await performanceTracker.measureAsync( 'linear-deep-nesting', async () => { const testDepths = [10, 100, 1000, 5000, 10000]; const results = []; for (const depth of testDepths) { let xml = '\n'; // Build deeply nested structure for (let i = 0; i < depth; i++) { xml += ' '.repeat(i) + `\n`; } xml += ' '.repeat(depth) + 'Invoice Data\n'; // Close all tags for (let i = depth - 1; i >= 0; i--) { xml += ' '.repeat(i) + `\n`; } const startTime = Date.now(); const startMemory = process.memoryUsage(); try { const result = await einvoice.parseXML(xml); const endTime = Date.now(); const endMemory = process.memoryUsage(); results.push({ depth, success: true, timeTaken: endTime - startTime, memoryUsed: endMemory.heapUsed - startMemory.heapUsed, hasData: !!result }); } catch (error) { results.push({ depth, success: false, error: error.message, isStackOverflow: error.message.includes('stack') || error.message.includes('depth') }); } } return results; } ); linearDeepNesting.forEach(result => { if (result.depth <= 1000) { t.ok(result.success, `Depth ${result.depth} should be handled`); } else { t.ok(!result.success || result.isStackOverflow, `Extreme depth ${result.depth} should be limited`); } }); // Test 2: Recursive element nesting const recursiveElementNesting = await performanceTracker.measureAsync( 'recursive-element-nesting', async () => { const createRecursiveStructure = (depth: number): string => { if (depth === 0) { return '100.00'; } return ` ITEM-${depth} ${createRecursiveStructure(depth - 1)} `; }; const testDepths = [5, 10, 20, 50]; const results = []; for (const depth of testDepths) { const xml = ` RECURSIVE-001 ${createRecursiveStructure(depth)} `; try { const startTime = Date.now(); const parsed = await einvoice.parseXML(xml); const endTime = Date.now(); // Count actual depth let actualDepth = 0; let current = parsed; while (current?.Items || current?.SubItems) { actualDepth++; current = current.Items || current.SubItems; } results.push({ requestedDepth: depth, actualDepth, success: true, timeTaken: endTime - startTime }); } catch (error) { results.push({ requestedDepth: depth, success: false, error: error.message }); } } return results; } ); recursiveElementNesting.forEach(result => { t.ok(result.success || result.error, `Recursive depth ${result.requestedDepth} was processed`); }); // Test 3: Namespace nesting complexity const namespaceNesting = await performanceTracker.measureAsync( 'namespace-nesting-complexity', async () => { const createNamespaceNesting = (depth: number): string => { let xml = '\n'; // Create nested elements with different namespaces for (let i = 0; i < depth; i++) { xml += ' '.repeat(i) + `\n`; } xml += ' '.repeat(depth) + 'Content\n'; // Close all namespace elements for (let i = depth - 1; i >= 0; i--) { xml += ' '.repeat(i) + `\n`; } return xml; }; const testDepths = [5, 10, 25, 50, 100]; const results = []; for (const depth of testDepths) { const xml = createNamespaceNesting(depth); try { const startTime = Date.now(); const parsed = await einvoice.parseXML(xml); const endTime = Date.now(); results.push({ depth, success: true, timeTaken: endTime - startTime, namespacesPreserved: true // Check if namespaces were preserved }); } catch (error) { results.push({ depth, success: false, error: error.message }); } } return results; } ); namespaceNesting.forEach(result => { if (result.depth <= 50) { t.ok(result.success, `Namespace depth ${result.depth} should be handled`); } }); // Test 4: Mixed content deep nesting const mixedContentNesting = await performanceTracker.measureAsync( 'mixed-content-deep-nesting', async () => { const createMixedNesting = (depth: number): string => { let xml = ''; for (let i = 0; i < depth; i++) { xml += `Text before `; } xml += 'Core Value'; for (let i = depth - 1; i >= 0; i--) { xml += ` text after`; } return xml; }; const testCases = [10, 50, 100, 500]; const results = []; for (const depth of testCases) { const xml = ` ${createMixedNesting(depth)} `; try { const parsed = await einvoice.parseXML(xml); results.push({ depth, success: true, hasMixedContent: true }); } catch (error) { results.push({ depth, success: false, error: error.message }); } } return results; } ); mixedContentNesting.forEach(result => { t.ok(result.success || result.error, `Mixed content depth ${result.depth} was handled`); }); // Test 5: Attribute-heavy deep nesting const attributeHeavyNesting = await performanceTracker.measureAsync( 'attribute-heavy-nesting', async () => { const createAttributeNesting = (depth: number, attrsPerLevel: number): string => { let xml = ''; for (let i = 0; i < depth; i++) { xml += `= 0; i--) { xml += ``; } return xml; }; const testCases = [ { depth: 10, attrs: 10 }, { depth: 50, attrs: 5 }, { depth: 100, attrs: 3 }, { depth: 500, attrs: 1 } ]; const results = []; for (const test of testCases) { const xml = ` ${createAttributeNesting(test.depth, test.attrs)} `; const startTime = Date.now(); const startMemory = process.memoryUsage(); try { await einvoice.parseXML(xml); const endTime = Date.now(); const endMemory = process.memoryUsage(); results.push({ depth: test.depth, attributesPerLevel: test.attrs, totalAttributes: test.depth * test.attrs, success: true, timeTaken: endTime - startTime, memoryUsed: endMemory.heapUsed - startMemory.heapUsed }); } catch (error) { results.push({ depth: test.depth, attributesPerLevel: test.attrs, success: false, error: error.message }); } } return results; } ); attributeHeavyNesting.forEach(result => { t.ok(result.success || result.error, `Attribute-heavy nesting (depth: ${result.depth}, attrs: ${result.attributesPerLevel}) was processed`); }); // Test 6: CDATA section nesting const cdataNesting = await performanceTracker.measureAsync( 'cdata-section-nesting', async () => { const depths = [5, 10, 20, 50]; const results = []; for (const depth of depths) { let xml = ''; // Create nested elements with CDATA for (let i = 0; i < depth; i++) { xml += ` characters & symbols]]>`; } // Close all elements for (let i = depth - 1; i >= 0; i--) { xml += ``; } xml += ''; try { const parsed = await einvoice.parseXML(xml); results.push({ depth, success: true, cdataPreserved: true }); } catch (error) { results.push({ depth, success: false, error: error.message }); } } return results; } ); cdataNesting.forEach(result => { t.ok(result.success, `CDATA nesting depth ${result.depth} should be handled`); }); // Test 7: Processing instruction nesting const processingInstructionNesting = await performanceTracker.measureAsync( 'processing-instruction-nesting', async () => { const createPINesting = (depth: number): string => { let xml = '\n'; for (let i = 0; i < depth; i++) { xml += `\n`; xml += `\n`; } xml += 'Content\n'; for (let i = depth - 1; i >= 0; i--) { xml += `\n`; } return xml; }; const depths = [10, 25, 50]; const results = []; for (const depth of depths) { const xml = createPINesting(depth); try { const parsed = await einvoice.parseXML(xml); results.push({ depth, success: true, processingInstructionsHandled: true }); } catch (error) { results.push({ depth, success: false, error: error.message }); } } return results; } ); processingInstructionNesting.forEach(result => { t.ok(result.success, `PI nesting depth ${result.depth} should be handled`); }); // Test 8: Real invoice format deep structures const realFormatDeepStructures = await performanceTracker.measureAsync( 'real-format-deep-structures', async () => { const formats = ['ubl', 'cii']; const results = []; for (const format of formats) { // Create deeply nested invoice structure let invoice; if (format === 'ubl') { invoice = ` DEEP-UBL-001 Deeply nested note Deep item property `; } else { invoice = ` DEEP-CII-001 Deep CII structure `; } try { const parsed = await einvoice.parseDocument(invoice); const validated = await einvoice.validate(parsed); results.push({ format, parsed: true, valid: validated?.isValid || false, deepStructureSupported: true }); } catch (error) { results.push({ format, parsed: false, error: error.message }); } } return results; } ); realFormatDeepStructures.forEach(result => { t.ok(result.parsed, `${result.format} deep structure should be parsed`); }); // Test 9: Stack overflow protection const stackOverflowProtection = await performanceTracker.measureAsync( 'stack-overflow-protection', async () => { const extremeDepths = [10000, 50000, 100000]; const results = []; for (const depth of extremeDepths) { // Create extremely deep structure efficiently const parts = []; parts.push(''); // Opening tags for (let i = 0; i < Math.min(depth, 1000); i++) { parts.push(``); } parts.push('Test'); // Closing tags for (let i = Math.min(depth - 1, 999); i >= 0; i--) { parts.push(``); } const xml = parts.join(''); const startTime = Date.now(); try { await einvoice.parseXML(xml, { maxDepth: 1000 }); const endTime = Date.now(); results.push({ depth, protected: true, method: 'depth-limit', timeTaken: endTime - startTime }); } catch (error) { const endTime = Date.now(); results.push({ depth, protected: true, method: error.message.includes('depth') ? 'depth-check' : 'stack-guard', timeTaken: endTime - startTime, error: error.message }); } } return results; } ); stackOverflowProtection.forEach(result => { t.ok(result.protected, `Stack overflow protection active for depth ${result.depth}`); }); // Test 10: Performance impact of nesting const nestingPerformanceImpact = await performanceTracker.measureAsync( 'nesting-performance-impact', async () => { const depths = [1, 10, 50, 100, 500, 1000]; const results = []; for (const depth of depths) { // Create invoice with specific nesting depth let xml = ''; // Create structure at depth let current = xml; for (let i = 0; i < depth; i++) { current += ``; } current += 'TEST100'; for (let i = depth - 1; i >= 0; i--) { current += ``; } current += ''; // Measure parsing time const iterations = 10; const times = []; for (let i = 0; i < iterations; i++) { const startTime = process.hrtime.bigint(); try { await einvoice.parseXML(current); } catch (error) { // Ignore errors for performance testing } const endTime = process.hrtime.bigint(); times.push(Number(endTime - startTime) / 1000000); // Convert to ms } const avgTime = times.reduce((a, b) => a + b, 0) / times.length; const minTime = Math.min(...times); const maxTime = Math.max(...times); results.push({ depth, avgTime, minTime, maxTime, complexity: avgTime / depth // Time per nesting level }); } return results; } ); // Verify performance doesn't degrade exponentially const complexities = nestingPerformanceImpact.map(r => r.complexity); const avgComplexity = complexities.reduce((a, b) => a + b, 0) / complexities.length; nestingPerformanceImpact.forEach(result => { t.ok(result.complexity < avgComplexity * 10, `Nesting depth ${result.depth} has reasonable performance`); }); // Print performance summary performanceTracker.printSummary(); }); // Run the test tap.start();