import { describe, it, expect } from 'vitest'; import { ErrorReporter } from './ErrorReporter'; describe('ErrorReporter', () => { describe('ErrorReporter interface', () => { it('should have report method', () => { const errors: Array<{ error: Error; context?: unknown }> = []; const reporter: ErrorReporter = { report: (error: Error, context?: unknown) => { errors.push({ error, context }); } }; const testError = new Error('Test error'); reporter.report(testError, { userId: 123 }); expect(errors).toHaveLength(1); expect(errors[0].error).toBe(testError); expect(errors[0].context).toEqual({ userId: 123 }); }); it('should support reporting without context', () => { const errors: Error[] = []; const reporter: ErrorReporter = { report: (error: Error) => { errors.push(error); } }; const testError = new Error('Test error'); reporter.report(testError); expect(errors).toHaveLength(1); expect(errors[0]).toBe(testError); }); it('should support different error types', () => { const errors: Array<{ error: Error; context?: unknown }> = []; const reporter: ErrorReporter = { report: (error: Error, context?: unknown) => { errors.push({ error, context }); } }; // Standard Error const standardError = new Error('Standard error'); reporter.report(standardError, { type: 'standard' }); // Custom Error class CustomError extends Error { constructor(message: string, public code: string) { super(message); this.name = 'CustomError'; } } const customError = new CustomError('Custom error', 'CUSTOM_CODE'); reporter.report(customError, { type: 'custom' }); // TypeError const typeError = new TypeError('Type error'); reporter.report(typeError, { type: 'type' }); expect(errors).toHaveLength(3); expect(errors[0].error).toBe(standardError); expect(errors[1].error).toBe(customError); expect(errors[2].error).toBe(typeError); }); it('should support complex context objects', () => { const errors: Array<{ error: Error; context?: unknown }> = []; const reporter: ErrorReporter = { report: (error: Error, context?: unknown) => { errors.push({ error, context }); } }; const complexContext = { user: { id: 'user-123', name: 'John Doe', email: 'john@example.com' }, request: { method: 'POST', url: '/api/users', headers: { 'content-type': 'application/json', 'authorization': 'Bearer token' } }, timestamp: new Date('2024-01-01T12:00:00Z'), metadata: { retryCount: 3, timeout: 5000 } }; const error = new Error('Request failed'); reporter.report(error, complexContext); expect(errors).toHaveLength(1); expect(errors[0].error).toBe(error); expect(errors[0].context).toEqual(complexContext); }); }); describe('ErrorReporter behavior', () => { it('should support logging error with stack trace', () => { const logs: Array<{ message: string; stack?: string; context?: unknown }> = []; const reporter: ErrorReporter = { report: (error: Error, context?: unknown) => { logs.push({ message: error.message, stack: error.stack, context }); } }; const error = new Error('Database connection failed'); reporter.report(error, { retryCount: 3 }); expect(logs).toHaveLength(1); expect(logs[0].message).toBe('Database connection failed'); expect(logs[0].stack).toBeDefined(); expect(logs[0].context).toEqual({ retryCount: 3 }); }); it('should support error aggregation', () => { const errorCounts: Record = {}; const reporter: ErrorReporter = { report: (error: Error) => { const errorType = error.name || 'Unknown'; errorCounts[errorType] = (errorCounts[errorType] || 0) + 1; } }; const error1 = new Error('Error 1'); const error2 = new TypeError('Type error'); const error3 = new Error('Error 2'); const error4 = new TypeError('Another type error'); reporter.report(error1); reporter.report(error2); reporter.report(error3); reporter.report(error4); expect(errorCounts['Error']).toBe(2); expect(errorCounts['TypeError']).toBe(2); }); it('should support error filtering', () => { const criticalErrors: Error[] = []; const warnings: Error[] = []; const reporter: ErrorReporter = { report: (error: Error, context?: unknown) => { const isCritical = context && typeof context === 'object' && 'severity' in context && (context as { severity: string }).severity === 'critical'; if (isCritical) { criticalErrors.push(error); } else { warnings.push(error); } } }; const criticalError = new Error('Critical failure'); const warningError = new Error('Warning'); reporter.report(criticalError, { severity: 'critical' }); reporter.report(warningError, { severity: 'warning' }); expect(criticalErrors).toHaveLength(1); expect(criticalErrors[0]).toBe(criticalError); expect(warnings).toHaveLength(1); expect(warnings[0]).toBe(warningError); }); it('should support error enrichment', () => { const enrichedErrors: Array<{ error: Error; enrichedContext: unknown }> = []; const reporter: ErrorReporter = { report: (error: Error, context?: unknown) => { const enrichedContext: Record = { errorName: error.name, errorMessage: error.message, timestamp: new Date().toISOString(), environment: process.env.NODE_ENV || 'development' }; if (context && typeof context === 'object') { Object.assign(enrichedContext, context); } enrichedErrors.push({ error, enrichedContext }); } }; const error = new Error('Something went wrong'); reporter.report(error, { userId: 'user-123', action: 'login' }); expect(enrichedErrors).toHaveLength(1); expect(enrichedErrors[0].error).toBe(error); expect(enrichedErrors[0].enrichedContext).toMatchObject({ userId: 'user-123', action: 'login', errorName: 'Error', errorMessage: 'Something went wrong', environment: 'test' }); }); it('should support error deduplication', () => { const uniqueErrors: Error[] = []; const seenMessages = new Set(); const reporter: ErrorReporter = { report: (error: Error) => { if (!seenMessages.has(error.message)) { uniqueErrors.push(error); seenMessages.add(error.message); } } }; const error1 = new Error('Duplicate error'); const error2 = new Error('Duplicate error'); const error3 = new Error('Unique error'); reporter.report(error1); reporter.report(error2); reporter.report(error3); expect(uniqueErrors).toHaveLength(2); expect(uniqueErrors[0].message).toBe('Duplicate error'); expect(uniqueErrors[1].message).toBe('Unique error'); }); it('should support error rate limiting', () => { const errors: Error[] = []; let errorCount = 0; const rateLimit = 5; const reporter: ErrorReporter = { report: (error: Error) => { errorCount++; if (errorCount <= rateLimit) { errors.push(error); } // Silently drop errors beyond rate limit } }; // Report 10 errors for (let i = 0; i < 10; i++) { reporter.report(new Error(`Error ${i}`)); } expect(errors).toHaveLength(rateLimit); expect(errorCount).toBe(10); }); }); describe('ErrorReporter implementation patterns', () => { it('should support console logger implementation', () => { const consoleErrors: string[] = []; const originalConsoleError = console.error; // Mock console.error console.error = (...args: unknown[]) => consoleErrors.push(args.join(' ')); const consoleReporter: ErrorReporter = { report: (error: Error, context?: unknown) => { console.error('Error:', error.message, 'Context:', context); } }; const error = new Error('Test error'); consoleReporter.report(error, { userId: 123 }); // Restore console.error console.error = originalConsoleError; expect(consoleErrors).toHaveLength(1); expect(consoleErrors[0]).toContain('Error:'); expect(consoleErrors[0]).toContain('Test error'); expect(consoleErrors[0]).toContain('Context:'); }); it('should support file logger implementation', () => { const fileLogs: Array<{ timestamp: string; error: string; context?: unknown }> = []; const fileReporter: ErrorReporter = { report: (error: Error, context?: unknown) => { fileLogs.push({ timestamp: new Date().toISOString(), error: error.message, context }); } }; const error = new Error('File error'); fileReporter.report(error, { file: 'test.txt', line: 42 }); expect(fileLogs).toHaveLength(1); expect(fileLogs[0].error).toBe('File error'); expect(fileLogs[0].context).toEqual({ file: 'test.txt', line: 42 }); }); it('should support remote reporter implementation', async () => { const remoteErrors: Array<{ error: string; context?: unknown }> = []; const remoteReporter: ErrorReporter = { report: async (error: Error, context?: unknown) => { remoteErrors.push({ error: error.message, context }); await new Promise(resolve => setTimeout(resolve, 1)); } }; const error = new Error('Remote error'); await remoteReporter.report(error, { endpoint: '/api/data' }); expect(remoteErrors).toHaveLength(1); expect(remoteErrors[0].error).toBe('Remote error'); expect(remoteErrors[0].context).toEqual({ endpoint: '/api/data' }); }); it('should support batch error reporting', () => { const batchErrors: Error[] = []; const batchSize = 3; let currentBatch: Error[] = []; const reporter: ErrorReporter = { report: (error: Error) => { currentBatch.push(error); if (currentBatch.length >= batchSize) { batchErrors.push(...currentBatch); currentBatch = []; } } }; // Report 7 errors for (let i = 0; i < 7; i++) { reporter.report(new Error(`Error ${i}`)); } // Add remaining errors if (currentBatch.length > 0) { batchErrors.push(...currentBatch); } expect(batchErrors).toHaveLength(7); }); }); });