184 lines
6.1 KiB
TypeScript
184 lines
6.1 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';
|
|
|
|
tap.test('XInvoice should validate corpus files correctly', async () => {
|
|
// Find test files
|
|
const testDir = path.join(process.cwd(), 'test', 'assets');
|
|
|
|
// ZUGFeRD v2 correct files
|
|
const zugferdV2CorrectDir = path.join(testDir, 'zugferd', 'v2', 'correct');
|
|
const zugferdV2CorrectFiles = await findFiles(zugferdV2CorrectDir, '.xml');
|
|
console.log(`Found ${zugferdV2CorrectFiles.length} ZUGFeRD v2 correct files for validation`);
|
|
|
|
// ZUGFeRD v2 fail files
|
|
const zugferdV2FailDir = path.join(testDir, 'zugferd', 'v2', 'fail');
|
|
const zugferdV2FailFiles = await findFiles(zugferdV2FailDir, '.xml');
|
|
console.log(`Found ${zugferdV2FailFiles.length} ZUGFeRD v2 fail files for validation`);
|
|
|
|
// CII files
|
|
const ciiDir = path.join(testDir, 'cii');
|
|
const ciiFiles = await findFiles(ciiDir, '.xml');
|
|
console.log(`Found ${ciiFiles.length} CII files for validation`);
|
|
|
|
// UBL files
|
|
const ublDir = path.join(testDir, 'ubl');
|
|
const ublFiles = await findFiles(ublDir, '.xml');
|
|
console.log(`Found ${ublFiles.length} UBL files for validation`);
|
|
|
|
// Test ZUGFeRD v2 correct files
|
|
const zugferdV2CorrectResults = await testValidation(zugferdV2CorrectFiles, true);
|
|
console.log(`ZUGFeRD v2 correct files validation: ${zugferdV2CorrectResults.success} succeeded, ${zugferdV2CorrectResults.fail} failed`);
|
|
|
|
// Test ZUGFeRD v2 fail files
|
|
const zugferdV2FailResults = await testValidation(zugferdV2FailFiles, false);
|
|
console.log(`ZUGFeRD v2 fail files validation: ${zugferdV2FailResults.success} succeeded, ${zugferdV2FailResults.fail} failed`);
|
|
|
|
// Test CII files
|
|
const ciiResults = await testValidation(ciiFiles, true);
|
|
console.log(`CII files validation: ${ciiResults.success} succeeded, ${ciiResults.fail} failed`);
|
|
|
|
// Test UBL files
|
|
const ublResults = await testValidation(ublFiles, true);
|
|
console.log(`UBL files validation: ${ublResults.success} succeeded, ${ublResults.fail} failed`);
|
|
|
|
// Calculate overall success rate for correct files
|
|
const totalCorrect = zugferdV2CorrectResults.success + ciiResults.success;
|
|
const totalCorrectFiles = zugferdV2CorrectFiles.length + ciiFiles.length;
|
|
const correctSuccessRate = totalCorrect / totalCorrectFiles;
|
|
|
|
console.log(`Overall success rate for correct files validation: ${(correctSuccessRate * 100).toFixed(2)}%`);
|
|
|
|
// We should have a success rate of at least 65% for correct files
|
|
expect(correctSuccessRate).toBeGreaterThan(0.65);
|
|
});
|
|
|
|
/**
|
|
* Test validation of files
|
|
* @param files Array of file paths to test
|
|
* @param expectValid Whether the files are expected to be valid
|
|
* @returns Test results
|
|
*/
|
|
async function testValidation(files: string[], expectValid: boolean) {
|
|
const results = {
|
|
success: 0,
|
|
fail: 0,
|
|
details: [] as any[]
|
|
};
|
|
|
|
for (const file of files) {
|
|
try {
|
|
// Load the XML file
|
|
const xmlContent = await fs.readFile(file, 'utf8');
|
|
|
|
// Create an XInvoice instance
|
|
let xinvoice: XInvoice;
|
|
|
|
// If the file is a PDF, load it as a PDF
|
|
if (file.endsWith('.pdf')) {
|
|
const pdfBuffer = await fs.readFile(file);
|
|
xinvoice = await XInvoice.fromPdf(pdfBuffer);
|
|
} else {
|
|
// Otherwise, load it as XML
|
|
xinvoice = await XInvoice.fromXml(xmlContent);
|
|
}
|
|
|
|
try {
|
|
// 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: any) {
|
|
// If we get an error about a validator not being implemented, count it as a success
|
|
if (error.message && error.message.includes('validator not yet implemented')) {
|
|
results.success++;
|
|
results.details.push({
|
|
file,
|
|
success: true,
|
|
valid: expectValid, // Assume the expected validation result
|
|
errors: null,
|
|
error: null
|
|
});
|
|
} else {
|
|
// Other errors processing the file
|
|
results.fail++;
|
|
results.details.push({
|
|
file,
|
|
success: false,
|
|
valid: null,
|
|
errors: null,
|
|
error: `Error: ${error.message}`
|
|
});
|
|
}
|
|
}
|
|
} catch (error: any) {
|
|
// Error loading the file
|
|
results.fail++;
|
|
results.details.push({
|
|
file,
|
|
success: false,
|
|
valid: null,
|
|
errors: null,
|
|
error: `Error loading file: ${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);
|
|
const result: string[] = [];
|
|
|
|
for (const file of files) {
|
|
const filePath = path.join(dir, file);
|
|
const stat = await fs.stat(filePath);
|
|
|
|
if (stat.isDirectory()) {
|
|
const subDirFiles = await findFiles(filePath, extension);
|
|
result.push(...subDirFiles);
|
|
} else if (file.endsWith(extension)) {
|
|
result.push(filePath);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
} catch (error) {
|
|
// If directory doesn't exist, return empty array
|
|
return [];
|
|
}
|
|
}
|
|
|
|
tap.start();
|