367 lines
11 KiB
TypeScript
367 lines
11 KiB
TypeScript
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<string, number> = {};
|
|
|
|
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<string, unknown> = {
|
|
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: 'development'
|
|
});
|
|
});
|
|
|
|
it('should support error deduplication', () => {
|
|
const uniqueErrors: Error[] = [];
|
|
const seenMessages = new Set<string>();
|
|
|
|
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);
|
|
});
|
|
});
|
|
});
|