import { beforeEach, describe, expect, it, vi } from "vitest"; const cookieGetMock = vi.fn(); const cookieSetMock = vi.fn(); const updateManyMock = vi.fn(); const getSessionPepperMock = vi.fn(); const hashRuleClaimTokenMock = vi.fn(); vi.mock("next/headers", () => ({ cookies: async () => ({ get: cookieGetMock, set: cookieSetMock, }), })); vi.mock("../../lib/server/db", () => ({ prisma: { publishedRule: { updateMany: (...args: unknown[]) => updateManyMock(...args), }, }, })); vi.mock("../../lib/server/env", () => ({ getSessionPepper: () => getSessionPepperMock(), })); vi.mock("../../lib/server/hash", () => ({ hashRuleClaimToken: (...args: unknown[]) => hashRuleClaimTokenMock(...args), })); vi.mock("../../lib/logger", () => ({ logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() }, })); import { claimGuestRulesForUser, issueGuestRuleClaimCookie, } from "../../lib/server/guestRuleClaim"; beforeEach(() => { cookieGetMock.mockReset(); cookieSetMock.mockReset(); updateManyMock.mockReset(); getSessionPepperMock.mockReset(); hashRuleClaimTokenMock.mockReset(); getSessionPepperMock.mockReturnValue("pepper"); hashRuleClaimTokenMock.mockImplementation((token: string) => `h-${token}`); }); describe("issueGuestRuleClaimCookie", () => { it("stores the token in an httpOnly cookie", async () => { cookieGetMock.mockReturnValue(undefined); await issueGuestRuleClaimCookie("token-aaaaaaaaaaaaaaaa"); expect(cookieSetMock).toHaveBeenCalledWith( "cr_rule_claim", JSON.stringify(["token-aaaaaaaaaaaaaaaa"]), expect.objectContaining({ httpOnly: true, path: "/", sameSite: "lax", }), ); }); it("appends without duplicating", async () => { cookieGetMock.mockReturnValue({ value: JSON.stringify(["token-aaaaaaaaaaaaaaaa"]), }); await issueGuestRuleClaimCookie("token-bbbbbbbbbbbbbbbb"); expect(cookieSetMock).toHaveBeenCalledWith( "cr_rule_claim", JSON.stringify(["token-aaaaaaaaaaaaaaaa", "token-bbbbbbbbbbbbbbbb"]), expect.any(Object), ); }); }); describe("claimGuestRulesForUser", () => { it("returns 0 when the cookie is empty", async () => { cookieGetMock.mockReturnValue(undefined); const claimed = await claimGuestRulesForUser("user-1"); expect(claimed).toBe(0); expect(updateManyMock).not.toHaveBeenCalled(); }); it("claims matching ownerless rows and clears the cookie", async () => { cookieGetMock.mockReturnValue({ value: JSON.stringify(["token-aaaaaaaaaaaaaaaa"]), }); updateManyMock.mockResolvedValueOnce({ count: 1 }); const claimed = await claimGuestRulesForUser("user-1"); expect(claimed).toBe(1); expect(hashRuleClaimTokenMock).toHaveBeenCalledWith( "token-aaaaaaaaaaaaaaaa", "pepper", ); expect(updateManyMock).toHaveBeenCalledWith({ where: { claimTokenHash: "h-token-aaaaaaaaaaaaaaaa", userId: null }, data: { userId: "user-1", claimTokenHash: null }, }); expect(cookieSetMock).toHaveBeenCalledWith( "cr_rule_claim", "", expect.objectContaining({ maxAge: 0 }), ); }); it("does not attach a rule that already has an owner", async () => { cookieGetMock.mockReturnValue({ value: JSON.stringify(["token-aaaaaaaaaaaaaaaa"]), }); updateManyMock.mockResolvedValueOnce({ count: 0 }); const claimed = await claimGuestRulesForUser("user-1"); expect(claimed).toBe(0); expect(cookieSetMock).toHaveBeenCalledWith( "cr_rule_claim", "", expect.objectContaining({ maxAge: 0 }), ); }); });