feat(core): improve in-memory validation, FatturaPA detection coverage, and published type compatibility

This commit is contained in:
2026-04-16 20:30:56 +00:00
parent 55bee02a2e
commit 3f37f6538c
60 changed files with 5723 additions and 6678 deletions
@@ -1,8 +1,8 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import { EInvoice } from '../../../ts/index.js';
import { InvoiceFormat, ValidationLevel } from '../../../ts/interfaces/common.js';
import { InvoiceFormat } from '../../../ts/interfaces/common.js';
import { FormatDetector } from '../../../ts/formats/utils/format.detector.js';
import { CorpusLoader } from '../../helpers/corpus.loader.js';
import { PerformanceTracker } from '../../helpers/performance.tracker.js';
import * as path from 'path';
/**
@@ -15,264 +15,40 @@ import * as path from 'path';
*/
tap.test('CORP-05: FatturaPA Corpus Processing - should process Italian FatturaPA files', async () => {
// Load FatturaPA test files
const fatturapaFiles = await CorpusLoader.loadCategory('FATTURAPA_OFFICIAL');
// Handle case where no files are found
if (fatturapaFiles.length === 0) {
console.log('⚠ No FatturaPA files found in corpus - skipping test');
return;
}
console.log(`Testing ${fatturapaFiles.length} FatturaPA files`);
const results = {
total: fatturapaFiles.length,
successful: 0,
failed: 0,
parseErrors: 0,
validationErrors: 0,
documentTypes: new Map<string, number>(),
transmissionFormats: new Map<string, number>(),
processingTimes: [] as number[]
};
const failures: Array<{
file: string;
error: string;
type: 'parse' | 'validation' | 'format';
}> = [];
// Italian-specific validation patterns
const italianValidations = {
vatNumber: /^IT\d{11}$/,
fiscalCode: /^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$/,
invoiceNumber: /^\w+\/\d{4}$/, // Common format: PREFIX/YEAR
codiceDestinatario: /^[A-Z0-9]{6,7}$/,
pecEmail: /^[a-zA-Z0-9._%+-]+@pec\.[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
};
let detectedCount = 0;
let unsupportedDecodeCount = 0;
for (const file of fatturapaFiles) {
try {
const xmlBuffer = await CorpusLoader.loadFile(file.path);
const xmlString = xmlBuffer.toString('utf-8');
// Track performance
const { result: invoice, metric } = await PerformanceTracker.track(
'fatturapa-processing',
async () => {
const einvoice = new EInvoice();
// FatturaPA has specific XML structure
if (xmlString.includes('FatturaElettronica')) {
// Process as FatturaPA
await einvoice.fromXmlString(xmlString);
einvoice.metadata = {
...einvoice.metadata,
format: InvoiceFormat.FATTURAPA
};
} else {
throw new Error('Not a valid FatturaPA file');
}
return einvoice;
},
{ file: file.path, size: file.size }
);
results.processingTimes.push(metric.duration);
// Extract FatturaPA specific information
const formatMatch = xmlString.match(/<FormatoTrasmissione>([^<]+)<\/FormatoTrasmissione>/);
const typeMatch = xmlString.match(/<TipoDocumento>([^<]+)<\/TipoDocumento>/);
if (formatMatch) {
const format = formatMatch[1];
results.transmissionFormats.set(format, (results.transmissionFormats.get(format) || 0) + 1);
}
if (typeMatch) {
const docType = typeMatch[1];
results.documentTypes.set(docType, (results.documentTypes.get(docType) || 0) + 1);
}
// Validate Italian-specific fields
const vatMatch = xmlString.match(/<IdCodice>(\d{11})<\/IdCodice>/);
const cfMatch = xmlString.match(/<CodiceFiscale>([A-Z0-9]{16})<\/CodiceFiscale>/);
const destMatch = xmlString.match(/<CodiceDestinatario>([A-Z0-9]{6,7})<\/CodiceDestinatario>/);
let italianFieldsValid = true;
if (vatMatch && !italianValidations.vatNumber.test('IT' + vatMatch[1])) {
italianFieldsValid = false;
console.log(` - Invalid VAT number format: ${vatMatch[1]}`);
}
if (cfMatch && !italianValidations.fiscalCode.test(cfMatch[1])) {
italianFieldsValid = false;
console.log(` - Invalid Codice Fiscale format: ${cfMatch[1]}`);
}
if (destMatch && !italianValidations.codiceDestinatario.test(destMatch[1])) {
italianFieldsValid = false;
console.log(` - Invalid Codice Destinatario: ${destMatch[1]}`);
}
// Validate the parsed invoice
try {
const validationResult = await invoice.validate(ValidationLevel.BUSINESS);
if (validationResult.valid && italianFieldsValid) {
results.successful++;
console.log(`${path.basename(file.path)}: Successfully processed`);
// Log key information
if (formatMatch) {
console.log(` - Transmission format: ${formatMatch[1]}`);
}
if (typeMatch) {
const docTypeMap: Record<string, string> = {
'TD01': 'Fattura',
'TD02': 'Acconto/Anticipo',
'TD03': 'Acconto/Anticipo su parcella',
'TD04': 'Nota di Credito',
'TD05': 'Nota di Debito',
'TD06': 'Parcella'
};
console.log(` - Document type: ${docTypeMap[typeMatch[1]] || typeMatch[1]}`);
}
} else {
results.validationErrors++;
failures.push({
file: path.basename(file.path),
error: validationResult.errors?.[0]?.message || 'Validation failed',
type: 'validation'
});
}
} catch (validationError: any) {
results.validationErrors++;
failures.push({
file: path.basename(file.path),
error: validationError.message,
type: 'validation'
});
}
} catch (error: any) {
results.failed++;
if (error.message.includes('Not a valid FatturaPA')) {
failures.push({
file: path.basename(file.path),
error: 'Invalid FatturaPA format',
type: 'format'
});
} else {
results.parseErrors++;
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=== FatturaPA Corpus Processing Summary ===');
console.log(`Total files: ${results.total}`);
console.log(`Successful: ${results.successful} (${(results.successful/results.total*100).toFixed(1)}%)`);
console.log(`Failed: ${results.failed}`);
console.log(` - Parse errors: ${results.parseErrors}`);
console.log(` - Validation errors: ${results.validationErrors}`);
console.log('\nTransmission Formats:');
results.transmissionFormats.forEach((count, format) => {
const formatMap: Record<string, string> = {
'FPA12': 'Pubblica Amministrazione',
'FPR12': 'Privati',
'SDI11': 'Sistema di Interscambio v1.1'
};
console.log(` - ${format}: ${formatMap[format] || format} (${count} files)`);
});
console.log('\nDocument Types:');
results.documentTypes.forEach((count, type) => {
const typeMap: Record<string, string> = {
'TD01': 'Fattura (Invoice)',
'TD02': 'Acconto/Anticipo (Advance)',
'TD03': 'Acconto/Anticipo su parcella',
'TD04': 'Nota di Credito (Credit Note)',
'TD05': 'Nota di Debito (Debit Note)',
'TD06': 'Parcella (Fee Note)'
};
console.log(` - ${type}: ${typeMap[type] || type} (${count} files)`);
});
if (failures.length > 0) {
console.log('\nFailure Details:');
failures.forEach(f => {
console.log(` ${f.file} [${f.type}]: ${f.error}`);
});
}
// Performance metrics
if (results.processingTimes.length > 0) {
const avgTime = results.processingTimes.reduce((a, b) => a + b, 0) / results.processingTimes.length;
const minTime = Math.min(...results.processingTimes);
const maxTime = Math.max(...results.processingTimes);
console.log('\nPerformance Metrics:');
console.log(` Average processing time: ${avgTime.toFixed(2)}ms`);
console.log(` Min time: ${minTime.toFixed(2)}ms`);
console.log(` Max time: ${maxTime.toFixed(2)}ms`);
}
// FatturaPA specific features validation
if (results.successful > 0 && fatturapaFiles.length > 0) {
// Test a sample file for specific features
const sampleFile = fatturapaFiles[0];
const xmlBuffer = await CorpusLoader.loadFile(sampleFile.path);
const xmlBuffer = await CorpusLoader.loadFile(file.path);
const xmlString = xmlBuffer.toString('utf-8');
console.log('\nFatturaPA Structure Analysis:');
// Check for mandatory sections
const mandatorySections = [
'FatturaElettronicaHeader',
'CedentePrestatore', // Seller
'CessionarioCommittente', // Buyer
'FatturaElettronicaBody',
'DatiGenerali',
'DatiBeniServizi'
];
for (const section of mandatorySections) {
if (xmlString.includes(section)) {
console.log(`✓ Contains mandatory section: ${section}`);
}
}
// Check for digital signature block
if (xmlString.includes('<ds:Signature') || xmlString.includes('<Signature')) {
console.log('✓ Contains digital signature block');
const fileName = path.basename(file.path);
const format = FormatDetector.detectFormat(xmlString);
expect(format).toEqual(InvoiceFormat.FATTURAPA);
detectedCount++;
try {
await EInvoice.fromXml(xmlString);
expect(true).toBeFalse();
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
expect(errorMessage.includes('FatturaPA decoder not yet implemented')).toBeTrue();
unsupportedDecodeCount++;
console.log(`${fileName}: Detection works and decode is explicitly unsupported`);
}
}
// Check if all failures are due to unimplemented decoder
const allNotImplemented = failures.every(f => f.error.includes('decoder not yet implemented'));
if (allNotImplemented && results.successful === 0) {
console.log('\n⚠ FatturaPA decoder not yet implemented - test skipped');
console.log(' This test will validate files once FatturaPA decoder is implemented');
return; // Skip success criteria
}
// Success criteria: at least 70% should pass (FatturaPA is complex)
const successRate = results.successful / results.total;
expect(successRate).toBeGreaterThan(0.7);
expect(detectedCount).toEqual(fatturapaFiles.length);
expect(unsupportedDecodeCount).toEqual(fatturapaFiles.length);
});
tap.start();
tap.start();
@@ -3,6 +3,7 @@ import { promises as fs } from 'fs';
import * as path from 'path';
import { CorpusLoader } from '../../helpers/corpus.loader.js';
import { PerformanceTracker } from '../../helpers/performance.tracker.js';
import { InvoiceFormat } from '../../../ts/interfaces/common.js';
tap.test('FD-09: FatturaPA Format Detection - should correctly identify Italian FatturaPA invoices', async () => {
// Get FatturaPA test files from corpus
@@ -18,8 +19,14 @@ tap.test('FD-09: FatturaPA Format Detection - should correctly identify Italian
// Import the format detector
const { FormatDetector } = await import('../../../ts/formats/utils/format.detector.js');
const sampledFiles = allFatturapaFiles.slice(0, 10);
for (const filePath of allFatturapaFiles.slice(0, 10)) { // Test first 10 for performance
if (sampledFiles.length === 0) {
console.log('No FatturaPA corpus files available for detection test');
return;
}
for (const filePath of sampledFiles) {
const fileName = path.basename(filePath);
try {
@@ -35,9 +42,7 @@ tap.test('FD-09: FatturaPA Format Detection - should correctly identify Italian
{ file: fileName }
);
// Verify it's detected as FatturaPA
if (format.toString().toLowerCase().includes('fatturapa') ||
format.toString().toLowerCase().includes('fattura')) {
if (format === InvoiceFormat.FATTURAPA) {
successCount++;
console.log(`${fileName}: Correctly detected as FatturaPA`);
} else {
@@ -46,9 +51,9 @@ tap.test('FD-09: FatturaPA Format Detection - should correctly identify Italian
file: fileName,
error: `Detected as ${format} instead of FatturaPA`
});
console.log(` ${fileName}: Detected as ${format} (FatturaPA detection may need implementation)`);
console.log(` ${fileName}: Detected as ${format} instead of FatturaPA`);
}
} catch (error) {
} catch (error: any) {
failureCount++;
failures.push({
file: fileName,
@@ -78,13 +83,8 @@ tap.test('FD-09: FatturaPA Format Detection - should correctly identify Italian
console.log(` P95: ${perfSummary.p95.toFixed(2)}ms`);
}
// Note: FatturaPA detection may not be fully implemented yet
if (successCount === 0 && allFatturapaFiles.length > 0) {
console.log('Note: FatturaPA format detection may need implementation');
}
// Expect at least some files to be processed without error
expect(successCount + failureCount).toBeGreaterThan(0);
expect(successCount).toEqual(sampledFiles.length);
expect(failureCount).toEqual(0);
});
tap.test('FD-09: FatturaPA Structure Detection - should detect FatturaPA by root element', async () => {
@@ -129,20 +129,8 @@ tap.test('FD-09: FatturaPA Structure Detection - should detect FatturaPA by root
);
console.log(`${test.name}: Detected as ${format}`);
// Should detect as FatturaPA (if implemented) or at least not as other formats
const formatStr = format.toString().toLowerCase();
const isNotOtherFormats = !formatStr.includes('ubl') &&
!formatStr.includes('cii') &&
!formatStr.includes('zugferd');
if (formatStr.includes('fattura')) {
console.log(` ✓ Correctly identified as FatturaPA`);
} else if (isNotOtherFormats) {
console.log(` ○ Not detected as other formats (FatturaPA detection may need implementation)`);
} else {
console.log(` ✗ Incorrectly detected as other format`);
}
expect(format).toEqual(InvoiceFormat.FATTURAPA);
console.log(' ✓ Correctly identified as FatturaPA');
}
});
@@ -181,14 +169,8 @@ tap.test('FD-09: FatturaPA Version Detection - should detect different FatturaPA
);
console.log(`FatturaPA ${test.version}: Detected as ${format}`);
// Should detect as FatturaPA regardless of version
const formatStr = format.toString().toLowerCase();
if (formatStr.includes('fattura')) {
console.log(` ✓ Version ${test.version} correctly detected`);
} else {
console.log(` ○ Version detection may need implementation`);
}
expect(format).toEqual(InvoiceFormat.FATTURAPA);
console.log(` ✓ Version ${test.version} correctly detected`);
}
});
@@ -229,16 +211,10 @@ tap.test('FD-09: FatturaPA vs Other Formats - should distinguish from other XML
);
console.log(`${test.name}: Detected as ${format}`);
const formatStr = format.toString().toLowerCase();
const hasExpectedFormat = formatStr.includes(test.expectedFormat);
if (hasExpectedFormat) {
console.log(` ✓ Correctly distinguished ${test.name}`);
} else {
console.log(` ○ Format distinction may need refinement`);
}
expect(format.toString().toLowerCase()).toContain(test.expectedFormat);
console.log(` ✓ Correctly distinguished ${test.name}`);
}
});
tap.start();
tap.start();