import { beforeEach, describe, expect, it, vi } from "vitest"; const actionState = vi.hoisted(() => ({ selected: [] as unknown[][], inserted: [] as unknown[], deleted: 0, authorized: 0, failAudit: false, })); vi.mock("@/lib/auth/require-admin", () => ({ requireAdminSession: async () => { actionState.authorized += 1; return { email: "admin@example.test", name: "Admin" }; }, })); vi.mock("next/headers", () => ({ headers: async () => new Headers() })); vi.mock("next/navigation", () => ({ redirect: (path: string) => { throw new Error(`REDIRECT:${path}`); }, })); vi.mock("@/lib/database", () => { function selection(response: unknown[]) { const chain: Record = {}; for (const method of ["from", "where", "orderBy"]) chain[method] = () => chain; chain.limit = () => Promise.resolve(response); chain.then = (resolve: (value: unknown[]) => unknown, reject: (reason: unknown) => unknown) => Promise.resolve(response).then(resolve, reject); return chain; } const tx = { execute: async () => undefined, select: () => selection(actionState.selected.shift() ?? []), delete: () => ({ where: async () => { actionState.deleted += 1; } }), insert: () => ({ values: async (value: unknown) => { if (actionState.failAudit && !Array.isArray(value)) throw new Error("audit unavailable"); actionState.inserted.push(value); }, }), }; return { db: { transaction: async (callback: (transaction: typeof tx) => Promise) => callback(tx) } }; }); import { replaceGroupSchedule } from "./actions"; function scheduleForm() { const formData = new FormData(); formData.set("groupId", "11111111-1111-4111-8111-111111111111"); formData.append("startMinuteOfWeek", "6960"); formData.append("endMinuteOfWeek", "7200"); return formData; } describe("replaceGroupSchedule", () => { beforeEach(() => { actionState.selected = [ [{ id: "11111111-1111-4111-8111-111111111111", name: "Friday friends" }], [{ startMinuteOfWeek: 480, endMinuteOfWeek: 540 }], ]; actionState.inserted = []; actionState.deleted = 0; actionState.authorized = 0; actionState.failAudit = false; }); it("reauthorizes and replaces all windows with an audit in one transaction", async () => { await expect(replaceGroupSchedule(scheduleForm())).rejects.toThrow("REDIRECT:/admin/groups/11111111-1111-4111-8111-111111111111?saved=schedule"); expect(actionState.authorized).toBe(1); expect(actionState.deleted).toBe(1); expect(actionState.inserted[0]).toEqual([{ groupId: "11111111-1111-4111-8111-111111111111", startMinuteOfWeek: 6960, endMinuteOfWeek: 7200, }]); expect(actionState.inserted[1]).toEqual(expect.objectContaining({ type: "games.minecraft.account-manager.group.schedule-updated", data: expect.objectContaining({ previousWindows: [{ startMinuteOfWeek: 480, endMinuteOfWeek: 540 }], windows: [{ startMinuteOfWeek: 6960, endMinuteOfWeek: 7200 }], adminEmail: "admin@example.test", }), })); }); it("does not report success when the atomic audit write fails", async () => { actionState.failAudit = true; await expect(replaceGroupSchedule(scheduleForm())).rejects.toThrow("audit unavailable"); expect(actionState.inserted).toEqual([[{ groupId: "11111111-1111-4111-8111-111111111111", startMinuteOfWeek: 6960, endMinuteOfWeek: 7200, }]]); }); });