import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { createStreamWatchdog } from './streamwatchdog'; describe('createStreamWatchdog', () => { beforeEach(() => vi.useFakeTimers()); afterEach(() => vi.useRealTimers()); it('fires onDead after the timeout with no activity', () => { const onDead = vi.fn(); const w = createStreamWatchdog({ timeoutMs: 25000, onDead }); w.reset(); vi.advanceTimersByTime(24999); expect(onDead).not.toHaveBeenCalled(); vi.advanceTimersByTime(1); expect(onDead).toHaveBeenCalledTimes(1); }); it('reset before the timeout re-arms and prevents the fire', () => { const onDead = vi.fn(); const w = createStreamWatchdog({ timeoutMs: 25000, onDead }); w.reset(); vi.advanceTimersByTime(20000); w.reset(); // a fresh event arrived; the deadline moves out vi.advanceTimersByTime(20000); expect(onDead).not.toHaveBeenCalled(); vi.advanceTimersByTime(5000); expect(onDead).toHaveBeenCalledTimes(1); }); it('a steady heartbeat keeps it from ever firing', () => { const onDead = vi.fn(); const w = createStreamWatchdog({ timeoutMs: 25000, onDead }); w.reset(); // A 10 s server heartbeat resets it well inside the 25 s window, indefinitely. for (let i = 0; i < 20; i++) { vi.advanceTimersByTime(10000); w.reset(); } expect(onDead).not.toHaveBeenCalled(); }); it('pause clears the pending timer so it cannot fire', () => { const onDead = vi.fn(); const w = createStreamWatchdog({ timeoutMs: 25000, onDead }); w.reset(); w.pause(); vi.advanceTimersByTime(60000); expect(onDead).not.toHaveBeenCalled(); }); it('resume re-arms after a pause and then fires on continued silence', () => { const onDead = vi.fn(); const w = createStreamWatchdog({ timeoutMs: 25000, onDead }); w.reset(); w.pause(); vi.advanceTimersByTime(60000); // stayed silent while paused: no fire expect(onDead).not.toHaveBeenCalled(); w.resume(); vi.advanceTimersByTime(24999); expect(onDead).not.toHaveBeenCalled(); vi.advanceTimersByTime(1); expect(onDead).toHaveBeenCalledTimes(1); }); it('clear stops a pending fire and does not re-arm', () => { const onDead = vi.fn(); const w = createStreamWatchdog({ timeoutMs: 25000, onDead }); w.reset(); w.clear(); vi.advanceTimersByTime(60000); expect(onDead).not.toHaveBeenCalled(); }); it('reset after a pause resumes watching (unpauses)', () => { const onDead = vi.fn(); const w = createStreamWatchdog({ timeoutMs: 25000, onDead }); w.pause(); w.reset(); // a reopen after a suspend must start watching again vi.advanceTimersByTime(25000); expect(onDead).toHaveBeenCalledTimes(1); }); });