import { tap, expect } from '@push.rocks/tapbundle'; import { XInvoice } from '../ts/classes.xinvoice.js'; import { InvoiceFormat, ValidationLevel } from '../ts/interfaces/common.js'; import * as fs from 'fs/promises'; import * as path from 'path'; // Test XML-Rechnung corpus (CII and UBL) tap.test('XInvoice should handle XML-Rechnung corpus', async () => { // Get all XML-Rechnung files const ciiFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/CII'), '.xml'); const ublFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/UBL'), '.xml'); const fxFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/FX'), '.xml'); // Log the number of files found console.log(`Found ${ciiFiles.length} CII files`); console.log(`Found ${ublFiles.length} UBL files`); console.log(`Found ${fxFiles.length} FX files`); // Test CII files const ciiResults = await testFiles(ciiFiles, InvoiceFormat.CII); console.log(`CII files: ${ciiResults.success} succeeded, ${ciiResults.fail} failed`); // Test UBL files const ublResults = await testFiles(ublFiles, InvoiceFormat.UBL); console.log(`UBL files: ${ublResults.success} succeeded, ${ublResults.fail} failed`); // Test FX files const fxResults = await testFiles(fxFiles, InvoiceFormat.FACTURX); console.log(`FX files: ${fxResults.success} succeeded, ${fxResults.fail} failed`); // Check that we have a reasonable success rate const totalSuccess = ciiResults.success + ublResults.success + fxResults.success; const totalFiles = ciiFiles.length + ublFiles.length + fxFiles.length; const successRate = totalSuccess / totalFiles; console.log(`Overall success rate: ${(successRate * 100).toFixed(2)}%`); // We should have a success rate of at least 80% for XML files expect(successRate).toBeGreaterThan(0.8); // Save the test results to a file const testDir = path.join(process.cwd(), 'test', 'output'); await fs.mkdir(testDir, { recursive: true }); const testResults = { cii: ciiResults, ubl: ublResults, fx: fxResults, totalSuccessRate: successRate }; await fs.writeFile( path.join(testDir, 'xml-rechnung-corpus-results.json'), JSON.stringify(testResults, null, 2) ); }); /** * Tests a list of XML files and returns the results * @param files List of files to test * @param expectedFormat Expected format of the files * @returns Test results */ async function testFiles(files: string[], expectedFormat: InvoiceFormat): Promise<{ success: number, fail: number, details: any[] }> { const results = { success: 0, fail: 0, details: [] as any[] }; for (const file of files) { try { // Read the file const xmlContent = await fs.readFile(file, 'utf8'); // Create XInvoice from XML const xinvoice = await XInvoice.fromXml(xmlContent); // Check that the XInvoice instance has the expected properties if (xinvoice && xinvoice.from && xinvoice.to && xinvoice.items) { // Check that the format is detected correctly const format = xinvoice.getFormat(); const isCorrectFormat = format === expectedFormat || (expectedFormat === InvoiceFormat.CII && format === InvoiceFormat.FACTURX) || (expectedFormat === InvoiceFormat.FACTURX && format === InvoiceFormat.CII) || (expectedFormat === InvoiceFormat.UBL && format === InvoiceFormat.XRECHNUNG) || (expectedFormat === InvoiceFormat.XRECHNUNG && format === InvoiceFormat.UBL); if (isCorrectFormat) { // Try to export the invoice back to XML try { let exportFormat = 'facturx'; if (format === InvoiceFormat.UBL || format === InvoiceFormat.XRECHNUNG) { exportFormat = 'xrechnung'; } const exportedXml = await xinvoice.exportXml(exportFormat as any); if (exportedXml) { // Success results.success++; results.details.push({ file, success: true, format, error: null }); } else { // Failed to export valid XML results.fail++; results.details.push({ file, success: false, format, error: 'Failed to export valid XML' }); } } catch (exportError) { // Failed to export XML results.fail++; results.details.push({ file, success: false, format, error: `Export error: ${exportError.message}` }); } } else { // Wrong format detected results.fail++; results.details.push({ file, success: false, format, error: `Wrong format detected: ${format}, expected: ${expectedFormat}` }); } } else { // Missing required properties results.fail++; results.details.push({ file, success: false, format: null, error: 'Missing required properties' }); } } catch (error) { // Error processing the file results.fail++; results.details.push({ file, success: false, format: null, error: `Error: ${error.message}` }); } } return results; } /** * Recursively finds files with a specific extension in a directory * @param dir Directory to search * @param extension File extension to look for * @returns Array of file paths */ async function findFiles(dir: string, extension: string): Promise { try { const files = await fs.readdir(dir, { withFileTypes: true }); const result: string[] = []; for (const file of files) { const filePath = path.join(dir, file.name); if (file.isDirectory()) { // Recursively search subdirectories const subDirFiles = await findFiles(filePath, extension); result.push(...subDirFiles); } else if (file.name.toLowerCase().endsWith(extension)) { // Add files with the specified extension to the list result.push(filePath); } } return result; } catch (error) { console.error(`Error finding files in ${dir}:`, error); return []; } } // Run the tests tap.start();