import { describe, it, expect } from 'vitest'; import { formatEventLine, parseEventLine, replay, activeSessionOf } from './oq-ledger.mjs'; describe('formatEventLine / parseEventLine', () => { it('сериализует событие в одну читаемую .md-строку с провенансом', () => { const line = formatEventLine({ at: '2026-06-21T12:00:00Z', kind: 'ANSWER', qid: 'q3', text: 'грузим фоном', provenance: 'внутр:auth.php:42', session: 'S1', }); expect(line).toBe('- [2026-06-21T12:00:00Z] ANSWER q3: грузим фоном · источник: внутр:auth.php:42 · сессия:S1'); expect(line).not.toContain('\n'); }); it('parseEventLine — обратная операция formatEventLine', () => { const ev = { at: '2026-06-21T12:00:00Z', kind: 'ANSWER', qid: 'q3', text: 'x', provenance: 'допущение', session: 'S1' }; expect(parseEventLine(formatEventLine(ev))).toEqual(ev); }); it('parseEventLine возвращает null на непарсящейся строке', () => { expect(parseEventLine('мусор без формата')).toBeNull(); }); }); describe('replay', () => { it('разбирает события и ОТБРАСЫВАЕТ неполную последнюю строку (обрыв)', () => { const raw = [ '- [2026-06-21T12:00:00Z] ANSWER q1: a · источник: воля:ответ#1 · сессия:S1', '- [2026-06-21T12:01:00Z] CLOSE q1: решён · источник: воля:ответ#1 · сессия:S1', '- [2026-06-21T12:02:00Z] ANSWER q2: обор', ].join('\n'); const events = replay(raw); expect(events).toHaveLength(2); expect(events[1].kind).toBe('CLOSE'); }); it('пустой ввод → пустой список', () => { expect(replay('')).toEqual([]); }); }); describe('activeSessionOf', () => { const nowMs = Date.parse('2026-06-21T12:40:00Z'); it('свежая запись другой сессии → занято (порог 30 мин)', () => { const raw = '- [2026-06-21T12:20:00Z] ANSWER q1: a · источник: воля:ответ#1 · сессия:S2'; expect(activeSessionOf(raw, { nowMs, staleMinutes: 30 })).toBe('S2'); }); it('протухшая запись (>30 мин) → свободно (null)', () => { const raw = '- [2026-06-21T12:00:00Z] ANSWER q1: a · источник: воля:ответ#1 · сессия:S2'; expect(activeSessionOf(raw, { nowMs, staleMinutes: 30 })).toBeNull(); }); it('пустой журнал → null', () => { expect(activeSessionOf('', { nowMs })).toBeNull(); }); });