import { describe, it, expect } from 'vitest'; import { ErrorLogBuffer } from '../src/services/error-log-buffer.js'; const line = (o: Record): string => JSON.stringify(o) + '\n'; describe('ErrorLogBuffer', () => { it('keeps error/fatal records and drops info/warn', () => { const b = new ErrorLogBuffer(); b.recordLine(line({ level: 30, time: 1, msg: 'info' })); b.recordLine(line({ level: 40, time: 2, msg: 'warn' })); b.recordLine(line({ level: 50, time: 3, msg: 'an error' })); b.recordLine(line({ level: 60, time: 4, msg: 'fatal', kind: 'BACKEND_TOKEN_DEAD' })); const recent = b.recent(); expect(recent.map((e) => e.msg)).toEqual(['fatal', 'an error']); // most-recent first expect(recent[0]?.kind).toBe('BACKEND_TOKEN_DEAD'); }); it('flattens an err object to its message', () => { const b = new ErrorLogBuffer(); b.recordLine(line({ level: 50, time: 1, msg: 'boom', err: { type: 'Error', message: 'OpenBao write … 403' } })); expect(b.recent()[0]?.err).toBe('OpenBao write … 403'); }); it('ignores blank and non-JSON lines', () => { const b = new ErrorLogBuffer(); b.recordLine(''); b.recordLine('not json at all'); b.recordLine(' '); expect(b.recent()).toHaveLength(0); }); it('caps at capacity (ring buffer)', () => { const b = new ErrorLogBuffer(3); for (let i = 1; i <= 5; i++) b.recordLine(line({ level: 50, time: i, msg: `e${i}` })); const recent = b.recent(10); expect(recent.map((e) => e.msg)).toEqual(['e5', 'e4', 'e3']); // oldest two evicted }); it('respects the limit argument', () => { const b = new ErrorLogBuffer(); for (let i = 1; i <= 10; i++) b.recordLine(line({ level: 50, time: i })); expect(b.recent(2)).toHaveLength(2); }); it('handles multiple records in one chunk via the stream', (ctx) => new Promise((resolve) => { const b = new ErrorLogBuffer(); const s = b.stream(); s.write(line({ level: 50, time: 1, msg: 'a' }) + line({ level: 30, time: 2 }) + line({ level: 60, time: 3, msg: 'b' })); s.end(() => { expect(b.recent().map((e) => e.msg)).toEqual(['b', 'a']); resolve(); }); })); });