xinvoice/test/test.zugferd-corpus.ts

206 lines
7.0 KiB
TypeScript

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<string[]> {
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();