import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as einvoice from '../../../ts/index.js'; import * as plugins from '../../plugins.js'; import { PerformanceTracker } from '../../helpers/performance.tracker.js'; import { CorpusLoader } from '../../helpers/corpus.loader.js'; tap.test('ERR-09: Transformation Errors - Handle XSLT and data transformation failures', async (t) => { const performanceTracker = new PerformanceTracker('ERR-09'); await t.test('XSLT transformation errors', async () => { performanceTracker.startOperation('xslt-errors'); const xsltErrors = [ { name: 'Invalid XSLT syntax', xslt: ` `, xml: 'TEST-001', expectedError: /undefined.*variable|xslt.*error/i }, { name: 'Circular reference', xslt: ` `, xml: 'TEST-001', expectedError: /circular|recursive|stack overflow/i }, { name: 'Missing required template', xslt: ` `, xml: 'TEST-001', expectedError: /no matching.*template|element not found/i } ]; for (const test of xsltErrors) { const startTime = performance.now(); try { // Simulate XSLT transformation const transformationError = new Error(`XSLT Error: ${test.name}`); throw transformationError; } catch (error) { expect(error).toBeTruthy(); console.log(`✓ ${test.name}: ${error.message}`); } performanceTracker.recordMetric('xslt-error', performance.now() - startTime); } performanceTracker.endOperation('xslt-errors'); }); await t.test('Data mapping errors', async () => { performanceTracker.startOperation('mapping-errors'); class DataMapper { private mappingRules = new Map any>(); addRule(sourcePath: string, transform: (value: any) => any): void { this.mappingRules.set(sourcePath, transform); } async map(sourceData: any, targetSchema: any): Promise { const errors: string[] = []; const result: any = {}; for (const [path, transform] of this.mappingRules) { try { const sourceValue = this.getValueByPath(sourceData, path); if (sourceValue === undefined) { errors.push(`Missing source field: ${path}`); continue; } const targetValue = transform(sourceValue); this.setValueByPath(result, path, targetValue); } catch (error) { errors.push(`Mapping error for ${path}: ${error.message}`); } } if (errors.length > 0) { throw new Error(`Data mapping failed:\n${errors.join('\n')}`); } return result; } private getValueByPath(obj: any, path: string): any { return path.split('.').reduce((curr, prop) => curr?.[prop], obj); } private setValueByPath(obj: any, path: string, value: any): void { const parts = path.split('.'); const last = parts.pop()!; const target = parts.reduce((curr, prop) => { if (!curr[prop]) curr[prop] = {}; return curr[prop]; }, obj); target[last] = value; } } const mapper = new DataMapper(); // Add mapping rules mapper.addRule('invoice.id', (v) => v.toUpperCase()); mapper.addRule('invoice.date', (v) => { const date = new Date(v); if (isNaN(date.getTime())) { throw new Error('Invalid date format'); } return date.toISOString(); }); mapper.addRule('invoice.amount', (v) => { const amount = parseFloat(v); if (isNaN(amount)) { throw new Error('Invalid amount'); } return amount.toFixed(2); }); const testData = [ { name: 'Valid data', source: { invoice: { id: 'test-001', date: '2024-01-01', amount: '100.50' } }, shouldSucceed: true }, { name: 'Missing required field', source: { invoice: { id: 'test-002', amount: '100' } }, shouldSucceed: false }, { name: 'Invalid data type', source: { invoice: { id: 'test-003', date: 'invalid-date', amount: '100' } }, shouldSucceed: false }, { name: 'Nested missing field', source: { wrongStructure: { id: 'test-004' } }, shouldSucceed: false } ]; for (const test of testData) { const startTime = performance.now(); try { const result = await mapper.map(test.source, {}); if (test.shouldSucceed) { console.log(`✓ ${test.name}: Mapping successful`); } else { console.log(`✗ ${test.name}: Should have failed but succeeded`); } } catch (error) { if (!test.shouldSucceed) { console.log(`✓ ${test.name}: Correctly failed - ${error.message.split('\n')[0]}`); } else { console.log(`✗ ${test.name}: Unexpected failure - ${error.message}`); } } performanceTracker.recordMetric('mapping-test', performance.now() - startTime); } performanceTracker.endOperation('mapping-errors'); }); await t.test('Schema transformation conflicts', async () => { performanceTracker.startOperation('schema-conflicts'); const schemaConflicts = [ { name: 'Incompatible data types', source: { type: 'string', value: '123' }, target: { type: 'number' }, transform: (v: string) => parseInt(v), expectedIssue: 'Type coercion required' }, { name: 'Missing mandatory field', source: { optional: 'value' }, target: { required: ['mandatory'] }, transform: (v: any) => v, expectedIssue: 'Required field missing' }, { name: 'Enumeration mismatch', source: { status: 'ACTIVE' }, target: { status: { enum: ['active', 'inactive'] } }, transform: (v: string) => v.toLowerCase(), expectedIssue: 'Enum value transformation' }, { name: 'Array to single value', source: { items: ['a', 'b', 'c'] }, target: { item: 'string' }, transform: (v: string[]) => v[0], expectedIssue: 'Data loss warning' } ]; for (const conflict of schemaConflicts) { const startTime = performance.now(); try { const result = conflict.transform(conflict.source); console.log(`⚠️ ${conflict.name}: ${conflict.expectedIssue}`); console.log(` Transformed: ${JSON.stringify(conflict.source)} → ${JSON.stringify(result)}`); } catch (error) { console.log(`✗ ${conflict.name}: Transformation failed - ${error.message}`); } performanceTracker.recordMetric('schema-conflict', performance.now() - startTime); } performanceTracker.endOperation('schema-conflicts'); }); await t.test('XPath evaluation errors', async () => { performanceTracker.startOperation('xpath-errors'); class XPathEvaluator { evaluate(xpath: string, xml: string): any { // Simulate XPath evaluation errors const errors = { '//invalid[': 'Unclosed bracket in XPath expression', '//invoice/amount/text() + 1': 'Type error: Cannot perform arithmetic on node set', '//namespace:element': 'Undefined namespace prefix: namespace', '//invoice[position() = $var]': 'Undefined variable: var', '//invoice/substring(id)': 'Invalid function syntax' }; if (errors[xpath]) { throw new Error(errors[xpath]); } // Simple valid paths if (xpath === '//invoice/id') { return 'TEST-001'; } return null; } } const evaluator = new XPathEvaluator(); const xpathTests = [ { path: '//invoice/id', shouldSucceed: true }, { path: '//invalid[', shouldSucceed: false }, { path: '//invoice/amount/text() + 1', shouldSucceed: false }, { path: '//namespace:element', shouldSucceed: false }, { path: '//invoice[position() = $var]', shouldSucceed: false }, { path: '//invoice/substring(id)', shouldSucceed: false } ]; for (const test of xpathTests) { const startTime = performance.now(); try { const result = evaluator.evaluate(test.path, 'TEST-001'); if (test.shouldSucceed) { console.log(`✓ XPath "${test.path}": Result = ${result}`); } else { console.log(`✗ XPath "${test.path}": Should have failed`); } } catch (error) { if (!test.shouldSucceed) { console.log(`✓ XPath "${test.path}": ${error.message}`); } else { console.log(`✗ XPath "${test.path}": Unexpected error - ${error.message}`); } } performanceTracker.recordMetric('xpath-evaluation', performance.now() - startTime); } performanceTracker.endOperation('xpath-errors'); }); await t.test('Format conversion pipeline errors', async () => { performanceTracker.startOperation('pipeline-errors'); class ConversionPipeline { private steps: Array<{ name: string; transform: (data: any) => any }> = []; addStep(name: string, transform: (data: any) => any): void { this.steps.push({ name, transform }); } async execute(input: any): Promise { let current = input; const executionLog: string[] = []; for (const step of this.steps) { try { executionLog.push(`Executing: ${step.name}`); current = await step.transform(current); executionLog.push(`✓ ${step.name} completed`); } catch (error) { executionLog.push(`✗ ${step.name} failed: ${error.message}`); throw new Error( `Pipeline failed at step "${step.name}": ${error.message}\n` + `Execution log:\n${executionLog.join('\n')}` ); } } return current; } } const pipeline = new ConversionPipeline(); // Add pipeline steps pipeline.addStep('Validate Input', (data) => { if (!data.invoice) { throw new Error('Missing invoice element'); } return data; }); pipeline.addStep('Normalize Dates', (data) => { if (data.invoice.date) { data.invoice.date = new Date(data.invoice.date).toISOString(); } return data; }); pipeline.addStep('Convert Currency', (data) => { if (data.invoice.amount && data.invoice.currency !== 'EUR') { throw new Error('Currency conversion not implemented'); } return data; }); pipeline.addStep('Apply Business Rules', (data) => { if (data.invoice.amount < 0) { throw new Error('Negative amounts not allowed'); } return data; }); const testCases = [ { name: 'Valid pipeline execution', input: { invoice: { id: 'TEST-001', date: '2024-01-01', amount: 100, currency: 'EUR' } }, shouldSucceed: true }, { name: 'Missing invoice element', input: { order: { id: 'ORDER-001' } }, shouldSucceed: false, failureStep: 'Validate Input' }, { name: 'Unsupported currency', input: { invoice: { id: 'TEST-002', amount: 100, currency: 'USD' } }, shouldSucceed: false, failureStep: 'Convert Currency' }, { name: 'Business rule violation', input: { invoice: { id: 'TEST-003', amount: -50, currency: 'EUR' } }, shouldSucceed: false, failureStep: 'Apply Business Rules' } ]; for (const test of testCases) { const startTime = performance.now(); try { const result = await pipeline.execute(test.input); if (test.shouldSucceed) { console.log(`✓ ${test.name}: Pipeline completed successfully`); } else { console.log(`✗ ${test.name}: Should have failed at ${test.failureStep}`); } } catch (error) { if (!test.shouldSucceed) { const failedStep = error.message.match(/step "([^"]+)"/)?.[1]; if (failedStep === test.failureStep) { console.log(`✓ ${test.name}: Failed at expected step (${failedStep})`); } else { console.log(`✗ ${test.name}: Failed at wrong step (expected ${test.failureStep}, got ${failedStep})`); } } else { console.log(`✗ ${test.name}: Unexpected failure`); } } performanceTracker.recordMetric('pipeline-execution', performance.now() - startTime); } performanceTracker.endOperation('pipeline-errors'); }); await t.test('Corpus transformation analysis', async () => { performanceTracker.startOperation('corpus-transformation'); const corpusLoader = new CorpusLoader(); const xmlFiles = await corpusLoader.getFiles(/\.xml$/); console.log(`\nAnalyzing transformation scenarios with ${xmlFiles.length} files...`); const transformationStats = { total: 0, ublToCii: 0, ciiToUbl: 0, zugferdToXrechnung: 0, errors: 0, unsupported: 0 }; const sampleSize = Math.min(20, xmlFiles.length); const sampledFiles = xmlFiles.slice(0, sampleSize); for (const file of sampledFiles) { transformationStats.total++; try { // Detect source format if (file.path.includes('UBL') || file.path.includes('.ubl.')) { transformationStats.ublToCii++; } else if (file.path.includes('CII') || file.path.includes('.cii.')) { transformationStats.ciiToUbl++; } else if (file.path.includes('ZUGFeRD') || file.path.includes('XRECHNUNG')) { transformationStats.zugferdToXrechnung++; } else { transformationStats.unsupported++; } } catch (error) { transformationStats.errors++; } } console.log('\nTransformation Scenarios:'); console.log(`Total files analyzed: ${transformationStats.total}`); console.log(`UBL → CII candidates: ${transformationStats.ublToCii}`); console.log(`CII → UBL candidates: ${transformationStats.ciiToUbl}`); console.log(`ZUGFeRD → XRechnung candidates: ${transformationStats.zugferdToXrechnung}`); console.log(`Unsupported formats: ${transformationStats.unsupported}`); console.log(`Analysis errors: ${transformationStats.errors}`); performanceTracker.endOperation('corpus-transformation'); }); await t.test('Transformation rollback mechanisms', async () => { performanceTracker.startOperation('rollback'); class TransformationContext { private snapshots: Array<{ stage: string; data: any }> = []; private currentData: any; constructor(initialData: any) { this.currentData = JSON.parse(JSON.stringify(initialData)); this.snapshots.push({ stage: 'initial', data: this.currentData }); } async transform(stage: string, transformer: (data: any) => any): Promise { try { const transformed = await transformer(this.currentData); this.currentData = transformed; this.snapshots.push({ stage, data: JSON.parse(JSON.stringify(transformed)) }); } catch (error) { throw new Error(`Transformation failed at stage "${stage}": ${error.message}`); } } rollbackTo(stage: string): void { const snapshot = this.snapshots.find(s => s.stage === stage); if (!snapshot) { throw new Error(`No snapshot found for stage: ${stage}`); } this.currentData = JSON.parse(JSON.stringify(snapshot.data)); // Remove all snapshots after this stage const index = this.snapshots.indexOf(snapshot); this.snapshots = this.snapshots.slice(0, index + 1); } getData(): any { return this.currentData; } getHistory(): string[] { return this.snapshots.map(s => s.stage); } } const initialData = { invoice: { id: 'TEST-001', amount: 100, items: ['item1', 'item2'] } }; const context = new TransformationContext(initialData); try { // Successful transformations await context.transform('add-date', (data) => { data.invoice.date = '2024-01-01'; return data; }); await context.transform('calculate-tax', (data) => { data.invoice.tax = data.invoice.amount * 0.19; return data; }); console.log('✓ Transformations applied:', context.getHistory()); // Failed transformation await context.transform('invalid-operation', (data) => { throw new Error('Invalid operation'); }); } catch (error) { console.log(`✓ Error caught: ${error.message}`); // Rollback to last successful state context.rollbackTo('calculate-tax'); console.log('✓ Rolled back to:', context.getHistory()); // Try rollback to initial state context.rollbackTo('initial'); console.log('✓ Rolled back to initial state'); const finalData = context.getData(); expect(JSON.stringify(finalData)).toEqual(JSON.stringify(initialData)); } performanceTracker.endOperation('rollback'); }); // Performance summary console.log('\n' + performanceTracker.getSummary()); // Transformation error handling best practices console.log('\nTransformation Error Handling Best Practices:'); console.log('1. Validate transformation rules before execution'); console.log('2. Implement checkpoints for complex transformation pipelines'); console.log('3. Provide detailed error context including failed step and data state'); console.log('4. Support rollback mechanisms for failed transformations'); console.log('5. Log all transformation steps for debugging'); console.log('6. Handle type mismatches and data loss gracefully'); console.log('7. Validate output against target schema'); console.log('8. Implement transformation preview/dry-run capability'); }); tap.start();