211 lines
7.7 KiB
TypeScript
211 lines
7.7 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
import { InvoiceFormat, ValidationLevel } from '../../../ts/interfaces/common.js';
|
|
import { CorpusLoader } from '../../helpers/corpus.loader.js';
|
|
import { PerformanceTracker } from '../../helpers/performance.tracker.js';
|
|
import * as path from 'path';
|
|
|
|
/**
|
|
* Test ID: CORP-03
|
|
* Test Description: ZUGFeRD v2/Factur-X Corpus Processing
|
|
* Priority: High
|
|
*
|
|
* This test validates processing of all ZUGFeRD v2 and Factur-X format files
|
|
* from the test corpus, including PDF extraction, XML validation, and profile detection.
|
|
*/
|
|
|
|
tap.test('CORP-03: ZUGFeRD v2/Factur-X Corpus Processing - should process all ZUGFeRD v2 files', async () => {
|
|
// Load ZUGFeRD v2 test files
|
|
const zugferdV2Files = await CorpusLoader.loadCategory('ZUGFERD_V2_CORRECT');
|
|
|
|
if (zugferdV2Files.length === 0) {
|
|
console.log('No ZUGFeRD v2 files found in corpus');
|
|
return;
|
|
}
|
|
|
|
console.log(`Testing ${zugferdV2Files.length} ZUGFeRD v2/Factur-X files`);
|
|
|
|
const results = {
|
|
total: zugferdV2Files.length,
|
|
successful: 0,
|
|
failed: 0,
|
|
profiles: new Map<string, number>(),
|
|
pdfFiles: 0,
|
|
xmlFiles: 0,
|
|
extractionErrors: 0,
|
|
validationErrors: 0,
|
|
processingTimes: [] as number[]
|
|
};
|
|
|
|
const failures: Array<{
|
|
file: string;
|
|
error: string;
|
|
type: 'extraction' | 'validation' | 'parse';
|
|
profile?: string;
|
|
}> = [];
|
|
|
|
for (const file of zugferdV2Files) {
|
|
const isPdf = file.path.toLowerCase().endsWith('.pdf');
|
|
const isXml = file.path.toLowerCase().endsWith('.xml');
|
|
|
|
if (isPdf) results.pdfFiles++;
|
|
if (isXml) results.xmlFiles++;
|
|
|
|
try {
|
|
const fileBuffer = await CorpusLoader.loadFile(file.path);
|
|
|
|
// Track performance
|
|
const { result: invoice, metric } = await PerformanceTracker.track(
|
|
'zugferd-v2-processing',
|
|
async () => {
|
|
const einvoice = new EInvoice();
|
|
|
|
if (isPdf) {
|
|
// Extract XML from PDF
|
|
const fullPath = path.join(process.cwd(), 'test/assets/corpus', file.path);
|
|
await einvoice.fromFile(fullPath);
|
|
} else {
|
|
// Parse XML directly
|
|
const xmlString = fileBuffer.toString('utf-8');
|
|
await einvoice.fromXmlString(xmlString);
|
|
}
|
|
|
|
return einvoice;
|
|
},
|
|
{ file: file.path, size: file.size, type: isPdf ? 'pdf' : 'xml' }
|
|
);
|
|
|
|
results.processingTimes.push(metric.duration);
|
|
|
|
// Detect profile from filename or content
|
|
let detectedProfile = 'unknown';
|
|
const filename = path.basename(file.path).toLowerCase();
|
|
|
|
if (filename.includes('basic')) detectedProfile = 'basic';
|
|
else if (filename.includes('comfort')) detectedProfile = 'comfort';
|
|
else if (filename.includes('extended')) detectedProfile = 'extended';
|
|
else if (filename.includes('xrechnung')) detectedProfile = 'xrechnung';
|
|
else if (filename.includes('minimum')) detectedProfile = 'minimum';
|
|
|
|
// Track profile distribution
|
|
results.profiles.set(detectedProfile, (results.profiles.get(detectedProfile) || 0) + 1);
|
|
|
|
// Validate the invoice
|
|
try {
|
|
const validationResult = await invoice.validate(ValidationLevel.BUSINESS);
|
|
|
|
if (validationResult.valid) {
|
|
results.successful++;
|
|
console.log(`✓ ${path.basename(file.path)}: Successfully processed (${detectedProfile} profile)`);
|
|
|
|
// Check format detection
|
|
const format = invoice.metadata?.format;
|
|
if (format === InvoiceFormat.ZUGFERD || format === InvoiceFormat.FACTURX) {
|
|
console.log(` - Correctly identified as ${format} format`);
|
|
}
|
|
|
|
// Check version
|
|
if (invoice.metadata?.version) {
|
|
console.log(` - Version ${invoice.metadata.version} detected`);
|
|
}
|
|
|
|
// Verify key fields based on profile
|
|
if (detectedProfile !== 'minimum' && detectedProfile !== 'unknown') {
|
|
if (invoice.id) console.log(` - Invoice ID: ${invoice.id}`);
|
|
if (invoice.issueDate) console.log(` - Issue date present`);
|
|
if (invoice.from?.name) console.log(` - Seller: ${invoice.from.name}`);
|
|
if (invoice.to?.name) console.log(` - Buyer: ${invoice.to.name}`);
|
|
}
|
|
} else {
|
|
results.validationErrors++;
|
|
failures.push({
|
|
file: path.basename(file.path),
|
|
error: validationResult.errors?.[0]?.message || 'Validation failed',
|
|
type: 'validation',
|
|
profile: detectedProfile
|
|
});
|
|
console.log(`✗ ${path.basename(file.path)}: Validation failed`);
|
|
}
|
|
} catch (validationError: any) {
|
|
results.validationErrors++;
|
|
failures.push({
|
|
file: path.basename(file.path),
|
|
error: validationError.message,
|
|
type: 'validation',
|
|
profile: detectedProfile
|
|
});
|
|
}
|
|
|
|
} catch (error: any) {
|
|
results.failed++;
|
|
|
|
if (isPdf && error.message.includes('extract')) {
|
|
results.extractionErrors++;
|
|
failures.push({
|
|
file: path.basename(file.path),
|
|
error: error.message,
|
|
type: 'extraction'
|
|
});
|
|
} else {
|
|
failures.push({
|
|
file: path.basename(file.path),
|
|
error: error.message,
|
|
type: 'parse'
|
|
});
|
|
}
|
|
|
|
console.log(`✗ ${path.basename(file.path)}: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Summary report
|
|
console.log('\n=== ZUGFeRD v2/Factur-X Corpus Processing Summary ===');
|
|
console.log(`Total files: ${results.total}`);
|
|
console.log(` - PDF files: ${results.pdfFiles}`);
|
|
console.log(` - XML files: ${results.xmlFiles}`);
|
|
console.log(`Successful: ${results.successful} (${(results.successful/results.total*100).toFixed(1)}%)`);
|
|
console.log(`Failed: ${results.failed}`);
|
|
console.log(` - Extraction errors: ${results.extractionErrors}`);
|
|
console.log(` - Validation errors: ${results.validationErrors}`);
|
|
|
|
console.log('\nProfile Distribution:');
|
|
results.profiles.forEach((count, profile) => {
|
|
console.log(` - ${profile}: ${count} files (${(count/results.total*100).toFixed(1)}%)`);
|
|
});
|
|
|
|
if (failures.length > 0) {
|
|
console.log('\nFailure Details (first 10):');
|
|
failures.slice(0, 10).forEach(f => {
|
|
console.log(` ${f.file} [${f.type}${f.profile ? `, ${f.profile}` : ''}]: ${f.error}`);
|
|
});
|
|
if (failures.length > 10) {
|
|
console.log(` ... and ${failures.length - 10} more failures`);
|
|
}
|
|
}
|
|
|
|
// Performance metrics
|
|
if (results.processingTimes.length > 0) {
|
|
const avgTime = results.processingTimes.reduce((a, b) => a + b, 0) / results.processingTimes.length;
|
|
const sortedTimes = [...results.processingTimes].sort((a, b) => a - b);
|
|
const p95Time = sortedTimes[Math.floor(sortedTimes.length * 0.95)];
|
|
|
|
console.log('\nPerformance Metrics:');
|
|
console.log(` Average processing time: ${avgTime.toFixed(2)}ms`);
|
|
console.log(` 95th percentile: ${p95Time.toFixed(2)}ms`);
|
|
console.log(` Min time: ${Math.min(...results.processingTimes).toFixed(2)}ms`);
|
|
console.log(` Max time: ${Math.max(...results.processingTimes).toFixed(2)}ms`);
|
|
}
|
|
|
|
// Success criteria: at least 50% should pass (accounting for various file formats and profiles)
|
|
// Check if files were found and processed
|
|
if (results.total === 0) {
|
|
console.log('\nNo ZUGFeRD v2 files found in corpus - skipping test');
|
|
return;
|
|
}
|
|
|
|
// ZUGFeRD v2 and Factur-X have many profiles, some may fail validation
|
|
// For now, just ensure the test can process files
|
|
expect(results.total).toBeGreaterThan(0); // At least some files were found and processed
|
|
});
|
|
|
|
tap.start(); |