import { describe, expect, test } from 'bun:test'; import { ALERTING_SWEEP_PATTERN, BACKUP_CADENCE_FLOOR_MINUTES, BACKUP_SWEEP_PATTERN, MONITOR_INTERVAL_FLOOR_MINUTES, cadenceMs, cadenceSchema, formatCadence, parseCadence, tickIntervalMinutes, } from './cadence'; describe('parseCadence', () => { test('every named period equals its duration equivalent', () => { expect(parseCadence('hourly')).toEqual(parseCadence('1h')); expect(parseCadence('daily')).toEqual(parseCadence('24h')); expect(parseCadence('weekly')).toEqual(parseCadence('7d')); expect(parseCadence('monthly')).toEqual(parseCadence('30d')); }); test('durations normalise to minutes', () => { expect(parseCadence('90m')).toEqual({ minutes: 90 }); expect(parseCadence('6h')).toEqual({ minutes: 360 }); expect(parseCadence('3d')).toEqual({ minutes: 4320 }); }); test('manual is its own value, not a number', () => { expect(parseCadence('manual')).toBe('manual'); expect(cadenceMs('manual')).toBe(Number.POSITIVE_INFINITY); }); test('rejects malformed, zero and negative values', () => { for (const bad of ['dailyy', '', '6', 'h6', '6w', '0h', '-1h', '1.5h', 'never']) { expect(parseCadence(bad)).toBeNull(); } }); }); describe('formatCadence', () => { test('prefers the word an operator would have typed', () => { expect(formatCadence({ minutes: 60 })).toBe('hourly'); expect(formatCadence({ minutes: 1440 })).toBe('daily'); expect(formatCadence('manual')).toBe('manual'); }); test('falls back to the coarsest exact duration', () => { expect(formatCadence({ minutes: 360 })).toBe('6h'); expect(formatCadence({ minutes: 2880 })).toBe('2d'); expect(formatCadence({ minutes: 90 })).toBe('90m'); }); }); describe('the floor follows the sweep tick', () => { test('each floor is the tick of the sweep that would serve it', () => { expect(BACKUP_CADENCE_FLOOR_MINUTES).toBe(tickIntervalMinutes(BACKUP_SWEEP_PATTERN)); expect(MONITOR_INTERVAL_FLOOR_MINUTES).toBe(tickIntervalMinutes(ALERTING_SWEEP_PATTERN)); }); test('a finer tick admits a cadence the coarser one refused', () => { const coarse = cadenceSchema({ floorMinutes: tickIntervalMinutes('timer.tick.1h') }); const fine = cadenceSchema({ floorMinutes: tickIntervalMinutes('timer.tick.5m') }); expect(coarse.safeParse('15m').success).toBe(false); expect(fine.safeParse('15m').success).toBe(true); }); test('a pattern that is not a timer tick is a programming error, not a floor of zero', () => { expect(() => tickIntervalMinutes('module.deployed')).toThrow(); }); }); describe('cadenceSchema', () => { const schema = cadenceSchema({ floorMinutes: 60 }); test('accepts words, durations at or above the floor, and manual', () => { for (const good of ['hourly', 'daily', 'weekly', 'monthly', 'manual', '6h', '2d', '60m']) { expect(schema.safeParse(good).success).toBe(true); } }); test('names the permitted forms when the value is unparseable', () => { const result = schema.safeParse('dailyy'); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toContain('named period'); expect(result.error.issues[0].message).toContain('manual'); } }); test('names the finest servable cadence when the value is below the floor', () => { const result = schema.safeParse('5m'); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toContain('hourly'); } }); });