import { expect, tap } from '@git.zone/tstest/tapbundle';
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';
tap.test('VAL-05: Calculation Validation - should validate invoice calculations and totals', async () => {
// Get EN16931 UBL test files that specifically test calculation rules (BR-CO-*)
const calculationFiles = await CorpusLoader.getFiles('EN16931_UBL_INVOICE');
const coFiles = calculationFiles.filter(f => path.basename(f).startsWith('BR-CO-') && f.endsWith('.xml'));
console.log(`Testing calculation validation on ${coFiles.length} BR-CO-* files`);
const { EInvoice } = await import('../../../ts/index.js');
let validCalculations = 0;
let invalidCalculations = 0;
let errorCount = 0;
const calculationErrors: { file: string; errors: string[] }[] = [];
for (const filePath of coFiles.slice(0, 10)) { // Test first 10 calculation files
const fileName = path.basename(filePath);
try {
const xmlContent = await fs.readFile(filePath, 'utf-8');
const { result: einvoice } = await PerformanceTracker.track(
'calculation-xml-loading',
async () => await EInvoice.fromXml(xmlContent)
);
const { result: validation } = await PerformanceTracker.track(
'calculation-validation',
async () => {
return await einvoice.validate(/* ValidationLevel.BUSINESS */);
},
{ file: fileName }
);
// BR-CO files are designed to test calculation violations
if (!validation.valid && validation.errors) {
const calcErrors = validation.errors.filter(e =>
e.code && (
e.code.includes('BR-CO') ||
e.message && (
e.message.toLowerCase().includes('calculation') ||
e.message.toLowerCase().includes('sum') ||
e.message.toLowerCase().includes('total') ||
e.message.toLowerCase().includes('amount')
)
)
);
if (calcErrors.length > 0) {
validCalculations++;
console.log(`✓ ${fileName}: Correctly detected calculation errors (${calcErrors.length})`);
calculationErrors.push({
file: fileName,
errors: calcErrors.map(e => `${e.code}: ${e.message}`)
});
} else {
invalidCalculations++;
console.log(`○ ${fileName}: No calculation errors detected (may need implementation)`);
}
} else if (validation.valid) {
invalidCalculations++;
console.log(`○ ${fileName}: Unexpectedly valid (should have calculation errors)`);
} else {
invalidCalculations++;
console.log(`○ ${fileName}: Invalid but no specific calculation errors found`);
}
} catch (error) {
errorCount++;
console.log(`✗ ${fileName}: Error - ${error.message}`);
}
}
console.log('\n=== CALCULATION VALIDATION SUMMARY ===');
console.log(`Correct calculation detection: ${validCalculations}`);
console.log(`Missed calculation errors: ${invalidCalculations}`);
console.log(`Processing errors: ${errorCount}`);
// Show sample calculation errors
if (calculationErrors.length > 0) {
console.log('\nSample calculation errors detected:');
calculationErrors.slice(0, 3).forEach(item => {
console.log(` ${item.file}:`);
item.errors.slice(0, 2).forEach(error => {
console.log(` - ${error}`);
});
});
}
// Performance summary
const perfSummary = await PerformanceTracker.getSummary('calculation-validation');
if (perfSummary) {
console.log(`\nCalculation Validation Performance:`);
console.log(` Average: ${perfSummary.average.toFixed(2)}ms`);
console.log(` P95: ${perfSummary.p95.toFixed(2)}ms`);
}
// Expect some calculation validation to work
expect(validCalculations + invalidCalculations).toBeGreaterThan(0);
});
tap.test('VAL-05: Line Item Calculation Validation - should validate individual line calculations', async () => {
const { EInvoice } = await import('../../../ts/index.js');
const lineCalculationTests = [
{
name: 'Correct line calculation',
xml: `
LINE-CALC-001
1
5
500.00
100.00
`,
shouldBeValid: true,
description: '5 × 100.00 = 500.00 (correct)'
},
{
name: 'Incorrect line calculation',
xml: `
LINE-CALC-002
1
5
600.00
100.00
`,
shouldBeValid: false,
description: '5 × 100.00 ≠ 600.00 (incorrect)'
},
{
name: 'Multiple line items with calculations',
xml: `
LINE-CALC-003
1
2
200.00
100.00
2
3
150.00
50.00
`,
shouldBeValid: true,
description: 'Line 1: 2×100=200, Line 2: 3×50=150 (both correct)'
}
];
for (const test of lineCalculationTests) {
try {
const { result: validation } = await PerformanceTracker.track(
'line-calculation-test',
async () => {
const einvoice = await EInvoice.fromXml(test.xml);
return await einvoice.validate();
}
);
console.log(`${test.name}: ${validation.valid ? 'VALID' : 'INVALID'}`);
console.log(` ${test.description}`);
if (!test.shouldBeValid && !validation.valid) {
console.log(` ✓ Correctly detected calculation error`);
if (validation.errors) {
const calcErrors = validation.errors.filter(e =>
e.message && e.message.toLowerCase().includes('calculation')
);
console.log(` Calculation errors: ${calcErrors.length}`);
}
} else if (test.shouldBeValid && validation.valid) {
console.log(` ✓ Correctly validated calculation`);
} else {
console.log(` ○ Unexpected result (calculation validation may need implementation)`);
}
} catch (error) {
console.log(`${test.name}: Error - ${error.message}`);
}
}
});
tap.test('VAL-05: Tax Calculation Validation - should validate VAT and tax calculations', async () => {
const { EInvoice } = await import('../../../ts/index.js');
const taxCalculationTests = [
{
name: 'Correct VAT calculation',
xml: `
TAX-001
190.00
1000.00
190.00
S
19
1000.00
1190.00
`,
shouldBeValid: true,
description: '1000.00 × 19% = 190.00, Total: 1190.00 (correct)'
},
{
name: 'Incorrect VAT calculation',
xml: `
TAX-002
200.00
1000.00
200.00
S
19
1000.00
1200.00
`,
shouldBeValid: false,
description: '1000.00 × 19% = 190.00, not 200.00 (incorrect)'
}
];
for (const test of taxCalculationTests) {
try {
const { result: validation } = await PerformanceTracker.track(
'tax-calculation-test',
async () => {
const einvoice = await EInvoice.fromXml(test.xml);
return await einvoice.validate();
}
);
console.log(`${test.name}: ${validation.valid ? 'VALID' : 'INVALID'}`);
console.log(` ${test.description}`);
if (!test.shouldBeValid && !validation.valid) {
console.log(` ✓ Correctly detected tax calculation error`);
if (validation.errors) {
const taxErrors = validation.errors.filter(e =>
e.message && (
e.message.toLowerCase().includes('tax') ||
e.message.toLowerCase().includes('vat') ||
e.message.toLowerCase().includes('calculation')
)
);
console.log(` Tax calculation errors: ${taxErrors.length}`);
}
} else if (test.shouldBeValid && validation.valid) {
console.log(` ✓ Correctly validated tax calculation`);
} else {
console.log(` ○ Unexpected result (tax calculation validation may need implementation)`);
}
} catch (error) {
console.log(`${test.name}: Error - ${error.message}`);
}
}
});
tap.test('VAL-05: Rounding and Precision Validation - should handle rounding correctly', async () => {
const { EInvoice } = await import('../../../ts/index.js');
const roundingTests = [
{
name: 'Proper rounding to 2 decimal places',
xml: `
ROUND-001
1
3
10.00
3.33
`,
description: '3 × 3.33 = 9.99 ≈ 10.00 (acceptable rounding)'
},
{
name: 'Excessive precision',
xml: `
ROUND-002
1
1
10.123456789
10.123456789
`,
description: 'Amounts with excessive decimal precision'
}
];
for (const test of roundingTests) {
try {
const { result: validation } = await PerformanceTracker.track(
'rounding-validation-test',
async () => {
const einvoice = await EInvoice.fromXml(test.xml);
return await einvoice.validate();
}
);
console.log(`${test.name}: ${validation.valid ? 'VALID' : 'INVALID'}`);
console.log(` ${test.description}`);
if (!validation.valid && validation.errors) {
const roundingErrors = validation.errors.filter(e =>
e.message && (
e.message.toLowerCase().includes('rounding') ||
e.message.toLowerCase().includes('precision') ||
e.message.toLowerCase().includes('decimal')
)
);
console.log(` Rounding/precision errors: ${roundingErrors.length}`);
} else {
console.log(` No rounding/precision issues detected`);
}
} catch (error) {
console.log(`${test.name}: Error - ${error.message}`);
}
}
});
tap.test('VAL-05: Complex Calculation Scenarios - should handle complex invoice calculations', async () => {
const { EInvoice } = await import('../../../ts/index.js');
// Test with a complex invoice involving discounts, allowances, and charges
const complexCalculationXml = `
COMPLEX-CALC
1
10
900.00
100.00
false
100.00
171.00
900.00
900.00
1071.00
`;
console.log('Testing complex calculation scenario');
try {
const { result: validation, metric } = await PerformanceTracker.track(
'complex-calculation-test',
async () => {
const einvoice = await EInvoice.fromXml(complexCalculationXml);
return await einvoice.validate();
}
);
console.log(`Complex calculation: ${validation.valid ? 'VALID' : 'INVALID'}`);
console.log(`Validation time: ${metric.duration.toFixed(2)}ms`);
console.log(`Calculation: 10×100 - 100 = 900, VAT: 171, Total: 1071`);
if (!validation.valid && validation.errors) {
const calcErrors = validation.errors.filter(e =>
e.message && e.message.toLowerCase().includes('calculation')
);
console.log(`Calculation issues found: ${calcErrors.length}`);
} else {
console.log(`Complex calculation validated successfully`);
}
// Should handle complex calculations efficiently
expect(metric.duration).toBeLessThan(100);
} catch (error) {
console.log(`Complex calculation test error: ${error.message}`);
}
});
tap.start();