import { describe, it, expect, beforeEach } from 'vitest'; import { createLogger, setLogLevel, getRecentLogs, clearRecentLogs, dumpLogs } from './logger'; describe('logger', () => { beforeEach(() => { clearRecentLogs(); setLogLevel('debug'); }); it('createLogger returns scoped logger with all levels', () => { const log = createLogger('test.context'); expect(log.debug).toBeTypeOf('function'); expect(log.info).toBeTypeOf('function'); expect(log.warn).toBeTypeOf('function'); expect(log.error).toBeTypeOf('function'); }); it('logs are stored in recent logs buffer', () => { const log = createLogger('test'); log.info('hello'); log.warn('warning'); const recent = getRecentLogs(); expect(recent).toHaveLength(2); expect(recent[0].level).toBe('info'); expect(recent[0].context).toBe('test'); expect(recent[0].message).toBe('hello'); expect(recent[1].level).toBe('warn'); }); it('clearRecentLogs empties the buffer', () => { const log = createLogger('test'); log.info('msg'); expect(getRecentLogs()).toHaveLength(1); clearRecentLogs(); expect(getRecentLogs()).toHaveLength(0); }); it('setLogLevel filters lower-priority logs', () => { setLogLevel('warn'); const log = createLogger('test'); log.debug('should be filtered'); log.info('should be filtered'); log.warn('should appear'); log.error('should appear'); const recent = getRecentLogs(); expect(recent).toHaveLength(2); expect(recent[0].level).toBe('warn'); expect(recent[1].level).toBe('error'); }); it('log entries include structured data', () => { const log = createLogger('test'); log.info('with data', { data: { userId: '123' } }); const recent = getRecentLogs(); expect(recent[0].data).toEqual({ userId: '123' }); }); it('log entries include error objects', () => { const log = createLogger('test'); const err = new Error('test error'); log.error('failed', { error: err }); const recent = getRecentLogs(); expect(recent[0].error).toBe(err); }); it('log entries have ISO timestamp', () => { const log = createLogger('test'); log.info('timestamped'); const recent = getRecentLogs(); expect(recent[0].timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T/); }); it('dumpLogs formats entries as readable string', () => { const log = createLogger('ctx'); log.info('message one', { data: { key: 'val' } }); log.error('message two'); const dump = dumpLogs(); expect(dump).toContain('[INFO]'); expect(dump).toContain('[ctx]'); expect(dump).toContain('message one'); expect(dump).toContain('"key": "val"'); expect(dump).toContain('[ERROR]'); expect(dump).toContain('message two'); }); it('getRecentLogs returns a copy, not the internal buffer', () => { const log = createLogger('test'); log.info('msg'); const copy = getRecentLogs(); copy.push({ level: 'debug', context: 'fake', message: 'injected', timestamp: '' }); expect(getRecentLogs()).toHaveLength(1); }); it('buffer caps at 100 entries', () => { const log = createLogger('test'); for (let i = 0; i < 110; i++) { log.info(`msg ${i}`); } const recent = getRecentLogs(); expect(recent).toHaveLength(100); expect(recent[0].message).toBe('msg 10'); expect(recent[99].message).toBe('msg 109'); }); });