core tests
This commit is contained in:
366
core/shared/application/ErrorReporter.test.ts
Normal file
366
core/shared/application/ErrorReporter.test.ts
Normal file
@@ -0,0 +1,366 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user