import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as einvoice from '../../../ts/index.js';
import * as plugins from '../../plugins.js';
import { CorpusLoader } from '../../helpers/corpus.loader.js';
import { PerformanceTracker } from '../../helpers/performance.tracker.js';
tap.test('PARSE-01: Well-Formed XML Parsing - Parse valid XML documents correctly', async (t) => {
const performanceTracker = new PerformanceTracker('PARSE-01');
const corpusLoader = new CorpusLoader();
await t.test('Basic XML structure parsing', async () => {
performanceTracker.startOperation('basic-xml-parsing');
const testCases = [
{
name: 'Minimal invoice',
xml: '\nTEST-001',
expectedStructure: {
hasDeclaration: true,
rootElement: 'invoice',
hasChildren: true
}
},
{
name: 'Invoice with namespaces',
xml: `
TEST-002
`,
expectedStructure: {
hasNamespaces: true,
namespaceCount: 2,
rootNamespace: 'ubl'
}
},
{
name: 'Complex nested structure',
xml: `
Product A
100.00
Product B
200.00
`,
expectedStructure: {
maxDepth: 4,
lineCount: 2
}
},
{
name: 'Invoice with attributes',
xml: `
TEST-004
1000.00
`,
expectedStructure: {
hasAttributes: true,
attributeCount: 5 // 3 on invoice, 1 on id, 2 on amount
}
}
];
for (const testCase of testCases) {
const startTime = performance.now();
try {
const invoice = new einvoice.EInvoice();
if (invoice.fromXmlString) {
await invoice.fromXmlString(testCase.xml);
console.log(`✓ ${testCase.name}: Parsed successfully`);
// Verify parsed data if available
if (invoice.data?.id) {
console.log(` Extracted ID: ${invoice.data.id}`);
}
} else {
console.log(`⚠️ ${testCase.name}: fromXmlString method not implemented`);
}
} catch (error) {
console.log(`✗ ${testCase.name}: Parsing failed - ${error.message}`);
}
performanceTracker.recordMetric('xml-parse', performance.now() - startTime);
}
performanceTracker.endOperation('basic-xml-parsing');
});
await t.test('Character data handling', async () => {
performanceTracker.startOperation('character-data');
const characterTests = [
{
name: 'Text content with special characters',
xml: `
Müller & Co. GmbH
Product with 50% discount & free shipping
`
},
{
name: 'Mixed content',
xml: `
This is a mixed content with inline elements.
`
},
{
name: 'Whitespace preservation',
xml: `
Line 1
Line 2
Line 3
`
},
{
name: 'Empty elements',
xml: `
0
`
}
];
for (const test of characterTests) {
const startTime = performance.now();
try {
const invoice = new einvoice.EInvoice();
if (invoice.fromXmlString) {
await invoice.fromXmlString(test.xml);
console.log(`✓ ${test.name}: Character data handled correctly`);
} else {
console.log(`⚠️ ${test.name}: Cannot test without fromXmlString`);
}
} catch (error) {
console.log(`✗ ${test.name}: Failed - ${error.message}`);
}
performanceTracker.recordMetric('character-handling', performance.now() - startTime);
}
performanceTracker.endOperation('character-data');
});
await t.test('XML comments and processing instructions', async () => {
performanceTracker.startOperation('comments-pi');
const xmlWithComments = `
100.00
`;
const startTime = performance.now();
try {
const invoice = new einvoice.EInvoice();
if (invoice.fromXmlString) {
await invoice.fromXmlString(xmlWithComments);
console.log('✓ XML with comments and processing instructions parsed');
} else {
console.log('⚠️ Cannot test comments/PI without fromXmlString');
}
} catch (error) {
console.log(`✗ Comments/PI parsing failed: ${error.message}`);
}
performanceTracker.recordMetric('comments-pi', performance.now() - startTime);
performanceTracker.endOperation('comments-pi');
});
await t.test('Namespace handling', async () => {
performanceTracker.startOperation('namespace-handling');
const namespaceTests = [
{
name: 'Default namespace',
xml: `
TEST-006
`
},
{
name: 'Multiple namespaces',
xml: `
TEST-007
Test Supplier
`
},
{
name: 'Namespace inheritance',
xml: `
Inherits ns1
`
}
];
for (const test of namespaceTests) {
const startTime = performance.now();
try {
const invoice = new einvoice.EInvoice();
if (invoice.fromXmlString) {
await invoice.fromXmlString(test.xml);
console.log(`✓ ${test.name}: Namespace parsing successful`);
} else {
console.log(`⚠️ ${test.name}: Cannot test without fromXmlString`);
}
} catch (error) {
console.log(`✗ ${test.name}: Failed - ${error.message}`);
}
performanceTracker.recordMetric('namespace-parsing', performance.now() - startTime);
}
performanceTracker.endOperation('namespace-handling');
});
await t.test('Corpus well-formed XML parsing', async () => {
performanceTracker.startOperation('corpus-parsing');
const xmlFiles = await corpusLoader.getFiles(/\.xml$/);
console.log(`\nTesting ${xmlFiles.length} XML files from corpus...`);
const results = {
total: 0,
success: 0,
failed: 0,
avgParseTime: 0
};
const sampleSize = Math.min(50, xmlFiles.length);
const sampledFiles = xmlFiles.slice(0, sampleSize);
let totalParseTime = 0;
for (const file of sampledFiles) {
results.total++;
const startTime = performance.now();
try {
const content = await plugins.fs.readFile(file.path, 'utf8');
const invoice = new einvoice.EInvoice();
if (invoice.fromXmlString) {
await invoice.fromXmlString(content);
results.success++;
} else {
// Fallback: just check if it's valid XML
if (content.includes('')) {
results.success++;
}
}
} catch (error) {
results.failed++;
console.log(` Failed: ${file.name} - ${error.message}`);
}
const parseTime = performance.now() - startTime;
totalParseTime += parseTime;
performanceTracker.recordMetric('file-parse', parseTime);
}
results.avgParseTime = totalParseTime / results.total;
console.log('\nCorpus Parsing Results:');
console.log(`Total files tested: ${results.total}`);
console.log(`Successfully parsed: ${results.success} (${(results.success/results.total*100).toFixed(1)}%)`);
console.log(`Failed to parse: ${results.failed}`);
console.log(`Average parse time: ${results.avgParseTime.toFixed(2)}ms`);
expect(results.success).toBeGreaterThan(results.total * 0.9); // Expect >90% success rate
performanceTracker.endOperation('corpus-parsing');
});
await t.test('DTD and entity references', async () => {
performanceTracker.startOperation('dtd-entities');
const xmlWithEntities = `
]>
&company;
© 2024 &company;
€1000.00
`;
const startTime = performance.now();
try {
const invoice = new einvoice.EInvoice();
if (invoice.fromXmlString) {
await invoice.fromXmlString(xmlWithEntities);
console.log('✓ XML with DTD and entities parsed');
} else {
console.log('⚠️ Cannot test DTD/entities without fromXmlString');
}
} catch (error) {
console.log(`⚠️ DTD/entity parsing: ${error.message}`);
// This might fail due to security restrictions, which is acceptable
}
performanceTracker.recordMetric('dtd-parsing', performance.now() - startTime);
performanceTracker.endOperation('dtd-entities');
});
await t.test('Large XML structure stress test', async () => {
performanceTracker.startOperation('large-xml-test');
// Generate a large but well-formed XML
const generateLargeXml = (lineCount: number): string => {
let xml = '\n\n';
xml += ' \n';
xml += ' \n';
for (let i = 1; i <= lineCount; i++) {
xml += `
Product ${i}
1
10.00
10.00
\n`;
}
xml += ' \n';
xml += ` ${lineCount * 10}.00\n`;
xml += '';
return xml;
};
const testSizes = [10, 100, 1000];
for (const size of testSizes) {
const startTime = performance.now();
const largeXml = generateLargeXml(size);
try {
const invoice = new einvoice.EInvoice();
if (invoice.fromXmlString) {
await invoice.fromXmlString(largeXml);
const parseTime = performance.now() - startTime;
console.log(`✓ Parsed ${size} line items in ${parseTime.toFixed(2)}ms`);
console.log(` Parse rate: ${(size / parseTime * 1000).toFixed(0)} items/second`);
} else {
console.log(`⚠️ Cannot test large XML without fromXmlString`);
}
} catch (error) {
console.log(`✗ Failed with ${size} items: ${error.message}`);
}
performanceTracker.recordMetric(`large-xml-${size}`, performance.now() - startTime);
}
performanceTracker.endOperation('large-xml-test');
});
// Performance summary
console.log('\n' + performanceTracker.getSummary());
// Parsing best practices
console.log('\nXML Parsing Best Practices:');
console.log('1. Always validate XML declaration and encoding');
console.log('2. Handle namespaces correctly throughout the document');
console.log('3. Preserve significant whitespace when required');
console.log('4. Process comments and PIs appropriately');
console.log('5. Handle empty elements consistently');
console.log('6. Be cautious with DTD processing (security implications)');
console.log('7. Optimize for large documents with streaming when possible');
});
tap.start();