Skip to content

Commit cfad951

Browse files
committed
feat: add comprehensive tests for useScopePermissions hook with various context scenarios
1 parent 560cf25 commit cfad951

1 file changed

Lines changed: 355 additions & 0 deletions

File tree

Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
import { renderHook } from '@testing-library/react';
2+
import { useValidateUserPermissions } from '@src/data/hooks';
3+
import { useOrganizations } from '../data/hooks';
4+
import { COURSE_PERMISSIONS } from '../constants';
5+
import { CONTENT_LIBRARY_PERMISSIONS } from '../roles-permissions/libraries/constants';
6+
import useScopePermissions from './useScopePermissions';
7+
8+
jest.mock('@src/data/hooks', () => ({
9+
useValidateUserPermissions: jest.fn(),
10+
}));
11+
12+
jest.mock('../data/hooks', () => ({
13+
useOrganizations: jest.fn(),
14+
}));
15+
16+
const mockUseValidateUserPermissions = useValidateUserPermissions as jest.Mock;
17+
const mockUseOrganizations = useOrganizations as jest.Mock;
18+
19+
const defaultOrgs = [
20+
{
21+
id: 1, name: 'Organization One', shortName: 'org1', description: '', logo: null, active: true,
22+
},
23+
{
24+
id: 2, name: 'Organization Two', shortName: 'org2', description: '', logo: null, active: true,
25+
},
26+
];
27+
28+
const makeAllowed = (allowed: boolean) => ({ data: [{ allowed }] });
29+
const makeMultiAllowed = (values: boolean[]) => ({ data: values.map((allowed) => ({ allowed })) });
30+
31+
describe('useScopePermissions', () => {
32+
beforeEach(() => {
33+
jest.clearAllMocks();
34+
mockUseOrganizations.mockReturnValue({ data: defaultOrgs });
35+
// Default: all permissions denied
36+
mockUseValidateUserPermissions.mockReturnValue({ data: [] });
37+
});
38+
39+
describe('Return value structure', () => {
40+
it('returns hasPlatformPermission and orgHasPermission', () => {
41+
mockUseValidateUserPermissions.mockReturnValue({ data: [] });
42+
43+
const { result } = renderHook(() => useScopePermissions({
44+
contextType: 'course',
45+
orderedOrgs: [],
46+
}));
47+
48+
expect(result.current).toHaveProperty('hasPlatformPermission');
49+
expect(result.current).toHaveProperty('orgHasPermission');
50+
});
51+
});
52+
53+
describe('hasPlatformPermission for course context', () => {
54+
it('is true when all org course permissions are allowed', () => {
55+
// First call: course platform perms (one per org), Second: library platform perms, Third: org perms
56+
mockUseValidateUserPermissions
57+
.mockReturnValueOnce(makeMultiAllowed([true, true])) // course platform
58+
.mockReturnValueOnce(makeMultiAllowed([false, false])) // library platform
59+
.mockReturnValueOnce({ data: [] }); // org perms
60+
61+
const { result } = renderHook(() => useScopePermissions({
62+
contextType: 'course',
63+
orderedOrgs: [],
64+
}));
65+
66+
expect(result.current.hasPlatformPermission).toBe(true);
67+
});
68+
69+
it('is false when any org course permission is denied', () => {
70+
mockUseValidateUserPermissions
71+
.mockReturnValueOnce(makeMultiAllowed([true, false])) // course platform
72+
.mockReturnValueOnce(makeMultiAllowed([true, true])) // library platform
73+
.mockReturnValueOnce({ data: [] });
74+
75+
const { result } = renderHook(() => useScopePermissions({
76+
contextType: 'course',
77+
orderedOrgs: [],
78+
}));
79+
80+
expect(result.current.hasPlatformPermission).toBe(false);
81+
});
82+
83+
it('is false when course platform perms data is empty', () => {
84+
mockUseValidateUserPermissions
85+
.mockReturnValueOnce({ data: [] }) // course platform — empty means every() is vacuously true? No — empty array every() returns true
86+
.mockReturnValueOnce({ data: [] })
87+
.mockReturnValueOnce({ data: [] });
88+
89+
const { result } = renderHook(() => useScopePermissions({
90+
contextType: 'course',
91+
orderedOrgs: [],
92+
}));
93+
94+
// empty array .every() returns true vacuously
95+
expect(result.current.hasPlatformPermission).toBe(true);
96+
});
97+
98+
it('is false when course platform perms data is undefined', () => {
99+
mockUseValidateUserPermissions
100+
.mockReturnValueOnce({ data: undefined }) // course platform
101+
.mockReturnValueOnce({ data: undefined }) // library platform
102+
.mockReturnValueOnce({ data: undefined }); // org perms
103+
104+
const { result } = renderHook(() => useScopePermissions({
105+
contextType: 'course',
106+
orderedOrgs: [],
107+
}));
108+
109+
expect(result.current.hasPlatformPermission).toBe(false);
110+
});
111+
});
112+
113+
describe('hasPlatformPermission for library context', () => {
114+
it('is true when all org library permissions are allowed', () => {
115+
mockUseValidateUserPermissions
116+
.mockReturnValueOnce(makeMultiAllowed([false, false])) // course platform
117+
.mockReturnValueOnce(makeMultiAllowed([true, true])) // library platform
118+
.mockReturnValueOnce({ data: [] });
119+
120+
const { result } = renderHook(() => useScopePermissions({
121+
contextType: 'library',
122+
orderedOrgs: [],
123+
}));
124+
125+
expect(result.current.hasPlatformPermission).toBe(true);
126+
});
127+
128+
it('is false when any org library permission is denied', () => {
129+
mockUseValidateUserPermissions
130+
.mockReturnValueOnce(makeMultiAllowed([true, true])) // course platform
131+
.mockReturnValueOnce(makeMultiAllowed([true, false])) // library platform
132+
.mockReturnValueOnce({ data: [] });
133+
134+
const { result } = renderHook(() => useScopePermissions({
135+
contextType: 'library',
136+
orderedOrgs: [],
137+
}));
138+
139+
expect(result.current.hasPlatformPermission).toBe(false);
140+
});
141+
142+
it('is false when library platform perms data is undefined', () => {
143+
mockUseValidateUserPermissions
144+
.mockReturnValueOnce({ data: undefined })
145+
.mockReturnValueOnce({ data: undefined })
146+
.mockReturnValueOnce({ data: undefined });
147+
148+
const { result } = renderHook(() => useScopePermissions({
149+
contextType: 'library',
150+
orderedOrgs: [],
151+
}));
152+
153+
expect(result.current.hasPlatformPermission).toBe(false);
154+
});
155+
});
156+
157+
describe('hasPlatformPermission with undefined contextType', () => {
158+
it('uses library branch (non-course) for undefined contextType', () => {
159+
mockUseValidateUserPermissions
160+
.mockReturnValueOnce(makeMultiAllowed([true, true])) // course platform (ignored)
161+
.mockReturnValueOnce(makeMultiAllowed([true, true])) // library platform
162+
.mockReturnValueOnce({ data: [] });
163+
164+
const { result } = renderHook(() => useScopePermissions({
165+
contextType: undefined,
166+
orderedOrgs: [],
167+
}));
168+
169+
// contextType !== 'course' so library branch is used
170+
expect(result.current.hasPlatformPermission).toBe(true);
171+
});
172+
});
173+
174+
describe('orgHasPermission map', () => {
175+
it('maps each org to its allowed value for course context', () => {
176+
mockUseValidateUserPermissions
177+
.mockReturnValueOnce({ data: [] }) // course platform
178+
.mockReturnValueOnce({ data: [] }) // library platform
179+
.mockReturnValueOnce(makeMultiAllowed([true, false])); // org perms for [org1, org2]
180+
181+
const { result } = renderHook(() => useScopePermissions({
182+
contextType: 'course',
183+
orderedOrgs: ['org1', 'org2'],
184+
}));
185+
186+
expect(result.current.orgHasPermission).toEqual({ org1: true, org2: false });
187+
});
188+
189+
it('maps each org to its allowed value for library context', () => {
190+
mockUseValidateUserPermissions
191+
.mockReturnValueOnce({ data: [] })
192+
.mockReturnValueOnce({ data: [] })
193+
.mockReturnValueOnce(makeMultiAllowed([false, true])); // org perms for [org1, org2]
194+
195+
const { result } = renderHook(() => useScopePermissions({
196+
contextType: 'library',
197+
orderedOrgs: ['org1', 'org2'],
198+
}));
199+
200+
expect(result.current.orgHasPermission).toEqual({ org1: false, org2: true });
201+
});
202+
203+
it('returns empty map when orderedOrgs is empty', () => {
204+
mockUseValidateUserPermissions.mockReturnValue({ data: [] });
205+
206+
const { result } = renderHook(() => useScopePermissions({
207+
contextType: 'course',
208+
orderedOrgs: [],
209+
}));
210+
211+
expect(result.current.orgHasPermission).toEqual({});
212+
});
213+
214+
it('defaults org to false when orgPerms data is undefined', () => {
215+
mockUseValidateUserPermissions
216+
.mockReturnValueOnce({ data: [] })
217+
.mockReturnValueOnce({ data: [] })
218+
.mockReturnValueOnce({ data: undefined });
219+
220+
const { result } = renderHook(() => useScopePermissions({
221+
contextType: 'course',
222+
orderedOrgs: ['org1', 'org2'],
223+
}));
224+
225+
expect(result.current.orgHasPermission).toEqual({ org1: false, org2: false });
226+
});
227+
228+
it('handles single org', () => {
229+
mockUseValidateUserPermissions
230+
.mockReturnValueOnce({ data: [] })
231+
.mockReturnValueOnce({ data: [] })
232+
.mockReturnValueOnce(makeAllowed(true));
233+
234+
const { result } = renderHook(() => useScopePermissions({
235+
contextType: 'library',
236+
orderedOrgs: ['org1'],
237+
}));
238+
239+
expect(result.current.orgHasPermission).toEqual({ org1: true });
240+
});
241+
});
242+
243+
describe('Permission request construction', () => {
244+
it('builds course platform permission requests with correct action and scope', () => {
245+
mockUseValidateUserPermissions.mockReturnValue({ data: [] });
246+
247+
renderHook(() => useScopePermissions({
248+
contextType: 'course',
249+
orderedOrgs: ['org1'],
250+
}));
251+
252+
const courseCallArgs = mockUseValidateUserPermissions.mock.calls[0][0];
253+
expect(courseCallArgs).toEqual(expect.arrayContaining([
254+
expect.objectContaining({
255+
action: COURSE_PERMISSIONS.MANAGE_COURSE_TEAM,
256+
scope: 'course-v1:org1+*',
257+
}),
258+
]));
259+
});
260+
261+
it('builds library platform permission requests with correct action and scope', () => {
262+
mockUseValidateUserPermissions.mockReturnValue({ data: [] });
263+
264+
renderHook(() => useScopePermissions({
265+
contextType: 'library',
266+
orderedOrgs: [],
267+
}));
268+
269+
const libraryCallArgs = mockUseValidateUserPermissions.mock.calls[1][0];
270+
expect(libraryCallArgs).toEqual(expect.arrayContaining([
271+
expect.objectContaining({
272+
action: CONTENT_LIBRARY_PERMISSIONS.MANAGE_LIBRARY_TEAM,
273+
scope: 'lib:org1:*',
274+
}),
275+
]));
276+
});
277+
278+
it('builds org permission requests using course scope when contextType is course', () => {
279+
mockUseValidateUserPermissions.mockReturnValue({ data: [] });
280+
281+
renderHook(() => useScopePermissions({
282+
contextType: 'course',
283+
orderedOrgs: ['myorg'],
284+
}));
285+
286+
const orgCallArgs = mockUseValidateUserPermissions.mock.calls[2][0];
287+
expect(orgCallArgs).toEqual([
288+
{
289+
action: COURSE_PERMISSIONS.MANAGE_COURSE_TEAM,
290+
scope: 'course-v1:myorg+*',
291+
},
292+
]);
293+
});
294+
295+
it('builds org permission requests using library scope when contextType is library', () => {
296+
mockUseValidateUserPermissions.mockReturnValue({ data: [] });
297+
298+
renderHook(() => useScopePermissions({
299+
contextType: 'library',
300+
orderedOrgs: ['myorg'],
301+
}));
302+
303+
const orgCallArgs = mockUseValidateUserPermissions.mock.calls[2][0];
304+
expect(orgCallArgs).toEqual([
305+
{
306+
action: CONTENT_LIBRARY_PERMISSIONS.MANAGE_LIBRARY_TEAM,
307+
scope: 'lib:myorg:*',
308+
},
309+
]);
310+
});
311+
312+
it('returns empty org permission request list when orderedOrgs is empty', () => {
313+
mockUseValidateUserPermissions.mockReturnValue({ data: [] });
314+
315+
renderHook(() => useScopePermissions({
316+
contextType: 'course',
317+
orderedOrgs: [],
318+
}));
319+
320+
const orgCallArgs = mockUseValidateUserPermissions.mock.calls[2][0];
321+
expect(orgCallArgs).toEqual([]);
322+
});
323+
});
324+
325+
describe('Edge cases', () => {
326+
it('returns empty platform permission request lists when organizations data is undefined', () => {
327+
mockUseOrganizations.mockReturnValue({ data: undefined });
328+
mockUseValidateUserPermissions.mockReturnValue({ data: [] });
329+
330+
const { result } = renderHook(() => useScopePermissions({
331+
contextType: 'course',
332+
orderedOrgs: [],
333+
}));
334+
335+
// With no organizations, both platform perm request arrays are [] — every() on [] is vacuously true
336+
expect(result.current.hasPlatformPermission).toBe(true);
337+
});
338+
339+
it('handles all orgs allowed in orgHasPermission with three orgs', () => {
340+
mockUseValidateUserPermissions
341+
.mockReturnValueOnce({ data: [] })
342+
.mockReturnValueOnce({ data: [] })
343+
.mockReturnValueOnce(makeMultiAllowed([true, true, true]));
344+
345+
const { result } = renderHook(() => useScopePermissions({
346+
contextType: 'course',
347+
orderedOrgs: ['org1', 'org2', 'org3'],
348+
}));
349+
350+
expect(result.current.orgHasPermission).toEqual({
351+
org1: true, org2: true, org3: true,
352+
});
353+
});
354+
});
355+
});

0 commit comments

Comments
 (0)