import { beforeEach, describe, expect, it, vi } from "vitest"; import { PermissionDeniedError, assertCanAccessAppointment, canAccessAppointment } from "../authorization"; /** * The registered-customer branch never touches the database (it's a plain * userId comparison), so it's tested here in isolation. The guest-token * branch's real hash/expiry/revocation behaviour is covered against a real * database in customer-booking.integration.test.ts — this file only checks * that authorization.ts calls into validateGuestAccessToken correctly and * respects its result. */ vi.mock("@/services/scheduling/guest-access.service", () => ({ validateGuestAccessToken: vi.fn(), })); const { validateGuestAccessToken } = await import("@/services/scheduling/guest-access.service"); const mockValidate = vi.mocked(validateGuestAccessToken); beforeEach(() => { mockValidate.mockClear(); }); // Not a real PrismaClient — the mocked guest-access service never touches it. const prisma = {} as never; describe("assertCanAccessAppointment", () => { it("allows a registered customer to access their own appointment", async () => { await expect( assertCanAccessAppointment(prisma, { id: "a1", customer: { userId: "user_1" } }, { sessionUserId: "user_1" }), ).resolves.toBeUndefined(); }); it("denies a different signed-in user from accessing a registered customer's appointment", async () => { await expect( assertCanAccessAppointment(prisma, { id: "a1", customer: { userId: "user_1" } }, { sessionUserId: "user_2" }), ).rejects.toThrow(PermissionDeniedError); }); it("denies an unauthenticated caller from accessing a registered customer's appointment", async () => { await expect( assertCanAccessAppointment(prisma, { id: "a1", customer: { userId: "user_1" } }, { sessionUserId: undefined }), ).rejects.toThrow(PermissionDeniedError); }); it("never even calls the guest-token validator for a registered customer's appointment", async () => { mockValidate.mockClear(); await assertCanAccessAppointment( prisma, { id: "a1", customer: { userId: "user_1" } }, { sessionUserId: "user_1" }, ).catch(() => {}); expect(mockValidate).not.toHaveBeenCalled(); }); it("allows a guest appointment when the guest-token validator confirms the token", async () => { mockValidate.mockResolvedValueOnce(true); await expect( assertCanAccessAppointment( prisma, { id: "a1", customer: { userId: null } }, { sessionUserId: undefined, guestToken: "correct-token" }, ), ).resolves.toBeUndefined(); expect(mockValidate).toHaveBeenCalledWith(prisma, { appointmentId: "a1", rawToken: "correct-token" }); }); it("denies a guest appointment when no token is supplied at all — the appointment id is no longer sufficient", async () => { await expect( assertCanAccessAppointment(prisma, { id: "a1", customer: { userId: null } }, { sessionUserId: undefined }), ).rejects.toThrow(PermissionDeniedError); expect(mockValidate).not.toHaveBeenCalled(); }); it("denies a guest appointment when the guest-token validator rejects the token", async () => { mockValidate.mockResolvedValueOnce(false); await expect( assertCanAccessAppointment( prisma, { id: "a1", customer: { userId: null } }, { sessionUserId: undefined, guestToken: "wrong-token" }, ), ).rejects.toThrow(PermissionDeniedError); }); }); describe("canAccessAppointment", () => { it("mirrors assertCanAccessAppointment as a boolean", async () => { expect( await canAccessAppointment(prisma, { id: "a1", customer: { userId: "user_1" } }, { sessionUserId: "user_1" }), ).toBe(true); expect( await canAccessAppointment(prisma, { id: "a1", customer: { userId: "user_1" } }, { sessionUserId: "user_2" }), ).toBe(false); mockValidate.mockResolvedValueOnce(true); expect( await canAccessAppointment( prisma, { id: "a1", customer: { userId: null } }, { sessionUserId: undefined, guestToken: "correct-token" }, ), ).toBe(true); }); });