Skip to content
File

Blob: tests/worker/routes/page-context.workers.test.ts

typescript111 lines
1import { beforeEach, describe, expect, it } from "vitest";
2 
3import { resetD1Tables } from "@tests/worker/helpers/db";
4import { apiRequest } from "@tests/worker/helpers/request";
5import { seedMembership, seedPage, seedPageShare, seedUser, seedWorkspace } from "@tests/worker/helpers/seeds";
6 
7type ContextResponse = {
8 workspace: { id: string; slug: string; name: string };
9 viewer: {
10 access_mode: "member" | "shared";
11 principal_type: "user" | "link";
12 route_kind: "canonical" | "shared";
13 workspace_slug: string | null;
14 workspace_role: "owner" | "admin" | "member" | "guest" | null;
15 };
16};
17 
18describe("GET /pages/:id/context", () => {
19 beforeEach(async () => {
20 await resetD1Tables();
21 });
22 
23 it("returns canonical member viewer metadata with writer workspace_role for full members", async () => {
24 const owner = await seedUser();
25 const memberCaller = await seedUser();
26 const workspace = await seedWorkspace({ owner_id: owner.id, slug: "demo" });
27 await seedMembership({ user_id: memberCaller.id, workspace_id: workspace.id, role: "member" });
28 const page = await seedPage({ workspace_id: workspace.id, created_by: owner.id, title: "Alpha" });
29 
30 const res = await apiRequest(`/api/v1/pages/${page.id}/context`, { userId: memberCaller.id });
31 expect(res.status).toBe(200);
32 
33 const body = (await res.json()) as ContextResponse;
34 expect(body.workspace).toMatchObject({ id: workspace.id, slug: "demo" });
35 expect(body.viewer).toMatchObject({
36 access_mode: "member",
37 principal_type: "user",
38 route_kind: "canonical",
39 workspace_slug: "demo",
40 workspace_role: "member",
41 });
42 });
43 
44 it("emits access_mode member with guest role for a canonical guest with a direct user share", async () => {
45 const owner = await seedUser();
46 const guestCaller = await seedUser();
47 const workspace = await seedWorkspace({ owner_id: owner.id, slug: "demo" });
48 await seedMembership({ user_id: guestCaller.id, workspace_id: workspace.id, role: "guest" });
49 const page = await seedPage({ workspace_id: workspace.id, created_by: owner.id });
50 await seedPageShare({
51 page_id: page.id,
52 created_by: owner.id,
53 grantee_type: "user",
54 grantee_id: guestCaller.id,
55 permission: "view",
56 });
57 
58 const res = await apiRequest(`/api/v1/pages/${page.id}/context`, { userId: guestCaller.id });
59 expect(res.status).toBe(200);
60 
61 const body = (await res.json()) as ContextResponse;
62 expect(body.viewer).toMatchObject({
63 access_mode: "member",
64 workspace_role: "guest",
65 });
66 });
67 
68 it("emits access_mode shared with null workspace_role for canonical share-only access (no workspace membership)", async () => {
69 const owner = await seedUser();
70 const outsider = await seedUser();
71 const workspace = await seedWorkspace({ owner_id: owner.id, slug: "demo" });
72 const page = await seedPage({ workspace_id: workspace.id, created_by: owner.id });
73 await seedPageShare({
74 page_id: page.id,
75 created_by: owner.id,
76 grantee_type: "user",
77 grantee_id: outsider.id,
78 permission: "view",
79 });
80 
81 const res = await apiRequest(`/api/v1/pages/${page.id}/context`, { userId: outsider.id });
82 expect(res.status).toBe(200);
83 
84 const body = (await res.json()) as ContextResponse;
85 expect(body.workspace).toMatchObject({ id: workspace.id, slug: "demo" });
86 expect(body.viewer).toMatchObject({
87 access_mode: "shared",
88 principal_type: "user",
89 route_kind: "canonical",
90 workspace_slug: "demo",
91 workspace_role: null,
92 });
93 });
94 
95 it("returns 403 when the caller has neither membership nor any share grant", async () => {
96 const owner = await seedUser();
97 const stranger = await seedUser();
98 const workspace = await seedWorkspace({ owner_id: owner.id, slug: "demo" });
99 const page = await seedPage({ workspace_id: workspace.id, created_by: owner.id });
100 
101 const res = await apiRequest(`/api/v1/pages/${page.id}/context`, { userId: stranger.id });
102 expect(res.status).toBe(403);
103 });
104 
105 it("returns 404 when the page does not exist", async () => {
106 const caller = await seedUser();
107 const res = await apiRequest(`/api/v1/pages/page-does-not-exist/context`, { userId: caller.id });
108 expect(res.status).toBe(404);
109 });
110});