306 lines
11 KiB
TypeScript
306 lines
11 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||
import * as einvoice from '../../../ts/index.js';
|
||
import * as plugins from '../../plugins.js';
|
||
|
||
tap.test('PARSE-10: CDATA Section Handling in e-invoices', async () => {
|
||
console.log('Testing CDATA section handling in e-invoices...\n');
|
||
|
||
// Test basic CDATA sections in invoice fields
|
||
const cdataTests = [
|
||
{
|
||
name: 'Simple CDATA content in notes',
|
||
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
<ubl:Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||
<cbc:ID>CDATA-001</cbc:ID>
|
||
<cbc:Note><![CDATA[This is plain text content with special chars: < > & " ']]></cbc:Note>
|
||
</ubl:Invoice>`,
|
||
expectedNote: "This is plain text content with special chars: < > & \" '",
|
||
description: 'Basic CDATA section preserves special characters'
|
||
},
|
||
{
|
||
name: 'CDATA with XML-like content',
|
||
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
<ubl:Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||
<cbc:ID>CDATA-002</cbc:ID>
|
||
<cbc:Note><![CDATA[<html><body>Invoice contains <b>HTML</b> markup</body></html>]]></cbc:Note>
|
||
</ubl:Invoice>`,
|
||
expectedNote: '<html><body>Invoice contains <b>HTML</b> markup</body></html>',
|
||
description: 'XML/HTML markup preserved as text in CDATA'
|
||
},
|
||
{
|
||
name: 'CDATA with line breaks and formatting',
|
||
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
<ubl:Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
|
||
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2">
|
||
<cbc:ID>CDATA-003</cbc:ID>
|
||
<cac:AccountingSupplierParty>
|
||
<cac:Party>
|
||
<cac:PartyName>
|
||
<cbc:Name><![CDATA[Company & Co.
|
||
Special Division
|
||
"International Sales"]]></cbc:Name>
|
||
</cac:PartyName>
|
||
</cac:Party>
|
||
</cac:AccountingSupplierParty>
|
||
</ubl:Invoice>`,
|
||
expectedSupplierName: 'Company & Co.\nSpecial Division\n"International Sales"',
|
||
description: 'CDATA preserves line breaks and special chars in company names'
|
||
},
|
||
{
|
||
name: 'Empty CDATA section',
|
||
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
<ubl:Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||
<cbc:ID>CDATA-004</cbc:ID>
|
||
<cbc:Note><![CDATA[]]></cbc:Note>
|
||
</ubl:Invoice>`,
|
||
expectedNote: '',
|
||
description: 'Empty CDATA section is valid'
|
||
},
|
||
{
|
||
name: 'CDATA with code snippets',
|
||
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
<ubl:Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||
<cbc:ID>CDATA-005</cbc:ID>
|
||
<cbc:Note><![CDATA[if (price < 100 && quantity > 5) { discount = 0.1; }]]></cbc:Note>
|
||
</ubl:Invoice>`,
|
||
expectedNote: 'if (price < 100 && quantity > 5) { discount = 0.1; }',
|
||
description: 'Code snippets with operators preserved'
|
||
}
|
||
];
|
||
|
||
for (const test of cdataTests) {
|
||
console.log(`\n${test.name}:`);
|
||
console.log(` Description: ${test.description}`);
|
||
|
||
try {
|
||
const invoice = new einvoice.EInvoice();
|
||
await invoice.fromXmlString(test.xml);
|
||
|
||
console.log(' ✓ CDATA parsed successfully');
|
||
console.log(` Invoice ID: ${invoice.id}`);
|
||
|
||
if (test.expectedNote !== undefined) {
|
||
const actualNote = invoice.notes?.[0] || '';
|
||
console.log(` Expected note: "${test.expectedNote}"`);
|
||
console.log(` Actual note: "${actualNote}"`);
|
||
expect(actualNote).toEqual(test.expectedNote);
|
||
}
|
||
|
||
if (test.expectedSupplierName !== undefined) {
|
||
const actualName = invoice.from?.name || '';
|
||
console.log(` Expected supplier: "${test.expectedSupplierName}"`);
|
||
console.log(` Actual supplier: "${actualName}"`);
|
||
expect(actualName).toEqual(test.expectedSupplierName);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.log(` ✗ Error: ${error.message}`);
|
||
}
|
||
}
|
||
});
|
||
|
||
tap.test('PARSE-10: CDATA edge cases and security', async () => {
|
||
console.log('\nTesting CDATA edge cases and security aspects...\n');
|
||
|
||
const edgeCases = [
|
||
{
|
||
name: 'CDATA-like content (not actual CDATA)',
|
||
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
<ubl:Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||
<cbc:ID>EDGE-001</cbc:ID>
|
||
<cbc:Note>Text with <![CDATA[ fake CDATA ]]> markers</cbc:Note>
|
||
</ubl:Invoice>`,
|
||
expectedNote: 'Text with <![CDATA[ fake CDATA ]]> markers',
|
||
description: 'Escaped CDATA markers are just text'
|
||
},
|
||
{
|
||
name: 'Multiple CDATA sections',
|
||
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
<ubl:Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||
<cbc:ID>EDGE-002</cbc:ID>
|
||
<cbc:Note><![CDATA[Part 1]]> and <![CDATA[Part 2]]></cbc:Note>
|
||
</ubl:Invoice>`,
|
||
expectedNote: 'Part 1 and Part 2',
|
||
description: 'Multiple CDATA sections in one element'
|
||
},
|
||
{
|
||
name: 'CDATA with Unicode characters',
|
||
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
<ubl:Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||
<cbc:ID>EDGE-003</cbc:ID>
|
||
<cbc:Note><![CDATA[Unicode: € £ ¥ © ® ™ 中文 العربية]]></cbc:Note>
|
||
</ubl:Invoice>`,
|
||
expectedNote: 'Unicode: € £ ¥ © ® ™ 中文 العربية',
|
||
description: 'Unicode characters in CDATA'
|
||
}
|
||
];
|
||
|
||
for (const test of edgeCases) {
|
||
console.log(`${test.name}:`);
|
||
console.log(` Description: ${test.description}`);
|
||
|
||
try {
|
||
const invoice = new einvoice.EInvoice();
|
||
await invoice.fromXmlString(test.xml);
|
||
|
||
const actualNote = invoice.notes?.[0] || '';
|
||
console.log(` Expected: "${test.expectedNote}"`);
|
||
console.log(` Actual: "${actualNote}"`);
|
||
|
||
if (test.expectedNote) {
|
||
expect(actualNote).toEqual(test.expectedNote);
|
||
console.log(' ✓ CDATA edge case handled correctly');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.log(` ℹ Result: ${error.message}`);
|
||
}
|
||
}
|
||
});
|
||
|
||
tap.test('PARSE-10: CDATA in real invoice scenarios', async () => {
|
||
console.log('\nTesting CDATA usage in real invoice scenarios...\n');
|
||
|
||
// Test CDATA in various invoice contexts
|
||
const realScenarios = [
|
||
{
|
||
name: 'Legal disclaimer with special formatting',
|
||
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
<ubl:Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||
<cbc:ID>REAL-001</cbc:ID>
|
||
<cbc:Note><![CDATA[
|
||
TERMS & CONDITIONS:
|
||
1. Payment due within 30 days
|
||
2. Late payment charge: 1.5% per month
|
||
3. All prices exclude VAT (currently 19%)
|
||
|
||
For questions contact: billing@company.com
|
||
]]></cbc:Note>
|
||
</ubl:Invoice>`,
|
||
description: 'Legal terms with special characters and formatting'
|
||
},
|
||
{
|
||
name: 'Product description with technical specs',
|
||
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
<ubl:Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
|
||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||
<cbc:ID>REAL-002</cbc:ID>
|
||
<cac:InvoiceLine>
|
||
<cbc:ID>1</cbc:ID>
|
||
<cac:Item>
|
||
<cbc:Name>Technical Component</cbc:Name>
|
||
<cbc:Description><![CDATA[
|
||
Component specs:
|
||
- Voltage: 12V DC
|
||
- Current: < 2A
|
||
- Temperature: -20°C to +85°C
|
||
- Compliance: CE & RoHS
|
||
- Dimensions: 50mm x 30mm x 15mm
|
||
]]></cbc:Description>
|
||
</cac:Item>
|
||
</cac:InvoiceLine>
|
||
</ubl:Invoice>`,
|
||
description: 'Technical specifications with symbols'
|
||
},
|
||
{
|
||
name: 'Address with special formatting',
|
||
xml: `<?xml version="1.0" encoding="UTF-8"?>
|
||
<ubl:Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
|
||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||
<cbc:ID>REAL-003</cbc:ID>
|
||
<cac:AccountingSupplierParty>
|
||
<cac:Party>
|
||
<cac:PartyName>
|
||
<cbc:Name><![CDATA[Smith & Jones Ltd.]]></cbc:Name>
|
||
</cac:PartyName>
|
||
<cac:PostalAddress>
|
||
<cbc:AdditionalStreetName><![CDATA[Building "A" - 3rd Floor]]></cbc:AdditionalStreetName>
|
||
</cac:PostalAddress>
|
||
</cac:Party>
|
||
</cac:AccountingSupplierParty>
|
||
</ubl:Invoice>`,
|
||
description: 'Company name and address with special characters'
|
||
}
|
||
];
|
||
|
||
for (const scenario of realScenarios) {
|
||
console.log(`${scenario.name}:`);
|
||
console.log(` Use case: ${scenario.description}`);
|
||
|
||
try {
|
||
const invoice = new einvoice.EInvoice();
|
||
await invoice.fromXmlString(scenario.xml);
|
||
|
||
console.log(` ✓ Invoice parsed: ID ${invoice.id}`);
|
||
|
||
if (invoice.notes?.length > 0) {
|
||
console.log(` Notes found: ${invoice.notes.length}`);
|
||
}
|
||
|
||
if (invoice.items?.length > 0) {
|
||
console.log(` Line items: ${invoice.items.length}`);
|
||
}
|
||
|
||
if (invoice.from?.name) {
|
||
console.log(` Supplier: ${invoice.from.name}`);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.log(` ✗ Error: ${error.message}`);
|
||
}
|
||
}
|
||
});
|
||
|
||
tap.test('PARSE-10: CDATA performance with large content', async () => {
|
||
console.log('\nTesting CDATA performance with large content...\n');
|
||
|
||
// Generate invoices with varying CDATA content sizes
|
||
const sizes = [
|
||
{ name: 'Small', chars: 100 },
|
||
{ name: 'Medium', chars: 1000 },
|
||
{ name: 'Large', chars: 10000 }
|
||
];
|
||
|
||
for (const size of sizes) {
|
||
// Generate content with special characters that would need escaping
|
||
const content = Array(size.chars / 10).fill('Text with <>&" chars ').join('');
|
||
|
||
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
||
<ubl:Invoice xmlns:ubl="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
|
||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||
<cbc:ID>PERF-${size.name}</cbc:ID>
|
||
<cbc:Note><![CDATA[${content}]]></cbc:Note>
|
||
</ubl:Invoice>`;
|
||
|
||
const startTime = Date.now();
|
||
|
||
try {
|
||
const invoice = new einvoice.EInvoice();
|
||
await invoice.fromXmlString(xml);
|
||
|
||
const parseTime = Date.now() - startTime;
|
||
|
||
console.log(`${size.name} CDATA (${size.chars} chars):`);
|
||
console.log(` Parse time: ${parseTime}ms`);
|
||
console.log(` Note length: ${invoice.notes?.[0]?.length || 0} chars`);
|
||
console.log(` ✓ Successfully parsed`);
|
||
|
||
} catch (error) {
|
||
console.log(` ✗ Error: ${error.message}`);
|
||
}
|
||
}
|
||
});
|
||
|
||
// Run the tests
|
||
tap.start(); |