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-06: Circular References'); tap.test('EDGE-06: Circular References - should handle circular reference scenarios', async (t) => { const einvoice = new EInvoice(); // Test 1: ID reference cycles in XML const idReferenceCycles = await performanceTracker.measureAsync( 'id-reference-cycles', async () => { const circularXML = ` INV-001 100 200 300 `; try { const parsed = await einvoice.parseXML(circularXML); // Try to resolve references const resolved = await einvoice.resolveReferences(parsed, { maxDepth: 10, detectCycles: true }); return { parsed: true, hasCircularRefs: resolved?.hasCircularReferences || false, cyclesDetected: resolved?.detectedCycles || [], resolutionStopped: resolved?.stoppedAtDepth || false }; } catch (error) { return { parsed: false, error: error.message, cycleError: error.message.includes('circular') || error.message.includes('cycle') }; } } ); t.ok(idReferenceCycles.parsed || idReferenceCycles.cycleError, 'Circular ID references were handled'); // Test 2: Entity reference loops const entityReferenceLoops = await performanceTracker.measureAsync( 'entity-reference-loops', async () => { const loopingEntities = [ { name: 'direct-loop', xml: ` ]> &a; ` }, { name: 'indirect-loop', xml: ` ]> &a; ` }, { name: 'self-reference', xml: ` ]> &recursive; ` } ]; const results = []; for (const test of loopingEntities) { try { await einvoice.parseXML(test.xml); results.push({ type: test.name, handled: true, method: 'parsed-without-expansion' }); } catch (error) { results.push({ type: test.name, handled: true, method: 'rejected', error: error.message }); } } return results; } ); entityReferenceLoops.forEach(result => { t.ok(result.handled, `Entity loop ${result.type} was handled`); }); // Test 3: Schema import cycles const schemaImportCycles = await performanceTracker.measureAsync( 'schema-import-cycles', async () => { // Simulate schemas that import each other const schemas = { 'schema1.xsd': ` `, 'schema2.xsd': ` `, 'schema3.xsd': ` ` }; try { const validation = await einvoice.validateWithSchemas(schemas, { maxImportDepth: 10, detectImportCycles: true }); return { handled: true, cycleDetected: validation?.importCycleDetected || false, importChain: validation?.importChain || [] }; } catch (error) { return { handled: true, error: error.message, isCycleError: error.message.includes('import') && error.message.includes('cycle') }; } } ); t.ok(schemaImportCycles.handled, 'Schema import cycles were handled'); // Test 4: Object graph cycles in parsed data const objectGraphCycles = await performanceTracker.measureAsync( 'object-graph-cycles', async () => { // Create invoice with potential object cycles const invoice = { id: 'INV-001', items: [], parent: null }; const item1 = { id: 'ITEM-001', invoice: invoice, relatedItems: [] }; const item2 = { id: 'ITEM-002', invoice: invoice, relatedItems: [item1] }; // Create circular reference item1.relatedItems.push(item2); invoice.items.push(item1, item2); invoice.parent = invoice; // Self-reference try { // Try to serialize/process the circular structure const result = await einvoice.processInvoiceObject(invoice, { detectCycles: true, maxTraversalDepth: 100 }); return { handled: true, cyclesDetected: result?.cyclesFound || false, serializable: result?.canSerialize || false, method: result?.handlingMethod }; } catch (error) { return { handled: false, error: error.message, isCircularError: error.message.includes('circular') || error.message.includes('Converting circular structure') }; } } ); t.ok(objectGraphCycles.handled || objectGraphCycles.isCircularError, 'Object graph cycles were handled'); // Test 5: Namespace circular dependencies const namespaceCirularDeps = await performanceTracker.measureAsync( 'namespace-circular-dependencies', async () => { const circularNamespaceXML = ` `; try { const parsed = await einvoice.parseXML(circularNamespaceXML); const analysis = await einvoice.analyzeNamespaceUsage(parsed); return { parsed: true, namespaceCount: analysis?.namespaces?.length || 0, hasCrossReferences: analysis?.hasCrossNamespaceRefs || false, complexityScore: analysis?.complexityScore || 0 }; } catch (error) { return { parsed: false, error: error.message }; } } ); t.ok(namespaceCirularDeps.parsed || namespaceCirularDeps.error, 'Namespace circular dependencies were processed'); // Test 6: Include/Import cycles in documents const includeImportCycles = await performanceTracker.measureAsync( 'include-import-cycles', async () => { const documents = { 'main.xml': ` `, 'part1.xml': ` `, 'part2.xml': ` ` }; try { const result = await einvoice.processWithIncludes(documents['main.xml'], { resolveIncludes: true, maxIncludeDepth: 10, includeMap: documents }); return { processed: true, includeDepthReached: result?.maxDepthReached || false, cycleDetected: result?.includeCycleDetected || false }; } catch (error) { return { processed: false, error: error.message, isIncludeError: error.message.includes('include') || error.message.includes('XInclude') }; } } ); t.ok(includeImportCycles.processed || includeImportCycles.isIncludeError, 'Include cycles were handled'); // Test 7: Circular parent-child relationships const parentChildCircular = await performanceTracker.measureAsync( 'parent-child-circular', async () => { // Test various parent-child circular scenarios const scenarios = [ { name: 'self-parent', xml: `001` }, { name: 'mutual-parents', xml: ` 001 002 ` }, { name: 'chain-loop', xml: ` A B C ` } ]; const results = []; for (const scenario of scenarios) { try { const parsed = await einvoice.parseXML(scenario.xml); const hierarchy = await einvoice.buildHierarchy(parsed, { detectCircular: true }); results.push({ scenario: scenario.name, handled: true, isCircular: hierarchy?.hasCircularParentage || false, maxDepth: hierarchy?.maxDepth || 0 }); } catch (error) { results.push({ scenario: scenario.name, handled: false, error: error.message }); } } return results; } ); parentChildCircular.forEach(result => { t.ok(result.handled || result.error, `Parent-child circular scenario ${result.scenario} was processed`); }); // Test 8: Circular calculations const circularCalculations = await performanceTracker.measureAsync( 'circular-calculations', async () => { const calculationXML = ` `; try { const parsed = await einvoice.parseXML(calculationXML); const calculated = await einvoice.evaluateCalculations(parsed, { maxIterations: 10, detectCircular: true }); return { evaluated: true, hasCircularDependency: calculated?.circularDependency || false, resolvedValues: calculated?.resolved || {}, iterations: calculated?.iterationsUsed || 0 }; } catch (error) { return { evaluated: false, error: error.message, isCircularCalc: error.message.includes('circular') && error.message.includes('calculation') }; } } ); t.ok(circularCalculations.evaluated || circularCalculations.isCircularCalc, 'Circular calculations were handled'); // Test 9: Memory safety with circular structures const memorySafetyCircular = await performanceTracker.measureAsync( 'memory-safety-circular', async () => { const startMemory = process.memoryUsage(); // Create a deeply circular structure const createCircularChain = (depth: number) => { const nodes = []; for (let i = 0; i < depth; i++) { nodes.push({ id: i, next: null, data: 'X'.repeat(1000) }); } // Link them circularly for (let i = 0; i < depth; i++) { nodes[i].next = nodes[(i + 1) % depth]; } return nodes[0]; }; const results = { smallCircle: false, mediumCircle: false, largeCircle: false, memoryStable: true }; try { // Test increasingly large circular structures const small = createCircularChain(10); await einvoice.processCircularStructure(small); results.smallCircle = true; const medium = createCircularChain(100); await einvoice.processCircularStructure(medium); results.mediumCircle = true; const large = createCircularChain(1000); await einvoice.processCircularStructure(large); results.largeCircle = true; const endMemory = process.memoryUsage(); const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed; results.memoryStable = memoryIncrease < 100 * 1024 * 1024; // Less than 100MB } catch (error) { // Expected for very large structures } return results; } ); t.ok(memorySafetyCircular.smallCircle, 'Small circular structures handled safely'); t.ok(memorySafetyCircular.memoryStable, 'Memory usage remained stable'); // Test 10: Format conversion with circular references const formatConversionCircular = await performanceTracker.measureAsync( 'format-conversion-circular', async () => { // Create UBL invoice with circular references const ublWithCircular = ` CIRC-001 CIRC-001 ORDER-001 CIRC-001 `; try { // Convert to CII const converted = await einvoice.convertFormat(ublWithCircular, 'cii', { handleCircularRefs: true, maxRefDepth: 5 }); // Check if circular refs were handled const analysis = await einvoice.analyzeReferences(converted); return { converted: true, circularRefsPreserved: analysis?.hasCircularRefs || false, refsFlattened: analysis?.refsFlattened || false, conversionMethod: analysis?.method }; } catch (error) { return { converted: false, error: error.message }; } } ); t.ok(formatConversionCircular.converted || formatConversionCircular.error, 'Format conversion with circular refs was handled'); // Print performance summary performanceTracker.printSummary(); }); // Run the test tap.start();