124 lines
4.9 KiB
TypeScript
124 lines
4.9 KiB
TypeScript
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);
|
|
});
|
|
});
|