einvoice/test/suite/einvoice_edge-cases/test.edge-03.deep-nesting.ts

294 lines
11 KiB
TypeScript

import { tap, expect } from '@git.zone/tstest/tapbundle';
import { EInvoice } from '../../../ts/index.js';
import { PerformanceTracker } from '../../helpers/performance.tracker.js';
import { ValidationLevel } from '../../../ts/interfaces/common.js';
tap.test('EDGE-03: Deeply Nested XML Structures - should handle extremely nested XML', async () => {
console.log('Testing deeply nested XML structures...');
// Test 1: Invoice with deeply nested item structure
console.log('\nTest 1: Creating invoice with deeply nested item names');
const { result: deeplyNestedResult, metric: deeplyNestedMetric } = await PerformanceTracker.track(
'deeply-nested-items',
async () => {
const einvoice = new EInvoice();
// Set basic invoice data
einvoice.id = 'NESTED-001';
einvoice.issueDate = new Date('2024-01-01');
einvoice.currency = 'EUR';
// Set supplier with nested address structure
einvoice.from = {
type: 'company',
name: 'Deep Nesting Test GmbH - Company with Complex Structure and Subsidiaries',
description: 'Main company > Division A > Department X > Team Alpha > Project Nested',
address: {
streetName: 'Very Long Street Name with Multiple Parts and Building Complex A Wing B Floor 3',
houseNumber: '123A-B-C',
postalCode: '12345',
city: 'City Name with District > Subdistrict > Neighborhood > Block',
country: 'DE'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'DE123456789',
registrationId: 'HRB 12345 / SubReg 67890 / Dept ABC',
registrationName: 'Berlin Registry > Commercial Court > Division B'
}
};
// Set customer with nested structure
einvoice.to = {
type: 'company',
name: 'Customer Corporation > European Division > German Branch > Berlin Office',
description: 'Subsidiary of Parent > Holding > Group > Corporation > Conglomerate',
address: {
streetName: 'Customer Avenue Section A Subsection B Part C',
houseNumber: '456-X-Y-Z',
postalCode: '54321',
city: 'Munich > Central District > Business Quarter > Tech Park',
country: 'DE'
},
status: 'active',
foundedDate: { year: 2018, month: 6, day: 15 },
registrationDetails: {
vatId: 'DE987654321',
registrationId: 'HRB 54321 > SubID 09876',
registrationName: 'Munich Registry > Division C > Subdiv 3'
}
};
// Create items with deeply nested descriptions in their names
einvoice.items = [];
const nestingLevels = 5;
for (let i = 0; i < nestingLevels; i++) {
let itemName = 'Product';
for (let j = 0; j <= i; j++) {
itemName += ` > Level ${j + 1}`;
if (j === i) {
itemName += ` > Category ${String.fromCharCode(65 + j)} > Subcategory ${j + 1} > Type ${j * 10 + 1}`;
}
}
einvoice.items.push({
position: i + 1,
name: itemName + ' > Final Product Description with Technical Specifications > Version 1.0 > Revision 3',
articleNumber: `NESTED-${i + 1}-${String.fromCharCode(65 + i)}-${(i + 1) * 100}`,
unitType: 'EA',
unitQuantity: (i + 1) * 2,
unitNetPrice: 100 + (i * 50),
vatPercentage: 19
});
}
// Test XML generation with nested structure
const xmlString = await einvoice.toXmlString('ubl');
// Test parsing back
const parsedInvoice = new EInvoice();
await parsedInvoice.fromXmlString(xmlString);
// Test validation
const validationResult = await parsedInvoice.validate(ValidationLevel.SYNTAX);
return {
itemCount: einvoice.items.length,
xmlSize: Buffer.byteLength(xmlString, 'utf8'),
deepestItemNameLength: Math.max(...einvoice.items.map(item => item.name.length)),
preservedItems: parsedInvoice.items?.length || 0,
validationResult,
xmlNestingDepth: (xmlString.match(/>/g) || []).length
};
}
);
console.log(` Created ${deeplyNestedResult.itemCount} items with nested structures`);
console.log(` XML size: ${(deeplyNestedResult.xmlSize / 1024).toFixed(2)} KB`);
console.log(` Deepest item name: ${deeplyNestedResult.deepestItemNameLength} chars`);
console.log(` XML nesting depth: ${deeplyNestedResult.xmlNestingDepth} tags`);
console.log(` Processing time: ${deeplyNestedMetric.duration}ms`);
expect(deeplyNestedResult.itemCount).toEqual(5);
expect(deeplyNestedResult.preservedItems).toEqual(5);
expect(deeplyNestedResult.validationResult.valid).toBeTrue();
// Test 2: Invoice with deeply nested XML namespace structure
console.log('\nTest 2: Testing XML with multiple namespace levels');
const { result: namespaceResult, metric: namespaceMetric } = await PerformanceTracker.track(
'namespace-nesting',
async () => {
// Create a complex CII XML with multiple namespaces
const complexXml = `<?xml version="1.0" encoding="UTF-8"?>
<rsm:CrossIndustryInvoice
xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<rsm:ExchangedDocumentContext>
<ram:GuidelineSpecifiedDocumentContextParameter>
<ram:ID>urn:cen.eu:en16931:2017</ram:ID>
</ram:GuidelineSpecifiedDocumentContextParameter>
</rsm:ExchangedDocumentContext>
<rsm:ExchangedDocument>
<ram:ID>NAMESPACE-TEST-001</ram:ID>
<ram:TypeCode>380</ram:TypeCode>
<ram:IssueDateTime>
<udt:DateTimeString format="102">20240101</udt:DateTimeString>
</ram:IssueDateTime>
</rsm:ExchangedDocument>
<rsm:SupplyChainTradeTransaction>
<ram:ApplicableHeaderTradeAgreement>
<ram:SellerTradeParty>
<ram:Name>Namespace Test Seller</ram:Name>
<ram:PostalTradeAddress>
<ram:LineOne>Test Street</ram:LineOne>
<ram:LineTwo>1</ram:LineTwo>
<ram:PostcodeCode>12345</ram:PostcodeCode>
<ram:CityName>Berlin</ram:CityName>
<ram:CountryID>DE</ram:CountryID>
</ram:PostalTradeAddress>
<ram:SpecifiedTaxRegistration>
<ram:ID schemeID="VA">DE123456789</ram:ID>
</ram:SpecifiedTaxRegistration>
</ram:SellerTradeParty>
<ram:BuyerTradeParty>
<ram:Name>Namespace Test Buyer</ram:Name>
<ram:PostalTradeAddress>
<ram:LineOne>Market Street</ram:LineOne>
<ram:LineTwo>2</ram:LineTwo>
<ram:PostcodeCode>54321</ram:PostcodeCode>
<ram:CityName>Munich</ram:CityName>
<ram:CountryID>DE</ram:CountryID>
</ram:PostalTradeAddress>
</ram:BuyerTradeParty>
</ram:ApplicableHeaderTradeAgreement>
<ram:ApplicableHeaderTradeSettlement>
<ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
</ram:ApplicableHeaderTradeSettlement>
</rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>`;
// Parse the complex XML
const invoice = new EInvoice();
await invoice.fromXmlString(complexXml);
// Count namespace declarations
const namespaceCount = (complexXml.match(/xmlns:/g) || []).length;
const elementCount = (complexXml.match(/<[^/][^>]*>/g) || []).length;
return {
parsedId: invoice.id,
namespaceCount,
elementCount,
fromName: invoice.from?.name,
toName: invoice.to?.name
};
}
);
console.log(` Parsed invoice ID: ${namespaceResult.parsedId}`);
console.log(` Namespace declarations: ${namespaceResult.namespaceCount}`);
console.log(` XML elements: ${namespaceResult.elementCount}`);
console.log(` Processing time: ${namespaceMetric.duration}ms`);
expect(namespaceResult.parsedId).toEqual('NAMESPACE-TEST-001');
expect(namespaceResult.namespaceCount).toBeGreaterThan(3);
// Test 3: Round-trip with nested structures
console.log('\nTest 3: Round-trip conversion with nested data');
const { result: roundTripResult, metric: roundTripMetric } = await PerformanceTracker.track(
'nested-round-trip',
async () => {
const invoice = new EInvoice();
// Create complex nested structure
invoice.id = 'ROUND-TRIP-NESTED-001';
invoice.issueDate = new Date('2024-01-01');
invoice.currency = 'EUR';
invoice.from = {
type: 'company',
name: 'Company A > Division B > Department C',
description: 'Nested company structure test',
address: {
streetName: 'Street > Section > Block',
houseNumber: '1A-2B-3C',
postalCode: '12345',
city: 'City > District > Zone',
country: 'DE'
},
status: 'active',
foundedDate: { year: 2020, month: 1, day: 1 },
registrationDetails: {
vatId: 'DE123456789',
registrationId: 'HRB 12345',
registrationName: 'Registry > Division'
}
};
invoice.to = {
type: 'person',
name: 'John',
surname: 'Doe',
salutation: 'Mr' as const,
sex: 'male' as const,
title: 'Doctor' as const,
description: 'Individual customer',
address: {
streetName: 'Simple Street',
houseNumber: '1',
postalCode: '54321',
city: 'Simple City',
country: 'DE'
}
};
// Add nested items
invoice.items = [{
position: 1,
name: 'Service > Category > Subcategory > Item > Variant > Option',
articleNumber: 'SRV-CAT-SUB-ITM-VAR-OPT',
unitType: 'HUR',
unitQuantity: 8,
unitNetPrice: 250,
vatPercentage: 19
}];
// Convert to both formats and back
const ublXml = await invoice.toXmlString('ubl');
const ciiXml = await invoice.toXmlString('cii');
const fromUbl = new EInvoice();
await fromUbl.fromXmlString(ublXml);
const fromCii = new EInvoice();
await fromCii.fromXmlString(ciiXml);
return {
originalItemName: invoice.items[0].name,
ublPreservedName: fromUbl.items?.[0]?.name,
ciiPreservedName: fromCii.items?.[0]?.name,
ublXmlSize: Buffer.byteLength(ublXml, 'utf8'),
ciiXmlSize: Buffer.byteLength(ciiXml, 'utf8')
};
}
);
console.log(` Original item name: ${roundTripResult.originalItemName}`);
console.log(` UBL preserved: ${roundTripResult.ublPreservedName === roundTripResult.originalItemName ? '✓' : '✗'}`);
console.log(` CII preserved: ${roundTripResult.ciiPreservedName === roundTripResult.originalItemName ? '✓' : '✗'}`);
console.log(` UBL XML size: ${(roundTripResult.ublXmlSize / 1024).toFixed(2)} KB`);
console.log(` CII XML size: ${(roundTripResult.ciiXmlSize / 1024).toFixed(2)} KB`);
console.log(` Processing time: ${roundTripMetric.duration}ms`);
expect(roundTripResult.ublPreservedName).toEqual(roundTripResult.originalItemName);
expect(roundTripResult.ciiPreservedName).toEqual(roundTripResult.originalItemName);
console.log('\n✓ All deeply nested XML tests completed successfully');
});
tap.start();