diff --git a/apps/web/src/app/dashboard/[guildId]/forms/[formId]/experiment/page.tsx b/apps/web/src/app/dashboard/[guildId]/forms/[formId]/experiment/page.tsx index 82bfc06..77aa31f 100644 --- a/apps/web/src/app/dashboard/[guildId]/forms/[formId]/experiment/page.tsx +++ b/apps/web/src/app/dashboard/[guildId]/forms/[formId]/experiment/page.tsx @@ -11,7 +11,7 @@ import { Button } from "@/components/ui/button"; import { getDict, getLocale } from "@/i18n"; import { requireUser } from "@/lib/auth"; import { getFormForEdit } from "@/lib/forms"; -import { canManageForms } from "@/lib/guild"; +import { canManageForm } from "@/lib/guild"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -27,7 +27,9 @@ export default async function ExperimentResultsPage({ const t = dict.experiment; const formsHref = `/dashboard/${guildId}/forms`; - if (!(await canManageForms(guildId, user.id))) { + // Per form, like editing and preview: a manager of only some forms sees the + // results of those. + if (!(await canManageForm(guildId, user.id, formId))) { return ( <> setSharing(form), }); } - if (form.experiment) { + if (form.manages && form.experiment) { items.push({ id: "ab", label: copy.abResults, diff --git a/apps/web/src/test/experiment-page-access.test.ts b/apps/web/src/test/experiment-page-access.test.ts new file mode 100644 index 0000000..6c31024 --- /dev/null +++ b/apps/web/src/test/experiment-page-access.test.ts @@ -0,0 +1,44 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const canManageForm = vi.fn(); +const canManageForms = vi.fn(); +const getFormForEdit = vi.fn(); + +vi.mock("@/lib/auth", () => ({ requireUser: async () => ({ id: "u1" }) })); +vi.mock("@/lib/guild", () => ({ canManageForm, canManageForms })); +vi.mock("@/lib/forms", () => ({ getFormForEdit })); +vi.mock("@msk-forms/db", () => ({ + prisma: { experimentStat: { findMany: async () => [] } }, +})); +vi.mock("@/i18n", async () => { + const { dictionaries } = await import("@/i18n/dictionaries"); + return { getDict: async () => dictionaries.en, getLocale: async () => "en" }; +}); + +const { default: ExperimentResultsPage } = + await import("@/app/dashboard/[guildId]/forms/[formId]/experiment/page"); + +const open = () => + ExperimentResultsPage({ params: Promise.resolve({ guildId: "g1", formId: "f1" }) }); + +describe("A/B results page access", () => { + beforeEach(() => { + vi.clearAllMocks(); + getFormForEdit.mockResolvedValue({ id: "f1", title: "Whitelist", settings: {} }); + }); + + it("opens for someone who manages only this form", async () => { + canManageForms.mockResolvedValue(false); + canManageForm.mockResolvedValue(true); + await open(); + expect(canManageForm).toHaveBeenCalledWith("g1", "u1", "f1"); + expect(getFormForEdit).toHaveBeenCalledWith("f1", "g1"); + }); + + it("stays closed without the right for this form", async () => { + canManageForms.mockResolvedValue(false); + canManageForm.mockResolvedValue(false); + await open(); + expect(getFormForEdit).not.toHaveBeenCalled(); + }); +});