import { expect, tap } from '@git.zone/tstest/tapbundle'; import { promises as fs } from 'fs'; import * as path from 'path'; import { CorpusLoader } from '../../helpers/corpus.loader.js'; import { PerformanceTracker } from '../../helpers/performance.tracker.js'; tap.test('VAL-04: XSD Schema Validation - should validate against XML Schema definitions', async () => { // Test schema validation for different formats const schemaTests = [ { category: 'UBL_XMLRECHNUNG', schemaType: 'UBL 2.1', description: 'UBL invoices should validate against UBL 2.1 schema' }, { category: 'CII_XMLRECHNUNG', schemaType: 'UN/CEFACT CII', description: 'CII invoices should validate against UN/CEFACT schema' }, { category: 'EN16931_UBL_EXAMPLES', schemaType: 'UBL 2.1', description: 'EN16931 UBL examples should be schema-valid' } ] as const; console.log('Testing XSD schema validation across formats'); const { EInvoice } = await import('../../../ts/index.js'); let totalFiles = 0; let validFiles = 0; let invalidFiles = 0; let errorFiles = 0; for (const test of schemaTests) { try { const files = await CorpusLoader.getFiles(test.category); const xmlFiles = files.filter(f => f.endsWith('.xml')).slice(0, 3); // Test 3 per category if (xmlFiles.length === 0) { console.log(`\n${test.category}: No XML files found, skipping`); continue; } console.log(`\n${test.category} (${test.schemaType}): Testing ${xmlFiles.length} files`); for (const filePath of xmlFiles) { const fileName = path.basename(filePath); totalFiles++; try { const xmlContent = await fs.readFile(filePath, 'utf-8'); const { result: einvoice } = await PerformanceTracker.track( 'schema-xml-loading', async () => await EInvoice.fromXml(xmlContent) ); // Perform schema validation (if available) const { result: validation } = await PerformanceTracker.track( 'xsd-schema-validation', async () => { // Try to validate with schema validation level return await einvoice.validate(/* ValidationLevel.SCHEMA */); }, { category: test.category, file: fileName, schemaType: test.schemaType } ); if (validation.valid) { validFiles++; console.log(` ✓ ${fileName}: Schema valid`); } else { invalidFiles++; console.log(` ○ ${fileName}: Schema validation failed`); if (validation.errors && validation.errors.length > 0) { const schemaErrors = validation.errors.filter(e => e.message && ( e.message.toLowerCase().includes('schema') || e.message.toLowerCase().includes('xsd') || e.message.toLowerCase().includes('element') ) ); console.log(` Schema errors: ${schemaErrors.length}`); schemaErrors.slice(0, 2).forEach(err => { console.log(` - ${err.code}: ${err.message}`); }); } } } catch (error) { errorFiles++; console.log(` ✗ ${fileName}: Error - ${error.message}`); } } } catch (error) { console.log(`Error testing ${test.category}: ${error.message}`); } } console.log('\n=== XSD SCHEMA VALIDATION SUMMARY ==='); console.log(`Total files tested: ${totalFiles}`); console.log(`Schema valid: ${validFiles}`); console.log(`Schema invalid: ${invalidFiles}`); console.log(`Errors: ${errorFiles}`); if (totalFiles > 0) { const validationRate = (validFiles / totalFiles * 100).toFixed(1); console.log(`Validation rate: ${validationRate}%`); // Performance summary const perfSummary = await PerformanceTracker.getSummary('xsd-schema-validation'); if (perfSummary) { console.log(`\nSchema Validation Performance:`); console.log(` Average: ${perfSummary.average.toFixed(2)}ms`); console.log(` Min: ${perfSummary.min.toFixed(2)}ms`); console.log(` Max: ${perfSummary.max.toFixed(2)}ms`); console.log(` P95: ${perfSummary.p95.toFixed(2)}ms`); } // Expect most files to process successfully (valid or invalid, but not error) expect((validFiles + invalidFiles) / totalFiles).toBeGreaterThan(0.8); } }); tap.test('VAL-04: Schema Validation Error Types - should identify different types of schema violations', async () => { const { EInvoice } = await import('../../../ts/index.js'); const schemaViolationTests = [ { name: 'Missing required element', xml: ` 2024-01-01 `, violationType: 'missing-element' }, { name: 'Invalid element order', xml: ` 2024-01-01 WRONG-ORDER `, violationType: 'element-order' }, { name: 'Invalid data type', xml: ` VALID-ID not-a-date `, violationType: 'data-type' }, { name: 'Unexpected element', xml: ` VALID-ID 2024-01-01 Not allowed `, violationType: 'unexpected-element' } ]; for (const test of schemaViolationTests) { try { const { result: validation } = await PerformanceTracker.track( 'schema-violation-test', async () => { const einvoice = await EInvoice.fromXml(test.xml); return await einvoice.validate(); } ); console.log(`${test.name}: ${validation.valid ? 'VALID' : 'INVALID'}`); if (!validation.valid && validation.errors) { const schemaErrors = validation.errors.filter(e => e.message && ( e.message.toLowerCase().includes('schema') || e.message.toLowerCase().includes('element') || e.message.toLowerCase().includes('type') ) ); console.log(` Schema errors detected: ${schemaErrors.length}`); schemaErrors.slice(0, 1).forEach(err => { console.log(` - ${err.code}: ${err.message}`); }); // Should detect schema violations expect(schemaErrors.length).toBeGreaterThan(0); } else { console.log(` ○ No schema violations detected (may need stricter validation)`); } } catch (error) { console.log(`${test.name}: Error - ${error.message}`); // Parsing errors are also a form of schema violation console.log(` ✓ Error during parsing indicates schema violation`); } } }); tap.test('VAL-04: Schema Validation Performance - should validate schemas efficiently', async () => { const { EInvoice } = await import('../../../ts/index.js'); // Generate test XMLs of different sizes function generateUBLInvoice(lineItems: number): string { let xml = ` PERF-${Date.now()} 2024-01-01 EUR`; for (let i = 1; i <= lineItems; i++) { xml += ` ${i} ${i} ${i * 100} `; } xml += '\n'; return xml; } const performanceTests = [ { name: 'Small invoice (5 lines)', lineItems: 5, threshold: 50 }, { name: 'Medium invoice (25 lines)', lineItems: 25, threshold: 100 }, { name: 'Large invoice (100 lines)', lineItems: 100, threshold: 200 } ]; console.log('Testing schema validation performance'); for (const test of performanceTests) { const xml = generateUBLInvoice(test.lineItems); console.log(`\n${test.name} (${Math.round(xml.length/1024)}KB)`); const { metric } = await PerformanceTracker.track( 'schema-performance-test', async () => { const einvoice = await EInvoice.fromXml(xml); return await einvoice.validate(); } ); console.log(` Validation time: ${metric.duration.toFixed(2)}ms`); console.log(` Memory used: ${metric.memory ? (metric.memory.used / 1024 / 1024).toFixed(2) : 'N/A'}MB`); // Performance assertions expect(metric.duration).toBeLessThan(test.threshold); if (metric.memory && metric.memory.used > 0) { const memoryMB = metric.memory.used / 1024 / 1024; expect(memoryMB).toBeLessThan(100); // Should not use more than 100MB } } }); tap.test('VAL-04: Schema Validation Caching - should cache schema validation results', async () => { const { EInvoice } = await import('../../../ts/index.js'); const testXml = ` CACHE-TEST 2024-01-01 EUR `; console.log('Testing schema validation caching'); const einvoice = await EInvoice.fromXml(testXml); // First validation (cold) const { metric: coldMetric } = await PerformanceTracker.track( 'schema-validation-cold', async () => await einvoice.validate() ); // Second validation (potentially cached) const { metric: warmMetric } = await PerformanceTracker.track( 'schema-validation-warm', async () => await einvoice.validate() ); console.log(`Cold validation: ${coldMetric.duration.toFixed(2)}ms`); console.log(`Warm validation: ${warmMetric.duration.toFixed(2)}ms`); // Warm validation should not be significantly slower const speedupRatio = coldMetric.duration / warmMetric.duration; console.log(`Speedup ratio: ${speedupRatio.toFixed(2)}x`); // Either caching helps (speedup) or both are fast const bothFast = coldMetric.duration < 20 && warmMetric.duration < 20; const cachingHelps = speedupRatio > 1.2; if (cachingHelps) { console.log('✓ Caching appears to improve performance'); } else if (bothFast) { console.log('✓ Both validations are fast (caching may not be needed)'); } else { console.log('○ Caching behavior unclear'); } expect(bothFast || cachingHelps).toEqual(true); }); tap.start();