import { describe, expect, it } from "vitest"; import { evaluateGroupSchedule, groupScheduleStatus, localWindowToUtc, parseScheduleWindows, utcWindowToLocal, type WeeklyAccessWindow, } from "./group-schedule"; const fridayEvening: WeeklyAccessWindow = { startMinuteOfWeek: 4 * 24 * 60 + 20 * 60, endMinuteOfWeek: 4 * 24 * 60 + 23 * 60 + 59, }; describe("weekly group access schedules", () => { it("summarizes whether a group has configured windows", () => { expect(groupScheduleStatus(0)).toBe("Unrestricted"); expect(groupScheduleStatus(1)).toBe("1 window"); expect(groupScheduleStatus(3)).toBe("3 windows"); }); it("allows an enabled group at any time when no schedule is configured", () => { expect(evaluateGroupSchedule([], new Date("2026-08-07T19:00:00Z"))).toEqual({ allowed: true, nextWindow: null, }); }); it("allows only inside a UTC window and identifies the next window when denied", () => { expect(evaluateGroupSchedule([fridayEvening], new Date("2026-08-07T21:30:00Z")).allowed).toBe(true); const denied = evaluateGroupSchedule([fridayEvening], new Date("2026-08-08T01:00:00Z")); expect(denied.allowed).toBe(false); expect(denied.nextWindow).toEqual({ start: new Date("2026-08-14T20:00:00.000Z"), end: new Date("2026-08-14T23:59:00.000Z"), }); }); it("uses inclusive starts and exclusive ends across the UTC week boundary", () => { const sundayNight = { startMinuteOfWeek: 6 * 1440 + 23 * 60, endMinuteOfWeek: 2 * 60 }; expect(evaluateGroupSchedule([sundayNight], new Date("2026-08-09T23:00:00Z")).allowed).toBe(true); expect(evaluateGroupSchedule([sundayNight], new Date("2026-08-10T01:59:59Z")).allowed).toBe(true); expect(evaluateGroupSchedule([sundayNight], new Date("2026-08-10T02:00:00Z")).allowed).toBe(false); }); it("selects the earliest upcoming window when several are configured", () => { const mondayMorning = { startMinuteOfWeek: 8 * 60, endMinuteOfWeek: 9 * 60 }; const decision = evaluateGroupSchedule( [fridayEvening, mondayMorning], new Date("2026-08-08T01:00:00Z"), ); expect(decision.nextWindow?.start).toEqual(new Date("2026-08-10T08:00:00.000Z")); expect(decision.nextWindow?.end).toEqual(new Date("2026-08-10T09:00:00.000Z")); }); it("fails closed for malformed persisted policy", () => { expect(evaluateGroupSchedule([ { startMinuteOfWeek: 100, endMinuteOfWeek: 200 }, { startMinuteOfWeek: 150, endMinuteOfWeek: 250 }, ], new Date("2026-08-03T02:30:00Z"))).toEqual({ allowed: false, nextWindow: null }); }); it("rejects malformed and overlapping submitted windows", () => { const valid = new FormData(); valid.append("startMinuteOfWeek", "6960"); valid.append("endMinuteOfWeek", "7199"); valid.append("startMinuteOfWeek", "480"); valid.append("endMinuteOfWeek", "540"); expect(parseScheduleWindows(valid)).toEqual([ { startMinuteOfWeek: 480, endMinuteOfWeek: 540 }, fridayEvening, ]); const overlapping = new FormData(); overlapping.append("startMinuteOfWeek", "100"); overlapping.append("endMinuteOfWeek", "200"); overlapping.append("startMinuteOfWeek", "150"); overlapping.append("endMinuteOfWeek", "250"); expect(parseScheduleWindows(overlapping)).toBeNull(); const wrappingOverlap = new FormData(); wrappingOverlap.append("startMinuteOfWeek", String(6 * 1440 + 23 * 60)); wrappingOverlap.append("endMinuteOfWeek", String(2 * 60)); wrappingOverlap.append("startMinuteOfWeek", String(60)); wrappingOverlap.append("endMinuteOfWeek", String(3 * 60)); expect(parseScheduleWindows(wrappingOverlap)).toBeNull(); const mismatched = new FormData(); mismatched.append("startMinuteOfWeek", "100"); expect(parseScheduleWindows(mismatched)).toBeNull(); const invalid = new FormData(); invalid.append("startMinuteOfWeek", "10080"); invalid.append("endMinuteOfWeek", "0"); expect(parseScheduleWindows(invalid)).toBeNull(); for (const malformedValue of ["", " ", "+1", "0x10", "1e2", "1.5"]) { const malformed = new FormData(); malformed.append("startMinuteOfWeek", malformedValue); malformed.append("endMinuteOfWeek", "2"); expect(parseScheduleWindows(malformed)).toBeNull(); } const tooMany = new FormData(); for (let index = 0; index < 51; index += 1) { tooMany.append("startMinuteOfWeek", String(index * 2)); tooMany.append("endMinuteOfWeek", String(index * 2 + 1)); } expect(parseScheduleWindows(tooMany)).toBeNull(); }); it("converts browser-local weekly values to authoritative UTC and back", () => { const local = { startMinuteOfWeek: 4 * 1440 + 20 * 60, endMinuteOfWeek: 4 * 1440 + 23 * 60 }; const utc = localWindowToUtc(local, 420); expect(utc).toEqual({ startMinuteOfWeek: 5 * 1440 + 3 * 60, endMinuteOfWeek: 5 * 1440 + 6 * 60, }); expect(utcWindowToLocal(utc, 420)).toEqual(local); }); });