import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { createLogger, LogLevel, getLogLevel, setLogLevel, setColorsEnabled } from '../src/logger'; describe('logger.ts', () => { let originalConsoleError: typeof console.error; let originalConsoleWarn: typeof console.warn; let originalConsoleLog: typeof console.log; let originalLogLevel: LogLevel; beforeEach(() => { // Save original console methods and log level originalConsoleError = console.error; originalConsoleWarn = console.warn; originalConsoleLog = console.log; originalLogLevel = getLogLevel(); // Disable colors for predictable test output setColorsEnabled(false); // Mock console methods console.error = vi.fn(); console.warn = vi.fn(); console.log = vi.fn(); }); afterEach(() => { // Restore original console methods and log level console.error = originalConsoleError; console.warn = originalConsoleWarn; console.log = originalConsoleLog; setLogLevel(originalLogLevel); }); describe('createLogger', () => { it('should create logger with context prefix', () => { const logger = createLogger('TestContext'); setLogLevel(LogLevel.INFO); logger.info('test message'); expect(console.log).toHaveBeenCalledWith('INFO [TestContext]', 'test message'); }); it('should handle multiple arguments', () => { const logger = createLogger('Test'); setLogLevel(LogLevel.INFO); logger.info('message', { data: 123 }, [1, 2, 3]); expect(console.log).toHaveBeenCalledWith('INFO [Test]', 'message', { data: 123 }, [1, 2, 3]); }); }); describe('Log levels', () => { it('should log error at ERROR level', () => { const logger = createLogger('Test'); setLogLevel(LogLevel.ERROR); logger.error('error message'); logger.warn('warn message'); logger.info('info message'); logger.debug('debug message'); expect(console.error).toHaveBeenCalledTimes(1); expect(console.warn).not.toHaveBeenCalled(); expect(console.log).not.toHaveBeenCalled(); }); it('should log error and warn at WARN level', () => { const logger = createLogger('Test'); setLogLevel(LogLevel.WARN); logger.error('error message'); logger.warn('warn message'); logger.info('info message'); logger.debug('debug message'); expect(console.error).toHaveBeenCalledTimes(1); // Note: warn uses console.log in Node.js implementation expect(console.log).toHaveBeenCalledTimes(1); }); it('should log error, warn, and info at INFO level', () => { const logger = createLogger('Test'); setLogLevel(LogLevel.INFO); logger.error('error message'); logger.warn('warn message'); logger.info('info message'); logger.debug('debug message'); expect(console.error).toHaveBeenCalledTimes(1); // Note: warn and info both use console.log in Node.js implementation expect(console.log).toHaveBeenCalledTimes(2); // warn + info, not debug }); it('should log all messages at DEBUG level', () => { const logger = createLogger('Test'); setLogLevel(LogLevel.DEBUG); logger.error('error message'); logger.warn('warn message'); logger.info('info message'); logger.debug('debug message'); expect(console.error).toHaveBeenCalledTimes(1); // Note: warn, info, debug all use console.log in Node.js implementation expect(console.log).toHaveBeenCalledTimes(3); // warn + info + debug }); }); describe('error method', () => { it('should use console.error', () => { const logger = createLogger('ErrorTest'); setLogLevel(LogLevel.ERROR); logger.error('error occurred', { code: 500 }); expect(console.error).toHaveBeenCalledWith('ERROR [ErrorTest]', 'error occurred', { code: 500, }); }); it('should not log when level is below ERROR', () => { const logger = createLogger('Test'); setLogLevel((LogLevel.ERROR - 1) as LogLevel); logger.error('should not appear'); expect(console.error).not.toHaveBeenCalled(); }); }); describe('warn method', () => { it('should use console.log with WARN prefix', () => { const logger = createLogger('WarnTest'); setLogLevel(LogLevel.WARN); logger.warn('warning message'); // Note: warn uses console.log in Node.js implementation expect(console.log).toHaveBeenCalledWith('WARN [WarnTest]', 'warning message'); }); it('should not log when level is below WARN', () => { const logger = createLogger('Test'); setLogLevel(LogLevel.ERROR); logger.warn('should not appear'); expect(console.log).not.toHaveBeenCalled(); }); }); describe('info method', () => { it('should use console.log', () => { const logger = createLogger('InfoTest'); setLogLevel(LogLevel.INFO); logger.info('info message'); expect(console.log).toHaveBeenCalledWith('INFO [InfoTest]', 'info message'); }); it('should not log when level is below INFO', () => { const logger = createLogger('Test'); setLogLevel(LogLevel.WARN); logger.info('should not appear'); expect(console.log).not.toHaveBeenCalled(); }); }); describe('debug method', () => { it('should use console.log with DEBUG prefix', () => { const logger = createLogger('DebugTest'); setLogLevel(LogLevel.DEBUG); logger.debug('debug details', { trace: '...' }); expect(console.log).toHaveBeenCalledWith('DEBUG [DebugTest]', 'debug details', { trace: '...', }); }); it('should not log when level is below DEBUG', () => { const logger = createLogger('Test'); setLogLevel(LogLevel.INFO); logger.debug('should not appear'); expect(console.log).not.toHaveBeenCalled(); }); }); describe('getLogLevel', () => { it('should return current log level', () => { setLogLevel(LogLevel.DEBUG); expect(getLogLevel()).toBe(LogLevel.DEBUG); setLogLevel(LogLevel.ERROR); expect(getLogLevel()).toBe(LogLevel.ERROR); }); }); describe('setLogLevel', () => { it('should change log level', () => { setLogLevel(LogLevel.WARN); expect(getLogLevel()).toBe(LogLevel.WARN); setLogLevel(LogLevel.DEBUG); expect(getLogLevel()).toBe(LogLevel.DEBUG); }); it('should affect subsequent logging', () => { const logger = createLogger('Test'); setLogLevel(LogLevel.ERROR); logger.info('should not log'); expect(console.log).not.toHaveBeenCalled(); setLogLevel(LogLevel.INFO); logger.info('should log'); expect(console.log).toHaveBeenCalledWith('INFO [Test]', 'should log'); }); }); describe('Multiple logger instances', () => { it('should maintain separate contexts', () => { const logger1 = createLogger('Service1'); const logger2 = createLogger('Service2'); setLogLevel(LogLevel.INFO); logger1.info('from service 1'); logger2.info('from service 2'); expect(console.log).toHaveBeenNthCalledWith(1, 'INFO [Service1]', 'from service 1'); expect(console.log).toHaveBeenNthCalledWith(2, 'INFO [Service2]', 'from service 2'); }); it('should share log level setting', () => { const logger1 = createLogger('Service1'); const logger2 = createLogger('Service2'); setLogLevel(LogLevel.ERROR); logger1.info('should not log'); logger2.info('should not log'); expect(console.log).not.toHaveBeenCalled(); }); }); describe('Edge cases', () => { it('should handle empty context string', () => { const logger = createLogger(''); setLogLevel(LogLevel.INFO); logger.info('message'); expect(console.log).toHaveBeenCalledWith('INFO []', 'message'); }); it('should handle context with special characters', () => { const logger = createLogger('Test-Service_v2.0'); setLogLevel(LogLevel.INFO); logger.info('message'); expect(console.log).toHaveBeenCalledWith('INFO [Test-Service_v2.0]', 'message'); }); it('should handle no arguments to log methods', () => { const logger = createLogger('Test'); setLogLevel(LogLevel.INFO); logger.info(); expect(console.log).toHaveBeenCalledWith('INFO [Test]'); }); it('should handle complex object arguments', () => { const logger = createLogger('Test'); setLogLevel(LogLevel.INFO); const complexObj = { nested: { deep: { value: 123 } }, array: [1, 2, 3], fn: () => {}, }; logger.info('complex', complexObj); expect(console.log).toHaveBeenCalledWith('INFO [Test]', 'complex', complexObj); }); }); });