import { tap, expect } from '@git.zone/tstest/tapbundle'; import * as plugins from '../../../ts/plugins.ts'; import { EInvoice } from '../../../ts/classes.xinvoice.ts'; import { CorpusLoader } from '../../helpers/corpus.loader.ts'; import { PerformanceTracker } from '../../helpers/performance.tracker.ts'; const testTimeout = 300000; // 5 minutes timeout for conversion processing // CONV-02: UBL to CII Conversion // Tests conversion from UBL Invoice format to CII (Cross-Industry Invoice) format // including field mapping, data preservation, and semantic equivalence tap.test('CONV-02: UBL to CII Conversion - Basic Conversion', async (tools) => { const startTime = Date.now(); try { // Create a sample UBL invoice for conversion testing const sampleUblXml = ` UBL-TO-CII-001 2024-01-01 380 EUR Test conversion from UBL to CII format UBL Test Supplier UBL Street 123 UBL City 12345 DE DE123456789 UBL Test Customer Customer Street 456 Customer City 54321 DE 1 2 100.00 UBL Test Product Product for UBL to CII conversion testing 19.00 50.00 19.00 100.00 19.00 19.00 VAT 100.00 100.00 119.00 119.00 `; const invoice = new EInvoice(); const parseResult = await invoice.fromXmlString(sampleUblXml); expect(parseResult).toBeTruthy(); // Test UBL to CII conversion if supported if (typeof invoice.convertTo === 'function') { tools.log('Testing UBL to CII conversion...'); try { const conversionResult = await invoice.convertTo('CII'); if (conversionResult) { tools.log('✓ UBL to CII conversion completed'); // Verify the converted format const convertedXml = await conversionResult.toXmlString(); expect(convertedXml).toBeTruthy(); expect(convertedXml.length).toBeGreaterThan(100); // Check for CII format characteristics const ciiChecks = { hasCiiNamespace: convertedXml.includes('CrossIndustryInvoice') || convertedXml.includes('urn:un:unece:uncefact:data:standard:CrossIndustryInvoice'), hasExchangedDocument: convertedXml.includes('ExchangedDocument'), hasSupplyChainTrade: convertedXml.includes('SupplyChainTradeTransaction'), hasOriginalId: convertedXml.includes('UBL-TO-CII-001'), hasOriginalCurrency: convertedXml.includes('EUR') }; tools.log('CII Format Verification:'); tools.log(` CII Namespace: ${ciiChecks.hasCiiNamespace}`); tools.log(` ExchangedDocument: ${ciiChecks.hasExchangedDocument}`); tools.log(` SupplyChainTrade: ${ciiChecks.hasSupplyChainTrade}`); tools.log(` Original ID preserved: ${ciiChecks.hasOriginalId}`); tools.log(` Currency preserved: ${ciiChecks.hasOriginalCurrency}`); if (ciiChecks.hasCiiNamespace && ciiChecks.hasExchangedDocument) { tools.log('✓ Valid CII format structure detected'); } else { tools.log('⚠ CII format structure not clearly detected'); } // Validate the converted invoice try { const validationResult = await conversionResult.validate(); if (validationResult.valid) { tools.log('✓ Converted CII invoice passes validation'); } else { tools.log(`⚠ Converted CII validation issues: ${validationResult.errors?.length || 0} errors`); } } catch (validationError) { tools.log(`⚠ Converted CII validation failed: ${validationError.message}`); } } else { tools.log('⚠ UBL to CII conversion returned no result'); } } catch (conversionError) { tools.log(`⚠ UBL to CII conversion failed: ${conversionError.message}`); } } else { tools.log('⚠ UBL to CII conversion not supported (convertTo method not available)'); // Test alternative conversion approach if available if (typeof invoice.toCii === 'function') { try { const ciiResult = await invoice.toCii(); if (ciiResult) { tools.log('✓ Alternative UBL to CII conversion successful'); } } catch (alternativeError) { tools.log(`⚠ Alternative conversion failed: ${alternativeError.message}`); } } } } catch (error) { tools.log(`Basic UBL to CII conversion test failed: ${error.message}`); } const duration = Date.now() - startTime; PerformanceTracker.recordMetric('conversion-ubl-to-cii-basic', duration); }); tap.test('CONV-02: UBL to CII Conversion - Corpus Testing', { timeout: testTimeout }, async (tools) => { const startTime = Date.now(); let processedFiles = 0; let successfulConversions = 0; let conversionErrors = 0; let totalConversionTime = 0; try { const ublFiles = await CorpusLoader.getFiles('UBL_XML_RECHNUNG'); tools.log(`Testing UBL to CII conversion with ${ublFiles.length} UBL files`); if (ublFiles.length === 0) { tools.log('⚠ No UBL files found in corpus for conversion testing'); return; } // Process a subset of files for performance const filesToProcess = ublFiles.slice(0, Math.min(8, ublFiles.length)); for (const filePath of filesToProcess) { const fileName = plugins.path.basename(filePath); const fileConversionStart = Date.now(); try { processedFiles++; const invoice = new EInvoice(); const parseResult = await invoice.fromFile(filePath); if (parseResult) { // Attempt conversion to CII if (typeof invoice.convertTo === 'function') { const conversionResult = await invoice.convertTo('CII'); const fileConversionTime = Date.now() - fileConversionStart; totalConversionTime += fileConversionTime; if (conversionResult) { successfulConversions++; tools.log(`✓ ${fileName}: Converted to CII (${fileConversionTime}ms)`); // Quick validation of converted content const convertedXml = await conversionResult.toXmlString(); if (convertedXml && convertedXml.length > 100) { tools.log(` Converted content length: ${convertedXml.length} chars`); // Test key field preservation const originalXml = await invoice.toXmlString(); const preservationChecks = { currencyPreserved: originalXml.includes('EUR') === convertedXml.includes('EUR'), datePreserved: originalXml.includes('2024') === convertedXml.includes('2024') }; if (preservationChecks.currencyPreserved && preservationChecks.datePreserved) { tools.log(` ✓ Key data preserved in conversion`); } } } else { conversionErrors++; tools.log(`⚠ ${fileName}: Conversion returned no result`); } } else { conversionErrors++; tools.log(`⚠ ${fileName}: Conversion method not available`); } } else { conversionErrors++; tools.log(`⚠ ${fileName}: Failed to parse original UBL`); } } catch (error) { conversionErrors++; const fileConversionTime = Date.now() - fileConversionStart; totalConversionTime += fileConversionTime; tools.log(`✗ ${fileName}: Conversion failed - ${error.message}`); } } // Calculate statistics const successRate = processedFiles > 0 ? (successfulConversions / processedFiles) * 100 : 0; const averageConversionTime = processedFiles > 0 ? totalConversionTime / processedFiles : 0; tools.log(`\nUBL to CII Conversion Summary:`); tools.log(`- Files processed: ${processedFiles}`); tools.log(`- Successful conversions: ${successfulConversions} (${successRate.toFixed(1)}%)`); tools.log(`- Conversion errors: ${conversionErrors}`); tools.log(`- Average conversion time: ${averageConversionTime.toFixed(1)}ms`); // Performance expectations if (processedFiles > 0) { expect(averageConversionTime).toBeLessThan(3000); // 3 seconds max per file } // We expect some conversions to work, but don't require 100% success // as some files might have format-specific features that can't be converted if (processedFiles > 0) { expect(successRate).toBeGreaterThan(0); // At least one conversion should work } } catch (error) { tools.log(`UBL to CII corpus testing failed: ${error.message}`); throw error; } const totalDuration = Date.now() - startTime; PerformanceTracker.recordMetric('conversion-ubl-to-cii-corpus', totalDuration); tools.log(`UBL to CII corpus testing completed in ${totalDuration}ms`); }); tap.test('CONV-02: UBL to CII Conversion - Field Mapping Verification', async (tools) => { const startTime = Date.now(); // Test specific field mappings between UBL and CII const fieldMappingTests = [ { name: 'Invoice Header Fields', ublXml: ` FIELD-MAP-001 2024-01-15 380 USD Field mapping test invoice `, expectedMappings: { 'ID': ['ExchangedDocument', 'ID'], 'IssueDate': ['ExchangedDocument', 'IssueDateTime'], 'InvoiceTypeCode': ['ExchangedDocument', 'TypeCode'], 'DocumentCurrencyCode': ['InvoiceCurrencyCode'], 'Note': ['IncludedNote'] } }, { name: 'Party Information', ublXml: ` PARTY-MAP-001 2024-01-15 380 Supplier Company Ltd Main Street 100 Business City 10001 US `, expectedMappings: { 'AccountingSupplierParty': ['SellerTradeParty'], 'PartyName/Name': ['Name'], 'PostalAddress': ['PostalTradeAddress'], 'StreetName': ['LineOne'], 'CityName': ['CityName'], 'PostalZone': ['PostcodeCode'], 'Country/IdentificationCode': ['CountryID'] } }, { name: 'Line Items and Pricing', ublXml: ` LINE-MAP-001 2024-01-15 380 1 5 250.00 Mapping Test Product Product for field mapping verification 50.00 `, expectedMappings: { 'InvoiceLine': ['IncludedSupplyChainTradeLineItem'], 'InvoiceLine/ID': ['AssociatedDocumentLineDocument/LineID'], 'InvoicedQuantity': ['SpecifiedLineTradeDelivery/BilledQuantity'], 'LineExtensionAmount': ['SpecifiedLineTradeSettlement/SpecifiedTradeSettlementLineMonetarySummation/LineTotalAmount'], 'Item/Name': ['SpecifiedTradeProduct/Name'], 'Price/PriceAmount': ['SpecifiedLineTradeAgreement/NetPriceProductTradePrice/ChargeAmount'] } } ]; for (const mappingTest of fieldMappingTests) { tools.log(`Testing ${mappingTest.name} field mapping...`); try { const invoice = new EInvoice(); const parseResult = await invoice.fromXmlString(mappingTest.ublXml); if (parseResult) { if (typeof invoice.convertTo === 'function') { const conversionResult = await invoice.convertTo('CII'); if (conversionResult) { const convertedXml = await conversionResult.toXmlString(); tools.log(` ✓ ${mappingTest.name} conversion completed`); tools.log(` Converted XML length: ${convertedXml.length} chars`); // Check for expected CII structure elements let mappingsFound = 0; let mappingsTotal = Object.keys(mappingTest.expectedMappings).length; for (const [ublField, ciiPath] of Object.entries(mappingTest.expectedMappings)) { const ciiElements = Array.isArray(ciiPath) ? ciiPath : [ciiPath]; const hasMapping = ciiElements.some(element => convertedXml.includes(element)); if (hasMapping) { mappingsFound++; tools.log(` ✓ ${ublField} → ${ciiElements.join('/')} mapped`); } else { tools.log(` ⚠ ${ublField} → ${ciiElements.join('/')} not found`); } } const mappingSuccessRate = (mappingsFound / mappingsTotal) * 100; tools.log(` Field mapping success rate: ${mappingSuccessRate.toFixed(1)}% (${mappingsFound}/${mappingsTotal})`); if (mappingSuccessRate >= 70) { tools.log(` ✓ Good field mapping coverage`); } else { tools.log(` ⚠ Low field mapping coverage - may need implementation`); } } else { tools.log(` ⚠ ${mappingTest.name} conversion returned no result`); } } else { tools.log(` ⚠ ${mappingTest.name} conversion not supported`); } } else { tools.log(` ⚠ ${mappingTest.name} UBL parsing failed`); } } catch (error) { tools.log(` ✗ ${mappingTest.name} test failed: ${error.message}`); } } const duration = Date.now() - startTime; PerformanceTracker.recordMetric('conversion-ubl-to-cii-field-mapping', duration); }); tap.test('CONV-02: UBL to CII Conversion - Data Integrity', async (tools) => { const startTime = Date.now(); // Test data integrity during conversion const integrityTestXml = ` INTEGRITY-TEST-001 2024-01-15 380 EUR Special characters: äöüß €£$¥ áéíóú àèìòù Tëst Suppliér Çômpány 1 3.5 175.50 Prödüct wíth spëcíàl chäractërs Testing unicode: 中文 日本語 한국어 العربية 50.14 33.35 175.50 175.50 208.85 208.85 `; try { const invoice = new EInvoice(); const parseResult = await invoice.fromXmlString(integrityTestXml); if (parseResult) { tools.log('Testing data integrity during UBL to CII conversion...'); if (typeof invoice.convertTo === 'function') { const conversionResult = await invoice.convertTo('CII'); if (conversionResult) { const convertedXml = await conversionResult.toXmlString(); const originalXml = await invoice.toXmlString(); // Test data integrity const integrityChecks = { invoiceIdPreserved: convertedXml.includes('INTEGRITY-TEST-001'), specialCharsPreserved: convertedXml.includes('äöüß') && convertedXml.includes('€£$¥'), unicodePreserved: convertedXml.includes('中文') || convertedXml.includes('日本語'), numbersPreserved: convertedXml.includes('175.50') && convertedXml.includes('50.14'), currencyPreserved: convertedXml.includes('EUR'), datePreserved: convertedXml.includes('2024-01-15') || convertedXml.includes('20240115') }; tools.log('Data Integrity Verification:'); tools.log(` Invoice ID preserved: ${integrityChecks.invoiceIdPreserved}`); tools.log(` Special characters preserved: ${integrityChecks.specialCharsPreserved}`); tools.log(` Unicode characters preserved: ${integrityChecks.unicodePreserved}`); tools.log(` Numbers preserved: ${integrityChecks.numbersPreserved}`); tools.log(` Currency preserved: ${integrityChecks.currencyPreserved}`); tools.log(` Date preserved: ${integrityChecks.datePreserved}`); const integrityScore = Object.values(integrityChecks).filter(Boolean).length; const totalChecks = Object.values(integrityChecks).length; const integrityPercentage = (integrityScore / totalChecks) * 100; tools.log(`Data integrity score: ${integrityScore}/${totalChecks} (${integrityPercentage.toFixed(1)}%)`); if (integrityPercentage >= 80) { tools.log('✓ Good data integrity maintained'); } else { tools.log('⚠ Data integrity issues detected'); } // Test round-trip if possible if (typeof conversionResult.convertTo === 'function') { try { const roundTripResult = await conversionResult.convertTo('UBL'); if (roundTripResult) { const roundTripXml = await roundTripResult.toXmlString(); if (roundTripXml.includes('INTEGRITY-TEST-001')) { tools.log('✓ Round-trip conversion preserves ID'); } } } catch (roundTripError) { tools.log(`⚠ Round-trip test failed: ${roundTripError.message}`); } } } else { tools.log('⚠ Data integrity conversion returned no result'); } } else { tools.log('⚠ Data integrity conversion not supported'); } } else { tools.log('⚠ Data integrity test - UBL parsing failed'); } } catch (error) { tools.log(`Data integrity test failed: ${error.message}`); } const duration = Date.now() - startTime; PerformanceTracker.recordMetric('conversion-ubl-to-cii-data-integrity', duration); }); tap.test('CONV-02: Performance Summary', async (tools) => { const operations = [ 'conversion-ubl-to-cii-basic', 'conversion-ubl-to-cii-corpus', 'conversion-ubl-to-cii-field-mapping', 'conversion-ubl-to-cii-data-integrity' ]; tools.log(`\n=== UBL to CII Conversion Performance Summary ===`); for (const operation of operations) { const summary = await PerformanceTracker.getSummary(operation); if (summary) { tools.log(`${operation}:`); tools.log(` avg=${summary.average}ms, min=${summary.min}ms, max=${summary.max}ms, p95=${summary.p95}ms`); } } tools.log(`\nUBL to CII conversion testing completed.`); });