373 lines
13 KiB
TypeScript
373 lines
13 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { Logger } from './Logger';
|
|
|
|
describe('Logger', () => {
|
|
describe('Logger interface', () => {
|
|
it('should have debug method', () => {
|
|
const logs: Array<{ message: string; context?: unknown }> = [];
|
|
|
|
const logger: Logger = {
|
|
debug: (message: string, context?: unknown) => {
|
|
logs.push({ message, context });
|
|
},
|
|
info: () => {},
|
|
warn: () => {},
|
|
error: () => {}
|
|
};
|
|
|
|
logger.debug('Debug message', { userId: 123 });
|
|
|
|
expect(logs).toHaveLength(1);
|
|
expect(logs[0].message).toBe('Debug message');
|
|
expect(logs[0].context).toEqual({ userId: 123 });
|
|
});
|
|
|
|
it('should have info method', () => {
|
|
const logs: Array<{ message: string; context?: unknown }> = [];
|
|
|
|
const logger: Logger = {
|
|
debug: () => {},
|
|
info: (message: string, context?: unknown) => {
|
|
logs.push({ message, context });
|
|
},
|
|
warn: () => {},
|
|
error: () => {}
|
|
};
|
|
|
|
logger.info('Info message', { action: 'login' });
|
|
|
|
expect(logs).toHaveLength(1);
|
|
expect(logs[0].message).toBe('Info message');
|
|
expect(logs[0].context).toEqual({ action: 'login' });
|
|
});
|
|
|
|
it('should have warn method', () => {
|
|
const logs: Array<{ message: string; context?: unknown }> = [];
|
|
|
|
const logger: Logger = {
|
|
debug: () => {},
|
|
info: () => {},
|
|
warn: (message: string, context?: unknown) => {
|
|
logs.push({ message, context });
|
|
},
|
|
error: () => {}
|
|
};
|
|
|
|
logger.warn('Warning message', { threshold: 0.8 });
|
|
|
|
expect(logs).toHaveLength(1);
|
|
expect(logs[0].message).toBe('Warning message');
|
|
expect(logs[0].context).toEqual({ threshold: 0.8 });
|
|
});
|
|
|
|
it('should have error method', () => {
|
|
const logs: Array<{ message: string; error?: Error; context?: unknown }> = [];
|
|
|
|
const logger: Logger = {
|
|
debug: () => {},
|
|
info: () => {},
|
|
warn: () => {},
|
|
error: (message: string, error?: Error, context?: unknown) => {
|
|
logs.push({ message, error, context });
|
|
}
|
|
};
|
|
|
|
const testError = new Error('Test error');
|
|
logger.error('Error occurred', testError, { userId: 456 });
|
|
|
|
expect(logs).toHaveLength(1);
|
|
expect(logs[0].message).toBe('Error occurred');
|
|
expect(logs[0].error).toBe(testError);
|
|
expect(logs[0].context).toEqual({ userId: 456 });
|
|
});
|
|
|
|
it('should support logging without context', () => {
|
|
const logs: string[] = [];
|
|
|
|
const logger: Logger = {
|
|
debug: (message) => logs.push(`DEBUG: ${message}`),
|
|
info: (message) => logs.push(`INFO: ${message}`),
|
|
warn: (message) => logs.push(`WARN: ${message}`),
|
|
error: (message) => logs.push(`ERROR: ${message}`)
|
|
};
|
|
|
|
logger.debug('Debug without context');
|
|
logger.info('Info without context');
|
|
logger.warn('Warn without context');
|
|
logger.error('Error without context');
|
|
|
|
expect(logs).toHaveLength(4);
|
|
expect(logs[0]).toBe('DEBUG: Debug without context');
|
|
expect(logs[1]).toBe('INFO: Info without context');
|
|
expect(logs[2]).toBe('WARN: Warn without context');
|
|
expect(logs[3]).toBe('ERROR: Error without context');
|
|
});
|
|
});
|
|
|
|
describe('Logger behavior', () => {
|
|
it('should support structured logging', () => {
|
|
const logs: Array<{ level: string; message: string; timestamp: string; data: unknown }> = [];
|
|
|
|
const logger: Logger = {
|
|
debug: (message, context) => {
|
|
logs.push({ level: 'debug', message, timestamp: new Date().toISOString(), data: context });
|
|
},
|
|
info: (message, context) => {
|
|
logs.push({ level: 'info', message, timestamp: new Date().toISOString(), data: context });
|
|
},
|
|
warn: (message, context) => {
|
|
logs.push({ level: 'warn', message, timestamp: new Date().toISOString(), data: context });
|
|
},
|
|
error: (message, error, context) => {
|
|
const data: Record<string, unknown> = { error };
|
|
if (context) {
|
|
Object.assign(data, context);
|
|
}
|
|
logs.push({ level: 'error', message, timestamp: new Date().toISOString(), data });
|
|
}
|
|
};
|
|
|
|
logger.info('User logged in', { userId: 'user-123', ip: '192.168.1.1' });
|
|
|
|
expect(logs).toHaveLength(1);
|
|
expect(logs[0].level).toBe('info');
|
|
expect(logs[0].message).toBe('User logged in');
|
|
expect(logs[0].data).toEqual({ userId: 'user-123', ip: '192.168.1.1' });
|
|
});
|
|
|
|
it('should support log level filtering', () => {
|
|
const logs: string[] = [];
|
|
|
|
const logger: Logger = {
|
|
debug: (message) => logs.push(`[DEBUG] ${message}`),
|
|
info: (message) => logs.push(`[INFO] ${message}`),
|
|
warn: (message) => logs.push(`[WARN] ${message}`),
|
|
error: (message) => logs.push(`[ERROR] ${message}`)
|
|
};
|
|
|
|
// Simulate different log levels
|
|
logger.debug('This is a debug message');
|
|
logger.info('This is an info message');
|
|
logger.warn('This is a warning message');
|
|
logger.error('This is an error message');
|
|
|
|
expect(logs).toHaveLength(4);
|
|
expect(logs[0]).toContain('[DEBUG]');
|
|
expect(logs[1]).toContain('[INFO]');
|
|
expect(logs[2]).toContain('[WARN]');
|
|
expect(logs[3]).toContain('[ERROR]');
|
|
});
|
|
|
|
it('should support error logging with stack trace', () => {
|
|
const logs: Array<{ message: string; error: Error; context?: unknown }> = [];
|
|
|
|
const logger: Logger = {
|
|
debug: () => {},
|
|
info: () => {},
|
|
warn: () => {},
|
|
error: (message: string, error?: Error, context?: unknown) => {
|
|
if (error) {
|
|
logs.push({ message, error, context });
|
|
}
|
|
}
|
|
};
|
|
|
|
const error = new Error('Database connection failed');
|
|
logger.error('Failed to connect to database', error, { retryCount: 3 });
|
|
|
|
expect(logs).toHaveLength(1);
|
|
expect(logs[0].message).toBe('Failed to connect to database');
|
|
expect(logs[0].error.message).toBe('Database connection failed');
|
|
expect(logs[0].error.stack).toBeDefined();
|
|
expect(logs[0].context).toEqual({ retryCount: 3 });
|
|
});
|
|
|
|
it('should support logging complex objects', () => {
|
|
const logs: Array<{ message: string; context: unknown }> = [];
|
|
|
|
const logger: Logger = {
|
|
debug: () => {},
|
|
info: (message, context) => logs.push({ message, context }),
|
|
warn: () => {},
|
|
error: () => {}
|
|
};
|
|
|
|
const complexObject = {
|
|
user: {
|
|
id: 'user-123',
|
|
profile: {
|
|
name: 'John Doe',
|
|
email: 'john@example.com',
|
|
preferences: {
|
|
theme: 'dark',
|
|
notifications: true
|
|
}
|
|
}
|
|
},
|
|
session: {
|
|
id: 'session-456',
|
|
expiresAt: new Date('2024-12-31T23:59:59Z')
|
|
}
|
|
};
|
|
|
|
logger.info('User session data', complexObject);
|
|
|
|
expect(logs).toHaveLength(1);
|
|
expect(logs[0].message).toBe('User session data');
|
|
expect(logs[0].context).toEqual(complexObject);
|
|
});
|
|
|
|
it('should support logging arrays', () => {
|
|
const logs: Array<{ message: string; context: unknown }> = [];
|
|
|
|
const logger: Logger = {
|
|
debug: () => {},
|
|
info: (message, context) => logs.push({ message, context }),
|
|
warn: () => {},
|
|
error: () => {}
|
|
};
|
|
|
|
const items = [
|
|
{ id: 1, name: 'Item 1' },
|
|
{ id: 2, name: 'Item 2' },
|
|
{ id: 3, name: 'Item 3' }
|
|
];
|
|
|
|
logger.info('Processing items', { items, count: items.length });
|
|
|
|
expect(logs).toHaveLength(1);
|
|
expect(logs[0].message).toBe('Processing items');
|
|
expect(logs[0].context).toEqual({ items, count: 3 });
|
|
});
|
|
|
|
it('should support logging with null and undefined values', () => {
|
|
const logs: Array<{ message: string; context?: unknown }> = [];
|
|
|
|
const logger: Logger = {
|
|
debug: () => {},
|
|
info: (message, context) => logs.push({ message, context }),
|
|
warn: () => {},
|
|
error: () => {}
|
|
};
|
|
|
|
logger.info('Null value', null);
|
|
logger.info('Undefined value', undefined);
|
|
logger.info('Mixed values', { a: null, b: undefined, c: 'value' });
|
|
|
|
expect(logs).toHaveLength(3);
|
|
expect(logs[0].context).toBe(null);
|
|
expect(logs[1].context).toBe(undefined);
|
|
expect(logs[2].context).toEqual({ a: null, b: undefined, c: 'value' });
|
|
});
|
|
});
|
|
|
|
describe('Logger implementation patterns', () => {
|
|
it('should support console logger implementation', () => {
|
|
const consoleLogs: string[] = [];
|
|
const originalConsoleDebug = console.debug;
|
|
const originalConsoleInfo = console.info;
|
|
const originalConsoleWarn = console.warn;
|
|
const originalConsoleError = console.error;
|
|
|
|
// Mock console methods
|
|
console.debug = (...args: unknown[]) => consoleLogs.push(`DEBUG: ${args.join(' ')}`);
|
|
console.info = (...args: unknown[]) => consoleLogs.push(`INFO: ${args.join(' ')}`);
|
|
console.warn = (...args: unknown[]) => consoleLogs.push(`WARN: ${args.join(' ')}`);
|
|
console.error = (...args: unknown[]) => consoleLogs.push(`ERROR: ${args.join(' ')}`);
|
|
|
|
const consoleLogger: Logger = {
|
|
debug: (message, context) => console.debug(message, context),
|
|
info: (message, context) => console.info(message, context),
|
|
warn: (message, context) => console.warn(message, context),
|
|
error: (message, error, context) => console.error(message, error, context)
|
|
};
|
|
|
|
consoleLogger.debug('Debug message', { data: 'test' });
|
|
consoleLogger.info('Info message', { data: 'test' });
|
|
consoleLogger.warn('Warn message', { data: 'test' });
|
|
consoleLogger.error('Error message', new Error('Test'), { data: 'test' });
|
|
|
|
// Restore console methods
|
|
console.debug = originalConsoleDebug;
|
|
console.info = originalConsoleInfo;
|
|
console.warn = originalConsoleWarn;
|
|
console.error = originalConsoleError;
|
|
|
|
expect(consoleLogs).toHaveLength(4);
|
|
expect(consoleLogs[0]).toContain('DEBUG:');
|
|
expect(consoleLogs[1]).toContain('INFO:');
|
|
expect(consoleLogs[2]).toContain('WARN:');
|
|
expect(consoleLogs[3]).toContain('ERROR:');
|
|
});
|
|
|
|
it('should support file logger implementation', () => {
|
|
const fileLogs: Array<{ timestamp: string; level: string; message: string; data?: unknown }> = [];
|
|
|
|
const fileLogger: Logger = {
|
|
debug: (message, context) => {
|
|
fileLogs.push({ timestamp: new Date().toISOString(), level: 'DEBUG', message, data: context });
|
|
},
|
|
info: (message, context) => {
|
|
fileLogs.push({ timestamp: new Date().toISOString(), level: 'INFO', message, data: context });
|
|
},
|
|
warn: (message, context) => {
|
|
fileLogs.push({ timestamp: new Date().toISOString(), level: 'WARN', message, data: context });
|
|
},
|
|
error: (message, error, context) => {
|
|
const data: Record<string, unknown> = { error };
|
|
if (context) {
|
|
Object.assign(data, context);
|
|
}
|
|
fileLogs.push({ timestamp: new Date().toISOString(), level: 'ERROR', message, data });
|
|
}
|
|
};
|
|
|
|
fileLogger.info('Application started', { version: '1.0.0' });
|
|
fileLogger.warn('High memory usage', { usage: '85%' });
|
|
fileLogger.error('Database error', new Error('Connection timeout'), { retry: 3 });
|
|
|
|
expect(fileLogs).toHaveLength(3);
|
|
expect(fileLogs[0].level).toBe('INFO');
|
|
expect(fileLogs[1].level).toBe('WARN');
|
|
expect(fileLogs[2].level).toBe('ERROR');
|
|
expect(fileLogs[0].data).toEqual({ version: '1.0.0' });
|
|
});
|
|
|
|
it('should support remote logger implementation', async () => {
|
|
const remoteLogs: Array<{ level: string; message: string; context?: unknown }> = [];
|
|
|
|
const remoteLogger: Logger = {
|
|
debug: async (message, context) => {
|
|
remoteLogs.push({ level: 'debug', message, context });
|
|
await new Promise(resolve => setTimeout(resolve, 1));
|
|
},
|
|
info: async (message, context) => {
|
|
remoteLogs.push({ level: 'info', message, context });
|
|
await new Promise(resolve => setTimeout(resolve, 1));
|
|
},
|
|
warn: async (message, context) => {
|
|
remoteLogs.push({ level: 'warn', message, context });
|
|
await new Promise(resolve => setTimeout(resolve, 1));
|
|
},
|
|
error: async (message, error, context) => {
|
|
const errorContext: Record<string, unknown> = { error };
|
|
if (context) {
|
|
Object.assign(errorContext, context);
|
|
}
|
|
remoteLogs.push({ level: 'error', message, context: errorContext });
|
|
await new Promise(resolve => setTimeout(resolve, 1));
|
|
}
|
|
};
|
|
|
|
await remoteLogger.info('User action', { action: 'click', element: 'button' });
|
|
await remoteLogger.warn('Performance warning', { duration: '2000ms' });
|
|
await remoteLogger.error('API failure', new Error('404 Not Found'), { endpoint: '/api/users' });
|
|
|
|
expect(remoteLogs).toHaveLength(3);
|
|
expect(remoteLogs[0].level).toBe('info');
|
|
expect(remoteLogs[1].level).toBe('warn');
|
|
expect(remoteLogs[2].level).toBe('error');
|
|
});
|
|
});
|
|
});
|