import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as einvoice from '../../../ts/index.js'; import * as plugins from '../../plugins.js'; import { PerformanceTracker } from '../../helpers/performance.tracker.js'; tap.test('ERR-10: Configuration Errors - Handle configuration and setup failures', async (t) => { const performanceTracker = new PerformanceTracker('ERR-10'); await t.test('Invalid configuration values', async () => { performanceTracker.startOperation('config-validation'); interface IEInvoiceConfig { validationLevel?: 'strict' | 'normal' | 'lenient'; maxFileSize?: number; timeout?: number; supportedFormats?: string[]; locale?: string; timezone?: string; apiEndpoint?: string; retryAttempts?: number; cacheTTL?: number; } class ConfigValidator { private errors: string[] = []; validate(config: IEInvoiceConfig): { valid: boolean; errors: string[] } { this.errors = []; // Validation level if (config.validationLevel && !['strict', 'normal', 'lenient'].includes(config.validationLevel)) { this.errors.push(`Invalid validation level: ${config.validationLevel}`); } // Max file size if (config.maxFileSize !== undefined) { if (config.maxFileSize <= 0) { this.errors.push('Max file size must be positive'); } if (config.maxFileSize > 1024 * 1024 * 1024) { // 1GB this.errors.push('Max file size exceeds reasonable limit (1GB)'); } } // Timeout if (config.timeout !== undefined) { if (config.timeout <= 0) { this.errors.push('Timeout must be positive'); } if (config.timeout > 300000) { // 5 minutes this.errors.push('Timeout exceeds maximum allowed (5 minutes)'); } } // Supported formats if (config.supportedFormats) { const validFormats = ['UBL', 'CII', 'ZUGFeRD', 'Factur-X', 'XRechnung', 'FatturaPA', 'PEPPOL']; const invalidFormats = config.supportedFormats.filter(f => !validFormats.includes(f)); if (invalidFormats.length > 0) { this.errors.push(`Unknown formats: ${invalidFormats.join(', ')}`); } } // Locale if (config.locale && !/^[a-z]{2}(-[A-Z]{2})?$/.test(config.locale)) { this.errors.push(`Invalid locale format: ${config.locale}`); } // Timezone if (config.timezone) { try { new Intl.DateTimeFormat('en', { timeZone: config.timezone }); } catch (e) { this.errors.push(`Invalid timezone: ${config.timezone}`); } } // API endpoint if (config.apiEndpoint) { try { new URL(config.apiEndpoint); } catch (e) { this.errors.push(`Invalid API endpoint URL: ${config.apiEndpoint}`); } } // Retry attempts if (config.retryAttempts !== undefined) { if (!Number.isInteger(config.retryAttempts) || config.retryAttempts < 0) { this.errors.push('Retry attempts must be a non-negative integer'); } if (config.retryAttempts > 10) { this.errors.push('Retry attempts exceeds reasonable limit (10)'); } } // Cache TTL if (config.cacheTTL !== undefined) { if (config.cacheTTL < 0) { this.errors.push('Cache TTL must be non-negative'); } if (config.cacheTTL > 86400000) { // 24 hours this.errors.push('Cache TTL exceeds maximum (24 hours)'); } } return { valid: this.errors.length === 0, errors: this.errors }; } } const validator = new ConfigValidator(); const testConfigs: Array<{ name: string; config: IEInvoiceConfig; shouldBeValid: boolean }> = [ { name: 'Valid configuration', config: { validationLevel: 'strict', maxFileSize: 10 * 1024 * 1024, timeout: 30000, supportedFormats: ['UBL', 'CII'], locale: 'en-US', timezone: 'Europe/Berlin', apiEndpoint: 'https://api.example.com/validate', retryAttempts: 3, cacheTTL: 3600000 }, shouldBeValid: true }, { name: 'Invalid validation level', config: { validationLevel: 'extreme' as any }, shouldBeValid: false }, { name: 'Negative max file size', config: { maxFileSize: -1 }, shouldBeValid: false }, { name: 'Excessive timeout', config: { timeout: 600000 }, shouldBeValid: false }, { name: 'Unknown format', config: { supportedFormats: ['UBL', 'UNKNOWN'] }, shouldBeValid: false }, { name: 'Invalid locale', config: { locale: 'english' }, shouldBeValid: false }, { name: 'Invalid timezone', config: { timezone: 'Mars/Olympus_Mons' }, shouldBeValid: false }, { name: 'Malformed API endpoint', config: { apiEndpoint: 'not-a-url' }, shouldBeValid: false }, { name: 'Excessive retry attempts', config: { retryAttempts: 100 }, shouldBeValid: false } ]; for (const test of testConfigs) { const startTime = performance.now(); const result = validator.validate(test.config); if (test.shouldBeValid) { expect(result.valid).toBeTrue(); console.log(`✓ ${test.name}: Configuration is valid`); } else { expect(result.valid).toBeFalse(); console.log(`✓ ${test.name}: Invalid - ${result.errors.join('; ')}`); } performanceTracker.recordMetric('config-validation', performance.now() - startTime); } performanceTracker.endOperation('config-validation'); }); await t.test('Missing required configuration', async () => { performanceTracker.startOperation('missing-config'); class EInvoiceService { private config: any; constructor(config?: any) { this.config = config || {}; } async initialize(): Promise { const required = ['apiKey', 'region', 'validationSchema']; const missing = required.filter(key => !this.config[key]); if (missing.length > 0) { throw new Error(`Missing required configuration: ${missing.join(', ')}`); } // Additional initialization checks if (this.config.region && !['EU', 'US', 'APAC'].includes(this.config.region)) { throw new Error(`Unsupported region: ${this.config.region}`); } if (this.config.validationSchema && !this.config.validationSchema.startsWith('http')) { throw new Error('Validation schema must be a valid URL'); } } } const testCases = [ { name: 'Complete configuration', config: { apiKey: 'test-key-123', region: 'EU', validationSchema: 'https://schema.example.com/v1' }, shouldSucceed: true }, { name: 'Missing API key', config: { region: 'EU', validationSchema: 'https://schema.example.com/v1' }, shouldSucceed: false }, { name: 'Missing multiple required fields', config: { apiKey: 'test-key-123' }, shouldSucceed: false }, { name: 'Invalid region', config: { apiKey: 'test-key-123', region: 'MARS', validationSchema: 'https://schema.example.com/v1' }, shouldSucceed: false }, { name: 'Invalid schema URL', config: { apiKey: 'test-key-123', region: 'EU', validationSchema: 'not-a-url' }, shouldSucceed: false } ]; for (const test of testCases) { const startTime = performance.now(); const service = new EInvoiceService(test.config); try { await service.initialize(); if (test.shouldSucceed) { console.log(`✓ ${test.name}: Initialization successful`); } else { console.log(`✗ ${test.name}: Should have failed`); } } catch (error) { if (!test.shouldSucceed) { console.log(`✓ ${test.name}: ${error.message}`); } else { console.log(`✗ ${test.name}: Unexpected failure - ${error.message}`); } } performanceTracker.recordMetric('initialization', performance.now() - startTime); } performanceTracker.endOperation('missing-config'); }); await t.test('Environment variable conflicts', async () => { performanceTracker.startOperation('env-conflicts'); class EnvironmentConfig { private env: { [key: string]: string | undefined }; constructor(env: { [key: string]: string | undefined } = {}) { this.env = env; } load(): any { const config: any = {}; const conflicts: string[] = []; // Check for conflicting environment variables if (this.env.EINVOICE_MODE && this.env.XINVOICE_MODE) { conflicts.push('Both EINVOICE_MODE and XINVOICE_MODE are set'); } if (this.env.EINVOICE_DEBUG === 'true' && this.env.NODE_ENV === 'production') { conflicts.push('Debug mode enabled in production environment'); } if (this.env.EINVOICE_PORT && this.env.PORT) { if (this.env.EINVOICE_PORT !== this.env.PORT) { conflicts.push(`Port conflict: EINVOICE_PORT=${this.env.EINVOICE_PORT}, PORT=${this.env.PORT}`); } } if (this.env.EINVOICE_LOG_LEVEL) { const validLevels = ['error', 'warn', 'info', 'debug', 'trace']; if (!validLevels.includes(this.env.EINVOICE_LOG_LEVEL)) { conflicts.push(`Invalid log level: ${this.env.EINVOICE_LOG_LEVEL}`); } } if (conflicts.length > 0) { throw new Error(`Environment configuration conflicts:\n${conflicts.join('\n')}`); } // Load configuration config.mode = this.env.EINVOICE_MODE || 'development'; config.debug = this.env.EINVOICE_DEBUG === 'true'; config.port = parseInt(this.env.EINVOICE_PORT || this.env.PORT || '3000'); config.logLevel = this.env.EINVOICE_LOG_LEVEL || 'info'; return config; } } const envTests = [ { name: 'Clean environment', env: { EINVOICE_MODE: 'production', EINVOICE_PORT: '3000', NODE_ENV: 'production' }, shouldSucceed: true }, { name: 'Legacy variable conflict', env: { EINVOICE_MODE: 'production', XINVOICE_MODE: 'development' }, shouldSucceed: false }, { name: 'Debug in production', env: { EINVOICE_DEBUG: 'true', NODE_ENV: 'production' }, shouldSucceed: false }, { name: 'Port conflict', env: { EINVOICE_PORT: '3000', PORT: '8080' }, shouldSucceed: false }, { name: 'Invalid log level', env: { EINVOICE_LOG_LEVEL: 'verbose' }, shouldSucceed: false } ]; for (const test of envTests) { const startTime = performance.now(); const envConfig = new EnvironmentConfig(test.env); try { const config = envConfig.load(); if (test.shouldSucceed) { console.log(`✓ ${test.name}: Configuration loaded successfully`); console.log(` Config: ${JSON.stringify(config)}`); } else { console.log(`✗ ${test.name}: Should have detected conflicts`); } } catch (error) { if (!test.shouldSucceed) { console.log(`✓ ${test.name}: Conflict detected`); console.log(` ${error.message.split('\n')[0]}`); } else { console.log(`✗ ${test.name}: Unexpected error - ${error.message}`); } } performanceTracker.recordMetric('env-check', performance.now() - startTime); } performanceTracker.endOperation('env-conflicts'); }); await t.test('Configuration file parsing errors', async () => { performanceTracker.startOperation('config-parsing'); class ConfigParser { parse(content: string, format: 'json' | 'yaml' | 'toml'): any { switch (format) { case 'json': return this.parseJSON(content); case 'yaml': return this.parseYAML(content); case 'toml': return this.parseTOML(content); default: throw new Error(`Unsupported configuration format: ${format}`); } } private parseJSON(content: string): any { try { return JSON.parse(content); } catch (error) { throw new Error(`Invalid JSON: ${error.message}`); } } private parseYAML(content: string): any { // Simplified YAML parsing simulation if (content.includes('\t')) { throw new Error('YAML parse error: tabs not allowed for indentation'); } if (content.includes(': -')) { throw new Error('YAML parse error: invalid sequence syntax'); } // Simulate successful parse for valid YAML if (content.trim().startsWith('einvoice:')) { return { einvoice: { parsed: true } }; } throw new Error('YAML parse error: invalid structure'); } private parseTOML(content: string): any { // Simplified TOML parsing simulation if (!content.includes('[') && !content.includes('=')) { throw new Error('TOML parse error: no valid sections or key-value pairs'); } if (content.includes('[[') && !content.includes(']]')) { throw new Error('TOML parse error: unclosed array of tables'); } return { toml: { parsed: true } }; } } const parser = new ConfigParser(); const parseTests = [ { name: 'Valid JSON', content: '{"einvoice": {"version": "1.0", "formats": ["UBL", "CII"]}}', format: 'json' as const, shouldSucceed: true }, { name: 'Invalid JSON', content: '{"einvoice": {"version": "1.0", "formats": ["UBL", "CII"]}', format: 'json' as const, shouldSucceed: false }, { name: 'Valid YAML', content: 'einvoice:\n version: "1.0"\n formats:\n - UBL\n - CII', format: 'yaml' as const, shouldSucceed: true }, { name: 'YAML with tabs', content: 'einvoice:\n\tversion: "1.0"', format: 'yaml' as const, shouldSucceed: false }, { name: 'Valid TOML', content: '[einvoice]\nversion = "1.0"\nformats = ["UBL", "CII"]', format: 'toml' as const, shouldSucceed: true }, { name: 'Invalid TOML', content: '[[einvoice.formats\nname = "UBL"', format: 'toml' as const, shouldSucceed: false } ]; for (const test of parseTests) { const startTime = performance.now(); try { const config = parser.parse(test.content, test.format); if (test.shouldSucceed) { console.log(`✓ ${test.name}: Parsed successfully`); } else { console.log(`✗ ${test.name}: Should have failed to parse`); } } catch (error) { if (!test.shouldSucceed) { console.log(`✓ ${test.name}: ${error.message}`); } else { console.log(`✗ ${test.name}: Unexpected parse error - ${error.message}`); } } performanceTracker.recordMetric('config-parse', performance.now() - startTime); } performanceTracker.endOperation('config-parsing'); }); await t.test('Configuration migration errors', async () => { performanceTracker.startOperation('config-migration'); class ConfigMigrator { private migrations = [ { version: '1.0', migrate: (config: any) => { // Rename old fields if (config.xmlValidation !== undefined) { config.validationLevel = config.xmlValidation ? 'strict' : 'lenient'; delete config.xmlValidation; } return config; } }, { version: '2.0', migrate: (config: any) => { // Convert format strings to array if (typeof config.format === 'string') { config.supportedFormats = [config.format]; delete config.format; } return config; } }, { version: '3.0', migrate: (config: any) => { // Restructure API settings if (config.apiKey || config.apiUrl) { config.api = { key: config.apiKey, endpoint: config.apiUrl }; delete config.apiKey; delete config.apiUrl; } return config; } } ]; async migrate(config: any, targetVersion: string): Promise { let currentConfig = { ...config }; const currentVersion = config.version || '1.0'; if (currentVersion === targetVersion) { return currentConfig; } const startIndex = this.migrations.findIndex(m => m.version === currentVersion); const endIndex = this.migrations.findIndex(m => m.version === targetVersion); if (startIndex === -1) { throw new Error(`Unknown source version: ${currentVersion}`); } if (endIndex === -1) { throw new Error(`Unknown target version: ${targetVersion}`); } if (startIndex > endIndex) { throw new Error('Downgrade migrations not supported'); } // Apply migrations in sequence for (let i = startIndex; i <= endIndex; i++) { try { currentConfig = this.migrations[i].migrate(currentConfig); currentConfig.version = this.migrations[i].version; } catch (error) { throw new Error(`Migration to v${this.migrations[i].version} failed: ${error.message}`); } } return currentConfig; } } const migrator = new ConfigMigrator(); const migrationTests = [ { name: 'v1.0 to v3.0 migration', config: { version: '1.0', xmlValidation: true, format: 'UBL', apiKey: 'key123', apiUrl: 'https://api.example.com' }, targetVersion: '3.0', shouldSucceed: true }, { name: 'Already at target version', config: { version: '3.0', validationLevel: 'strict' }, targetVersion: '3.0', shouldSucceed: true }, { name: 'Unknown source version', config: { version: '0.9', oldField: true }, targetVersion: '3.0', shouldSucceed: false }, { name: 'Downgrade attempt', config: { version: '3.0', api: { key: 'test' } }, targetVersion: '1.0', shouldSucceed: false } ]; for (const test of migrationTests) { const startTime = performance.now(); try { const migrated = await migrator.migrate(test.config, test.targetVersion); if (test.shouldSucceed) { console.log(`✓ ${test.name}: Migration successful`); console.log(` Result: ${JSON.stringify(migrated)}`); } else { console.log(`✗ ${test.name}: Should have failed`); } } catch (error) { if (!test.shouldSucceed) { console.log(`✓ ${test.name}: ${error.message}`); } else { console.log(`✗ ${test.name}: Unexpected failure - ${error.message}`); } } performanceTracker.recordMetric('config-migration', performance.now() - startTime); } performanceTracker.endOperation('config-migration'); }); await t.test('Circular configuration dependencies', async () => { performanceTracker.startOperation('circular-deps'); class ConfigResolver { private resolved = new Map(); private resolving = new Set(); resolve(config: any, key: string): any { if (this.resolved.has(key)) { return this.resolved.get(key); } if (this.resolving.has(key)) { throw new Error(`Circular dependency detected: ${Array.from(this.resolving).join(' -> ')} -> ${key}`); } this.resolving.add(key); try { const value = config[key]; if (typeof value === 'string' && value.startsWith('${') && value.endsWith('}')) { // Reference to another config value const refKey = value.slice(2, -1); const resolvedValue = this.resolve(config, refKey); this.resolved.set(key, resolvedValue); return resolvedValue; } this.resolved.set(key, value); return value; } finally { this.resolving.delete(key); } } } const circularTests = [ { name: 'No circular dependency', config: { baseUrl: 'https://api.example.com', apiEndpoint: '${baseUrl}/v1', validationEndpoint: '${apiEndpoint}/validate' }, resolveKey: 'validationEndpoint', shouldSucceed: true }, { name: 'Direct circular dependency', config: { a: '${b}', b: '${a}' }, resolveKey: 'a', shouldSucceed: false }, { name: 'Indirect circular dependency', config: { a: '${b}', b: '${c}', c: '${a}' }, resolveKey: 'a', shouldSucceed: false }, { name: 'Self-reference', config: { recursive: '${recursive}' }, resolveKey: 'recursive', shouldSucceed: false } ]; for (const test of circularTests) { const startTime = performance.now(); const resolver = new ConfigResolver(); try { const resolved = resolver.resolve(test.config, test.resolveKey); if (test.shouldSucceed) { console.log(`✓ ${test.name}: Resolved to "${resolved}"`); } else { console.log(`✗ ${test.name}: Should have detected circular dependency`); } } catch (error) { if (!test.shouldSucceed) { console.log(`✓ ${test.name}: ${error.message}`); } else { console.log(`✗ ${test.name}: Unexpected error - ${error.message}`); } } performanceTracker.recordMetric('circular-check', performance.now() - startTime); } performanceTracker.endOperation('circular-deps'); }); // Performance summary console.log('\n' + performanceTracker.getSummary()); // Configuration error handling best practices console.log('\nConfiguration Error Handling Best Practices:'); console.log('1. Validate all configuration values on startup'); console.log('2. Provide clear error messages for invalid configurations'); console.log('3. Support configuration migration between versions'); console.log('4. Detect and prevent circular dependencies'); console.log('5. Use schema validation for configuration files'); console.log('6. Implement sensible defaults for optional settings'); console.log('7. Check for environment variable conflicts'); console.log('8. Log configuration loading process for debugging'); }); tap.start();