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 validation of corpus files tap.test('XInvoice should validate corpus files correctly', async () => { // Get a subset of files for validation testing const zugferdV2CorrectFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/ZUGFeRDv2/correct'), '.pdf', 5); const zugferdV2FailFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/ZUGFeRDv2/fail'), '.pdf', 5); const ciiFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/CII'), '.xml', 5); const ublFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/UBL'), '.xml', 5); // Log the number of files found console.log(`Found ${zugferdV2CorrectFiles.length} ZUGFeRD v2 correct files for validation`); console.log(`Found ${zugferdV2FailFiles.length} ZUGFeRD v2 fail files for validation`); console.log(`Found ${ciiFiles.length} CII files for validation`); console.log(`Found ${ublFiles.length} UBL files for validation`); // Test ZUGFeRD v2 correct files const zugferdV2CorrectResults = await testValidation(zugferdV2CorrectFiles, true, true); console.log(`ZUGFeRD v2 correct files validation: ${zugferdV2CorrectResults.success} succeeded, ${zugferdV2CorrectResults.fail} failed`); // Test ZUGFeRD v2 fail files const zugferdV2FailResults = await testValidation(zugferdV2FailFiles, true, false); console.log(`ZUGFeRD v2 fail files validation: ${zugferdV2FailResults.success} succeeded, ${zugferdV2FailResults.fail} failed`); // Test CII files const ciiResults = await testValidation(ciiFiles, false, true); console.log(`CII files validation: ${ciiResults.success} succeeded, ${ciiResults.fail} failed`); // Test UBL files const ublResults = await testValidation(ublFiles, false, true); console.log(`UBL files validation: ${ublResults.success} succeeded, ${ublResults.fail} failed`); // Check that we have a reasonable success rate for correct files const totalCorrectSuccess = zugferdV2CorrectResults.success + ciiResults.success + ublResults.success; const totalCorrectFiles = zugferdV2CorrectFiles.length + ciiFiles.length + ublFiles.length; const correctSuccessRate = totalCorrectSuccess / totalCorrectFiles; console.log(`Overall success rate for correct files validation: ${(correctSuccessRate * 100).toFixed(2)}%`); // We should have a success rate of at least 60% for correct files // Note: This is lower than ideal because we haven't implemented the XRechnung validator yet expect(correctSuccessRate).toBeGreaterThan(0.6); // Save the test results to a file const testDir = path.join(process.cwd(), 'test', 'output'); await fs.mkdir(testDir, { recursive: true }); const testResults = { zugferdV2Correct: zugferdV2CorrectResults, zugferdV2Fail: zugferdV2FailResults, cii: ciiResults, ubl: ublResults, totalCorrectSuccessRate: correctSuccessRate }; await fs.writeFile( path.join(testDir, 'validation-corpus-results.json'), JSON.stringify(testResults, null, 2) ); }); /** * Tests validation of files and returns the results * @param files List of files to test * @param isPdf Whether the files are PDFs * @param expectValid Whether we expect the files to be valid * @returns Test results */ async function testValidation(files: string[], isPdf: boolean, expectValid: boolean): Promise<{ success: number, fail: number, details: any[] }> { const results = { success: 0, fail: 0, details: [] as any[] }; for (const file of files) { try { // Create XInvoice from file let xinvoice: XInvoice; if (isPdf) { const fileBuffer = await fs.readFile(file); xinvoice = await XInvoice.fromPdf(fileBuffer); } else { const xmlContent = await fs.readFile(file, 'utf8'); xinvoice = await XInvoice.fromXml(xmlContent); } // Validate the invoice const validationResult = await xinvoice.validate(ValidationLevel.SYNTAX); // Check if the validation result matches our expectation if (validationResult.valid === expectValid) { // Success results.success++; results.details.push({ file, success: true, valid: validationResult.valid, errors: validationResult.errors, error: null }); } else { // Validation result doesn't match expectation results.fail++; results.details.push({ file, success: false, valid: validationResult.valid, errors: validationResult.errors, error: `Validation result (${validationResult.valid}) doesn't match expectation (${expectValid})` }); } } catch (error) { // Error processing the file results.fail++; results.details.push({ file, success: false, valid: null, errors: 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 * @param limit Maximum number of files to return * @returns Array of file paths */ async function findFiles(dir: string, extension: string, limit?: number): Promise { try { const files = await fs.readdir(dir, { withFileTypes: true }); const result: string[] = []; for (const file of files) { if (limit && result.length >= limit) { break; } const filePath = path.join(dir, file.name); if (file.isDirectory()) { // Recursively search subdirectories const remainingLimit = limit ? limit - result.length : undefined; const subDirFiles = await findFiles(filePath, extension, remainingLimit); result.push(...subDirFiles); if (limit && result.length >= limit) { break; } } 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();