294 lines
11 KiB
TypeScript
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(); |