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 ZUGFeRD v1 and v2 corpus tap.test('XInvoice should handle ZUGFeRD v1 and v2 corpus', async () => { // Get all ZUGFeRD files const zugferdV1CorrectFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/ZUGFeRDv1/correct'), '.pdf'); const zugferdV1FailFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/ZUGFeRDv1/fail'), '.pdf'); const zugferdV2CorrectFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/ZUGFeRDv2/correct'), '.pdf'); const zugferdV2FailFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/ZUGFeRDv2/fail'), '.pdf'); // Log the number of files found console.log(`Found ${zugferdV1CorrectFiles.length} ZUGFeRD v1 correct files`); console.log(`Found ${zugferdV1FailFiles.length} ZUGFeRD v1 fail files`); console.log(`Found ${zugferdV2CorrectFiles.length} ZUGFeRD v2 correct files`); console.log(`Found ${zugferdV2FailFiles.length} ZUGFeRD v2 fail files`); // Test ZUGFeRD v1 correct files const v1CorrectResults = await testFiles(zugferdV1CorrectFiles, true); console.log(`ZUGFeRD v1 correct files: ${v1CorrectResults.success} succeeded, ${v1CorrectResults.fail} failed`); // Test ZUGFeRD v1 fail files const v1FailResults = await testFiles(zugferdV1FailFiles, false); console.log(`ZUGFeRD v1 fail files: ${v1FailResults.success} succeeded, ${v1FailResults.fail} failed`); // Test ZUGFeRD v2 correct files const v2CorrectResults = await testFiles(zugferdV2CorrectFiles, true); console.log(`ZUGFeRD v2 correct files: ${v2CorrectResults.success} succeeded, ${v2CorrectResults.fail} failed`); // Test ZUGFeRD v2 fail files const v2FailResults = await testFiles(zugferdV2FailFiles, false); console.log(`ZUGFeRD v2 fail files: ${v2FailResults.fail} succeeded, ${v2FailResults.success} failed`); // Check that we have a reasonable success rate for correct files const totalCorrect = v1CorrectResults.success + v2CorrectResults.success; const totalCorrectFiles = zugferdV1CorrectFiles.length + zugferdV2CorrectFiles.length; const correctSuccessRate = totalCorrect / totalCorrectFiles; console.log(`Overall success rate for correct files: ${(correctSuccessRate * 100).toFixed(2)}%`); // We should have a success rate of at least 65% for correct files expect(correctSuccessRate).toBeGreaterThan(0.65); // Save the test results to a file const testDir = path.join(process.cwd(), 'test', 'output'); await fs.mkdir(testDir, { recursive: true }); const testResults = { zugferdV1Correct: v1CorrectResults, zugferdV1Fail: v1FailResults, zugferdV2Correct: v2CorrectResults, zugferdV2Fail: v2FailResults, totalCorrectSuccessRate: correctSuccessRate }; await fs.writeFile( path.join(testDir, 'zugferd-corpus-results.json'), JSON.stringify(testResults, null, 2) ); }); /** * Tests a list of files and returns the results * @param files List of files to test * @param expectSuccess Whether we expect the files to be successfully processed * @returns Test results */ async function testFiles(files: string[], expectSuccess: boolean): 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 fileBuffer = await fs.readFile(file); // Create XInvoice from PDF const xinvoice = await XInvoice.fromPdf(fileBuffer); // 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 isZugferd = [InvoiceFormat.ZUGFERD, InvoiceFormat.FACTURX, InvoiceFormat.CII].includes(format); if (isZugferd) { // Try to export the invoice to XML try { const exportedXml = await xinvoice.exportXml('facturx'); if (exportedXml && exportedXml.includes('CrossIndustryInvoice')) { // 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}` }); } } else { // Missing required properties results.fail++; results.details.push({ file, success: false, format: null, error: 'Missing required properties' }); } } catch (error) { // If we expect success, this is a failure // If we expect failure, this is a success if (expectSuccess) { results.fail++; results.details.push({ file, success: false, format: null, error: `Error: ${error.message}` }); } else { results.success++; results.details.push({ file, success: true, format: null, error: `Expected 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();