Skip to content
File

Blob: tests/shared/entitlements.test.ts

typescript199 lines
1import { describe, expect, it } from "vitest";
2import {
3 canCreateLinkShare,
4 canCreateUserShare,
5 canCreateUserShareByEmail,
6 canRevealLinkTokens,
7 canRevealShareGranteeEmails,
8 canRevokeShare,
9 getPageAiEntitlements,
10 getPageEditEntitlements,
11 getPageStructureEntitlements,
12} from "@/shared/entitlements";
13 
14describe("shared entitlements", () => {
15 describe("page edit entitlements", () => {
16 it("allows canonical edit surfaces to insert page mentions", () => {
17 expect(getPageEditEntitlements("canonical", "edit")).toMatchObject({
18 editDocument: true,
19 editPageMetadata: true,
20 insertPageMention: true,
21 uploadImage: true,
22 });
23 });
24 
25 it("blocks page mention insertion on shared edit surfaces", () => {
26 expect(getPageEditEntitlements("shared", "edit")).toMatchObject({
27 editDocument: true,
28 editPageMetadata: true,
29 insertPageMention: false,
30 uploadImage: true,
31 });
32 });
33 });
34 
35 describe("page structure entitlements", () => {
36 it("lets members create and move pages", () => {
37 expect(getPageStructureEntitlements("member", false)).toMatchObject({
38 createPage: true,
39 movePage: true,
40 archivePage: false,
41 });
42 });
43 
44 it("denies guests every archive pathway (they cannot create pages either)", () => {
45 expect(getPageStructureEntitlements("guest", true)).toMatchObject({
46 createPage: false,
47 movePage: false,
48 archivePage: false,
49 archiveOwnPage: false,
50 archiveAnyPage: false,
51 });
52 expect(getPageStructureEntitlements("guest", false).archivePage).toBe(false);
53 });
54 
55 it("exposes archive ownership scope so affordances can distinguish hidden from ownership-restricted", () => {
56 expect(getPageStructureEntitlements("owner", false)).toMatchObject({
57 archiveAnyPage: true,
58 archiveOwnPage: true,
59 });
60 expect(getPageStructureEntitlements("member", false)).toMatchObject({
61 archiveAnyPage: false,
62 archiveOwnPage: true,
63 });
64 expect(getPageStructureEntitlements("guest", false)).toMatchObject({
65 archiveAnyPage: false,
66 archiveOwnPage: false,
67 });
68 expect(getPageStructureEntitlements("none", false)).toMatchObject({
69 archiveAnyPage: false,
70 archiveOwnPage: false,
71 });
72 });
73 });
74 
75 describe("share management entitlements", () => {
76 it("lets members share only with workspace members", () => {
77 expect(canCreateUserShare("member", true)).toBe(true);
78 expect(canCreateUserShare("member", false)).toBe(false);
79 expect(canCreateUserShareByEmail("member")).toBe(false);
80 });
81 
82 it("reserves link-share creation and token visibility to admins/owners", () => {
83 expect(canCreateLinkShare("admin")).toBe(true);
84 expect(canCreateLinkShare("member")).toBe(false);
85 expect(canRevealLinkTokens("owner")).toBe(true);
86 expect(canRevealLinkTokens("member")).toBe(false);
87 });
88 
89 it("lets members revoke only member user shares they created", () => {
90 expect(
91 canRevokeShare({
92 workspaceRole: "member",
93 granteeType: "user",
94 shareCreatedByViewer: true,
95 granteeIsWorkspaceMember: true,
96 }),
97 ).toBe(true);
98 expect(
99 canRevokeShare({
100 workspaceRole: "member",
101 granteeType: "link",
102 shareCreatedByViewer: true,
103 granteeIsWorkspaceMember: false,
104 }),
105 ).toBe(false);
106 expect(
107 canRevokeShare({
108 workspaceRole: "member",
109 granteeType: "user",
110 shareCreatedByViewer: false,
111 granteeIsWorkspaceMember: true,
112 }),
113 ).toBe(false);
114 });
115 
116 it("keeps guest and non-member email visibility redacted", () => {
117 expect(canRevealShareGranteeEmails("guest")).toBe(false);
118 expect(canRevealShareGranteeEmails("none")).toBe(false);
119 expect(canRevealShareGranteeEmails("member")).toBe(true);
120 });
121 });
122 
123 describe("page AI entitlements", () => {
124 it("allows full members to rewrite, generate, summarize, and ask on edit pages", () => {
125 expect(getPageAiEntitlements("canonical", "edit", "member")).toEqual({
126 useAiRewrite: true,
127 useAiGenerate: true,
128 summarizePage: true,
129 askPage: true,
130 });
131 expect(getPageAiEntitlements("canonical", "edit", "admin")).toEqual({
132 useAiRewrite: true,
133 useAiGenerate: true,
134 summarizePage: true,
135 askPage: true,
136 });
137 expect(getPageAiEntitlements("canonical", "edit", "owner")).toEqual({
138 useAiRewrite: true,
139 useAiGenerate: true,
140 summarizePage: true,
141 askPage: true,
142 });
143 });
144 
145 it("restricts canonical view-only access to read-only AI actions for writer roles", () => {
146 expect(getPageAiEntitlements("canonical", "view", "member")).toEqual({
147 useAiRewrite: false,
148 useAiGenerate: false,
149 summarizePage: true,
150 askPage: true,
151 });
152 });
153 
154 it("denies any AI action for canonical/none", () => {
155 expect(getPageAiEntitlements("canonical", "none", "member")).toEqual({
156 useAiRewrite: false,
157 useAiGenerate: false,
158 summarizePage: false,
159 askPage: false,
160 });
161 });
162 
163 it("denies all AI actions for guests on the canonical surface regardless of page access", () => {
164 for (const access of ["none", "view", "edit"] as const) {
165 expect(getPageAiEntitlements("canonical", access, "guest")).toEqual({
166 useAiRewrite: false,
167 useAiGenerate: false,
168 summarizePage: false,
169 askPage: false,
170 });
171 }
172 });
173 
174 it("denies all AI actions for non-members on the canonical surface", () => {
175 for (const access of ["none", "view", "edit"] as const) {
176 expect(getPageAiEntitlements("canonical", access, "none")).toEqual({
177 useAiRewrite: false,
178 useAiGenerate: false,
179 summarizePage: false,
180 askPage: false,
181 });
182 }
183 });
184 
185 it("denies all AI actions on the shared surface regardless of role and access level", () => {
186 for (const role of ["owner", "admin", "member", "guest", "none"] as const) {
187 for (const access of ["none", "view", "edit"] as const) {
188 expect(getPageAiEntitlements("shared", access, role)).toEqual({
189 useAiRewrite: false,
190 useAiGenerate: false,
191 summarizePage: false,
192 askPage: false,
193 });
194 }
195 }
196 });
197 });
198});