Finalize no longer requires a magic link. Guest rows stay off the catalog until sign-in on the same browser attaches ownership, and the login modal kebab no longer acts as a second close. Co-authored-by: Cursor <cursoragent@cursor.com>
127 lines
3.6 KiB
TypeScript
127 lines
3.6 KiB
TypeScript
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 }),
|
|
);
|
|
});
|
|
});
|