Skip to content
File

Blob: tests/worker/routes/workspaces-role-management.workers.test.ts

typescript111 lines
1import { and, eq } from "drizzle-orm";
2import { beforeEach, describe, expect, it } from "vitest";
3 
4import { memberships } from "@/worker/db/d1/schema";
5import { getDb, resetD1Tables } from "@tests/worker/helpers/db";
6import { apiRequest } from "@tests/worker/helpers/request";
7import { seedMembership, seedUser, seedWorkspace } from "@tests/worker/helpers/seeds";
8 
9async function roleOf(userId: string, workspaceId: string) {
10 const row = await getDb()
11 .select({ role: memberships.role })
12 .from(memberships)
13 .where(and(eq(memberships.user_id, userId), eq(memberships.workspace_id, workspaceId)))
14 .get();
15 return row?.role ?? null;
16}
17 
18describe("PATCH /workspaces/:id/members/:uid - role management", () => {
19 beforeEach(async () => {
20 await resetD1Tables();
21 });
22 
23 it("lets the owner promote a member to admin", async () => {
24 const owner = await seedUser();
25 const target = await seedUser();
26 const workspace = await seedWorkspace({ owner_id: owner.id });
27 await seedMembership({ user_id: target.id, workspace_id: workspace.id, role: "member" });
28 
29 const res = await apiRequest(`/api/v1/workspaces/${workspace.id}/members/${target.id}`, {
30 method: "PATCH",
31 userId: owner.id,
32 body: { role: "admin" },
33 });
34 
35 expect(res.status).toBe(200);
36 expect(await roleOf(target.id, workspace.id)).toBe("admin");
37 });
38 
39 it("blocks an admin from promoting another member to admin", async () => {
40 const owner = await seedUser();
41 const admin = await seedUser();
42 const target = await seedUser();
43 const workspace = await seedWorkspace({ owner_id: owner.id });
44 await seedMembership({ user_id: admin.id, workspace_id: workspace.id, role: "admin" });
45 await seedMembership({ user_id: target.id, workspace_id: workspace.id, role: "member" });
46 
47 const res = await apiRequest(`/api/v1/workspaces/${workspace.id}/members/${target.id}`, {
48 method: "PATCH",
49 userId: admin.id,
50 body: { role: "admin" },
51 });
52 
53 expect(res.status).toBe(403);
54 const body = (await res.json()) as { message: string };
55 expect(body.message).toMatch(/Only the owner/);
56 expect(await roleOf(target.id, workspace.id)).toBe("member");
57 });
58 
59 it("lets an admin demote a member to guest", async () => {
60 const owner = await seedUser();
61 const admin = await seedUser();
62 const target = await seedUser();
63 const workspace = await seedWorkspace({ owner_id: owner.id });
64 await seedMembership({ user_id: admin.id, workspace_id: workspace.id, role: "admin" });
65 await seedMembership({ user_id: target.id, workspace_id: workspace.id, role: "member" });
66 
67 const res = await apiRequest(`/api/v1/workspaces/${workspace.id}/members/${target.id}`, {
68 method: "PATCH",
69 userId: admin.id,
70 body: { role: "guest" },
71 });
72 
73 expect(res.status).toBe(200);
74 expect(await roleOf(target.id, workspace.id)).toBe("guest");
75 });
76 
77 it("refuses to change the owner's role", async () => {
78 const owner = await seedUser();
79 const admin = await seedUser();
80 const workspace = await seedWorkspace({ owner_id: owner.id });
81 await seedMembership({ user_id: admin.id, workspace_id: workspace.id, role: "admin" });
82 
83 const res = await apiRequest(`/api/v1/workspaces/${workspace.id}/members/${owner.id}`, {
84 method: "PATCH",
85 userId: admin.id,
86 body: { role: "member" },
87 });
88 
89 expect(res.status).toBe(403);
90 expect(await roleOf(owner.id, workspace.id)).toBe("owner");
91 });
92 
93 it("rejects members and guests from changing roles", async () => {
94 const owner = await seedUser();
95 const member = await seedUser();
96 const victim = await seedUser();
97 const workspace = await seedWorkspace({ owner_id: owner.id });
98 await seedMembership({ user_id: member.id, workspace_id: workspace.id, role: "member" });
99 await seedMembership({ user_id: victim.id, workspace_id: workspace.id, role: "member" });
100 
101 const res = await apiRequest(`/api/v1/workspaces/${workspace.id}/members/${victim.id}`, {
102 method: "PATCH",
103 userId: member.id,
104 body: { role: "guest" },
105 });
106 
107 expect(res.status).toBe(403);
108 expect(await roleOf(victim.id, workspace.id)).toBe("member");
109 });
110});