@powerhousedao/vetra 6.0.0-dev.23 → 6.0.0-dev.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/subgraphs/__tests__/app-module-permissions.test.d.ts +2 -0
  2. package/dist/subgraphs/__tests__/app-module-permissions.test.d.ts.map +1 -0
  3. package/dist/subgraphs/__tests__/app-module-permissions.test.js +436 -0
  4. package/dist/subgraphs/__tests__/permission-utils.test.d.ts +2 -0
  5. package/dist/subgraphs/__tests__/permission-utils.test.d.ts.map +1 -0
  6. package/dist/subgraphs/__tests__/permission-utils.test.js +505 -0
  7. package/dist/subgraphs/__tests__/vetra-read-model-permissions.test.d.ts +2 -0
  8. package/dist/subgraphs/__tests__/vetra-read-model-permissions.test.d.ts.map +1 -0
  9. package/dist/subgraphs/__tests__/vetra-read-model-permissions.test.js +319 -0
  10. package/dist/subgraphs/app-module/resolvers.d.ts.map +1 -1
  11. package/dist/subgraphs/app-module/resolvers.js +53 -9
  12. package/dist/subgraphs/document-editor/resolvers.d.ts.map +1 -1
  13. package/dist/subgraphs/document-editor/resolvers.js +45 -7
  14. package/dist/subgraphs/permission-utils.d.ts +31 -0
  15. package/dist/subgraphs/permission-utils.d.ts.map +1 -0
  16. package/dist/subgraphs/permission-utils.js +101 -0
  17. package/dist/subgraphs/processor-module/resolvers.d.ts.map +1 -1
  18. package/dist/subgraphs/processor-module/resolvers.js +49 -8
  19. package/dist/subgraphs/subgraph-module/resolvers.d.ts.map +1 -1
  20. package/dist/subgraphs/subgraph-module/resolvers.js +37 -5
  21. package/dist/subgraphs/vetra-package/resolvers.d.ts.map +1 -1
  22. package/dist/subgraphs/vetra-package/resolvers.js +69 -13
  23. package/dist/subgraphs/vetra-read-model/resolvers.d.ts +2 -2
  24. package/dist/subgraphs/vetra-read-model/resolvers.d.ts.map +1 -1
  25. package/dist/subgraphs/vetra-read-model/resolvers.js +16 -2
  26. package/dist/tsconfig.tsbuildinfo +1 -1
  27. package/dist/vitest.config.d.ts.map +1 -1
  28. package/dist/vitest.config.js +1 -0
  29. package/package.json +15 -15
@@ -0,0 +1,505 @@
1
+ import { GraphQLError } from "graphql";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { assertCanExecuteOperation, assertCanRead, assertCanWrite, canReadDocument, canWriteDocument, hasGlobalReadAccess, hasGlobalWriteAccess, } from "../permission-utils.js";
4
+ describe("permission-utils", () => {
5
+ // Helper to create context with different permission levels
6
+ const createContext = (options) => ({
7
+ user: options.userAddress ? { address: options.userAddress } : undefined,
8
+ isAdmin: vi.fn().mockReturnValue(options.isAdmin ?? false),
9
+ isUser: vi.fn().mockReturnValue(options.isUser ?? false),
10
+ isGuest: vi.fn().mockReturnValue(options.isGuest ?? false),
11
+ });
12
+ describe("hasGlobalReadAccess", () => {
13
+ afterEach(() => {
14
+ delete process.env.FREE_ENTRY;
15
+ });
16
+ describe("Role-based access", () => {
17
+ it("should return true when user is global admin", () => {
18
+ const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
19
+ const result = hasGlobalReadAccess(ctx);
20
+ expect(result).toBe(true);
21
+ expect(ctx.isAdmin).toHaveBeenCalledWith("0xadmin");
22
+ });
23
+ it("should return true when user is global user", () => {
24
+ const ctx = createContext({ isUser: true, userAddress: "0xuser" });
25
+ const result = hasGlobalReadAccess(ctx);
26
+ expect(result).toBe(true);
27
+ expect(ctx.isUser).toHaveBeenCalledWith("0xuser");
28
+ });
29
+ it("should return true when user is global guest", () => {
30
+ const ctx = createContext({ isGuest: true, userAddress: "0xguest" });
31
+ const result = hasGlobalReadAccess(ctx);
32
+ expect(result).toBe(true);
33
+ expect(ctx.isGuest).toHaveBeenCalledWith("0xguest");
34
+ });
35
+ it("should return false when user has no global role", () => {
36
+ const ctx = createContext({ userAddress: "0xnorole" });
37
+ const result = hasGlobalReadAccess(ctx);
38
+ expect(result).toBe(false);
39
+ });
40
+ it("should return false when user is not authenticated (no address)", () => {
41
+ const ctx = createContext({});
42
+ const result = hasGlobalReadAccess(ctx);
43
+ expect(result).toBe(false);
44
+ // Should call with empty string when no user address
45
+ expect(ctx.isAdmin).toHaveBeenCalledWith("");
46
+ });
47
+ });
48
+ describe("FREE_ENTRY environment variable", () => {
49
+ it("should return true when FREE_ENTRY is 'true' regardless of roles", () => {
50
+ process.env.FREE_ENTRY = "true";
51
+ const ctx = createContext({ userAddress: "0xanyone" });
52
+ const result = hasGlobalReadAccess(ctx);
53
+ expect(result).toBe(true);
54
+ });
55
+ it("should return true when FREE_ENTRY is 'true' even without authentication", () => {
56
+ process.env.FREE_ENTRY = "true";
57
+ const ctx = createContext({});
58
+ const result = hasGlobalReadAccess(ctx);
59
+ expect(result).toBe(true);
60
+ });
61
+ it("should not grant access when FREE_ENTRY is 'false'", () => {
62
+ process.env.FREE_ENTRY = "false";
63
+ const ctx = createContext({ userAddress: "0xanyone" });
64
+ const result = hasGlobalReadAccess(ctx);
65
+ expect(result).toBe(false);
66
+ });
67
+ it("should not grant access when FREE_ENTRY is not set", () => {
68
+ const ctx = createContext({ userAddress: "0xanyone" });
69
+ const result = hasGlobalReadAccess(ctx);
70
+ expect(result).toBe(false);
71
+ });
72
+ });
73
+ describe("Combined roles", () => {
74
+ it("should return true when user has multiple roles (admin + user)", () => {
75
+ const ctx = createContext({
76
+ isAdmin: true,
77
+ isUser: true,
78
+ userAddress: "0xmultirole",
79
+ });
80
+ const result = hasGlobalReadAccess(ctx);
81
+ expect(result).toBe(true);
82
+ });
83
+ it("should return true when all roles are true (AUTH_ENABLED=false scenario)", () => {
84
+ const ctx = createContext({
85
+ isAdmin: true,
86
+ isUser: true,
87
+ isGuest: true,
88
+ userAddress: "0xanyone",
89
+ });
90
+ const result = hasGlobalReadAccess(ctx);
91
+ expect(result).toBe(true);
92
+ });
93
+ });
94
+ });
95
+ describe("hasGlobalWriteAccess", () => {
96
+ describe("Role-based access", () => {
97
+ it("should return true when user is global admin", () => {
98
+ const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
99
+ const result = hasGlobalWriteAccess(ctx);
100
+ expect(result).toBe(true);
101
+ });
102
+ it("should return true when user is global user", () => {
103
+ const ctx = createContext({ isUser: true, userAddress: "0xuser" });
104
+ const result = hasGlobalWriteAccess(ctx);
105
+ expect(result).toBe(true);
106
+ });
107
+ it("should return false when user is only global guest", () => {
108
+ const ctx = createContext({ isGuest: true, userAddress: "0xguest" });
109
+ const result = hasGlobalWriteAccess(ctx);
110
+ expect(result).toBe(false);
111
+ });
112
+ it("should return false when user has no global role", () => {
113
+ const ctx = createContext({ userAddress: "0xnorole" });
114
+ const result = hasGlobalWriteAccess(ctx);
115
+ expect(result).toBe(false);
116
+ });
117
+ it("should return false when user is not authenticated", () => {
118
+ const ctx = createContext({});
119
+ const result = hasGlobalWriteAccess(ctx);
120
+ expect(result).toBe(false);
121
+ });
122
+ });
123
+ describe("Guest cannot write", () => {
124
+ it("should return false for guest even with FREE_ENTRY", () => {
125
+ process.env.FREE_ENTRY = "true";
126
+ const ctx = createContext({ isGuest: true, userAddress: "0xguest" });
127
+ const result = hasGlobalWriteAccess(ctx);
128
+ expect(result).toBe(false);
129
+ delete process.env.FREE_ENTRY;
130
+ });
131
+ });
132
+ describe("Combined roles", () => {
133
+ it("should return true when user is both guest and user (user wins)", () => {
134
+ const ctx = createContext({
135
+ isGuest: true,
136
+ isUser: true,
137
+ userAddress: "0xmixed",
138
+ });
139
+ const result = hasGlobalWriteAccess(ctx);
140
+ expect(result).toBe(true);
141
+ });
142
+ });
143
+ });
144
+ describe("canReadDocument", () => {
145
+ let mockSubgraph;
146
+ let mockDocumentPermissionService;
147
+ beforeEach(() => {
148
+ mockDocumentPermissionService = {
149
+ canRead: vi.fn().mockResolvedValue(false),
150
+ canWrite: vi.fn().mockResolvedValue(false),
151
+ canReadDocument: vi.fn().mockResolvedValue(false),
152
+ canWriteDocument: vi.fn().mockResolvedValue(false),
153
+ };
154
+ mockSubgraph = {
155
+ documentPermissionService: mockDocumentPermissionService,
156
+ reactorClient: {
157
+ getParents: vi.fn().mockResolvedValue({
158
+ results: [],
159
+ options: { limit: 10 },
160
+ }),
161
+ },
162
+ };
163
+ });
164
+ describe("Global access bypass", () => {
165
+ it("should return true immediately when user has global read access", async () => {
166
+ const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
167
+ const result = await canReadDocument(mockSubgraph, "doc-123", ctx);
168
+ expect(result).toBe(true);
169
+ expect(mockDocumentPermissionService.canRead).not.toHaveBeenCalled();
170
+ });
171
+ it("should return true for guest without checking document permissions", async () => {
172
+ const ctx = createContext({ isGuest: true, userAddress: "0xguest" });
173
+ const result = await canReadDocument(mockSubgraph, "doc-123", ctx);
174
+ expect(result).toBe(true);
175
+ expect(mockDocumentPermissionService.canRead).not.toHaveBeenCalled();
176
+ });
177
+ });
178
+ describe("Document-level permissions", () => {
179
+ it("should check document permission service when no global access", async () => {
180
+ const ctx = createContext({ userAddress: "0xuser" });
181
+ vi.mocked(mockDocumentPermissionService.canRead).mockResolvedValue(true);
182
+ const result = await canReadDocument(mockSubgraph, "doc-123", ctx);
183
+ expect(result).toBe(true);
184
+ expect(mockDocumentPermissionService.canRead).toHaveBeenCalledWith("doc-123", "0xuser", expect.any(Function));
185
+ });
186
+ it("should return false when user has no document permission", async () => {
187
+ const ctx = createContext({ userAddress: "0xunpermitted" });
188
+ vi.mocked(mockDocumentPermissionService.canRead).mockResolvedValue(false);
189
+ const result = await canReadDocument(mockSubgraph, "doc-123", ctx);
190
+ expect(result).toBe(false);
191
+ });
192
+ it("should pass undefined user address to permission service when no user", async () => {
193
+ const ctx = createContext({});
194
+ await canReadDocument(mockSubgraph, "doc-123", ctx);
195
+ expect(mockDocumentPermissionService.canRead).toHaveBeenCalledWith("doc-123", undefined, expect.any(Function));
196
+ });
197
+ });
198
+ describe("No permission service", () => {
199
+ it("should return false when no permission service is available", async () => {
200
+ const subgraphWithoutService = {
201
+ documentPermissionService: undefined,
202
+ reactorClient: mockSubgraph.reactorClient,
203
+ };
204
+ const ctx = createContext({ userAddress: "0xuser" });
205
+ const result = await canReadDocument(subgraphWithoutService, "doc-123", ctx);
206
+ expect(result).toBe(false);
207
+ });
208
+ });
209
+ describe("Hierarchy function (getParentIdsFn)", () => {
210
+ it("should pass a function that retrieves parent IDs", async () => {
211
+ const ctx = createContext({ userAddress: "0xuser" });
212
+ const mockParents = [
213
+ { header: { id: "parent-1" } },
214
+ { header: { id: "parent-2" } },
215
+ ];
216
+ vi.mocked(mockSubgraph.reactorClient.getParents).mockResolvedValue({
217
+ results: mockParents,
218
+ options: { limit: 10 },
219
+ });
220
+ let capturedGetParentsFn = null;
221
+ vi.mocked(mockDocumentPermissionService.canRead).mockImplementation(async (_docId, _user, getParentsFn) => {
222
+ capturedGetParentsFn = getParentsFn;
223
+ return false;
224
+ });
225
+ await canReadDocument(mockSubgraph, "child-doc", ctx);
226
+ expect(capturedGetParentsFn).not.toBeNull();
227
+ const parentIds = await capturedGetParentsFn("child-doc");
228
+ expect(parentIds).toEqual(["parent-1", "parent-2"]);
229
+ expect(mockSubgraph.reactorClient.getParents).toHaveBeenCalledWith("child-doc");
230
+ });
231
+ it("should return empty array if getParents fails", async () => {
232
+ const ctx = createContext({ userAddress: "0xuser" });
233
+ vi.mocked(mockSubgraph.reactorClient.getParents).mockRejectedValue(new Error("Not found"));
234
+ let capturedGetParentsFn = null;
235
+ vi.mocked(mockDocumentPermissionService.canRead).mockImplementation(async (_docId, _user, getParentsFn) => {
236
+ capturedGetParentsFn = getParentsFn;
237
+ return false;
238
+ });
239
+ await canReadDocument(mockSubgraph, "child-doc", ctx);
240
+ const parentIds = await capturedGetParentsFn("child-doc");
241
+ expect(parentIds).toEqual([]);
242
+ });
243
+ });
244
+ });
245
+ describe("canWriteDocument", () => {
246
+ let mockSubgraph;
247
+ let mockDocumentPermissionService;
248
+ beforeEach(() => {
249
+ mockDocumentPermissionService = {
250
+ canRead: vi.fn().mockResolvedValue(false),
251
+ canWrite: vi.fn().mockResolvedValue(false),
252
+ };
253
+ mockSubgraph = {
254
+ documentPermissionService: mockDocumentPermissionService,
255
+ reactorClient: {
256
+ getParents: vi.fn().mockResolvedValue({
257
+ results: [],
258
+ options: { limit: 10 },
259
+ }),
260
+ },
261
+ };
262
+ });
263
+ describe("Global access bypass", () => {
264
+ it("should return true immediately when user is global admin", async () => {
265
+ const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
266
+ const result = await canWriteDocument(mockSubgraph, "doc-123", ctx);
267
+ expect(result).toBe(true);
268
+ expect(mockDocumentPermissionService.canWrite).not.toHaveBeenCalled();
269
+ });
270
+ it("should return true immediately when user is global user", async () => {
271
+ const ctx = createContext({ isUser: true, userAddress: "0xuser" });
272
+ const result = await canWriteDocument(mockSubgraph, "doc-123", ctx);
273
+ expect(result).toBe(true);
274
+ expect(mockDocumentPermissionService.canWrite).not.toHaveBeenCalled();
275
+ });
276
+ it("should NOT return true for global guest (guests cannot write)", async () => {
277
+ const ctx = createContext({ isGuest: true, userAddress: "0xguest" });
278
+ const result = await canWriteDocument(mockSubgraph, "doc-123", ctx);
279
+ // Guest has no global write access, so should check document permissions
280
+ expect(mockDocumentPermissionService.canWrite).toHaveBeenCalled();
281
+ expect(result).toBe(false);
282
+ });
283
+ });
284
+ describe("Document-level permissions", () => {
285
+ it("should check document permission service when no global write access", async () => {
286
+ const ctx = createContext({ userAddress: "0xuser" });
287
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(true);
288
+ const result = await canWriteDocument(mockSubgraph, "doc-123", ctx);
289
+ expect(result).toBe(true);
290
+ expect(mockDocumentPermissionService.canWrite).toHaveBeenCalledWith("doc-123", "0xuser", expect.any(Function));
291
+ });
292
+ it("should return false when user has no document write permission", async () => {
293
+ const ctx = createContext({ userAddress: "0xunpermitted" });
294
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(false);
295
+ const result = await canWriteDocument(mockSubgraph, "doc-123", ctx);
296
+ expect(result).toBe(false);
297
+ });
298
+ });
299
+ describe("No permission service", () => {
300
+ it("should return false when no permission service is available", async () => {
301
+ const subgraphWithoutService = {
302
+ documentPermissionService: undefined,
303
+ reactorClient: mockSubgraph.reactorClient,
304
+ };
305
+ const ctx = createContext({ userAddress: "0xuser" });
306
+ const result = await canWriteDocument(subgraphWithoutService, "doc-123", ctx);
307
+ expect(result).toBe(false);
308
+ });
309
+ });
310
+ });
311
+ describe("assertCanRead", () => {
312
+ let mockSubgraph;
313
+ let mockDocumentPermissionService;
314
+ beforeEach(() => {
315
+ mockDocumentPermissionService = {
316
+ canRead: vi.fn().mockResolvedValue(false),
317
+ };
318
+ mockSubgraph = {
319
+ documentPermissionService: mockDocumentPermissionService,
320
+ reactorClient: {
321
+ getParents: vi.fn().mockResolvedValue({
322
+ results: [],
323
+ options: { limit: 10 },
324
+ }),
325
+ },
326
+ };
327
+ });
328
+ it("should not throw when user has global read access", async () => {
329
+ const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
330
+ await expect(assertCanRead(mockSubgraph, "doc-123", ctx)).resolves.not.toThrow();
331
+ });
332
+ it("should not throw when user has document read permission", async () => {
333
+ vi.mocked(mockDocumentPermissionService.canRead).mockResolvedValue(true);
334
+ const ctx = createContext({ userAddress: "0xpermitted" });
335
+ await expect(assertCanRead(mockSubgraph, "doc-123", ctx)).resolves.not.toThrow();
336
+ });
337
+ it("should throw GraphQLError when user cannot read", async () => {
338
+ vi.mocked(mockDocumentPermissionService.canRead).mockResolvedValue(false);
339
+ const ctx = createContext({ userAddress: "0xunpermitted" });
340
+ await expect(assertCanRead(mockSubgraph, "doc-123", ctx)).rejects.toThrow(GraphQLError);
341
+ });
342
+ it("should throw with correct error message", async () => {
343
+ vi.mocked(mockDocumentPermissionService.canRead).mockResolvedValue(false);
344
+ const ctx = createContext({ userAddress: "0xunpermitted" });
345
+ await expect(assertCanRead(mockSubgraph, "doc-123", ctx)).rejects.toThrow("Forbidden: insufficient permissions to read this document");
346
+ });
347
+ it("should throw for unauthenticated user", async () => {
348
+ const ctx = createContext({});
349
+ await expect(assertCanRead(mockSubgraph, "doc-123", ctx)).rejects.toThrow("Forbidden");
350
+ });
351
+ });
352
+ describe("assertCanWrite", () => {
353
+ let mockSubgraph;
354
+ let mockDocumentPermissionService;
355
+ beforeEach(() => {
356
+ mockDocumentPermissionService = {
357
+ canWrite: vi.fn().mockResolvedValue(false),
358
+ };
359
+ mockSubgraph = {
360
+ documentPermissionService: mockDocumentPermissionService,
361
+ reactorClient: {
362
+ getParents: vi.fn().mockResolvedValue({
363
+ results: [],
364
+ options: { limit: 10 },
365
+ }),
366
+ },
367
+ };
368
+ });
369
+ it("should not throw when user has global write access", async () => {
370
+ const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
371
+ await expect(assertCanWrite(mockSubgraph, "doc-123", ctx)).resolves.not.toThrow();
372
+ });
373
+ it("should not throw when user has document write permission", async () => {
374
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(true);
375
+ const ctx = createContext({ userAddress: "0xpermitted" });
376
+ await expect(assertCanWrite(mockSubgraph, "doc-123", ctx)).resolves.not.toThrow();
377
+ });
378
+ it("should throw GraphQLError when user cannot write", async () => {
379
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(false);
380
+ const ctx = createContext({ userAddress: "0xunpermitted" });
381
+ await expect(assertCanWrite(mockSubgraph, "doc-123", ctx)).rejects.toThrow(GraphQLError);
382
+ });
383
+ it("should throw with correct error message", async () => {
384
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(false);
385
+ const ctx = createContext({ userAddress: "0xunpermitted" });
386
+ await expect(assertCanWrite(mockSubgraph, "doc-123", ctx)).rejects.toThrow("Forbidden: insufficient permissions to write to this document");
387
+ });
388
+ it("should throw for guest user (guests cannot write)", async () => {
389
+ const ctx = createContext({ isGuest: true, userAddress: "0xguest" });
390
+ await expect(assertCanWrite(mockSubgraph, "doc-123", ctx)).rejects.toThrow("Forbidden");
391
+ });
392
+ });
393
+ describe("assertCanExecuteOperation", () => {
394
+ let mockSubgraph;
395
+ let mockDocumentPermissionService;
396
+ beforeEach(() => {
397
+ mockDocumentPermissionService = {
398
+ isOperationRestricted: vi.fn().mockResolvedValue(false),
399
+ canExecuteOperation: vi.fn().mockResolvedValue(false),
400
+ };
401
+ mockSubgraph = {
402
+ documentPermissionService: mockDocumentPermissionService,
403
+ };
404
+ });
405
+ describe("No permission service", () => {
406
+ it("should not throw when no permission service is available", async () => {
407
+ const subgraphWithoutService = {
408
+ documentPermissionService: undefined,
409
+ };
410
+ const ctx = createContext({ userAddress: "0xuser" });
411
+ await expect(assertCanExecuteOperation(subgraphWithoutService, "doc-123", "SET_NAME", ctx)).resolves.not.toThrow();
412
+ });
413
+ });
414
+ describe("Admin bypass", () => {
415
+ it("should not throw when user is global admin", async () => {
416
+ const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
417
+ await expect(assertCanExecuteOperation(mockSubgraph, "doc-123", "SET_NAME", ctx)).resolves.not.toThrow();
418
+ // Should not check operation restrictions for admin
419
+ expect(mockDocumentPermissionService.isOperationRestricted).not.toHaveBeenCalled();
420
+ });
421
+ });
422
+ describe("Unrestricted operations", () => {
423
+ it("should not throw when operation is not restricted", async () => {
424
+ vi.mocked(mockDocumentPermissionService.isOperationRestricted).mockResolvedValue(false);
425
+ const ctx = createContext({ userAddress: "0xuser" });
426
+ await expect(assertCanExecuteOperation(mockSubgraph, "doc-123", "COMMON_OPERATION", ctx)).resolves.not.toThrow();
427
+ expect(mockDocumentPermissionService.isOperationRestricted).toHaveBeenCalledWith("doc-123", "COMMON_OPERATION");
428
+ expect(mockDocumentPermissionService.canExecuteOperation).not.toHaveBeenCalled();
429
+ });
430
+ });
431
+ describe("Restricted operations", () => {
432
+ beforeEach(() => {
433
+ vi.mocked(mockDocumentPermissionService.isOperationRestricted).mockResolvedValue(true);
434
+ });
435
+ it("should not throw when user has operation permission", async () => {
436
+ vi.mocked(mockDocumentPermissionService.canExecuteOperation).mockResolvedValue(true);
437
+ const ctx = createContext({ userAddress: "0xauthorized" });
438
+ await expect(assertCanExecuteOperation(mockSubgraph, "doc-123", "RESTRICTED_OP", ctx)).resolves.not.toThrow();
439
+ expect(mockDocumentPermissionService.canExecuteOperation).toHaveBeenCalledWith("doc-123", "RESTRICTED_OP", "0xauthorized");
440
+ });
441
+ it("should throw when user lacks operation permission", async () => {
442
+ vi.mocked(mockDocumentPermissionService.canExecuteOperation).mockResolvedValue(false);
443
+ const ctx = createContext({ userAddress: "0xunauthorized" });
444
+ await expect(assertCanExecuteOperation(mockSubgraph, "doc-123", "RESTRICTED_OP", ctx)).rejects.toThrow(GraphQLError);
445
+ });
446
+ it("should throw with operation name in error message", async () => {
447
+ vi.mocked(mockDocumentPermissionService.canExecuteOperation).mockResolvedValue(false);
448
+ const ctx = createContext({ userAddress: "0xunauthorized" });
449
+ await expect(assertCanExecuteOperation(mockSubgraph, "doc-123", "SET_SENSITIVE_DATA", ctx)).rejects.toThrow('Forbidden: insufficient permissions to execute operation "SET_SENSITIVE_DATA" on this document');
450
+ });
451
+ it("should pass undefined user address when not authenticated", async () => {
452
+ vi.mocked(mockDocumentPermissionService.canExecuteOperation).mockResolvedValue(false);
453
+ const ctx = createContext({});
454
+ await expect(assertCanExecuteOperation(mockSubgraph, "doc-123", "RESTRICTED_OP", ctx)).rejects.toThrow("Forbidden");
455
+ expect(mockDocumentPermissionService.canExecuteOperation).toHaveBeenCalledWith("doc-123", "RESTRICTED_OP", undefined);
456
+ });
457
+ });
458
+ });
459
+ describe("Edge Cases", () => {
460
+ describe("Null/undefined context fields", () => {
461
+ it("hasGlobalReadAccess should handle context without isAdmin function", () => {
462
+ const ctx = {
463
+ user: { address: "0xuser" },
464
+ isUser: vi.fn().mockReturnValue(true),
465
+ isGuest: vi.fn().mockReturnValue(false),
466
+ };
467
+ const result = hasGlobalReadAccess(ctx);
468
+ expect(result).toBe(true);
469
+ });
470
+ it("hasGlobalWriteAccess should handle context without isUser function", () => {
471
+ const ctx = {
472
+ user: { address: "0xadmin" },
473
+ isAdmin: vi.fn().mockReturnValue(true),
474
+ isGuest: vi.fn().mockReturnValue(false),
475
+ };
476
+ const result = hasGlobalWriteAccess(ctx);
477
+ expect(result).toBe(true);
478
+ });
479
+ });
480
+ describe("Empty string user address", () => {
481
+ it("should handle empty string user address", () => {
482
+ const ctx = createContext({ userAddress: "" });
483
+ const result = hasGlobalReadAccess(ctx);
484
+ expect(result).toBe(false);
485
+ expect(ctx.isAdmin).toHaveBeenCalledWith("");
486
+ });
487
+ });
488
+ describe("Case sensitivity", () => {
489
+ it("should pass user address as-is to permission checks", async () => {
490
+ const mockDocPermService = {
491
+ canRead: vi.fn().mockResolvedValue(true),
492
+ };
493
+ const mockSubgraph = {
494
+ documentPermissionService: mockDocPermService,
495
+ reactorClient: {
496
+ getParents: vi.fn().mockResolvedValue({ results: [] }),
497
+ },
498
+ };
499
+ const ctx = createContext({ userAddress: "0xABCDEF" });
500
+ await canReadDocument(mockSubgraph, "doc-123", ctx);
501
+ expect(mockDocPermService.canRead).toHaveBeenCalledWith("doc-123", "0xABCDEF", expect.any(Function));
502
+ });
503
+ });
504
+ });
505
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=vetra-read-model-permissions.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vetra-read-model-permissions.test.d.ts","sourceRoot":"","sources":["../../../subgraphs/__tests__/vetra-read-model-permissions.test.ts"],"names":[],"mappings":""}