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();