import { getPayload, Payload } from "payload"; import config from "@/payload.config"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import type { User, WikiPage } from "@/payload-types"; import { WIKI_CATEGORIES } from "@/lib/wiki/categories"; import { WikiPages } from "@/collections/wiki/WikiPages"; import { slugify } from "@/lib/wiki/slugify"; let payload: Payload; const RUN = `wikidoc-${Date.now().toString(36)}`; /** * User deletion trips FK constraints unless the hook-provisioned personal bank * account and profile are removed first (same helper as wiki.int.spec). */ const deleteUserWithRelations = async (pg: Payload, id: number): Promise => { const accounts = await pg .find({ collection: "bank-accounts", where: { ownerUser: { equals: id } }, limit: 5, depth: 0, overrideAccess: true, }) .catch(() => null); for (const account of accounts?.docs ?? []) { await pg .delete({ collection: "bank-accounts", id: account.id, overrideAccess: true }) .catch(() => {}); } const profiles = await pg .find({ collection: "profiles", where: { user: { equals: id } }, limit: 5, depth: 0, overrideAccess: true, }) .catch(() => null); for (const profile of profiles?.docs ?? []) { await pg .delete({ collection: "profiles", id: profile.id, overrideAccess: true }) .catch(() => {}); } await pg.delete({ collection: "users", id, overrideAccess: true }).catch(() => {}); }; describe("Wiki Documentation category", () => { let plainUser: User; let adminUser: User; let developerUser: User; const userIds: number[] = []; const pageIds: number[] = []; const makeUser = async (username: string, roles: User["roles"]): Promise => { const u = (await payload.create({ collection: "users", data: { username, discordUsername: username, displayName: "DOC TEST", steamId: `7656119${Math.floor(Math.random() * 1e9)}`, password: "Test123", roles, }, overrideAccess: true, depth: 0, })) as unknown as User; userIds.push(u.id); return u; }; beforeAll(async () => { const payloadConfig = await config; payload = await getPayload({ config: payloadConfig }); plainUser = await makeUser(`${RUN}-plain`, ["user"]); adminUser = await makeUser(`${RUN}-admin`, ["admin"]); developerUser = await makeUser(`${RUN}-dev`, ["developer"]); }); afterAll(async () => { for (const id of pageIds) { const revisions = await payload .find({ collection: "wiki-revisions", where: { page: { equals: id } }, limit: 500, depth: 0, overrideAccess: true, }) .catch(() => null); for (const revision of revisions?.docs ?? []) { await payload .delete({ collection: "wiki-revisions", id: revision.id, overrideAccess: true }) .catch(() => {}); } await payload.delete({ collection: "wiki-pages", id, overrideAccess: true }).catch(() => {}); } for (const id of userIds) { await deleteUserWithRelations(payload, id); } }); describe("categories list", () => { it("includes Documentation in the shared category registry", () => { expect(WIKI_CATEGORIES).toContain("Documentation"); expect(WIKI_CATEGORIES).toHaveLength(9); }); it("keeps the collection select options in sync with the registry", () => { const optionValues = WikiPages.fields.filter( (field) => "name" in field && field.name === "category", ); const options = (optionValues[0] as unknown as { options: Array<{ value: string }> }).options; expect(options.map((option) => option.value)).toEqual([...WIKI_CATEGORIES]); }); }); describe("create gating", () => { it("a plain user cannot create a Documentation page", async () => { await expect( payload.create({ collection: "wiki-pages", data: { title: `${RUN} Doc`, slug: slugify(`${RUN} Doc`), category: "Documentation", body: "Body", }, user: plainUser, overrideAccess: false, }), ).rejects.toThrow(); }); it("an admin can create a Documentation page", async () => { const page = (await payload.create({ collection: "wiki-pages", data: { title: `${RUN} Admin Doc`, slug: slugify(`Admin Doc`), category: "Documentation", body: "Body", }, user: adminUser, overrideAccess: false, })) as unknown as WikiPage; pageIds.push(page.id); expect(page.category).toBe("Documentation"); }); it("a developer can create a Documentation page", async () => { const page = (await payload.create({ collection: "wiki-pages", data: { title: `${RUN} Dev Doc`, slug: slugify(`Dev Doc`), category: "Documentation", body: "Body", }, user: developerUser, overrideAccess: false, })) as unknown as WikiPage; pageIds.push(page.id); expect(page.category).toBe("Documentation"); }); it("a plain user can still create pages in other categories", async () => { const page = (await payload.create({ collection: "wiki-pages", data: { title: `${RUN} User Lore`, slug: slugify(`User Lore`), category: "Lore", body: "Body", }, user: plainUser, overrideAccess: false, })) as unknown as WikiPage; pageIds.push(page.id); expect(page.category).toBe("Lore"); }); it("a guest cannot create anything", async () => { await expect( payload.create({ collection: "wiki-pages", data: { title: `${RUN} Guest`, slug: slugify(`${RUN} Guest`), category: "Guides", body: "Body", }, overrideAccess: false, }), ).rejects.toThrow(); }); }); describe("update gating", () => { it("a plain user cannot edit an existing Documentation page", async () => { const page = (await payload.create({ collection: "wiki-pages", data: { title: `${RUN} Locked Doc`, slug: slugify(`Locked Doc`), category: "Documentation", body: "v1", }, user: adminUser, overrideAccess: true, })) as unknown as WikiPage; pageIds.push(page.id); await expect( payload.update({ collection: "wiki-pages", id: page.id, data: { body: "v2 by plain user" }, user: plainUser, overrideAccess: false, }), ).rejects.toThrow(); }); it("a developer can edit an existing Documentation page", async () => { const page = (await payload.create({ collection: "wiki-pages", data: { title: `${RUN} Dev Edit Doc`, slug: slugify(`Dev Edit Doc`), category: "Documentation", body: "v1", }, user: adminUser, overrideAccess: true, })) as unknown as WikiPage; pageIds.push(page.id); const updated = (await payload.update({ collection: "wiki-pages", id: page.id, data: { body: "v2 by developer" }, user: developerUser, overrideAccess: false, })) as unknown as WikiPage; expect(updated.body).toBe("v2 by developer"); }); it("a plain user cannot escalate another category into Documentation", async () => { const page = (await payload.create({ collection: "wiki-pages", data: { title: `${RUN} Escalate`, slug: slugify(`Escalate`), category: "Guides", body: "Body", }, user: plainUser, overrideAccess: true, })) as unknown as WikiPage; pageIds.push(page.id); await expect( payload.update({ collection: "wiki-pages", id: page.id, data: { category: "Documentation" }, user: plainUser, overrideAccess: false, }), ).rejects.toThrow(); }); it("a plain user can still edit pages in other categories", async () => { const page = (await payload.create({ collection: "wiki-pages", data: { title: `${RUN} Still Editable`, slug: slugify(`Still Editable`), category: "Meta", body: "v1", }, user: plainUser, overrideAccess: true, })) as unknown as WikiPage; pageIds.push(page.id); const updated = (await payload.update({ collection: "wiki-pages", id: page.id, data: { body: "v2" }, user: plainUser, overrideAccess: false, })) as unknown as WikiPage; expect(updated.body).toBe("v2"); }); }); describe("read gating", () => { it("any logged-in user can read Documentation pages", async () => { const result = await payload.find({ collection: "wiki-pages", where: { category: { equals: "Documentation" } }, user: plainUser, overrideAccess: false, }); expect(result.docs.length).toBeGreaterThan(0); }); it("a guest is forbidden from reading wiki pages", async () => { await expect( payload.find({ collection: "wiki-pages", where: { category: { equals: "Documentation" } }, overrideAccess: false, }), ).rejects.toThrow(/not allowed/i); }); }); describe("slug integration", () => { it("slugifies Documentation titles the same as any other category", () => { expect(slugify("Logistics and Storage Rules")).toBe("logistics-and-storage-rules"); }); }); });