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 = { 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 = { 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 = { 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'); }); }); });