@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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=app-module-permissions.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-module-permissions.test.d.ts","sourceRoot":"","sources":["../../../subgraphs/__tests__/app-module-permissions.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,436 @@
1
+ import { GraphQLError } from "graphql";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { appModuleDocumentType } from "../../document-models/app-module/index.js";
4
+ import { getResolvers } from "../app-module/resolvers.js";
5
+ describe("AppModule Subgraph Permission Checks", () => {
6
+ let mockSubgraph;
7
+ let mockDocumentPermissionService;
8
+ let mockReactor;
9
+ let resolvers;
10
+ // Mock document
11
+ const createMockDocument = (id, name) => ({
12
+ header: {
13
+ id,
14
+ slug: id,
15
+ name,
16
+ documentType: appModuleDocumentType,
17
+ revision: { global: 1 },
18
+ createdAtUtcIso: new Date().toISOString(),
19
+ lastModifiedAtUtcIso: new Date().toISOString(),
20
+ },
21
+ state: {
22
+ global: {
23
+ name,
24
+ status: "draft",
25
+ documentTypes: [],
26
+ dragAndDropEnabled: false,
27
+ },
28
+ local: {},
29
+ },
30
+ initialState: {
31
+ global: {
32
+ name,
33
+ status: "draft",
34
+ documentTypes: [],
35
+ dragAndDropEnabled: false,
36
+ },
37
+ local: {},
38
+ },
39
+ operations: {
40
+ global: [],
41
+ local: [],
42
+ },
43
+ attachments: {},
44
+ clipboard: [],
45
+ });
46
+ const mockDocument = createMockDocument("app-123", "Test App Module");
47
+ // Helper to create context with different permission levels
48
+ const createContext = (options) => ({
49
+ user: options.userAddress ? { address: options.userAddress } : undefined,
50
+ isAdmin: vi.fn().mockReturnValue(options.isAdmin ?? false),
51
+ isUser: vi.fn().mockReturnValue(options.isUser ?? false),
52
+ isGuest: vi.fn().mockReturnValue(options.isGuest ?? false),
53
+ });
54
+ beforeEach(() => {
55
+ vi.clearAllMocks();
56
+ delete process.env.FREE_ENTRY;
57
+ // Create mock DocumentPermissionService
58
+ mockDocumentPermissionService = {
59
+ canRead: vi.fn().mockResolvedValue(false),
60
+ canWrite: vi.fn().mockResolvedValue(false),
61
+ canReadDocument: vi.fn().mockResolvedValue(false),
62
+ canWriteDocument: vi.fn().mockResolvedValue(false),
63
+ isOperationRestricted: vi.fn().mockResolvedValue(false),
64
+ canExecuteOperation: vi.fn().mockResolvedValue(false),
65
+ };
66
+ // Create mock reactor
67
+ mockReactor = {
68
+ getDocument: vi.fn().mockResolvedValue(mockDocument),
69
+ getDocuments: vi.fn().mockResolvedValue(["app-123"]),
70
+ addDocument: vi.fn().mockResolvedValue(mockDocument),
71
+ addAction: vi.fn().mockResolvedValue({
72
+ status: "SUCCESS",
73
+ operations: [{ index: 1 }],
74
+ }),
75
+ };
76
+ // Create mock subgraph
77
+ mockSubgraph = {
78
+ reactor: mockReactor,
79
+ documentPermissionService: mockDocumentPermissionService,
80
+ reactorClient: {
81
+ getParents: vi.fn().mockResolvedValue({
82
+ results: [],
83
+ options: { limit: 10 },
84
+ }),
85
+ },
86
+ };
87
+ // Get resolvers
88
+ resolvers = getResolvers(mockSubgraph);
89
+ });
90
+ afterEach(() => {
91
+ delete process.env.FREE_ENTRY;
92
+ });
93
+ describe("Query: AppModule.getDocument", () => {
94
+ const callGetDocument = async (ctx, docId, driveId) => {
95
+ const queryResolver = resolvers.Query?.AppModule;
96
+ const queryObject = queryResolver(null, {}, ctx);
97
+ return queryObject.getDocument({ docId, driveId });
98
+ };
99
+ describe("Global Role Access", () => {
100
+ it("should allow access when user is global admin", async () => {
101
+ const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
102
+ const result = await callGetDocument(ctx, "app-123");
103
+ expect(result).toBeDefined();
104
+ expect(result.id).toBe("app-123");
105
+ expect(mockDocumentPermissionService.canRead).not.toHaveBeenCalled();
106
+ });
107
+ it("should allow access when user is global user", async () => {
108
+ const ctx = createContext({ isUser: true, userAddress: "0xuser" });
109
+ const result = await callGetDocument(ctx, "app-123");
110
+ expect(result).toBeDefined();
111
+ expect(mockDocumentPermissionService.canRead).not.toHaveBeenCalled();
112
+ });
113
+ it("should allow access when user is global guest", async () => {
114
+ const ctx = createContext({ isGuest: true, userAddress: "0xguest" });
115
+ const result = await callGetDocument(ctx, "app-123");
116
+ expect(result).toBeDefined();
117
+ expect(mockDocumentPermissionService.canRead).not.toHaveBeenCalled();
118
+ });
119
+ it("should allow access when FREE_ENTRY is true", async () => {
120
+ process.env.FREE_ENTRY = "true";
121
+ const ctx = createContext({ userAddress: "0xanyone" });
122
+ const result = await callGetDocument(ctx, "app-123");
123
+ expect(result).toBeDefined();
124
+ });
125
+ });
126
+ describe("Document Permission Access", () => {
127
+ it("should check document permissions when no global access", async () => {
128
+ vi.mocked(mockDocumentPermissionService.canRead).mockResolvedValue(true);
129
+ const ctx = createContext({ userAddress: "0xpermitted" });
130
+ const result = await callGetDocument(ctx, "app-123");
131
+ expect(result).toBeDefined();
132
+ expect(mockDocumentPermissionService.canRead).toHaveBeenCalledWith("app-123", "0xpermitted", expect.any(Function));
133
+ });
134
+ it("should deny access when user has no permissions", async () => {
135
+ vi.mocked(mockDocumentPermissionService.canRead).mockResolvedValue(false);
136
+ const ctx = createContext({ userAddress: "0xunpermitted" });
137
+ await expect(callGetDocument(ctx, "app-123")).rejects.toThrow(GraphQLError);
138
+ await expect(callGetDocument(ctx, "app-123")).rejects.toThrow("Forbidden");
139
+ });
140
+ it("should deny access when user is not authenticated", async () => {
141
+ const ctx = createContext({});
142
+ await expect(callGetDocument(ctx, "app-123")).rejects.toThrow("Forbidden");
143
+ });
144
+ });
145
+ describe("Validation", () => {
146
+ it("should throw error when docId is not provided", async () => {
147
+ const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
148
+ await expect(callGetDocument(ctx, "")).rejects.toThrow("Document id is required");
149
+ });
150
+ it("should verify document is in specified drive when driveId provided", async () => {
151
+ mockReactor.getDocuments = vi.fn().mockResolvedValue(["other-doc"]);
152
+ const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
153
+ await expect(callGetDocument(ctx, "app-123", "drive-1")).rejects.toThrow("is not part of");
154
+ });
155
+ });
156
+ });
157
+ describe("Query: AppModule.getDocuments", () => {
158
+ const callGetDocuments = async (ctx, driveId) => {
159
+ const queryResolver = resolvers.Query?.AppModule;
160
+ const queryObject = queryResolver(null, {}, ctx);
161
+ return queryObject.getDocuments({ driveId });
162
+ };
163
+ describe("Global Role Access", () => {
164
+ it("should return all documents for admin", async () => {
165
+ const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
166
+ const result = await callGetDocuments(ctx, "drive-1");
167
+ expect(result).toHaveLength(1);
168
+ expect(mockDocumentPermissionService.canRead).not.toHaveBeenCalled();
169
+ });
170
+ });
171
+ describe("Document Permission Filtering", () => {
172
+ beforeEach(() => {
173
+ // Setup multiple documents
174
+ mockReactor.getDocuments = vi
175
+ .fn()
176
+ .mockResolvedValue(["app-1", "app-2", "app-3"]);
177
+ mockReactor.getDocument = vi
178
+ .fn()
179
+ .mockImplementation((id) => Promise.resolve(createMockDocument(id, `App ${id}`)));
180
+ });
181
+ it("should filter documents based on permissions when no global access", async () => {
182
+ // User can read drive-1 (required), app-1, and app-3, but not app-2
183
+ vi.mocked(mockDocumentPermissionService.canRead).mockImplementation(async (docId) => docId === "drive-1" || docId === "app-1" || docId === "app-3");
184
+ const ctx = createContext({ userAddress: "0xpartial" });
185
+ const result = await callGetDocuments(ctx, "drive-1");
186
+ expect(result).toHaveLength(2);
187
+ expect(result.map((d) => d.id).sort()).toEqual(["app-1", "app-3"]);
188
+ });
189
+ it("should return empty array when user has no document permissions", async () => {
190
+ // User can read drive-1 but no documents inside it
191
+ vi.mocked(mockDocumentPermissionService.canRead).mockImplementation(async (docId) => docId === "drive-1");
192
+ const ctx = createContext({ userAddress: "0xnopermissions" });
193
+ const result = await callGetDocuments(ctx, "drive-1");
194
+ expect(result).toHaveLength(0);
195
+ });
196
+ });
197
+ });
198
+ describe("Mutation: AppModule_createDocument", () => {
199
+ const callCreateDocument = async (ctx, name, driveId) => {
200
+ const mutation = resolvers.Mutation?.AppModule_createDocument;
201
+ return mutation(null, { name, driveId }, ctx);
202
+ };
203
+ describe("Global Role Access", () => {
204
+ it("should allow creation when user is global admin", async () => {
205
+ const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
206
+ const result = await callCreateDocument(ctx, "New App");
207
+ expect(result).toBe("app-123");
208
+ expect(mockDocumentPermissionService.canWrite).not.toHaveBeenCalled();
209
+ });
210
+ it("should allow creation when user is global user", async () => {
211
+ const ctx = createContext({ isUser: true, userAddress: "0xuser" });
212
+ const result = await callCreateDocument(ctx, "New App");
213
+ expect(result).toBe("app-123");
214
+ });
215
+ it("should deny creation when user is only global guest", async () => {
216
+ const ctx = createContext({ isGuest: true, userAddress: "0xguest" });
217
+ await expect(callCreateDocument(ctx, "New App")).rejects.toThrow("Forbidden: insufficient permissions to create documents");
218
+ });
219
+ });
220
+ describe("With driveId", () => {
221
+ it("should check write permission on drive when driveId provided", async () => {
222
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(true);
223
+ const ctx = createContext({ userAddress: "0xpermitted" });
224
+ const result = await callCreateDocument(ctx, "New App", "drive-1");
225
+ expect(result).toBe("app-123");
226
+ expect(mockDocumentPermissionService.canWrite).toHaveBeenCalledWith("drive-1", "0xpermitted", expect.any(Function));
227
+ });
228
+ it("should deny creation when user cannot write to drive", async () => {
229
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(false);
230
+ const ctx = createContext({ userAddress: "0xunpermitted" });
231
+ await expect(callCreateDocument(ctx, "New App", "drive-1")).rejects.toThrow("Forbidden");
232
+ });
233
+ });
234
+ });
235
+ describe("Mutation: AppModule_setAppName", () => {
236
+ const callSetAppName = async (ctx, docId, name) => {
237
+ const mutation = resolvers.Mutation?.AppModule_setAppName;
238
+ return mutation(null, { docId, input: { name } }, ctx);
239
+ };
240
+ describe("Write Permission Check", () => {
241
+ it("should check write permission before executing operation", async () => {
242
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(true);
243
+ const ctx = createContext({ userAddress: "0xpermitted" });
244
+ await callSetAppName(ctx, "app-123", "New Name");
245
+ expect(mockDocumentPermissionService.canWrite).toHaveBeenCalledWith("app-123", "0xpermitted", expect.any(Function));
246
+ });
247
+ it("should deny operation when user cannot write", async () => {
248
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(false);
249
+ const ctx = createContext({ userAddress: "0xunpermitted" });
250
+ await expect(callSetAppName(ctx, "app-123", "New Name")).rejects.toThrow("Forbidden: insufficient permissions to write");
251
+ });
252
+ it("should allow operation for global admin without permission check", async () => {
253
+ const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
254
+ const result = await callSetAppName(ctx, "app-123", "New Name");
255
+ expect(result).toBe(true);
256
+ expect(mockDocumentPermissionService.canWrite).not.toHaveBeenCalled();
257
+ });
258
+ });
259
+ describe("Operation-Level Permission Check", () => {
260
+ it("should check operation restriction", async () => {
261
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(true);
262
+ vi.mocked(mockDocumentPermissionService.isOperationRestricted).mockResolvedValue(false);
263
+ const ctx = createContext({ userAddress: "0xpermitted" });
264
+ await callSetAppName(ctx, "app-123", "New Name");
265
+ expect(mockDocumentPermissionService.isOperationRestricted).toHaveBeenCalledWith("app-123", "SET_APP_NAME");
266
+ });
267
+ it("should deny operation when restricted and user lacks permission", async () => {
268
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(true);
269
+ vi.mocked(mockDocumentPermissionService.isOperationRestricted).mockResolvedValue(true);
270
+ vi.mocked(mockDocumentPermissionService.canExecuteOperation).mockResolvedValue(false);
271
+ const ctx = createContext({ userAddress: "0xunauthorized" });
272
+ await expect(callSetAppName(ctx, "app-123", "New Name")).rejects.toThrow('Forbidden: insufficient permissions to execute operation "SET_APP_NAME"');
273
+ });
274
+ it("should allow operation when restricted and user has permission", async () => {
275
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(true);
276
+ vi.mocked(mockDocumentPermissionService.isOperationRestricted).mockResolvedValue(true);
277
+ vi.mocked(mockDocumentPermissionService.canExecuteOperation).mockResolvedValue(true);
278
+ const ctx = createContext({ userAddress: "0xauthorized" });
279
+ const result = await callSetAppName(ctx, "app-123", "New Name");
280
+ expect(result).toBe(true);
281
+ });
282
+ it("should skip restriction check for global admin", async () => {
283
+ vi.mocked(mockDocumentPermissionService.isOperationRestricted).mockResolvedValue(true);
284
+ const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
285
+ await callSetAppName(ctx, "app-123", "New Name");
286
+ expect(mockDocumentPermissionService.isOperationRestricted).not.toHaveBeenCalled();
287
+ });
288
+ });
289
+ });
290
+ describe("Mutation: AppModule_setAppStatus", () => {
291
+ const callSetAppStatus = async (ctx, docId, status) => {
292
+ const mutation = resolvers.Mutation?.AppModule_setAppStatus;
293
+ return mutation(null, { docId, input: { status } }, ctx);
294
+ };
295
+ it("should check write and operation permissions", async () => {
296
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(true);
297
+ vi.mocked(mockDocumentPermissionService.isOperationRestricted).mockResolvedValue(true);
298
+ vi.mocked(mockDocumentPermissionService.canExecuteOperation).mockResolvedValue(true);
299
+ const ctx = createContext({ userAddress: "0xpermitted" });
300
+ // Use valid status value: "CONFIRMED" or "DRAFT"
301
+ await callSetAppStatus(ctx, "app-123", "CONFIRMED");
302
+ expect(mockDocumentPermissionService.isOperationRestricted).toHaveBeenCalledWith("app-123", "SET_APP_STATUS");
303
+ expect(mockDocumentPermissionService.canExecuteOperation).toHaveBeenCalledWith("app-123", "SET_APP_STATUS", "0xpermitted");
304
+ });
305
+ });
306
+ describe("Mutation: AppModule_addDocumentType", () => {
307
+ const callAddDocumentType = async (ctx, docId, input) => {
308
+ const mutation = resolvers.Mutation?.AppModule_addDocumentType;
309
+ return mutation(null, { docId, input }, ctx);
310
+ };
311
+ it("should check operation permission for ADD_DOCUMENT_TYPE", async () => {
312
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(true);
313
+ vi.mocked(mockDocumentPermissionService.isOperationRestricted).mockResolvedValue(true);
314
+ vi.mocked(mockDocumentPermissionService.canExecuteOperation).mockResolvedValue(true);
315
+ const ctx = createContext({ userAddress: "0xpermitted" });
316
+ await callAddDocumentType(ctx, "app-123", {
317
+ documentType: "powerhouse/test",
318
+ });
319
+ expect(mockDocumentPermissionService.isOperationRestricted).toHaveBeenCalledWith("app-123", "ADD_DOCUMENT_TYPE");
320
+ });
321
+ });
322
+ describe("Mutation: AppModule_removeDocumentType", () => {
323
+ const callRemoveDocumentType = async (ctx, docId, input) => {
324
+ const mutation = resolvers.Mutation
325
+ ?.AppModule_removeDocumentType;
326
+ return mutation(null, { docId, input }, ctx);
327
+ };
328
+ it("should check operation permission for REMOVE_DOCUMENT_TYPE", async () => {
329
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(true);
330
+ vi.mocked(mockDocumentPermissionService.isOperationRestricted).mockResolvedValue(true);
331
+ vi.mocked(mockDocumentPermissionService.canExecuteOperation).mockResolvedValue(true);
332
+ const ctx = createContext({ userAddress: "0xpermitted" });
333
+ await callRemoveDocumentType(ctx, "app-123", {
334
+ documentType: "powerhouse/test",
335
+ });
336
+ expect(mockDocumentPermissionService.isOperationRestricted).toHaveBeenCalledWith("app-123", "REMOVE_DOCUMENT_TYPE");
337
+ });
338
+ });
339
+ describe("Mutation: AppModule_setDocumentTypes", () => {
340
+ const callSetDocumentTypes = async (ctx, docId, input) => {
341
+ const mutation = resolvers.Mutation?.AppModule_setDocumentTypes;
342
+ return mutation(null, { docId, input }, ctx);
343
+ };
344
+ it("should check operation permission for SET_DOCUMENT_TYPES", async () => {
345
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(true);
346
+ vi.mocked(mockDocumentPermissionService.isOperationRestricted).mockResolvedValue(true);
347
+ vi.mocked(mockDocumentPermissionService.canExecuteOperation).mockResolvedValue(true);
348
+ const ctx = createContext({ userAddress: "0xpermitted" });
349
+ await callSetDocumentTypes(ctx, "app-123", {
350
+ documentTypes: ["powerhouse/test1", "powerhouse/test2"],
351
+ });
352
+ expect(mockDocumentPermissionService.isOperationRestricted).toHaveBeenCalledWith("app-123", "SET_DOCUMENT_TYPES");
353
+ });
354
+ });
355
+ describe("Mutation: AppModule_setDragAndDropEnabled", () => {
356
+ const callSetDragAndDropEnabled = async (ctx, docId, input) => {
357
+ const mutation = resolvers.Mutation
358
+ ?.AppModule_setDragAndDropEnabled;
359
+ return mutation(null, { docId, input }, ctx);
360
+ };
361
+ it("should check operation permission for SET_DRAG_AND_DROP_ENABLED", async () => {
362
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(true);
363
+ vi.mocked(mockDocumentPermissionService.isOperationRestricted).mockResolvedValue(true);
364
+ vi.mocked(mockDocumentPermissionService.canExecuteOperation).mockResolvedValue(true);
365
+ const ctx = createContext({ userAddress: "0xpermitted" });
366
+ await callSetDragAndDropEnabled(ctx, "app-123", { enabled: true });
367
+ expect(mockDocumentPermissionService.isOperationRestricted).toHaveBeenCalledWith("app-123", "SET_DRAG_AND_DROP_ENABLED");
368
+ });
369
+ });
370
+ describe("Permission Inheritance", () => {
371
+ it("should use getParentIdsFn for hierarchy checks", async () => {
372
+ const mockParents = [createMockDocument("parent-app", "Parent App")];
373
+ vi.mocked(mockSubgraph.reactorClient.getParents).mockResolvedValue({
374
+ results: mockParents,
375
+ options: { limit: 10 },
376
+ });
377
+ let capturedGetParentsFn = null;
378
+ vi.mocked(mockDocumentPermissionService.canRead).mockImplementation(async (_docId, _user, getParentsFn) => {
379
+ capturedGetParentsFn = getParentsFn;
380
+ return true;
381
+ });
382
+ const ctx = createContext({ userAddress: "0xuser" });
383
+ const queryResolver = resolvers.Query?.AppModule;
384
+ const queryObject = queryResolver(null, {}, ctx);
385
+ await queryObject.getDocument({ docId: "child-app" });
386
+ expect(capturedGetParentsFn).not.toBeNull();
387
+ const parentIds = await capturedGetParentsFn("child-app");
388
+ expect(parentIds).toEqual(["parent-app"]);
389
+ });
390
+ });
391
+ describe("No Permission Service", () => {
392
+ let resolversWithoutPermService;
393
+ beforeEach(() => {
394
+ const subgraphWithoutService = {
395
+ reactor: mockReactor,
396
+ documentPermissionService: undefined,
397
+ reactorClient: mockSubgraph.reactorClient,
398
+ };
399
+ resolversWithoutPermService = getResolvers(subgraphWithoutService);
400
+ });
401
+ it("should deny read access when no permission service and no global access", async () => {
402
+ const ctx = createContext({ userAddress: "0xuser" });
403
+ const queryResolver = resolversWithoutPermService.Query
404
+ ?.AppModule;
405
+ const queryObject = queryResolver(null, {}, ctx);
406
+ await expect(queryObject.getDocument({ docId: "app-123" })).rejects.toThrow("Forbidden");
407
+ });
408
+ it("should allow access with global roles even without permission service", async () => {
409
+ const ctx = createContext({ isAdmin: true, userAddress: "0xadmin" });
410
+ const queryResolver = resolversWithoutPermService.Query
411
+ ?.AppModule;
412
+ const queryObject = queryResolver(null, {}, ctx);
413
+ const result = await queryObject.getDocument({ docId: "app-123" });
414
+ expect(result).toBeDefined();
415
+ });
416
+ });
417
+ describe("Edge Cases", () => {
418
+ it("should handle document not found", async () => {
419
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(true);
420
+ mockReactor.getDocument = vi.fn().mockResolvedValue(null);
421
+ const ctx = createContext({ userAddress: "0xpermitted" });
422
+ const mutation = resolvers.Mutation?.AppModule_setAppName;
423
+ await expect(mutation(null, { docId: "non-existent", input: { name: "New" } }, ctx)).rejects.toThrow("Document not found");
424
+ });
425
+ it("should handle reactor action failure", async () => {
426
+ vi.mocked(mockDocumentPermissionService.canWrite).mockResolvedValue(true);
427
+ mockReactor.addAction = vi.fn().mockResolvedValue({
428
+ status: "ERROR",
429
+ error: { message: "Action failed" },
430
+ });
431
+ const ctx = createContext({ userAddress: "0xpermitted" });
432
+ const mutation = resolvers.Mutation?.AppModule_setAppName;
433
+ await expect(mutation(null, { docId: "app-123", input: { name: "New" } }, ctx)).rejects.toThrow("Action failed");
434
+ });
435
+ });
436
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=permission-utils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-utils.test.d.ts","sourceRoot":"","sources":["../../../subgraphs/__tests__/permission-utils.test.ts"],"names":[],"mappings":""}