166 lines
5.3 KiB
TypeScript
166 lines
5.3 KiB
TypeScript
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||
|
import { XInvoice } from '../ts/classes.xinvoice.js';
|
||
|
import { InvoiceFormat } from '../ts/interfaces/common.js';
|
||
|
import * as fs from 'fs/promises';
|
||
|
import * as path from 'path';
|
||
|
|
||
|
// Test circular export/import of corpus files
|
||
|
tap.test('XInvoice should maintain data integrity through export/import cycle', async () => {
|
||
|
// Get a subset of files for circular testing
|
||
|
const ciiFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/CII'), '.xml', 3);
|
||
|
const ublFiles = await findFiles(path.join(process.cwd(), 'test/assets/corpus/XML-Rechnung/UBL'), '.xml', 3);
|
||
|
|
||
|
// Log the number of files found
|
||
|
console.log(`Found ${ciiFiles.length} CII files for circular testing`);
|
||
|
console.log(`Found ${ublFiles.length} UBL files for circular testing`);
|
||
|
|
||
|
// Test CII files
|
||
|
const ciiResults = await testCircular(ciiFiles, 'facturx');
|
||
|
console.log(`CII files circular testing: ${ciiResults.success} succeeded, ${ciiResults.fail} failed`);
|
||
|
|
||
|
// Test UBL files
|
||
|
const ublResults = await testCircular(ublFiles, 'xrechnung');
|
||
|
console.log(`UBL files circular testing: ${ublResults.success} succeeded, ${ublResults.fail} failed`);
|
||
|
|
||
|
// Check that we have a reasonable success rate
|
||
|
const totalSuccess = ciiResults.success + ublResults.success;
|
||
|
const totalFiles = ciiFiles.length + ublFiles.length;
|
||
|
const successRate = totalSuccess / totalFiles;
|
||
|
|
||
|
console.log(`Overall success rate for circular testing: ${(successRate * 100).toFixed(2)}%`);
|
||
|
|
||
|
// We should have a success rate of at least 80% for circular testing
|
||
|
expect(successRate).toBeGreaterThan(0.8);
|
||
|
|
||
|
// Save the test results to a file
|
||
|
const testDir = path.join(process.cwd(), 'test', 'output');
|
||
|
await fs.mkdir(testDir, { recursive: true });
|
||
|
|
||
|
const testResults = {
|
||
|
cii: ciiResults,
|
||
|
ubl: ublResults,
|
||
|
totalSuccessRate: successRate
|
||
|
};
|
||
|
|
||
|
await fs.writeFile(
|
||
|
path.join(testDir, 'circular-corpus-results.json'),
|
||
|
JSON.stringify(testResults, null, 2)
|
||
|
);
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Tests circular export/import of files and returns the results
|
||
|
* @param files List of files to test
|
||
|
* @param exportFormat Format to export to
|
||
|
* @returns Test results
|
||
|
*/
|
||
|
async function testCircular(files: string[], exportFormat: string): 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 xmlContent = await fs.readFile(file, 'utf8');
|
||
|
|
||
|
// Create XInvoice from XML
|
||
|
const xinvoice = await XInvoice.fromXml(xmlContent);
|
||
|
|
||
|
// Export to XML
|
||
|
const exportedXml = await xinvoice.exportXml(exportFormat as any);
|
||
|
|
||
|
// Create a new XInvoice from the exported XML
|
||
|
const reimportedXInvoice = await XInvoice.fromXml(exportedXml);
|
||
|
|
||
|
// Check that key properties match
|
||
|
const keysMatch =
|
||
|
reimportedXInvoice.from.name === xinvoice.from.name &&
|
||
|
reimportedXInvoice.to.name === xinvoice.to.name &&
|
||
|
reimportedXInvoice.items.length === xinvoice.items.length;
|
||
|
|
||
|
if (keysMatch) {
|
||
|
// Success
|
||
|
results.success++;
|
||
|
results.details.push({
|
||
|
file,
|
||
|
success: true,
|
||
|
error: null
|
||
|
});
|
||
|
|
||
|
// Save the exported XML for inspection
|
||
|
const testDir = path.join(process.cwd(), 'test', 'output', 'circular');
|
||
|
await fs.mkdir(testDir, { recursive: true });
|
||
|
|
||
|
const fileName = path.basename(file);
|
||
|
await fs.writeFile(path.join(testDir, `${fileName}-exported.xml`), exportedXml);
|
||
|
} else {
|
||
|
// Key properties don't match
|
||
|
results.fail++;
|
||
|
results.details.push({
|
||
|
file,
|
||
|
success: false,
|
||
|
error: 'Key properties don\'t match after reimport'
|
||
|
});
|
||
|
}
|
||
|
} catch (error) {
|
||
|
// Error processing the file
|
||
|
results.fail++;
|
||
|
results.details.push({
|
||
|
file,
|
||
|
success: false,
|
||
|
error: `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
|
||
|
* @param limit Maximum number of files to return
|
||
|
* @returns Array of file paths
|
||
|
*/
|
||
|
async function findFiles(dir: string, extension: string, limit?: number): Promise<string[]> {
|
||
|
try {
|
||
|
const files = await fs.readdir(dir, { withFileTypes: true });
|
||
|
|
||
|
const result: string[] = [];
|
||
|
|
||
|
for (const file of files) {
|
||
|
if (limit && result.length >= limit) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
const filePath = path.join(dir, file.name);
|
||
|
|
||
|
if (file.isDirectory()) {
|
||
|
// Recursively search subdirectories
|
||
|
const remainingLimit = limit ? limit - result.length : undefined;
|
||
|
const subDirFiles = await findFiles(filePath, extension, remainingLimit);
|
||
|
result.push(...subDirFiles);
|
||
|
|
||
|
if (limit && result.length >= limit) {
|
||
|
break;
|
||
|
}
|
||
|
} 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();
|