einvoice/test/suite/einvoice_error-handling/test.err-02.validation-errors.ts
2025-05-25 19:45:37 +00:00

844 lines
32 KiB
TypeScript

import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.ts';
import { EInvoice } from '../../../ts/classes.xinvoice.ts';
import { CorpusLoader } from '../../helpers/corpus.loader.ts';
import { PerformanceTracker } from '../../helpers/performance.tracker.ts';
const testTimeout = 300000; // 5 minutes timeout for error handling tests
// ERR-02: Validation Error Details
// Tests detailed validation error reporting including error messages,
// error locations, error codes, and actionable error information
tap.test('ERR-02: Validation Error Details - Business Rule Violations', async (tools) => {
const startTime = Date.now();
// Test validation errors for various business rule violations
const businessRuleViolations = [
{
name: 'BR-01: Missing invoice number',
xml: `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<IssueDate>2024-01-15</IssueDate>
<InvoiceTypeCode>380</InvoiceTypeCode>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
<LegalMonetaryTotal>
<PayableAmount currencyID="EUR">100.00</PayableAmount>
</LegalMonetaryTotal>
</Invoice>`,
expectedErrors: ['BR-01', 'invoice number', 'ID', 'required'],
errorCount: 1
},
{
name: 'BR-CO-10: Sum of line amounts validation',
xml: `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<ID>BR-TEST-001</ID>
<IssueDate>2024-01-15</IssueDate>
<InvoiceTypeCode>380</InvoiceTypeCode>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
<InvoiceLine>
<ID>1</ID>
<InvoicedQuantity unitCode="C62">2</InvoicedQuantity>
<LineExtensionAmount currencyID="EUR">100.00</LineExtensionAmount>
<Price>
<PriceAmount currencyID="EUR">50.00</PriceAmount>
</Price>
</InvoiceLine>
<InvoiceLine>
<ID>2</ID>
<InvoicedQuantity unitCode="C62">3</InvoicedQuantity>
<LineExtensionAmount currencyID="EUR">150.00</LineExtensionAmount>
<Price>
<PriceAmount currencyID="EUR">50.00</PriceAmount>
</Price>
</InvoiceLine>
<LegalMonetaryTotal>
<LineExtensionAmount currencyID="EUR">200.00</LineExtensionAmount>
<PayableAmount currencyID="EUR">200.00</PayableAmount>
</LegalMonetaryTotal>
</Invoice>`,
expectedErrors: ['BR-CO-10', 'sum', 'line', 'amount', 'calculation'],
errorCount: 1
},
{
name: 'Multiple validation errors',
xml: `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<ID>MULTI-ERROR-001</ID>
<InvoiceTypeCode>999</InvoiceTypeCode>
<DocumentCurrencyCode>INVALID</DocumentCurrencyCode>
<TaxTotal>
<TaxAmount currencyID="EUR">-50.00</TaxAmount>
</TaxTotal>
<LegalMonetaryTotal>
<PayableAmount currencyID="XXX">100.00</PayableAmount>
</LegalMonetaryTotal>
</Invoice>`,
expectedErrors: ['issue date', 'invoice type', 'currency', 'negative', 'tax'],
errorCount: 5
}
];
for (const testCase of businessRuleViolations) {
tools.log(`Testing ${testCase.name}...`);
try {
const invoice = new EInvoice();
const parseResult = await invoice.fromXmlString(testCase.xml);
if (parseResult) {
const validationResult = await invoice.validate();
if (validationResult.valid) {
tools.log(` ⚠ Expected validation errors but validation passed`);
} else {
tools.log(` ✓ Validation failed as expected`);
// Analyze validation errors
const errors = validationResult.errors || [];
tools.log(` Found ${errors.length} validation errors:`);
for (const error of errors) {
tools.log(`\n Error ${errors.indexOf(error) + 1}:`);
// Check error structure
expect(error).toHaveProperty('message');
expect(error.message).toBeTruthy();
expect(error.message.length).toBeGreaterThan(10);
tools.log(` Message: ${error.message}`);
// Check optional error properties
if (error.code) {
tools.log(` Code: ${error.code}`);
expect(error.code).toBeTruthy();
}
if (error.path) {
tools.log(` Path: ${error.path}`);
expect(error.path).toBeTruthy();
}
if (error.severity) {
tools.log(` Severity: ${error.severity}`);
expect(['error', 'warning', 'info']).toContain(error.severity);
}
if (error.rule) {
tools.log(` Rule: ${error.rule}`);
}
if (error.element) {
tools.log(` Element: ${error.element}`);
}
if (error.value) {
tools.log(` Value: ${error.value}`);
}
if (error.expected) {
tools.log(` Expected: ${error.expected}`);
}
if (error.actual) {
tools.log(` Actual: ${error.actual}`);
}
if (error.suggestion) {
tools.log(` Suggestion: ${error.suggestion}`);
}
// Check if error contains expected keywords
const errorLower = error.message.toLowerCase();
let keywordMatches = 0;
for (const keyword of testCase.expectedErrors) {
if (errorLower.includes(keyword.toLowerCase())) {
keywordMatches++;
}
}
if (keywordMatches > 0) {
tools.log(` ✓ Error contains expected keywords (${keywordMatches}/${testCase.expectedErrors.length})`);
} else {
tools.log(` ⚠ Error doesn't contain expected keywords`);
}
}
// Check error count
if (testCase.errorCount > 0) {
if (errors.length >= testCase.errorCount) {
tools.log(`\n ✓ Expected at least ${testCase.errorCount} errors, found ${errors.length}`);
} else {
tools.log(`\n ⚠ Expected at least ${testCase.errorCount} errors, but found only ${errors.length}`);
}
}
}
} else {
tools.log(` ✗ Parsing failed unexpectedly`);
}
} catch (error) {
tools.log(` ✗ Unexpected error during validation: ${error.message}`);
throw error;
}
}
const duration = Date.now() - startTime;
PerformanceTracker.recordMetric('validation-error-details-business-rules', duration);
});
tap.test('ERR-02: Validation Error Details - Schema Validation Errors', async (tools) => {
const startTime = Date.now();
// Test schema validation error details
const schemaViolations = [
{
name: 'Invalid element order',
xml: `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<InvoiceTypeCode>380</InvoiceTypeCode>
<ID>SCHEMA-001</ID>
<IssueDate>2024-01-15</IssueDate>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
</Invoice>`,
expectedErrors: ['order', 'sequence', 'element'],
description: 'Elements in wrong order'
},
{
name: 'Unknown element',
xml: `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<ID>SCHEMA-002</ID>
<IssueDate>2024-01-15</IssueDate>
<UnknownElement>This should not be here</UnknownElement>
<InvoiceTypeCode>380</InvoiceTypeCode>
</Invoice>`,
expectedErrors: ['unknown', 'element', 'unexpected'],
description: 'Contains unknown element'
},
{
name: 'Invalid attribute',
xml: `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" invalidAttribute="value">
<ID>SCHEMA-003</ID>
<IssueDate>2024-01-15</IssueDate>
<InvoiceTypeCode>380</InvoiceTypeCode>
</Invoice>`,
expectedErrors: ['attribute', 'invalid', 'unexpected'],
description: 'Invalid attribute on root element'
},
{
name: 'Missing required child element',
xml: `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<ID>SCHEMA-004</ID>
<IssueDate>2024-01-15</IssueDate>
<InvoiceTypeCode>380</InvoiceTypeCode>
<TaxTotal>
<TaxAmount currencyID="EUR">19.00</TaxAmount>
<!-- Missing TaxSubtotal -->
</TaxTotal>
</Invoice>`,
expectedErrors: ['required', 'missing', 'TaxSubtotal'],
description: 'Missing required child element'
}
];
for (const testCase of schemaViolations) {
tools.log(`Testing ${testCase.name}: ${testCase.description}`);
try {
const invoice = new EInvoice();
const parseResult = await invoice.fromXmlString(testCase.xml);
if (parseResult) {
const validationResult = await invoice.validate();
if (validationResult.valid) {
tools.log(` ⚠ Expected schema validation errors but validation passed`);
} else {
tools.log(` ✓ Schema validation failed as expected`);
const errors = validationResult.errors || [];
tools.log(` Found ${errors.length} validation errors`);
// Analyze schema-specific error details
let schemaErrorFound = false;
for (const error of errors) {
const errorLower = error.message.toLowerCase();
// Check if this is a schema-related error
const isSchemaError = errorLower.includes('schema') ||
errorLower.includes('element') ||
errorLower.includes('attribute') ||
errorLower.includes('structure') ||
errorLower.includes('xml');
if (isSchemaError) {
schemaErrorFound = true;
tools.log(` Schema error: ${error.message}`);
// Check for XPath or location information
if (error.path) {
tools.log(` Location: ${error.path}`);
expect(error.path).toMatch(/^\/|^\w+/); // Should look like a path
}
// Check for line/column information
if (error.line) {
tools.log(` Line: ${error.line}`);
expect(error.line).toBeGreaterThan(0);
}
if (error.column) {
tools.log(` Column: ${error.column}`);
expect(error.column).toBeGreaterThan(0);
}
// Check if error mentions expected keywords
let keywordMatch = false;
for (const keyword of testCase.expectedErrors) {
if (errorLower.includes(keyword.toLowerCase())) {
keywordMatch = true;
break;
}
}
if (keywordMatch) {
tools.log(` ✓ Error contains expected keywords`);
}
}
}
if (!schemaErrorFound) {
tools.log(` ⚠ No schema-specific errors found`);
}
}
} else {
tools.log(` Schema validation may have failed at parse time`);
}
} catch (error) {
tools.log(` Parse/validation error: ${error.message}`);
// Check if the error message is helpful
const errorLower = error.message.toLowerCase();
if (errorLower.includes('schema') || errorLower.includes('invalid')) {
tools.log(` ✓ Error message indicates schema issue`);
}
}
}
const duration = Date.now() - startTime;
PerformanceTracker.recordMetric('validation-error-details-schema', duration);
});
tap.test('ERR-02: Validation Error Details - Field-Specific Errors', async (tools) => {
const startTime = Date.now();
// Test field-specific validation error details
const fieldErrors = [
{
name: 'Invalid date format',
xml: `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<ID>FIELD-001</ID>
<IssueDate>15-01-2024</IssueDate>
<InvoiceTypeCode>380</InvoiceTypeCode>
<DueDate>2024/02/15</DueDate>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
</Invoice>`,
expectedFields: ['IssueDate', 'DueDate'],
expectedErrors: ['date', 'format', 'ISO', 'YYYY-MM-DD']
},
{
name: 'Invalid currency codes',
xml: `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<ID>FIELD-002</ID>
<IssueDate>2024-01-15</IssueDate>
<InvoiceTypeCode>380</InvoiceTypeCode>
<DocumentCurrencyCode>EURO</DocumentCurrencyCode>
<LegalMonetaryTotal>
<PayableAmount currencyID="$$$">100.00</PayableAmount>
</LegalMonetaryTotal>
</Invoice>`,
expectedFields: ['DocumentCurrencyCode', 'currencyID'],
expectedErrors: ['currency', 'ISO 4217', 'invalid', 'code']
},
{
name: 'Invalid numeric values',
xml: `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<ID>FIELD-003</ID>
<IssueDate>2024-01-15</IssueDate>
<InvoiceTypeCode>380</InvoiceTypeCode>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
<InvoiceLine>
<ID>1</ID>
<InvoicedQuantity unitCode="C62">ABC</InvoicedQuantity>
<LineExtensionAmount currencyID="EUR">not-a-number</LineExtensionAmount>
</InvoiceLine>
<TaxTotal>
<TaxAmount currencyID="EUR">19.999999999</TaxAmount>
</TaxTotal>
</Invoice>`,
expectedFields: ['InvoicedQuantity', 'LineExtensionAmount', 'TaxAmount'],
expectedErrors: ['numeric', 'number', 'decimal', 'invalid']
},
{
name: 'Invalid code values',
xml: `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<ID>FIELD-004</ID>
<IssueDate>2024-01-15</IssueDate>
<InvoiceTypeCode>999</InvoiceTypeCode>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
<PaymentMeans>
<PaymentMeansCode>99</PaymentMeansCode>
</PaymentMeans>
<InvoiceLine>
<ID>1</ID>
<InvoicedQuantity unitCode="INVALID">1</InvoicedQuantity>
</InvoiceLine>
</Invoice>`,
expectedFields: ['InvoiceTypeCode', 'PaymentMeansCode', 'unitCode'],
expectedErrors: ['code', 'list', 'valid', 'allowed']
}
];
for (const testCase of fieldErrors) {
tools.log(`Testing ${testCase.name}...`);
try {
const invoice = new EInvoice();
const parseResult = await invoice.fromXmlString(testCase.xml);
if (parseResult) {
const validationResult = await invoice.validate();
if (validationResult.valid) {
tools.log(` ⚠ Expected field validation errors but validation passed`);
} else {
tools.log(` ✓ Field validation failed as expected`);
const errors = validationResult.errors || [];
tools.log(` Found ${errors.length} validation errors`);
// Track which expected fields have errors
const fieldsWithErrors = new Set<string>();
for (const error of errors) {
tools.log(`\n Field error: ${error.message}`);
// Check if error identifies the field
if (error.path || error.element || error.field) {
const fieldIdentifier = error.path || error.element || error.field;
tools.log(` Field: ${fieldIdentifier}`);
// Check if this is one of our expected fields
for (const expectedField of testCase.expectedFields) {
if (fieldIdentifier.includes(expectedField)) {
fieldsWithErrors.add(expectedField);
}
}
}
// Check if error provides value information
if (error.value) {
tools.log(` Invalid value: ${error.value}`);
}
// Check if error provides expected format/values
if (error.expected) {
tools.log(` Expected: ${error.expected}`);
}
// Check if error suggests correction
if (error.suggestion) {
tools.log(` Suggestion: ${error.suggestion}`);
expect(error.suggestion).toBeTruthy();
}
// Check for specific error keywords
const errorLower = error.message.toLowerCase();
let hasExpectedKeyword = false;
for (const keyword of testCase.expectedErrors) {
if (errorLower.includes(keyword.toLowerCase())) {
hasExpectedKeyword = true;
break;
}
}
if (hasExpectedKeyword) {
tools.log(` ✓ Error contains expected keywords`);
}
}
// Check if all expected fields had errors
tools.log(`\n Fields with errors: ${Array.from(fieldsWithErrors).join(', ')}`);
if (fieldsWithErrors.size > 0) {
tools.log(` ✓ Errors reported for ${fieldsWithErrors.size}/${testCase.expectedFields.length} expected fields`);
} else {
tools.log(` ⚠ No field-specific errors identified`);
}
}
} else {
tools.log(` Parsing failed - field validation may have failed at parse time`);
}
} catch (error) {
tools.log(` Error during validation: ${error.message}`);
}
}
const duration = Date.now() - startTime;
PerformanceTracker.recordMetric('validation-error-details-fields', duration);
});
tap.test('ERR-02: Validation Error Details - Error Grouping and Summarization', async (tools) => {
const startTime = Date.now();
// Test error grouping and summarization for complex validation scenarios
const complexValidationXml = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<ID>COMPLEX-001</ID>
<IssueDate>invalid-date</IssueDate>
<InvoiceTypeCode>999</InvoiceTypeCode>
<DocumentCurrencyCode>XXX</DocumentCurrencyCode>
<AccountingSupplierParty>
<Party>
<!-- Missing required party name -->
<PostalAddress>
<StreetName></StreetName>
<CityName></CityName>
<Country>
<IdentificationCode>XX</IdentificationCode>
</Country>
</PostalAddress>
<PartyTaxScheme>
<CompanyID>INVALID-VAT</CompanyID>
</PartyTaxScheme>
</Party>
</AccountingSupplierParty>
<InvoiceLine>
<ID>1</ID>
<InvoicedQuantity unitCode="INVALID">-5</InvoicedQuantity>
<LineExtensionAmount currencyID="USD">-100.00</LineExtensionAmount>
<Item>
<!-- Missing item name -->
<ClassifiedTaxCategory>
<Percent>999</Percent>
</ClassifiedTaxCategory>
</Item>
<Price>
<PriceAmount currencyID="GBP">-20.00</PriceAmount>
</Price>
</InvoiceLine>
<InvoiceLine>
<ID>2</ID>
<InvoicedQuantity>10</InvoicedQuantity>
<LineExtensionAmount currencyID="JPY">invalid</LineExtensionAmount>
</InvoiceLine>
<TaxTotal>
<TaxAmount currencyID="CHF">invalid-amount</TaxAmount>
<TaxSubtotal>
<!-- Missing required elements -->
</TaxSubtotal>
</TaxTotal>
<LegalMonetaryTotal>
<LineExtensionAmount currencyID="EUR">NaN</LineExtensionAmount>
<TaxExclusiveAmount currencyID="EUR">-50.00</TaxExclusiveAmount>
<PayableAmount currencyID="">0.00</PayableAmount>
</LegalMonetaryTotal>
</Invoice>`;
try {
const invoice = new EInvoice();
const parseResult = await invoice.fromXmlString(complexValidationXml);
if (parseResult) {
const validationResult = await invoice.validate();
if (!validationResult.valid && validationResult.errors) {
const errors = validationResult.errors;
tools.log(`Total validation errors: ${errors.length}`);
// Group errors by category
const errorGroups: { [key: string]: any[] } = {
'Date/Time Errors': [],
'Currency Errors': [],
'Code List Errors': [],
'Numeric Value Errors': [],
'Required Field Errors': [],
'Business Rule Errors': [],
'Other Errors': []
};
// Categorize each error
for (const error of errors) {
const errorLower = error.message.toLowerCase();
if (errorLower.includes('date') || errorLower.includes('time')) {
errorGroups['Date/Time Errors'].push(error);
} else if (errorLower.includes('currency') || errorLower.includes('currencyid')) {
errorGroups['Currency Errors'].push(error);
} else if (errorLower.includes('code') || errorLower.includes('type') || errorLower.includes('list')) {
errorGroups['Code List Errors'].push(error);
} else if (errorLower.includes('numeric') || errorLower.includes('number') ||
errorLower.includes('negative') || errorLower.includes('amount')) {
errorGroups['Numeric Value Errors'].push(error);
} else if (errorLower.includes('required') || errorLower.includes('missing') ||
errorLower.includes('must')) {
errorGroups['Required Field Errors'].push(error);
} else if (errorLower.includes('br-') || errorLower.includes('rule')) {
errorGroups['Business Rule Errors'].push(error);
} else {
errorGroups['Other Errors'].push(error);
}
}
// Display grouped errors
tools.log(`\nError Summary by Category:`);
for (const [category, categoryErrors] of Object.entries(errorGroups)) {
if (categoryErrors.length > 0) {
tools.log(`\n${category}: ${categoryErrors.length} errors`);
// Show first few errors in each category
const samplesToShow = Math.min(3, categoryErrors.length);
for (let i = 0; i < samplesToShow; i++) {
const error = categoryErrors[i];
tools.log(` - ${error.message}`);
if (error.path) {
tools.log(` at: ${error.path}`);
}
}
if (categoryErrors.length > samplesToShow) {
tools.log(` ... and ${categoryErrors.length - samplesToShow} more`);
}
}
}
// Error statistics
tools.log(`\nError Statistics:`);
// Count errors by severity if available
const severityCounts: { [key: string]: number } = {};
for (const error of errors) {
const severity = error.severity || 'error';
severityCounts[severity] = (severityCounts[severity] || 0) + 1;
}
for (const [severity, count] of Object.entries(severityCounts)) {
tools.log(` ${severity}: ${count}`);
}
// Identify most common error patterns
const errorPatterns: { [key: string]: number } = {};
for (const error of errors) {
// Extract error pattern (first few words)
const pattern = error.message.split(' ').slice(0, 3).join(' ').toLowerCase();
errorPatterns[pattern] = (errorPatterns[pattern] || 0) + 1;
}
const commonPatterns = Object.entries(errorPatterns)
.sort(([,a], [,b]) => b - a)
.slice(0, 5);
if (commonPatterns.length > 0) {
tools.log(`\nMost Common Error Patterns:`);
for (const [pattern, count] of commonPatterns) {
tools.log(` "${pattern}...": ${count} occurrences`);
}
}
// Check if errors provide actionable information
let actionableErrors = 0;
for (const error of errors) {
if (error.suggestion || error.expected ||
error.message.includes('should') || error.message.includes('must')) {
actionableErrors++;
}
}
const actionablePercentage = (actionableErrors / errors.length) * 100;
tools.log(`\nActionable errors: ${actionableErrors}/${errors.length} (${actionablePercentage.toFixed(1)}%)`);
if (actionablePercentage >= 50) {
tools.log(`✓ Good error actionability`);
} else {
tools.log(`⚠ Low error actionability - errors may not be helpful enough`);
}
} else {
tools.log(`⚠ Expected validation errors but none found or validation passed`);
}
} else {
tools.log(`Parsing failed - unable to test validation error details`);
}
} catch (error) {
tools.log(`Error during complex validation test: ${error.message}`);
}
const duration = Date.now() - startTime;
PerformanceTracker.recordMetric('validation-error-details-grouping', duration);
});
tap.test('ERR-02: Validation Error Details - Corpus Error Analysis', { timeout: testTimeout }, async (tools) => {
const startTime = Date.now();
const errorStatistics = {
totalFiles: 0,
filesWithErrors: 0,
totalErrors: 0,
errorTypes: {} as { [key: string]: number },
errorsBySeverity: {} as { [key: string]: number },
averageErrorsPerFile: 0,
maxErrorsInFile: 0,
fileWithMostErrors: ''
};
try {
// Analyze validation errors across corpus files
const files = await CorpusLoader.getFiles('UBL_XML_RECHNUNG');
const filesToProcess = files.slice(0, 10); // Process first 10 files
for (const filePath of filesToProcess) {
errorStatistics.totalFiles++;
const fileName = plugins.path.basename(filePath);
try {
const invoice = new EInvoice();
const parseResult = await invoice.fromFile(filePath);
if (parseResult) {
const validationResult = await invoice.validate();
if (!validationResult.valid && validationResult.errors) {
errorStatistics.filesWithErrors++;
const fileErrorCount = validationResult.errors.length;
errorStatistics.totalErrors += fileErrorCount;
if (fileErrorCount > errorStatistics.maxErrorsInFile) {
errorStatistics.maxErrorsInFile = fileErrorCount;
errorStatistics.fileWithMostErrors = fileName;
}
// Analyze error types
for (const error of validationResult.errors) {
// Categorize error type
const errorType = categorizeError(error);
errorStatistics.errorTypes[errorType] = (errorStatistics.errorTypes[errorType] || 0) + 1;
// Count by severity
const severity = error.severity || 'error';
errorStatistics.errorsBySeverity[severity] = (errorStatistics.errorsBySeverity[severity] || 0) + 1;
// Check error quality
const hasGoodMessage = error.message && error.message.length > 20;
const hasLocation = !!(error.path || error.element || error.line);
const hasContext = !!(error.value || error.expected || error.code);
if (!hasGoodMessage || !hasLocation || !hasContext) {
tools.log(` ⚠ Low quality error in ${fileName}:`);
tools.log(` Message quality: ${hasGoodMessage}`);
tools.log(` Has location: ${hasLocation}`);
tools.log(` Has context: ${hasContext}`);
}
}
}
}
} catch (error) {
tools.log(`Error processing ${fileName}: ${error.message}`);
}
}
// Calculate statistics
errorStatistics.averageErrorsPerFile = errorStatistics.filesWithErrors > 0
? errorStatistics.totalErrors / errorStatistics.filesWithErrors
: 0;
// Display analysis results
tools.log(`\n=== Corpus Validation Error Analysis ===`);
tools.log(`Files analyzed: ${errorStatistics.totalFiles}`);
tools.log(`Files with errors: ${errorStatistics.filesWithErrors} (${(errorStatistics.filesWithErrors / errorStatistics.totalFiles * 100).toFixed(1)}%)`);
tools.log(`Total errors found: ${errorStatistics.totalErrors}`);
tools.log(`Average errors per file with errors: ${errorStatistics.averageErrorsPerFile.toFixed(1)}`);
tools.log(`Maximum errors in single file: ${errorStatistics.maxErrorsInFile} (${errorStatistics.fileWithMostErrors})`);
if (Object.keys(errorStatistics.errorTypes).length > 0) {
tools.log(`\nError Types Distribution:`);
const sortedTypes = Object.entries(errorStatistics.errorTypes)
.sort(([,a], [,b]) => b - a);
for (const [type, count] of sortedTypes) {
const percentage = (count / errorStatistics.totalErrors * 100).toFixed(1);
tools.log(` ${type}: ${count} (${percentage}%)`);
}
}
if (Object.keys(errorStatistics.errorsBySeverity).length > 0) {
tools.log(`\nErrors by Severity:`);
for (const [severity, count] of Object.entries(errorStatistics.errorsBySeverity)) {
tools.log(` ${severity}: ${count}`);
}
}
} catch (error) {
tools.log(`Corpus error analysis failed: ${error.message}`);
throw error;
}
const totalDuration = Date.now() - startTime;
PerformanceTracker.recordMetric('validation-error-details-corpus', totalDuration);
tools.log(`\nCorpus error analysis completed in ${totalDuration}ms`);
});
// Helper function to categorize errors
function categorizeError(error: any): string {
const message = error.message?.toLowerCase() || '';
const code = error.code?.toLowerCase() || '';
if (message.includes('required') || message.includes('missing')) return 'Required Field';
if (message.includes('date') || message.includes('time')) return 'Date/Time';
if (message.includes('currency')) return 'Currency';
if (message.includes('amount') || message.includes('number') || message.includes('numeric')) return 'Numeric';
if (message.includes('code') || message.includes('type')) return 'Code List';
if (message.includes('tax') || message.includes('vat')) return 'Tax Related';
if (message.includes('format') || message.includes('pattern')) return 'Format';
if (code.includes('br-')) return 'Business Rule';
if (message.includes('schema') || message.includes('xml')) return 'Schema';
return 'Other';
}
tap.test('ERR-02: Performance Summary', async (tools) => {
const operations = [
'validation-error-details-business-rules',
'validation-error-details-schema',
'validation-error-details-fields',
'validation-error-details-grouping',
'validation-error-details-corpus'
];
tools.log(`\n=== Validation Error Details Performance Summary ===`);
for (const operation of operations) {
const summary = await PerformanceTracker.getSummary(operation);
if (summary) {
tools.log(`${operation}:`);
tools.log(` avg=${summary.average}ms, min=${summary.min}ms, max=${summary.max}ms, p95=${summary.p95}ms`);
}
}
tools.log(`\nValidation error details testing completed.`);
tools.log(`Good error reporting should include: message, location, severity, suggestions, and context.`);
});