test(wiki): integration coverage for Documentation access gating
Covers the category registry sync, create and update gating per role (plain user, admin, developer, guest), the update-time escalation block, and read gating.
This commit is contained in:
parent
d1ae4c3bb7
commit
471e4eaacf
1 changed files with 330 additions and 0 deletions
330
tests/int/wiki-documentation.int.spec.ts
Normal file
330
tests/int/wiki-documentation.int.spec.ts
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
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<void> => {
|
||||
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<User> => {
|
||||
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");
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue