@@ -37,6 +37,7 @@ describe("PermissionCheckService", () => {
3737 getMembershipByMembershipId : vi . fn ( ) ,
3838 getMembershipByUserAndTeam : vi . fn ( ) ,
3939 getOrgMembership : vi . fn ( ) ,
40+ getTeamById : vi . fn ( ) ,
4041 getUserMemberships : vi . fn ( ) ,
4142 checkRolePermission : vi . fn ( ) ,
4243 checkRolePermissions : vi . fn ( ) ,
@@ -67,28 +68,14 @@ describe("PermissionCheckService", () => {
6768
6869 describe ( "checkPermission" , ( ) => {
6970 it ( "should check permission with PBAC enabled" , async ( ) => {
70- const membership = {
71+ mockFeaturesRepository . checkIfTeamHasFeature . mockResolvedValueOnce ( true ) ;
72+ mockRepository . getMembershipByUserAndTeam . mockResolvedValueOnce ( {
7173 id : 1 ,
7274 teamId : 1 ,
7375 userId : 1 ,
74- accepted : true ,
75- role : "ADMIN" as MembershipRole ,
7676 customRoleId : "admin_role" ,
77- disableImpersonation : false ,
78- createdAt : new Date ( ) ,
79- updatedAt : new Date ( ) ,
80- } ;
81-
82- ( MembershipRepository . findUniqueByUserIdAndTeamId as Mock ) . mockResolvedValueOnce ( membership ) ;
83- mockFeaturesRepository . checkIfTeamHasFeature . mockResolvedValueOnce ( true ) ;
84- mockRepository . getMembershipByMembershipId . mockResolvedValueOnce ( {
85- id : membership . id ,
86- teamId : membership . teamId ,
87- userId : membership . userId ,
88- customRoleId : membership . customRoleId ,
8977 team : { parentId : null } ,
9078 } ) ;
91- mockRepository . getOrgMembership . mockResolvedValueOnce ( null ) ;
9279 mockRepository . checkRolePermission . mockResolvedValueOnce ( true ) ;
9380
9481 const result = await service . checkPermission ( {
@@ -99,12 +86,8 @@ describe("PermissionCheckService", () => {
9986 } ) ;
10087
10188 expect ( result ) . toBe ( true ) ;
102- expect ( MembershipRepository . findUniqueByUserIdAndTeamId ) . toHaveBeenCalledWith ( {
103- userId : 1 ,
104- teamId : 1 ,
105- } ) ;
10689 expect ( mockFeaturesRepository . checkIfTeamHasFeature ) . toHaveBeenCalledWith ( 1 , "pbac" ) ;
107- expect ( mockRepository . getMembershipByMembershipId ) . toHaveBeenCalledWith ( 1 ) ;
90+ expect ( mockRepository . getMembershipByUserAndTeam ) . toHaveBeenCalledWith ( 1 , 1 ) ;
10891 expect ( mockRepository . checkRolePermission ) . toHaveBeenCalledWith ( "admin_role" , "eventType.create" ) ;
10992 } ) ;
11093
@@ -140,7 +123,8 @@ describe("PermissionCheckService", () => {
140123 expect ( mockRepository . checkRolePermission ) . not . toHaveBeenCalled ( ) ;
141124 } ) ;
142125
143- it ( "should return false if membership not found" , async ( ) => {
126+ it ( "should return false if membership not found when PBAC disabled" , async ( ) => {
127+ mockFeaturesRepository . checkIfTeamHasFeature . mockResolvedValueOnce ( false ) ;
144128 ( MembershipRepository . findUniqueByUserIdAndTeamId as Mock ) . mockResolvedValueOnce ( null ) ;
145129
146130 const result = await service . checkPermission ( {
@@ -153,21 +137,47 @@ describe("PermissionCheckService", () => {
153137 expect ( result ) . toBe ( false ) ;
154138 } ) ;
155139
156- it ( "should return false if PBAC enabled but no customRoleId" , async ( ) => {
157- const membership = {
140+ it ( "should check org-level permissions when user has no team membership but PBAC is enabled" , async ( ) => {
141+ mockFeaturesRepository . checkIfTeamHasFeature . mockResolvedValueOnce ( true ) ;
142+ mockRepository . getMembershipByUserAndTeam . mockResolvedValueOnce ( null ) ;
143+ mockRepository . getTeamById . mockResolvedValueOnce ( { id : 1 , parentId : 2 } ) ;
144+ mockRepository . getOrgMembership . mockResolvedValueOnce ( {
145+ id : 100 ,
146+ teamId : 2 ,
147+ userId : 1 ,
148+ customRoleId : "org_admin_role" ,
149+ } ) ;
150+ mockRepository . checkRolePermission . mockResolvedValueOnce ( true ) ;
151+
152+ const result = await service . checkPermission ( {
153+ userId : 1 ,
154+ teamId : 1 ,
155+ permission : "eventType.create" ,
156+ fallbackRoles : [ "ADMIN" , "OWNER" ] ,
157+ } ) ;
158+
159+ expect ( result ) . toBe ( true ) ;
160+ expect ( mockRepository . getMembershipByUserAndTeam ) . toHaveBeenCalledWith ( 1 , 1 ) ;
161+ expect ( mockRepository . getTeamById ) . toHaveBeenCalledWith ( 1 ) ;
162+ expect ( mockRepository . getOrgMembership ) . toHaveBeenCalledWith ( 1 , 2 ) ;
163+ expect ( mockRepository . checkRolePermission ) . toHaveBeenCalledWith ( "org_admin_role" , "eventType.create" ) ;
164+ } ) ;
165+
166+ it ( "should return false if PBAC enabled but no customRoleId on team or org" , async ( ) => {
167+ mockFeaturesRepository . checkIfTeamHasFeature . mockResolvedValueOnce ( true ) ;
168+ mockRepository . getMembershipByUserAndTeam . mockResolvedValueOnce ( {
158169 id : 1 ,
159170 teamId : 1 ,
160171 userId : 1 ,
161- accepted : true ,
162- role : "ADMIN" as MembershipRole ,
163172 customRoleId : null ,
164- disableImpersonation : false ,
165- createdAt : new Date ( ) ,
166- updatedAt : new Date ( ) ,
167- } ;
168-
169- ( MembershipRepository . findUniqueByUserIdAndTeamId as Mock ) . mockResolvedValueOnce ( membership ) ;
170- mockFeaturesRepository . checkIfTeamHasFeature . mockResolvedValueOnce ( true ) ;
173+ team : { parentId : 2 } ,
174+ } ) ;
175+ mockRepository . getOrgMembership . mockResolvedValueOnce ( {
176+ id : 100 ,
177+ teamId : 2 ,
178+ userId : 1 ,
179+ customRoleId : null ,
180+ } ) ;
171181
172182 const result = await service . checkPermission ( {
173183 userId : 1 ,
@@ -182,28 +192,14 @@ describe("PermissionCheckService", () => {
182192
183193 describe ( "checkPermissions" , ( ) => {
184194 it ( "should check multiple permissions with PBAC enabled" , async ( ) => {
185- const membership = {
195+ mockFeaturesRepository . checkIfTeamHasFeature . mockResolvedValueOnce ( true ) ;
196+ mockRepository . getMembershipByUserAndTeam . mockResolvedValueOnce ( {
186197 id : 1 ,
187198 teamId : 1 ,
188199 userId : 1 ,
189- accepted : true ,
190- role : "ADMIN" as MembershipRole ,
191200 customRoleId : "admin_role" ,
192- disableImpersonation : false ,
193- createdAt : new Date ( ) ,
194- updatedAt : new Date ( ) ,
195- } ;
196-
197- ( MembershipRepository . findUniqueByUserIdAndTeamId as Mock ) . mockResolvedValueOnce ( membership ) ;
198- mockFeaturesRepository . checkIfTeamHasFeature . mockResolvedValueOnce ( true ) ;
199- mockRepository . getMembershipByMembershipId . mockResolvedValueOnce ( {
200- id : membership . id ,
201- teamId : membership . teamId ,
202- userId : membership . userId ,
203- customRoleId : membership . customRoleId ,
204201 team : { parentId : null } ,
205202 } ) ;
206- mockRepository . getOrgMembership . mockResolvedValueOnce ( null ) ;
207203 mockRepository . checkRolePermissions . mockResolvedValueOnce ( true ) ;
208204
209205 const result = await service . checkPermissions ( {
@@ -214,12 +210,8 @@ describe("PermissionCheckService", () => {
214210 } ) ;
215211
216212 expect ( result ) . toBe ( true ) ;
217- expect ( MembershipRepository . findUniqueByUserIdAndTeamId ) . toHaveBeenCalledWith ( {
218- userId : 1 ,
219- teamId : 1 ,
220- } ) ;
221213 expect ( mockFeaturesRepository . checkIfTeamHasFeature ) . toHaveBeenCalledWith ( 1 , "pbac" ) ;
222- expect ( mockRepository . getMembershipByMembershipId ) . toHaveBeenCalledWith ( 1 ) ;
214+ expect ( mockRepository . getMembershipByUserAndTeam ) . toHaveBeenCalledWith ( 1 , 1 ) ;
223215 expect ( mockRepository . checkRolePermissions ) . toHaveBeenCalledWith ( "admin_role" , [
224216 "eventType.create" ,
225217 "team.invite" ,
@@ -259,25 +251,12 @@ describe("PermissionCheckService", () => {
259251 } ) ;
260252
261253 it ( "should return false when permissions array is empty" , async ( ) => {
262- const membership = {
254+ mockFeaturesRepository . checkIfTeamHasFeature . mockResolvedValueOnce ( true ) ;
255+ mockRepository . getMembershipByUserAndTeam . mockResolvedValueOnce ( {
263256 id : 1 ,
264257 teamId : 1 ,
265258 userId : 1 ,
266- accepted : true ,
267- role : "MEMBER" as MembershipRole , // Change to MEMBER so fallback also fails
268259 customRoleId : "admin_role" ,
269- disableImpersonation : false ,
270- createdAt : new Date ( ) ,
271- updatedAt : new Date ( ) ,
272- } ;
273-
274- ( MembershipRepository . findUniqueByUserIdAndTeamId as Mock ) . mockResolvedValueOnce ( membership ) ;
275- mockFeaturesRepository . checkIfTeamHasFeature . mockResolvedValueOnce ( true ) ;
276- mockRepository . getMembershipByMembershipId . mockResolvedValueOnce ( {
277- id : membership . id ,
278- teamId : membership . teamId ,
279- userId : membership . userId ,
280- customRoleId : membership . customRoleId ,
281260 team : { parentId : null } ,
282261 } ) ;
283262 mockRepository . getOrgMembership . mockResolvedValueOnce ( null ) ;
@@ -293,6 +272,35 @@ describe("PermissionCheckService", () => {
293272 expect ( result ) . toBe ( false ) ;
294273 expect ( mockRepository . checkRolePermissions ) . toHaveBeenCalledWith ( "admin_role" , [ ] ) ;
295274 } ) ;
275+
276+ it ( "should check org-level permissions when user has no team membership with checkPermissions" , async ( ) => {
277+ mockFeaturesRepository . checkIfTeamHasFeature . mockResolvedValueOnce ( true ) ;
278+ mockRepository . getMembershipByUserAndTeam . mockResolvedValueOnce ( null ) ;
279+ mockRepository . getTeamById . mockResolvedValueOnce ( { id : 1 , parentId : 2 } ) ;
280+ mockRepository . getOrgMembership . mockResolvedValueOnce ( {
281+ id : 100 ,
282+ teamId : 2 ,
283+ userId : 1 ,
284+ customRoleId : "org_admin_role" ,
285+ } ) ;
286+ mockRepository . checkRolePermissions . mockResolvedValueOnce ( true ) ;
287+
288+ const result = await service . checkPermissions ( {
289+ userId : 1 ,
290+ teamId : 1 ,
291+ permissions : [ "eventType.create" , "team.invite" ] ,
292+ fallbackRoles : [ "ADMIN" , "OWNER" ] ,
293+ } ) ;
294+
295+ expect ( result ) . toBe ( true ) ;
296+ expect ( mockRepository . getMembershipByUserAndTeam ) . toHaveBeenCalledWith ( 1 , 1 ) ;
297+ expect ( mockRepository . getTeamById ) . toHaveBeenCalledWith ( 1 ) ;
298+ expect ( mockRepository . getOrgMembership ) . toHaveBeenCalledWith ( 1 , 2 ) ;
299+ expect ( mockRepository . checkRolePermissions ) . toHaveBeenCalledWith ( "org_admin_role" , [
300+ "eventType.create" ,
301+ "team.invite" ,
302+ ] ) ;
303+ } ) ;
296304 } ) ;
297305
298306 describe ( "getUserPermissions" , ( ) => {
@@ -392,6 +400,33 @@ describe("PermissionCheckService", () => {
392400 } ) ;
393401
394402 describe ( "getResourcePermissions" , ( ) => {
403+ it ( "should return org permissions when user has no team membership but has org membership" , async ( ) => {
404+ mockFeaturesRepository . checkIfTeamHasFeature . mockResolvedValueOnce ( true ) ;
405+ mockRepository . getMembershipByUserAndTeam . mockResolvedValueOnce ( null ) ;
406+ mockRepository . getTeamById . mockResolvedValueOnce ( { id : 1 , parentId : 2 } ) ;
407+ mockRepository . getOrgMembership . mockResolvedValueOnce ( {
408+ id : 100 ,
409+ teamId : 2 ,
410+ userId : 1 ,
411+ customRoleId : "org_role" ,
412+ } ) ;
413+ mockRepository . getResourcePermissionsByRoleId . mockResolvedValueOnce ( [ "create" , "read" , "update" ] ) ;
414+
415+ const result = await service . getResourcePermissions ( {
416+ userId : 1 ,
417+ teamId : 1 ,
418+ resource : Resource . EventType ,
419+ } ) ;
420+
421+ expect ( result ) . toEqual ( [ "eventType.create" , "eventType.read" , "eventType.update" ] ) ;
422+ expect ( mockRepository . getMembershipByUserAndTeam ) . toHaveBeenCalledWith ( 1 , 1 ) ;
423+ expect ( mockRepository . getTeamById ) . toHaveBeenCalledWith ( 1 ) ;
424+ expect ( mockRepository . getOrgMembership ) . toHaveBeenCalledWith ( 1 , 2 ) ;
425+ expect ( mockRepository . getResourcePermissionsByRoleId ) . toHaveBeenCalledWith (
426+ "org_role" ,
427+ Resource . EventType
428+ ) ;
429+ } ) ;
395430 it ( "should return empty array when PBAC is disabled" , async ( ) => {
396431 mockFeaturesRepository . checkIfTeamHasFeature . mockResolvedValueOnce ( false ) ;
397432
0 commit comments