@lightdash/common 0.1936.2 → 0.1937.1

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 (157) hide show
  1. package/dist/cjs/authorization/index.d.ts +8 -4
  2. package/dist/cjs/authorization/index.d.ts.map +1 -1
  3. package/dist/cjs/authorization/index.js +26 -3
  4. package/dist/cjs/authorization/index.js.map +1 -1
  5. package/dist/cjs/authorization/index.mock.d.ts +2 -0
  6. package/dist/cjs/authorization/index.mock.d.ts.map +1 -1
  7. package/dist/cjs/authorization/index.mock.js +2 -0
  8. package/dist/cjs/authorization/index.mock.js.map +1 -1
  9. package/dist/cjs/authorization/organizationMemberAbility.mock.d.ts.map +1 -1
  10. package/dist/cjs/authorization/organizationMemberAbility.mock.js +1 -0
  11. package/dist/cjs/authorization/organizationMemberAbility.mock.js.map +1 -1
  12. package/dist/cjs/authorization/parseScopes.d.ts +3 -2
  13. package/dist/cjs/authorization/parseScopes.d.ts.map +1 -1
  14. package/dist/cjs/authorization/parseScopes.js +17 -8
  15. package/dist/cjs/authorization/parseScopes.js.map +1 -1
  16. package/dist/cjs/authorization/parseScopes.test.js +9 -14
  17. package/dist/cjs/authorization/parseScopes.test.js.map +1 -1
  18. package/dist/cjs/authorization/projectMemberAbility.mock.d.ts.map +1 -1
  19. package/dist/cjs/authorization/projectMemberAbility.mock.js +1 -0
  20. package/dist/cjs/authorization/projectMemberAbility.mock.js.map +1 -1
  21. package/dist/cjs/authorization/roleToScopeMapping.d.ts +19 -0
  22. package/dist/cjs/authorization/roleToScopeMapping.d.ts.map +1 -0
  23. package/dist/cjs/authorization/roleToScopeMapping.js +163 -0
  24. package/dist/cjs/authorization/roleToScopeMapping.js.map +1 -0
  25. package/dist/cjs/authorization/roleToScopeMapping.test.d.ts +2 -0
  26. package/dist/cjs/authorization/roleToScopeMapping.test.d.ts.map +1 -0
  27. package/dist/cjs/authorization/roleToScopeMapping.test.js +549 -0
  28. package/dist/cjs/authorization/roleToScopeMapping.test.js.map +1 -0
  29. package/dist/cjs/authorization/roleToScopeMapping.testUtils.d.ts +1393 -0
  30. package/dist/cjs/authorization/roleToScopeMapping.testUtils.d.ts.map +1 -0
  31. package/dist/cjs/authorization/roleToScopeMapping.testUtils.js +329 -0
  32. package/dist/cjs/authorization/roleToScopeMapping.testUtils.js.map +1 -0
  33. package/dist/cjs/authorization/scopeAbilityBuilder.d.ts +14 -8
  34. package/dist/cjs/authorization/scopeAbilityBuilder.d.ts.map +1 -1
  35. package/dist/cjs/authorization/scopeAbilityBuilder.js +7 -7
  36. package/dist/cjs/authorization/scopeAbilityBuilder.js.map +1 -1
  37. package/dist/cjs/authorization/scopeAbilityBuilder.test.js +258 -185
  38. package/dist/cjs/authorization/scopeAbilityBuilder.test.js.map +1 -1
  39. package/dist/cjs/authorization/scopes.d.ts.map +1 -1
  40. package/dist/cjs/authorization/scopes.js +132 -187
  41. package/dist/cjs/authorization/scopes.js.map +1 -1
  42. package/dist/cjs/ee/AiAgent/schemas/tools/index.d.ts +1 -0
  43. package/dist/cjs/ee/AiAgent/schemas/tools/index.d.ts.map +1 -1
  44. package/dist/cjs/ee/AiAgent/schemas/tools/index.js +1 -0
  45. package/dist/cjs/ee/AiAgent/schemas/tools/index.js.map +1 -1
  46. package/dist/cjs/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.d.ts +2845 -0
  47. package/dist/cjs/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.d.ts.map +1 -0
  48. package/dist/cjs/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.js +41 -0
  49. package/dist/cjs/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.js.map +1 -0
  50. package/dist/cjs/types/organizationMemberProfile.d.ts +1 -0
  51. package/dist/cjs/types/organizationMemberProfile.d.ts.map +1 -1
  52. package/dist/cjs/types/organizationMemberProfile.js.map +1 -1
  53. package/dist/cjs/types/projectMemberProfile.d.ts +1 -0
  54. package/dist/cjs/types/projectMemberProfile.d.ts.map +1 -1
  55. package/dist/cjs/types/scopes.d.ts +19 -9
  56. package/dist/cjs/types/scopes.d.ts.map +1 -1
  57. package/dist/cjs/types/search.d.ts +20 -0
  58. package/dist/cjs/types/search.d.ts.map +1 -1
  59. package/dist/cjs/types/search.js.map +1 -1
  60. package/dist/cjs/types/user.d.ts +1 -0
  61. package/dist/cjs/types/user.d.ts.map +1 -1
  62. package/dist/cjs/types/user.js.map +1 -1
  63. package/dist/esm/authorization/index.d.ts +8 -4
  64. package/dist/esm/authorization/index.d.ts.map +1 -1
  65. package/dist/esm/authorization/index.js +26 -3
  66. package/dist/esm/authorization/index.js.map +1 -1
  67. package/dist/esm/authorization/index.mock.d.ts +2 -0
  68. package/dist/esm/authorization/index.mock.d.ts.map +1 -1
  69. package/dist/esm/authorization/index.mock.js +2 -0
  70. package/dist/esm/authorization/index.mock.js.map +1 -1
  71. package/dist/esm/authorization/organizationMemberAbility.mock.d.ts.map +1 -1
  72. package/dist/esm/authorization/organizationMemberAbility.mock.js +1 -0
  73. package/dist/esm/authorization/organizationMemberAbility.mock.js.map +1 -1
  74. package/dist/esm/authorization/parseScopes.d.ts +3 -2
  75. package/dist/esm/authorization/parseScopes.d.ts.map +1 -1
  76. package/dist/esm/authorization/parseScopes.js +15 -7
  77. package/dist/esm/authorization/parseScopes.js.map +1 -1
  78. package/dist/esm/authorization/parseScopes.test.js +9 -14
  79. package/dist/esm/authorization/parseScopes.test.js.map +1 -1
  80. package/dist/esm/authorization/projectMemberAbility.mock.d.ts.map +1 -1
  81. package/dist/esm/authorization/projectMemberAbility.mock.js +1 -0
  82. package/dist/esm/authorization/projectMemberAbility.mock.js.map +1 -1
  83. package/dist/esm/authorization/roleToScopeMapping.d.ts +19 -0
  84. package/dist/esm/authorization/roleToScopeMapping.d.ts.map +1 -0
  85. package/dist/esm/authorization/roleToScopeMapping.js +157 -0
  86. package/dist/esm/authorization/roleToScopeMapping.js.map +1 -0
  87. package/dist/esm/authorization/roleToScopeMapping.test.d.ts +2 -0
  88. package/dist/esm/authorization/roleToScopeMapping.test.d.ts.map +1 -0
  89. package/dist/esm/authorization/roleToScopeMapping.test.js +547 -0
  90. package/dist/esm/authorization/roleToScopeMapping.test.js.map +1 -0
  91. package/dist/esm/authorization/roleToScopeMapping.testUtils.d.ts +1393 -0
  92. package/dist/esm/authorization/roleToScopeMapping.testUtils.d.ts.map +1 -0
  93. package/dist/esm/authorization/roleToScopeMapping.testUtils.js +319 -0
  94. package/dist/esm/authorization/roleToScopeMapping.testUtils.js.map +1 -0
  95. package/dist/esm/authorization/scopeAbilityBuilder.d.ts +14 -8
  96. package/dist/esm/authorization/scopeAbilityBuilder.d.ts.map +1 -1
  97. package/dist/esm/authorization/scopeAbilityBuilder.js +7 -7
  98. package/dist/esm/authorization/scopeAbilityBuilder.js.map +1 -1
  99. package/dist/esm/authorization/scopeAbilityBuilder.test.js +259 -186
  100. package/dist/esm/authorization/scopeAbilityBuilder.test.js.map +1 -1
  101. package/dist/esm/authorization/scopes.d.ts.map +1 -1
  102. package/dist/esm/authorization/scopes.js +132 -187
  103. package/dist/esm/authorization/scopes.js.map +1 -1
  104. package/dist/esm/ee/AiAgent/schemas/tools/index.d.ts +1 -0
  105. package/dist/esm/ee/AiAgent/schemas/tools/index.d.ts.map +1 -1
  106. package/dist/esm/ee/AiAgent/schemas/tools/index.js +1 -0
  107. package/dist/esm/ee/AiAgent/schemas/tools/index.js.map +1 -1
  108. package/dist/esm/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.d.ts +2845 -0
  109. package/dist/esm/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.d.ts.map +1 -0
  110. package/dist/esm/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.js +38 -0
  111. package/dist/esm/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.js.map +1 -0
  112. package/dist/esm/types/organizationMemberProfile.d.ts +1 -0
  113. package/dist/esm/types/organizationMemberProfile.d.ts.map +1 -1
  114. package/dist/esm/types/organizationMemberProfile.js.map +1 -1
  115. package/dist/esm/types/projectMemberProfile.d.ts +1 -0
  116. package/dist/esm/types/projectMemberProfile.d.ts.map +1 -1
  117. package/dist/esm/types/scopes.d.ts +19 -9
  118. package/dist/esm/types/scopes.d.ts.map +1 -1
  119. package/dist/esm/types/search.d.ts +20 -0
  120. package/dist/esm/types/search.d.ts.map +1 -1
  121. package/dist/esm/types/search.js.map +1 -1
  122. package/dist/esm/types/user.d.ts +1 -0
  123. package/dist/esm/types/user.d.ts.map +1 -1
  124. package/dist/esm/types/user.js.map +1 -1
  125. package/dist/tsconfig.types.tsbuildinfo +1 -1
  126. package/dist/types/authorization/index.d.ts +8 -4
  127. package/dist/types/authorization/index.d.ts.map +1 -1
  128. package/dist/types/authorization/index.mock.d.ts +2 -0
  129. package/dist/types/authorization/index.mock.d.ts.map +1 -1
  130. package/dist/types/authorization/organizationMemberAbility.mock.d.ts.map +1 -1
  131. package/dist/types/authorization/parseScopes.d.ts +3 -2
  132. package/dist/types/authorization/parseScopes.d.ts.map +1 -1
  133. package/dist/types/authorization/projectMemberAbility.mock.d.ts.map +1 -1
  134. package/dist/types/authorization/roleToScopeMapping.d.ts +19 -0
  135. package/dist/types/authorization/roleToScopeMapping.d.ts.map +1 -0
  136. package/dist/types/authorization/roleToScopeMapping.test.d.ts +2 -0
  137. package/dist/types/authorization/roleToScopeMapping.test.d.ts.map +1 -0
  138. package/dist/types/authorization/roleToScopeMapping.testUtils.d.ts +1393 -0
  139. package/dist/types/authorization/roleToScopeMapping.testUtils.d.ts.map +1 -0
  140. package/dist/types/authorization/scopeAbilityBuilder.d.ts +14 -8
  141. package/dist/types/authorization/scopeAbilityBuilder.d.ts.map +1 -1
  142. package/dist/types/authorization/scopes.d.ts.map +1 -1
  143. package/dist/types/ee/AiAgent/schemas/tools/index.d.ts +1 -0
  144. package/dist/types/ee/AiAgent/schemas/tools/index.d.ts.map +1 -1
  145. package/dist/types/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.d.ts +2845 -0
  146. package/dist/types/ee/AiAgent/schemas/tools/toolSearchFieldValuesArgs.d.ts.map +1 -0
  147. package/dist/types/types/organizationMemberProfile.d.ts +1 -0
  148. package/dist/types/types/organizationMemberProfile.d.ts.map +1 -1
  149. package/dist/types/types/projectMemberProfile.d.ts +1 -0
  150. package/dist/types/types/projectMemberProfile.d.ts.map +1 -1
  151. package/dist/types/types/scopes.d.ts +19 -9
  152. package/dist/types/types/scopes.d.ts.map +1 -1
  153. package/dist/types/types/search.d.ts +20 -0
  154. package/dist/types/types/search.d.ts.map +1 -1
  155. package/dist/types/types/user.d.ts +1 -0
  156. package/dist/types/types/user.d.ts.map +1 -1
  157. package/package.json +1 -1
@@ -7,17 +7,24 @@ const scopeAbilityBuilder_1 = require("./scopeAbilityBuilder");
7
7
  describe('scopeAbilityBuilder', () => {
8
8
  describe('buildAbilityFromScopes', () => {
9
9
  const baseContext = {
10
- organizationUuid: 'org-123',
11
10
  isEnterprise: false,
12
11
  organizationRole: 'admin',
13
12
  projectUuid: 'project-123',
13
+ userUuid: 'user1',
14
14
  scopes: [],
15
15
  };
16
+ const baseContextWithOrg = {
17
+ ...baseContext,
18
+ projectUuid: undefined,
19
+ organizationUuid: 'org-123',
20
+ };
16
21
  it('should build ability with organization view permissions', () => {
17
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
18
- ...baseContext,
22
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
23
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
24
+ ...baseContextWithOrg,
19
25
  scopes: ['view:organization'],
20
- });
26
+ }, builder);
27
+ const ability = builder.build();
21
28
  expect(ability.can('view', (0, ability_1.subject)('Organization', {
22
29
  organizationUuid: 'org-123',
23
30
  projectUuid: 'project-123',
@@ -28,10 +35,12 @@ describe('scopeAbilityBuilder', () => {
28
35
  }))).toBe(false);
29
36
  });
30
37
  it('should build ability with dashboard view permissions', () => {
31
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
38
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
39
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
32
40
  ...baseContext,
33
41
  scopes: ['view:dashboard'],
34
- });
42
+ }, builder);
43
+ const ability = builder.build();
35
44
  // Should be able to view public dashboards
36
45
  expect(ability.can('view', (0, ability_1.subject)('Dashboard', {
37
46
  organizationUuid: 'org-123',
@@ -50,10 +59,12 @@ describe('scopeAbilityBuilder', () => {
50
59
  ...baseContext,
51
60
  userUuid: 'user-456',
52
61
  };
53
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
62
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
63
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
54
64
  ...contextWithUser,
55
65
  scopes: ['view:dashboard'],
56
- });
66
+ }, builder);
67
+ const ability = builder.build();
57
68
  // Can view dashboards with user access
58
69
  expect(ability.can('view', (0, ability_1.subject)('Dashboard', {
59
70
  organizationUuid: 'org-123',
@@ -66,20 +77,24 @@ describe('scopeAbilityBuilder', () => {
66
77
  ...baseContext,
67
78
  projectUuid: 'project-789',
68
79
  };
69
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
80
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
81
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
70
82
  ...projectContext,
71
83
  scopes: ['view:project'],
72
- });
84
+ }, builder);
85
+ const ability = builder.build();
73
86
  expect(ability.can('view', (0, ability_1.subject)('Project', {
74
87
  organizationUuid: 'org-123',
75
88
  projectUuid: 'project-789',
76
89
  }))).toBe(true);
77
90
  });
78
91
  it('should build ability with project creation permissions and type restrictions', () => {
79
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
80
- ...baseContext,
92
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
93
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
94
+ ...baseContextWithOrg,
81
95
  scopes: ['create:project'],
82
- });
96
+ }, builder);
97
+ const ability = builder.build();
83
98
  // Can create preview projects
84
99
  expect(ability.can('create', (0, ability_1.subject)('Project', {
85
100
  organizationUuid: 'org-123',
@@ -93,13 +108,15 @@ describe('scopeAbilityBuilder', () => {
93
108
  });
94
109
  it('should build ability with editor permissions for dashboards', () => {
95
110
  const editorContext = {
96
- ...baseContext,
111
+ ...baseContextWithOrg,
97
112
  userUuid: 'user-456',
98
113
  };
99
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
114
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
115
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
100
116
  ...editorContext,
101
117
  scopes: ['manage:dashboard'],
102
- });
118
+ }, builder);
119
+ const ability = builder.build();
103
120
  // Can manage dashboards where user is editor
104
121
  expect(ability.can('manage', (0, ability_1.subject)('Dashboard', {
105
122
  organizationUuid: 'org-123',
@@ -113,13 +130,15 @@ describe('scopeAbilityBuilder', () => {
113
130
  });
114
131
  it('should build ability with admin permissions for spaces', () => {
115
132
  const adminContext = {
116
- ...baseContext,
133
+ ...baseContextWithOrg,
117
134
  userUuid: 'user-456',
118
135
  };
119
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
136
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
137
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
120
138
  ...adminContext,
121
139
  scopes: ['manage:space'],
122
- });
140
+ }, builder);
141
+ const ability = builder.build();
123
142
  // Can manage spaces where user is admin
124
143
  expect(ability.can('manage', (0, ability_1.subject)('Space', {
125
144
  organizationUuid: 'org-123',
@@ -136,10 +155,12 @@ describe('scopeAbilityBuilder', () => {
136
155
  ...baseContext,
137
156
  userUuid: 'user-456',
138
157
  };
139
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
158
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
159
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
140
160
  ...userContext,
141
161
  scopes: ['view:job_status'],
142
- });
162
+ }, builder);
163
+ const ability = builder.build();
143
164
  // Can view job status created by the user
144
165
  expect(ability.can('view', (0, ability_1.subject)('JobStatus', {
145
166
  createdByUserUuid: 'user-456',
@@ -155,10 +176,12 @@ describe('scopeAbilityBuilder', () => {
155
176
  userUuid: 'user-456',
156
177
  isEnterprise: true,
157
178
  };
158
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
179
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
180
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
159
181
  ...userContext,
160
182
  scopes: ['manage:ai_agent_thread'],
161
- });
183
+ }, builder);
184
+ const ability = builder.build();
162
185
  // Can manage user's own AI agent threads
163
186
  expect(ability.can('manage', (0, ability_1.subject)('AiAgentThread', {
164
187
  organizationUuid: 'org-123',
@@ -172,10 +195,12 @@ describe('scopeAbilityBuilder', () => {
172
195
  });
173
196
  it('should build ability with basic permissions for scopes without custom logic', () => {
174
197
  // These scopes don't have custom applyConditions
175
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
198
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
199
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
176
200
  ...baseContext,
177
201
  scopes: ['view:analytics', 'manage:tags'],
178
- });
202
+ }, builder);
203
+ const ability = builder.build();
179
204
  expect(ability.can('view', (0, ability_1.subject)('Analytics', {
180
205
  organizationUuid: 'org-123',
181
206
  projectUuid: 'project-123',
@@ -186,7 +211,9 @@ describe('scopeAbilityBuilder', () => {
186
211
  }))).toBe(true);
187
212
  });
188
213
  it('should handle unknown scopes gracefully', () => {
189
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(baseContext);
214
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
215
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(baseContext, builder);
216
+ const ability = builder.build();
190
217
  // Unknown scope should not add any abilities
191
218
  expect(ability.rules.length).toBe(0);
192
219
  });
@@ -195,15 +222,16 @@ describe('scopeAbilityBuilder', () => {
195
222
  ...baseContext,
196
223
  isEnterprise: false,
197
224
  };
198
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(nonEnterpriseContext);
225
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
226
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(nonEnterpriseContext, builder);
227
+ const ability = builder.build();
199
228
  // Enterprise scope should not add abilities in non-enterprise context
200
229
  expect(ability.rules.length).toBe(0);
201
230
  });
202
231
  it('should build a complete ability from multiple scopes', () => {
203
232
  const context = {
204
- organizationUuid: 'org-123',
205
233
  userUuid: 'user-456',
206
- projectUuid: 'project-789',
234
+ organizationUuid: 'org-123',
207
235
  isEnterprise: false,
208
236
  organizationRole: 'admin',
209
237
  scopes: [
@@ -212,7 +240,9 @@ describe('scopeAbilityBuilder', () => {
212
240
  'view:project',
213
241
  ],
214
242
  };
215
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(context);
243
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
244
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(context, builder);
245
+ const ability = builder.build();
216
246
  // Check that all abilities were applied
217
247
  expect(ability.can('view', (0, ability_1.subject)('Dashboard', {
218
248
  organizationUuid: 'org-123',
@@ -241,7 +271,9 @@ describe('scopeAbilityBuilder', () => {
241
271
  userUuid: 'user-456',
242
272
  scopes: ['manage:organization', 'manage:saved_chart'],
243
273
  };
244
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(contextWithOrgManage);
274
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
275
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(contextWithOrgManage, builder);
276
+ const ability = builder.build();
245
277
  // Should have organization-wide permissions for saved charts
246
278
  expect(ability.can('manage', (0, ability_1.subject)('SavedChart', {
247
279
  organizationUuid: 'org-123',
@@ -260,7 +292,9 @@ describe('scopeAbilityBuilder', () => {
260
292
  userUuid: 'user-456',
261
293
  scopes: ['manage:saved_chart'],
262
294
  };
263
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(contextWithoutOrgManage);
295
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
296
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(contextWithoutOrgManage, builder);
297
+ const ability = builder.build();
264
298
  // Should require user access restrictions
265
299
  expect(ability.can('manage', (0, ability_1.subject)('SavedChart', {
266
300
  organizationUuid: 'org-123',
@@ -285,7 +319,9 @@ describe('scopeAbilityBuilder', () => {
285
319
  userUuid: 'user-456',
286
320
  scopes: ['manage:project', 'manage:space'],
287
321
  };
288
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(contextWithProjectManage);
322
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
323
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(contextWithProjectManage, builder);
324
+ const ability = builder.build();
289
325
  // Should allow managing public spaces when user has project management
290
326
  expect(ability.can('manage', (0, ability_1.subject)('Space', {
291
327
  organizationUuid: 'org-123',
@@ -316,13 +352,17 @@ describe('scopeAbilityBuilder', () => {
316
352
  scopes: ['promote:dashboard'],
317
353
  };
318
354
  // Test dashboard promotion with organization management
319
- const abilityWithOrg = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(contextWithOrgManage);
355
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
356
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(contextWithOrgManage, builder);
357
+ const abilityWithOrg = builder.build();
320
358
  expect(abilityWithOrg.can('promote', (0, ability_1.subject)('Dashboard', {
321
359
  organizationUuid: 'org-123',
322
360
  projectUuid: 'project-123',
323
361
  }))).toBe(true);
324
362
  // Test dashboard promotion without organization management
325
- const abilityWithoutOrg = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(contextWithoutOrgManage);
363
+ const builderWithoutOrg = new ability_1.AbilityBuilder(ability_1.Ability);
364
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(contextWithoutOrgManage, builderWithoutOrg);
365
+ const abilityWithoutOrg = builderWithoutOrg.build();
326
366
  expect(abilityWithoutOrg.can('promote', (0, ability_1.subject)('Dashboard', {
327
367
  organizationUuid: 'org-123',
328
368
  projectUuid: 'project-123',
@@ -343,18 +383,21 @@ describe('scopeAbilityBuilder', () => {
343
383
  });
344
384
  describe('edge cases and error handling', () => {
345
385
  it('should handle empty scope array', () => {
346
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(baseContext);
386
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
387
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)(baseContext, builder);
388
+ const ability = builder.build();
347
389
  expect(ability.rules.length).toBe(0);
348
390
  });
349
391
  it('should handle undefined userUuid in context', () => {
350
392
  const contextWithoutUser = {
351
393
  ...baseContext,
352
- userUuid: undefined,
353
394
  };
354
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
395
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
396
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
355
397
  ...contextWithoutUser,
356
398
  scopes: ['view:dashboard'],
357
- });
399
+ }, builder);
400
+ const ability = builder.build();
358
401
  // Should only allow viewing public dashboards
359
402
  expect(ability.can('view', (0, ability_1.subject)('Dashboard', {
360
403
  organizationUuid: 'org-123',
@@ -367,62 +410,35 @@ describe('scopeAbilityBuilder', () => {
367
410
  isPrivate: true,
368
411
  }))).toBe(false);
369
412
  });
370
- it('should handle missing projectUuid in context', () => {
371
- const contextWithoutProject = {
372
- ...baseContext,
373
- projectUuid: '',
374
- };
375
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
376
- ...contextWithoutProject,
377
- scopes: ['view:dashboard'],
378
- });
379
- // Should work with public dashboards without project restriction
380
- expect(ability.can('view', (0, ability_1.subject)('Dashboard', {
381
- organizationUuid: 'org-123',
382
- isPrivate: false,
383
- }))).toBe(true);
384
- });
385
413
  it('should handle mixed valid and invalid scopes', () => {
386
- // Should throw error for invalid scopes
387
- expect(() => {
388
- (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
389
- ...baseContext,
390
- scopes: [
391
- 'view:dashboard',
392
- 'view:project',
393
- 'invalid:scope',
394
- ],
395
- });
396
- }).toThrow('Invalid scope: invalid:Scope. Please check the scope name and try again.');
397
- // Should work with only valid scopes
398
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
414
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
415
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
399
416
  ...baseContext,
400
- scopes: ['view:dashboard', 'view:project'],
401
- });
402
- // Should apply valid scopes
403
- expect(ability.can('view', (0, ability_1.subject)('Dashboard', {
404
- organizationUuid: 'org-123',
405
- projectUuid: 'project-123',
406
- isPrivate: false,
407
- }))).toBe(true);
408
- expect(ability.can('view', (0, ability_1.subject)('Project', {
409
- organizationUuid: 'org-123',
410
- projectUuid: 'project-123',
411
- }))).toBe(true);
412
- // Should have rules from valid scopes
413
- expect(ability.rules.length).toBeGreaterThan(0);
417
+ scopes: [
418
+ 'view:dashboard',
419
+ 'view:project',
420
+ 'invalid:scope',
421
+ ],
422
+ }, builder);
423
+ const ability = builder.build();
424
+ // We have 3 valid rules, 2 for dashboard and 1 for project, dropping the invalid scope
425
+ expect(ability.rules.length).toBe(3);
426
+ expect(ability.rules.filter((r) => r.subject === 'Dashboard')).toHaveLength(2);
427
+ expect(ability.rules.find((r) => r.subject === 'Project')).toBeDefined();
414
428
  });
415
429
  });
416
430
  describe('cross-boundary access tests', () => {
417
431
  it('should not allow access to resources from different organizations', () => {
418
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
432
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
433
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
419
434
  ...baseContext,
420
435
  scopes: [
421
436
  'view:dashboard',
422
437
  'manage:saved_chart',
423
438
  'view:space',
424
439
  ],
425
- });
440
+ }, builder);
441
+ const ability = builder.build();
426
442
  // Should not access dashboard from different org
427
443
  expect(ability.can('view', (0, ability_1.subject)('Dashboard', {
428
444
  organizationUuid: 'different-org',
@@ -439,10 +455,12 @@ describe('scopeAbilityBuilder', () => {
439
455
  }))).toBe(false);
440
456
  });
441
457
  it('should not allow access to resources from different projects', () => {
442
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
458
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
459
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
443
460
  ...baseContext,
444
461
  scopes: ['view:saved_chart'],
445
- });
462
+ }, builder);
463
+ const ability = builder.build();
446
464
  // Should not access saved chart from different project
447
465
  expect(ability.can('view', (0, ability_1.subject)('SavedChart', {
448
466
  organizationUuid: 'org-123',
@@ -454,13 +472,15 @@ describe('scopeAbilityBuilder', () => {
454
472
  describe('private resource access with space roles', () => {
455
473
  it('should handle viewer role access to private resources', () => {
456
474
  const contextWithUser = {
457
- ...baseContext,
475
+ ...baseContextWithOrg,
458
476
  userUuid: 'user-456',
459
477
  };
460
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
478
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
479
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
461
480
  ...contextWithUser,
462
481
  scopes: ['view:dashboard'],
463
- });
482
+ }, builder);
483
+ const ability = builder.build();
464
484
  // Can view private dashboard with viewer access
465
485
  expect(ability.can('view', (0, ability_1.subject)('Dashboard', {
466
486
  organizationUuid: 'org-123',
@@ -492,13 +512,15 @@ describe('scopeAbilityBuilder', () => {
492
512
  });
493
513
  it('should handle editor role for managing resources', () => {
494
514
  const contextWithUser = {
495
- ...baseContext,
515
+ ...baseContextWithOrg,
496
516
  userUuid: 'user-456',
497
517
  };
498
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
518
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
519
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
499
520
  ...contextWithUser,
500
521
  scopes: ['manage:dashboard'],
501
- });
522
+ }, builder);
523
+ const ability = builder.build();
502
524
  // Can manage dashboard with editor role
503
525
  expect(ability.can('manage', (0, ability_1.subject)('Dashboard', {
504
526
  organizationUuid: 'org-123',
@@ -540,13 +562,15 @@ describe('scopeAbilityBuilder', () => {
540
562
  ...baseContext,
541
563
  userUuid: 'user-456',
542
564
  };
543
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
565
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
566
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
544
567
  ...contextWithUser,
545
- scopes: ['manage:space'],
546
- });
568
+ scopes: ['manage:space@assigned'],
569
+ }, builder);
570
+ const ability = builder.build();
547
571
  // Can manage space with admin role
548
572
  expect(ability.can('manage', (0, ability_1.subject)('Space', {
549
- organizationUuid: 'org-123',
573
+ projectUuid: baseContext.projectUuid,
550
574
  access: [
551
575
  {
552
576
  userUuid: 'user-456',
@@ -584,10 +608,12 @@ describe('scopeAbilityBuilder', () => {
584
608
  ...baseContext,
585
609
  userUuid: 'user-456',
586
610
  };
587
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
611
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
612
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
588
613
  ...contextWithUser,
589
614
  scopes: ['view:job'],
590
- });
615
+ }, builder);
616
+ const ability = builder.build();
591
617
  // Can view own jobs
592
618
  expect(ability.can('view', (0, ability_1.subject)('Job', {
593
619
  userUuid: 'user-456',
@@ -598,14 +624,12 @@ describe('scopeAbilityBuilder', () => {
598
624
  }))).toBe(false);
599
625
  });
600
626
  it('should handle view:job_status permissions for organization context', () => {
601
- const contextWithoutUser = {
627
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
628
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
602
629
  ...baseContext,
603
- userUuid: undefined,
604
- };
605
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
606
- ...contextWithoutUser,
607
630
  scopes: ['view:job_status'],
608
- });
631
+ }, builder);
632
+ const ability = builder.build();
609
633
  // Cannot view job status without user context when no manage:Organization scope
610
634
  expect(ability.can('view', (0, ability_1.subject)('JobStatus', {
611
635
  organizationUuid: 'org-123',
@@ -616,14 +640,12 @@ describe('scopeAbilityBuilder', () => {
616
640
  }))).toBe(false);
617
641
  });
618
642
  it('should handle view:job_status permissions with manage:Organization scope', () => {
619
- const contextWithoutUser = {
620
- ...baseContext,
621
- userUuid: undefined,
622
- };
623
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
624
- ...contextWithoutUser,
643
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
644
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
645
+ ...baseContextWithOrg,
625
646
  scopes: ['view:job_status', 'manage:organization'],
626
- });
647
+ }, builder);
648
+ const ability = builder.build();
627
649
  // Can view all job status in organization when manage:Organization scope is present
628
650
  expect(ability.can('view', (0, ability_1.subject)('JobStatus', {
629
651
  organizationUuid: 'org-123',
@@ -638,10 +660,12 @@ describe('scopeAbilityBuilder', () => {
638
660
  ...baseContext,
639
661
  userUuid: 'user-456',
640
662
  };
641
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
663
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
664
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
642
665
  ...contextWithUser,
643
666
  scopes: ['view:job_status'],
644
- });
667
+ }, builder);
668
+ const ability = builder.build();
645
669
  // Can view own job status
646
670
  expect(ability.can('view', (0, ability_1.subject)('JobStatus', {
647
671
  createdByUserUuid: 'user-456',
@@ -654,10 +678,12 @@ describe('scopeAbilityBuilder', () => {
654
678
  });
655
679
  describe('semantic viewer permissions', () => {
656
680
  it('should handle view:semantic_viewer permissions', () => {
657
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
681
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
682
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
658
683
  ...baseContext,
659
684
  scopes: ['view:semantic_viewer'],
660
- });
685
+ }, builder);
686
+ const ability = builder.build();
661
687
  expect(ability.can('view', (0, ability_1.subject)('SemanticViewer', {
662
688
  organizationUuid: 'org-123',
663
689
  projectUuid: 'project-123',
@@ -665,14 +691,19 @@ describe('scopeAbilityBuilder', () => {
665
691
  });
666
692
  it('should handle manage:semantic_viewer with organization scope', () => {
667
693
  const contextWithOrgManage = {
668
- ...baseContext,
694
+ ...baseContextWithOrg,
669
695
  userUuid: 'user-456',
670
696
  scopes: ['manage:organization'],
671
697
  };
672
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
698
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
699
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
673
700
  ...contextWithOrgManage,
674
- scopes: ['manage:organization', 'manage:semantic_viewer'],
675
- });
701
+ scopes: [
702
+ 'manage:organization',
703
+ 'manage:semantic_viewer',
704
+ ],
705
+ }, builder);
706
+ const ability = builder.build();
676
707
  // Can manage semantic viewer organization-wide
677
708
  expect(ability.can('manage', (0, ability_1.subject)('SemanticViewer', {
678
709
  organizationUuid: 'org-123',
@@ -680,13 +711,15 @@ describe('scopeAbilityBuilder', () => {
680
711
  });
681
712
  it('should handle manage:semantic_viewer with editor role', () => {
682
713
  const contextWithUser = {
683
- ...baseContext,
714
+ ...baseContextWithOrg,
684
715
  userUuid: 'user-456',
685
716
  };
686
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
717
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
718
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
687
719
  ...contextWithUser,
688
720
  scopes: ['manage:semantic_viewer'],
689
- });
721
+ }, builder);
722
+ const ability = builder.build();
690
723
  // Can manage semantic viewer with editor role
691
724
  expect(ability.can('manage', (0, ability_1.subject)('SemanticViewer', {
692
725
  organizationUuid: 'org-123',
@@ -711,10 +744,12 @@ describe('scopeAbilityBuilder', () => {
711
744
  });
712
745
  describe('create space permissions', () => {
713
746
  it('should handle create:space permissions', () => {
714
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
747
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
748
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
715
749
  ...baseContext,
716
750
  scopes: ['create:space'],
717
- });
751
+ }, builder);
752
+ const ability = builder.build();
718
753
  expect(ability.can('create', (0, ability_1.subject)('Space', {
719
754
  organizationUuid: 'org-123',
720
755
  projectUuid: 'project-123',
@@ -723,20 +758,24 @@ describe('scopeAbilityBuilder', () => {
723
758
  });
724
759
  describe('export permissions', () => {
725
760
  it('should handle export csv permissions', () => {
726
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
761
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
762
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
727
763
  ...baseContext,
728
764
  scopes: ['manage:export_csv'],
729
- });
765
+ }, builder);
766
+ const ability = builder.build();
730
767
  expect(ability.can('manage', (0, ability_1.subject)('ExportCsv', {
731
768
  organizationUuid: 'org-123',
732
769
  projectUuid: 'project-123',
733
770
  }))).toBe(true);
734
771
  });
735
772
  it('should handle change csv results permissions', () => {
736
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
773
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
774
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
737
775
  ...baseContext,
738
776
  scopes: ['manage:change_csv_results'],
739
- });
777
+ }, builder);
778
+ const ability = builder.build();
740
779
  expect(ability.can('manage', (0, ability_1.subject)('ChangeCsvResults', {
741
780
  organizationUuid: 'org-123',
742
781
  projectUuid: 'project-123',
@@ -745,10 +784,12 @@ describe('scopeAbilityBuilder', () => {
745
784
  });
746
785
  describe('underlying data permissions', () => {
747
786
  it('should handle view:underlying_data permissions', () => {
748
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
787
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
788
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
749
789
  ...baseContext,
750
790
  scopes: ['view:underlying_data'],
751
- });
791
+ }, builder);
792
+ const ability = builder.build();
752
793
  expect(ability.can('view', (0, ability_1.subject)('UnderlyingData', {
753
794
  organizationUuid: 'org-123',
754
795
  projectUuid: 'project-123',
@@ -757,20 +798,24 @@ describe('scopeAbilityBuilder', () => {
757
798
  });
758
799
  describe('sql runner and custom sql permissions', () => {
759
800
  it('should handle manage:sql_runner permissions', () => {
760
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
801
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
802
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
761
803
  ...baseContext,
762
804
  scopes: ['manage:sql_runner'],
763
- });
805
+ }, builder);
806
+ const ability = builder.build();
764
807
  expect(ability.can('manage', (0, ability_1.subject)('SqlRunner', {
765
808
  organizationUuid: 'org-123',
766
809
  projectUuid: 'project-123',
767
810
  }))).toBe(true);
768
811
  });
769
812
  it('should handle manage:custom_sql permissions', () => {
770
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
813
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
814
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
771
815
  ...baseContext,
772
816
  scopes: ['manage:custom_sql'],
773
- });
817
+ }, builder);
818
+ const ability = builder.build();
774
819
  expect(ability.can('manage', (0, ability_1.subject)('CustomSql', {
775
820
  organizationUuid: 'org-123',
776
821
  projectUuid: 'project-123',
@@ -778,33 +823,31 @@ describe('scopeAbilityBuilder', () => {
778
823
  });
779
824
  });
780
825
  describe('project delete permissions', () => {
781
- it('should handle delete:project with projectUuid', () => {
782
- const contextWithProject = {
826
+ it('should handle delete:project@self permissions', () => {
827
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
828
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
783
829
  ...baseContext,
784
- projectUuid: 'project-456',
785
- };
786
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
787
- ...contextWithProject,
788
- scopes: ['delete:project'],
789
- });
830
+ userUuid: 'user-456',
831
+ scopes: ['delete:project@self'],
832
+ }, builder);
833
+ const ability = builder.build();
790
834
  // Can delete specific project
791
835
  expect(ability.can('delete', (0, ability_1.subject)('Project', {
792
- projectUuid: 'project-456',
836
+ createdByUserUuid: 'user-456',
837
+ type: projects_1.ProjectType.PREVIEW,
793
838
  }))).toBe(true);
794
- // Cannot delete different project
795
839
  expect(ability.can('delete', (0, ability_1.subject)('Project', {
796
- projectUuid: 'different-project',
840
+ createdByUserUuid: 'different-user',
841
+ type: projects_1.ProjectType.PREVIEW,
797
842
  }))).toBe(false);
798
843
  });
799
844
  it('should handle delete:project for preview projects', () => {
800
- const contextWithoutProject = {
801
- ...baseContext,
802
- projectUuid: '',
803
- };
804
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
805
- ...contextWithoutProject,
845
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
846
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
847
+ ...baseContextWithOrg,
806
848
  scopes: ['delete:project'],
807
- });
849
+ }, builder);
850
+ const ability = builder.build();
808
851
  // Can delete preview projects in organization
809
852
  expect(ability.can('delete', (0, ability_1.subject)('Project', {
810
853
  organizationUuid: 'org-123',
@@ -819,20 +862,24 @@ describe('scopeAbilityBuilder', () => {
819
862
  });
820
863
  describe('pinned items permissions', () => {
821
864
  it('should handle view:pinned_items permissions', () => {
822
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
865
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
866
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
823
867
  ...baseContext,
824
868
  scopes: ['view:pinned_items'],
825
- });
869
+ }, builder);
870
+ const ability = builder.build();
826
871
  expect(ability.can('view', (0, ability_1.subject)('PinnedItems', {
827
872
  organizationUuid: 'org-123',
828
873
  projectUuid: 'project-123',
829
874
  }))).toBe(true);
830
875
  });
831
876
  it('should handle manage:pinned_items permissions', () => {
832
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
877
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
878
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
833
879
  ...baseContext,
834
880
  scopes: ['manage:pinned_items'],
835
- });
881
+ }, builder);
882
+ const ability = builder.build();
836
883
  expect(ability.can('manage', (0, ability_1.subject)('PinnedItems', {
837
884
  organizationUuid: 'org-123',
838
885
  projectUuid: 'project-123',
@@ -841,10 +888,12 @@ describe('scopeAbilityBuilder', () => {
841
888
  });
842
889
  describe('explore permissions', () => {
843
890
  it('should handle manage:explore permissions', () => {
844
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
891
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
892
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
845
893
  ...baseContext,
846
894
  scopes: ['manage:explore'],
847
- });
895
+ }, builder);
896
+ const ability = builder.build();
848
897
  expect(ability.can('manage', (0, ability_1.subject)('Explore', {
849
898
  organizationUuid: 'org-123',
850
899
  projectUuid: 'project-123',
@@ -853,10 +902,12 @@ describe('scopeAbilityBuilder', () => {
853
902
  });
854
903
  describe('virtual view permissions', () => {
855
904
  it('should handle create:virtual_view permissions', () => {
856
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
905
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
906
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
857
907
  ...baseContext,
858
908
  scopes: ['create:virtual_view'],
859
- });
909
+ }, builder);
910
+ const ability = builder.build();
860
911
  expect(ability.can('create', (0, ability_1.subject)('VirtualView', {
861
912
  organizationUuid: 'org-123',
862
913
  projectUuid: 'project-123',
@@ -868,10 +919,12 @@ describe('scopeAbilityBuilder', () => {
868
919
  }))).toBe(false);
869
920
  });
870
921
  it('should handle delete:virtual_view permissions', () => {
871
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
922
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
923
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
872
924
  ...baseContext,
873
925
  scopes: ['delete:virtual_view'],
874
- });
926
+ }, builder);
927
+ const ability = builder.build();
875
928
  expect(ability.can('delete', (0, ability_1.subject)('VirtualView', {
876
929
  organizationUuid: 'org-123',
877
930
  projectUuid: 'project-123',
@@ -883,10 +936,12 @@ describe('scopeAbilityBuilder', () => {
883
936
  }))).toBe(false);
884
937
  });
885
938
  it('should handle manage:virtual_view permissions for both create and delete', () => {
886
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
939
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
940
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
887
941
  ...baseContext,
888
942
  scopes: ['manage:virtual_view'],
889
- });
943
+ }, builder);
944
+ const ability = builder.build();
890
945
  // Should be able to manage (create and delete)
891
946
  expect(ability.can('manage', (0, ability_1.subject)('VirtualView', {
892
947
  organizationUuid: 'org-123',
@@ -903,14 +958,16 @@ describe('scopeAbilityBuilder', () => {
903
958
  }))).toBe(true);
904
959
  });
905
960
  it('should not allow virtual view actions for different organizations', () => {
906
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
907
- ...baseContext,
961
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
962
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
963
+ ...baseContextWithOrg,
908
964
  scopes: [
909
965
  'create:virtual_view',
910
966
  'delete:virtual_view',
911
967
  'manage:virtual_view',
912
968
  ],
913
- });
969
+ }, builder);
970
+ const ability = builder.build();
914
971
  // Should not access virtual views from different org
915
972
  expect(ability.can('create', (0, ability_1.subject)('VirtualView', {
916
973
  organizationUuid: 'different-org',
@@ -926,14 +983,16 @@ describe('scopeAbilityBuilder', () => {
926
983
  }))).toBe(false);
927
984
  });
928
985
  it('should allow virtual view actions for different projects within same organization', () => {
929
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
930
- ...baseContext,
986
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
987
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
988
+ ...baseContextWithOrg,
931
989
  scopes: [
932
990
  'create:virtual_view',
933
991
  'delete:virtual_view',
934
992
  'manage:virtual_view',
935
993
  ],
936
- });
994
+ }, builder);
995
+ const ability = builder.build();
937
996
  // Virtual view permissions are organization-scoped, not project-scoped
938
997
  // So they should work across different projects within the same org
939
998
  expect(ability.can('create', (0, ability_1.subject)('VirtualView', {
@@ -952,25 +1011,29 @@ describe('scopeAbilityBuilder', () => {
952
1011
  });
953
1012
  describe('organization member profile permissions', () => {
954
1013
  it('should handle view:organization_member_profile permissions', () => {
955
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
956
- ...baseContext,
1014
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
1015
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
1016
+ ...baseContextWithOrg,
957
1017
  scopes: ['view:organization_member_profile'],
958
- });
1018
+ }, builder);
1019
+ const ability = builder.build();
959
1020
  expect(ability.can('view', (0, ability_1.subject)('OrganizationMemberProfile', {
960
1021
  organizationUuid: 'org-123',
961
1022
  projectUuid: 'project-123',
962
1023
  }))).toBe(true);
963
- // Cannot view profiles from different organization
1024
+ // Cannot view profiles from different project
964
1025
  expect(ability.can('view', (0, ability_1.subject)('OrganizationMemberProfile', {
965
1026
  organizationUuid: 'different-org',
966
1027
  projectUuid: 'project-123',
967
1028
  }))).toBe(false);
968
1029
  });
969
1030
  it('should handle manage:organization_member_profile permissions', () => {
970
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
1031
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
1032
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
971
1033
  ...baseContext,
972
1034
  scopes: ['manage:organization_member_profile'],
973
- });
1035
+ }, builder);
1036
+ const ability = builder.build();
974
1037
  expect(ability.can('manage', (0, ability_1.subject)('OrganizationMemberProfile', {
975
1038
  organizationUuid: 'org-123',
976
1039
  projectUuid: 'project-123',
@@ -979,7 +1042,8 @@ describe('scopeAbilityBuilder', () => {
979
1042
  });
980
1043
  describe('personal access token permissions', () => {
981
1044
  it('should allow managing PAT when enabled and user has allowed role', () => {
982
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
1045
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
1046
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
983
1047
  ...baseContext,
984
1048
  isEnterprise: true,
985
1049
  organizationRole: 'admin',
@@ -990,11 +1054,13 @@ describe('scopeAbilityBuilder', () => {
990
1054
  allowedOrgRoles: ['admin', 'developer'],
991
1055
  },
992
1056
  },
993
- });
1057
+ }, builder);
1058
+ const ability = builder.build();
994
1059
  expect(ability.can('manage', 'PersonalAccessToken')).toBe(true);
995
1060
  });
996
1061
  it('should not allow managing PAT when disabled', () => {
997
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
1062
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
1063
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
998
1064
  ...baseContext,
999
1065
  isEnterprise: true,
1000
1066
  organizationRole: 'admin',
@@ -1005,11 +1071,13 @@ describe('scopeAbilityBuilder', () => {
1005
1071
  allowedOrgRoles: ['admin', 'developer'],
1006
1072
  },
1007
1073
  },
1008
- });
1074
+ }, builder);
1075
+ const ability = builder.build();
1009
1076
  expect(ability.can('manage', (0, ability_1.subject)('PersonalAccessToken', {}))).toBe(false);
1010
1077
  });
1011
1078
  it('should not allow managing PAT when user role not in allowed roles', () => {
1012
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
1079
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
1080
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
1013
1081
  ...baseContext,
1014
1082
  isEnterprise: true,
1015
1083
  organizationRole: 'developer',
@@ -1020,21 +1088,25 @@ describe('scopeAbilityBuilder', () => {
1020
1088
  allowedOrgRoles: ['admin'],
1021
1089
  },
1022
1090
  },
1023
- });
1091
+ }, builder);
1092
+ const ability = builder.build();
1024
1093
  expect(ability.can('manage', (0, ability_1.subject)('PersonalAccessToken', {}))).toBe(false);
1025
1094
  });
1026
1095
  it('should not allow managing PAT when no permissions config provided', () => {
1027
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
1096
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
1097
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
1028
1098
  ...baseContext,
1029
1099
  isEnterprise: true,
1030
1100
  organizationRole: 'admin',
1031
1101
  scopes: ['manage:personal_access_token'],
1032
1102
  // No permissionsConfig provided
1033
- });
1103
+ }, builder);
1104
+ const ability = builder.build();
1034
1105
  expect(ability.can('manage', (0, ability_1.subject)('PersonalAccessToken', {}))).toBe(false);
1035
1106
  });
1036
1107
  it('should not allow managing PAT when no organization role provided', () => {
1037
- const ability = (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
1108
+ const builder = new ability_1.AbilityBuilder(ability_1.Ability);
1109
+ (0, scopeAbilityBuilder_1.buildAbilityFromScopes)({
1038
1110
  ...baseContext,
1039
1111
  isEnterprise: true,
1040
1112
  organizationRole: '', // Empty organization role
@@ -1045,7 +1117,8 @@ describe('scopeAbilityBuilder', () => {
1045
1117
  allowedOrgRoles: ['admin', 'developer'],
1046
1118
  },
1047
1119
  },
1048
- });
1120
+ }, builder);
1121
+ const ability = builder.build();
1049
1122
  expect(ability.can('manage', (0, ability_1.subject)('PersonalAccessToken', {}))).toBe(false);
1050
1123
  });
1051
1124
  });