import { describe, expect, test } from 'bun:test'; import { type SchedulableMonitor, selectDueMonitors } from './sweep'; const NOW = new Date('2026-07-28T12:00:00Z'); const minutesAgo = (n: number) => new Date(NOW.getTime() - n * 60_000); function monitor(over: Partial = {}): SchedulableMonitor { return { id: 'm1', intervalMinutes: 15, enabled: true, lastRunAt: minutesAgo(20), ...over }; } describe('selectDueMonitors', () => { test('a monitor past its interval is due', () => { expect(selectDueMonitors([monitor()], NOW)).toHaveLength(1); }); test('a monitor within its interval is not due', () => { expect(selectDueMonitors([monitor({ lastRunAt: minutesAgo(5) })], NOW)).toEqual([]); }); // Otherwise a newly created monitor stays silent for a full interval, which // reads as "monitoring is broken" exactly when someone just switched it on. test('a monitor that has never run is due immediately', () => { expect(selectDueMonitors([monitor({ lastRunAt: null })], NOW)).toHaveLength(1); }); test('a disabled monitor is never due, even when overdue', () => { expect( selectDueMonitors([monitor({ enabled: false, lastRunAt: minutesAgo(999) })], NOW), ).toEqual([]); }); test('a disabled monitor that has never run is still not due', () => { expect(selectDueMonitors([monitor({ enabled: false, lastRunAt: null })], NOW)).toEqual([]); }); test('exactly at the interval boundary is due', () => { expect(selectDueMonitors([monitor({ lastRunAt: minutesAgo(15) })], NOW)).toHaveLength(1); }); test('selects only the due subset, preserving order', () => { const due = selectDueMonitors( [ monitor({ id: 'a', lastRunAt: minutesAgo(20) }), monitor({ id: 'b', lastRunAt: minutesAgo(1) }), monitor({ id: 'c', lastRunAt: null }), monitor({ id: 'd', enabled: false, lastRunAt: null }), ], NOW, ); expect(due.map((m) => m.id)).toEqual(['a', 'c']); }); // A 1h monitor swept on a 5m grid: not due at 55m, due at 60m. The grid // rounds a period UP, it never fires one early. test('a long interval is not fired early by a frequent sweep', () => { const hourly = monitor({ intervalMinutes: 60, lastRunAt: minutesAgo(55) }); expect(selectDueMonitors([hourly], NOW)).toEqual([]); const later = new Date(NOW.getTime() + 5 * 60_000); expect(selectDueMonitors([hourly], later)).toHaveLength(1); }); });