import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { createLogger } from '@/observability/logger' // Mock only the logger-context for most tests, but we'll test integration without mocks vi.mock('@/observability/logger/lib/logger-context') describe('createLogger', () => { let originalEnv: typeof process.env let originalConsoleLog: typeof console.log let originalConsoleError: typeof console.error const consoleLogs: string[] = [] const consoleErrors: any[] = [] beforeEach(() => { // Store original environment and console methods originalEnv = { ...process.env } originalConsoleLog = console.log originalConsoleError = console.error // Mock console methods to capture output console.log = vi.fn((message: string) => { consoleLogs.push(message) }) console.error = vi.fn((error: any) => { consoleErrors.push(error) }) // Clear captured output consoleLogs.length = 0 consoleErrors.length = 0 // Set default environment vi.stubEnv('NODE_ENV', 'development') vi.stubEnv('LOG_LEVEL', 'debug') }) afterEach(() => { // Restore original environment and console methods process.env = originalEnv console.log = originalConsoleLog console.error = originalConsoleError vi.clearAllMocks() }) describe('constructor validation', () => { it('should throw error when filePath is not provided', () => { expect(() => createLogger('')).toThrow( 'createLogger must be called with the import.meta.url argument', ) }) it('should throw error when filePath is null or undefined', () => { expect(() => createLogger(null as any)).toThrow( 'createLogger must be called with the import.meta.url argument', ) expect(() => createLogger(undefined as any)).toThrow( 'createLogger must be called with the import.meta.url argument', ) }) it('should create logger successfully with valid filePath', () => { const logger = createLogger('file:///path/to/test.js') expect(logger).toHaveProperty('error') expect(logger).toHaveProperty('warn') expect(logger).toHaveProperty('info') expect(logger).toHaveProperty('debug') }) }) describe('logging patterns in development mode', () => { let logger: ReturnType beforeEach(() => { vi.stubEnv('NODE_ENV', 'development') logger = createLogger('file:///path/to/test.js') }) it('should log simple messages (Pattern 1)', () => { logger.info('Hello world') expect(consoleLogs).toContain('[INFO] Hello world') }) it('should log messages with extra data (Pattern 2)', () => { logger.info('User logged in', { userId: 123, email: 'test@example.com' }) expect(consoleLogs).toContain('[INFO] User logged in') }) it('should log multiple message parts (Pattern 3)', () => { logger.info('User', 'action', 123, true) expect(consoleLogs).toContain('[INFO] User action 123 true') }) it('should log multiple message parts with extra data (Pattern 4)', () => { logger.info('User', 'login', 'success', { userId: 123 }) expect(consoleLogs).toContain('[INFO] User login success') }) it('should log messages with Error objects (Pattern 5)', () => { const error = new Error('Database connection failed') logger.error('Database error', error) expect(consoleLogs).toContain('[ERROR] Database error: Database connection failed') expect(consoleErrors).toContain(error) }) it('should log messages with multiple errors and parts (Pattern 6)', () => { const error1 = new Error('First error') const error2 = new Error('Second error') logger.error('Multiple failures', error1, error2) expect(consoleLogs).toContain('[ERROR] Multiple failures: First error, Second error') expect(consoleErrors).toContain(error1) expect(consoleErrors).toContain(error2) }) it('should handle mixed arguments with errors and extra data', () => { const error = new Error('Test error') logger.error('Operation failed', 'with code', 500, error, { operation: 'delete' }) expect(consoleLogs).toContain('[ERROR] Operation failed with code 500: Test error') expect(consoleErrors).toContain(error) }) it('should log all levels in development', () => { logger.debug('Debug message') logger.info('Info message') logger.warn('Warning message') logger.error('Error message') expect(consoleLogs).toContain('[DEBUG] Debug message') expect(consoleLogs).toContain('[INFO] Info message') expect(consoleLogs).toContain('[WARN] Warning message') expect(consoleLogs).toContain('[ERROR] Error message') }) }) describe('logging with mocked context', () => { let logger: ReturnType beforeEach(() => { logger = createLogger('file:///path/to/test.js') }) it('should use development format when context is mocked', () => { logger.info('Test message') // Check that a log was output in development format expect(consoleLogs).toHaveLength(1) const logOutput = consoleLogs[0] expect(logOutput).toBe('[INFO] Test message') }) it('should include extra data in development logs', () => { logger.info('User action', { userId: 123, action: 'login' }) expect(consoleLogs).toHaveLength(1) const logOutput = consoleLogs[0] expect(logOutput).toBe('[INFO] User action') }) it('should format errors properly in development logs', () => { const error = new Error('Test error') error.stack = 'Error: Test error\n at test.js:1:1' logger.error('Something failed', error) expect(consoleLogs).toHaveLength(1) expect(consoleErrors).toHaveLength(1) const logOutput = consoleLogs[0] expect(logOutput).toBe('[ERROR] Something failed: Test error') expect(consoleErrors[0]).toBe(error) }) it('should handle multiple errors in development logs', () => { const error1 = new Error('First error') const error2 = new Error('Second error') error1.stack = 'Error: First error\n at test.js:1:1' error2.stack = 'Error: Second error\n at test.js:2:1' logger.error('Multiple errors', error1, error2) // In development mode, each error triggers a separate console.log + console.error expect(consoleLogs).toHaveLength(2) expect(consoleErrors).toHaveLength(2) // Both log entries should have the same message expect(consoleLogs[0]).toBe('[ERROR] Multiple errors: First error, Second error') expect(consoleLogs[1]).toBe('[ERROR] Multiple errors: First error, Second error') expect(consoleErrors[0]).toBe(error1) expect(consoleErrors[1]).toBe(error2) }) }) describe('log level filtering', () => { let logger: ReturnType beforeEach(() => { // Clear console logs before each test consoleLogs.length = 0 consoleErrors.length = 0 }) it('should respect LOG_LEVEL=error setting', () => { // Mock the function to return error level (0) and dynamically import logger vi.stubEnv('LOG_LEVEL', 'error') logger = createLogger('file:///path/to/test.js') logger.debug('Debug message') logger.info('Info message') logger.warn('Warn message') logger.error('Error message') expect(consoleLogs).not.toContain('[DEBUG] Debug message') expect(consoleLogs).not.toContain('[INFO] Info message') expect(consoleLogs).not.toContain('[WARN] Warn message') expect(consoleLogs).toContain('[ERROR] Error message') }) it('should respect LOG_LEVEL=warn setting', () => { vi.stubEnv('LOG_LEVEL', 'warn') logger = createLogger('file:///path/to/test.js') logger.debug('Debug message') logger.info('Info message') logger.warn('Warn message') logger.error('Error message') expect(consoleLogs).not.toContain('[DEBUG] Debug message') expect(consoleLogs).not.toContain('[INFO] Info message') expect(consoleLogs).toContain('[WARN] Warn message') expect(consoleLogs).toContain('[ERROR] Error message') }) it('should respect LOG_LEVEL=info setting', () => { vi.stubEnv('LOG_LEVEL', 'info') logger = createLogger('file:///path/to/test.js') logger.debug('Debug message') logger.info('Info message') logger.warn('Warn message') logger.error('Error message') expect(consoleLogs).not.toContain('[DEBUG] Debug message') expect(consoleLogs).toContain('[INFO] Info message') expect(consoleLogs).toContain('[WARN] Warn message') expect(consoleLogs).toContain('[ERROR] Error message') }) }) describe('edge cases and error handling', () => { let logger: ReturnType beforeEach(() => { // Clear console logs before each test consoleLogs.length = 0 consoleErrors.length = 0 logger = createLogger('file:///path/to/test.js') }) it('should handle null and undefined values in extra data', () => { logger.info('Test message', { nullValue: null, undefinedValue: undefined }) expect(consoleLogs).toContain('[INFO] Test message') }) it('should handle arrays in extra data', () => { logger.info('Test message', { items: [1, 2, 3] }) expect(consoleLogs).toContain('[INFO] Test message') }) it('should handle Date objects in extra data', () => { const date = new Date('2023-01-01T00:00:00.000Z') logger.info('Test message', { timestamp: date }) expect(consoleLogs).toContain('[INFO] Test message') }) it('should handle nested objects properly', () => { logger.info('Test message', { user: { id: 123, profile: { name: 'John', age: 30 }, }, }) expect(consoleLogs).toContain('[INFO] Test message') }) it('should distinguish between plain objects and class instances', () => { class CustomClass { constructor(public value: string) {} } const instance = new CustomClass('test') logger.info('Custom object', instance) expect(consoleLogs).toContain('[INFO] Custom object [object Object]') }) it('should handle empty arguments gracefully', () => { logger.info('Just a message') expect(consoleLogs).toContain('[INFO] Just a message') }) it('should handle boolean and number arguments', () => { logger.info('Values:', true, false, 42, 0, -1) expect(consoleLogs).toContain('[INFO] Values: true false 42 0 -1') }) }) describe('file path handling in development', () => { it('should log file paths in development format', () => { const logger = createLogger('file:///Users/test/project/src/test.js') logger.info('Test message') expect(consoleLogs).toHaveLength(1) const logOutput = consoleLogs[0] expect(logOutput).toBe('[INFO] Test message') }) it('should handle relative paths in development logs', () => { const logger = createLogger('file:///absolute/path/to/src/component/test.ts') logger.info('Test message') expect(consoleLogs).toHaveLength(1) const logOutput = consoleLogs[0] expect(logOutput).toBe('[INFO] Test message') }) }) describe('logger context integration with mocks', () => { let logger: ReturnType beforeEach(() => { logger = createLogger('file:///path/to/test.js') }) it('should handle missing logger context gracefully in development', () => { logger.info('No context test') expect(consoleLogs).toHaveLength(1) const logOutput = consoleLogs[0] expect(logOutput).toBe('[INFO] No context test') }) }) describe('complex argument combinations', () => { let logger: ReturnType beforeEach(() => { // Clear console logs before each test consoleLogs.length = 0 consoleErrors.length = 0 logger = createLogger('file:///path/to/test.js') }) it('should handle error at the beginning of arguments', () => { const error = new Error('First error') logger.error('Error occurred', error, 'additional', 'info', { extra: 'data' }) expect(consoleLogs).toContain('[ERROR] Error occurred additional info: First error') expect(consoleErrors).toContain(error) }) it('should handle error in the middle of arguments', () => { const error = new Error('Middle error') logger.error('Error', 'in', error, 'middle', { context: 'test' }) expect(consoleLogs).toContain('[ERROR] Error in middle: Middle error') expect(consoleErrors).toContain(error) }) it('should handle multiple data types in arguments', () => { logger.info('Mixed', 123, true, 'string', { data: 'object' }) expect(consoleLogs).toContain('[INFO] Mixed 123 true string') }) it('should prioritize plain objects as extra data over other objects', () => { vi.stubEnv('LOG_LIKE_PRODUCTION', 'true') // Create new logger instance logger = createLogger('file:///path/to/test.js') const date = new Date() const plainObject = { key: 'value' } logger.info('Test', date, 'string', plainObject) expect(consoleLogs).toHaveLength(1) const logOutput = consoleLogs[0] // The message should contain the full string with date converted to string expect(logOutput).toContain('message="Test') expect(logOutput).toContain('string"') // The plain object should be in the included context expect(logOutput).toContain('included.key=value') }) }) })