feat: let guests publish a CommunityRule and claim it on this browser later
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>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
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 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user