import { expect, tap } from '@git.zone/tstest/tapbundle'; import { EInvoice } from '../../../ts/index.js'; tap.test('EDGE-06: Circular References - should handle circular reference scenarios', async () => { console.log('Testing circular reference handling in e-invoices...\n'); // Test 1: Self-referencing invoice documents const testSelfReferencingInvoice = async () => { try { const einvoice = new EInvoice(); einvoice.issueDate = new Date(2024, 0, 1); einvoice.id = 'CIRC-001'; // Set up basic invoice data for EN16931 compliance einvoice.from = { type: 'company', name: 'Circular Test Company', description: 'Testing circular references', 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: 'company', name: 'Customer Company', description: 'Customer', address: { streetName: 'Customer Street', houseNumber: '2', postalCode: '54321', city: 'Customer City', country: 'DE' }, status: 'active', foundedDate: { year: 2019, month: 1, day: 1 }, registrationDetails: { vatId: 'DE987654321', registrationId: 'HRB 54321', registrationName: 'Commercial Register' } }; einvoice.items = [{ position: 1, name: 'Test Product', articleNumber: 'CIRC-001', unitType: 'EA', unitQuantity: 1, unitNetPrice: 100, vatPercentage: 19 }]; // Try to create XML - should not cause infinite loops const xmlString = await einvoice.toXmlString('ubl'); // Test round-trip const newInvoice = new EInvoice(); await newInvoice.fromXmlString(xmlString); const roundTripSuccess = (newInvoice.id === 'CIRC-001' || newInvoice.invoiceId === 'CIRC-001' || newInvoice.accountingDocId === 'CIRC-001'); console.log('Test 1 - Self-referencing invoice:'); console.log(` XML generation successful: Yes`); console.log(` Round-trip successful: ${roundTripSuccess ? 'Yes' : 'No'}`); console.log(` No infinite loops detected: Yes`); return { success: true, roundTrip: roundTripSuccess }; } catch (error) { console.log('Test 1 - Self-referencing invoice:'); console.log(` Error: ${error.message}`); return { success: false, roundTrip: false, error: error.message }; } }; // Test 2: XML with circular element references const testXmlCircularReferences = async () => { // Create XML with potential circular references const circularXml = ` 2.1 CIRCULAR-REF-TEST 2024-01-01 EUR CIRCULAR-REF-TEST 380 Circular Test Company Test Street 1 12345 Test City DE Customer Company Customer Street 2 54321 Customer City DE 1 1 100.00 Test Product 100.00 `; try { const einvoice = new EInvoice(); await einvoice.fromXmlString(circularXml); // Try to export back to XML await einvoice.toXmlString('ubl'); console.log('\nTest 2 - XML circular references:'); console.log(` Circular XML parsed: Yes`); console.log(` Re-export successful: Yes`); console.log(` No infinite loops in parsing: Yes`); return { parsed: true, exported: true }; } catch (error) { console.log('\nTest 2 - XML circular references:'); console.log(` Error: ${error.message}`); return { parsed: false, exported: false, error: error.message }; } }; // Test 3: Deep object nesting that could cause stack overflow const testDeepObjectNesting = async () => { try { const einvoice = new EInvoice(); einvoice.id = 'DEEP-NEST-TEST'; einvoice.issueDate = new Date(2024, 0, 1); // Create deeply nested structure einvoice.from = { type: 'company', name: 'Deep Nesting Company', description: 'Testing deep object nesting', 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' } }; // Create many items to test deep arrays einvoice.items = []; for (let i = 1; i <= 100; i++) { einvoice.items.push({ position: i, name: `Product ${i}`, articleNumber: `DEEP-${i.toString().padStart(3, '0')}`, unitType: 'EA', unitQuantity: 1, unitNetPrice: 10 + (i % 10), vatPercentage: 19 }); } // Test XML generation with deep structure const xmlString = await einvoice.toXmlString('ubl'); // Test parsing back const newInvoice = new EInvoice(); await newInvoice.fromXmlString(xmlString); const itemsMatch = newInvoice.items?.length === 100; console.log('\nTest 3 - Deep object nesting:'); console.log(` Deep structure generated: Yes`); console.log(` XML parsing successful: Yes`); console.log(` Items preserved: ${itemsMatch ? 'Yes' : 'No'} (${newInvoice.items?.length || 0}/100)`); console.log(` No stack overflow: Yes`); return { generated: true, parsed: true, itemsMatch }; } catch (error) { console.log('\nTest 3 - Deep object nesting:'); console.log(` Error: ${error.message}`); return { generated: false, parsed: false, itemsMatch: false, error: error.message }; } }; // Test 4: JSON circular reference detection const testJsonCircularReferences = async () => { try { const einvoice = new EInvoice(); einvoice.id = 'JSON-CIRC-TEST'; einvoice.issueDate = new Date(2024, 0, 1); einvoice.from = { type: 'company', name: 'JSON Test Company', description: 'Testing JSON circular references', 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: 'JSON Test Product', articleNumber: 'JSON-001', unitType: 'EA', unitQuantity: 1, unitNetPrice: 100, vatPercentage: 19 }]; // Test JSON stringification (should not cause circular reference errors) const jsonString = JSON.stringify(einvoice); const parsedBack = JSON.parse(jsonString); console.log('\nTest 4 - JSON circular references:'); console.log(` JSON stringify successful: Yes`); console.log(` JSON parse successful: Yes`); console.log(` Object structure preserved: ${parsedBack.id === 'JSON-CIRC-TEST' ? 'Yes' : 'No'}`); return { stringified: true, parsed: true, preserved: parsedBack.id === 'JSON-CIRC-TEST' }; } catch (error) { console.log('\nTest 4 - JSON circular references:'); console.log(` Error: ${error.message}`); return { stringified: false, parsed: false, preserved: false, error: error.message }; } }; // Test 5: Format conversion with potential circular references const testFormatConversionCircular = async () => { try { const einvoice = new EInvoice(); einvoice.id = 'FORMAT-CIRC-TEST'; einvoice.issueDate = new Date(2024, 0, 1); einvoice.from = { type: 'company', name: 'Format Test Company', description: 'Testing format conversion circular references', 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: 'Format Test Product', articleNumber: 'FORMAT-001', unitType: 'EA', unitQuantity: 1, unitNetPrice: 100, vatPercentage: 19 }]; // Test conversion between formats (UBL -> CII -> UBL) const ublXml = await einvoice.toXmlString('ubl'); const ublInvoice = new EInvoice(); await ublInvoice.fromXmlString(ublXml); const ciiXml = await ublInvoice.toXmlString('cii'); const ciiInvoice = new EInvoice(); await ciiInvoice.fromXmlString(ciiXml); const finalUblXml = await ciiInvoice.toXmlString('ubl'); const finalInvoice = new EInvoice(); await finalInvoice.fromXmlString(finalUblXml); const idPreserved = (finalInvoice.id === 'FORMAT-CIRC-TEST' || finalInvoice.invoiceId === 'FORMAT-CIRC-TEST' || finalInvoice.accountingDocId === 'FORMAT-CIRC-TEST'); console.log('\nTest 5 - Format conversion circular:'); console.log(` UBL generation: Yes`); console.log(` UBL->CII conversion: Yes`); console.log(` CII->UBL conversion: Yes`); console.log(` ID preserved through conversions: ${idPreserved ? 'Yes' : 'No'}`); console.log(` No infinite loops in conversion: Yes`); return { ublGenerated: true, ciiConverted: true, backConverted: true, idPreserved }; } catch (error) { console.log('\nTest 5 - Format conversion circular:'); console.log(` Error: ${error.message}`); return { ublGenerated: false, ciiConverted: false, backConverted: false, idPreserved: false, error: error.message }; } }; // Run all tests const selfRefResult = await testSelfReferencingInvoice(); const xmlCircularResult = await testXmlCircularReferences(); const deepNestingResult = await testDeepObjectNesting(); const jsonCircularResult = await testJsonCircularReferences(); const formatConversionResult = await testFormatConversionCircular(); console.log(`\n=== Circular References Test Summary ===`); console.log(`Self-referencing invoice: ${selfRefResult.success ? 'Working' : 'Issues'}`); console.log(`XML circular references: ${xmlCircularResult.parsed ? 'Working' : 'Issues'}`); console.log(`Deep object nesting: ${deepNestingResult.generated && deepNestingResult.parsed ? 'Working' : 'Issues'}`); console.log(`JSON circular detection: ${jsonCircularResult.stringified && jsonCircularResult.parsed ? 'Working' : 'Issues'}`); console.log(`Format conversion: ${formatConversionResult.ublGenerated && formatConversionResult.backConverted ? 'Working' : 'Issues'}`); // Test passes if basic operations work without infinite loops expect(selfRefResult.success).toBeTrue(); expect(jsonCircularResult.stringified && jsonCircularResult.parsed).toBeTrue(); expect(deepNestingResult.generated && deepNestingResult.parsed).toBeTrue(); }); // Run the test tap.start();