@@ -4,7 +4,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
44import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth' ;
55import {
66 useLibrary , usePermissionsByRole , useTeamMembers , useAssignTeamMembersRole , useRevokeUserRoles ,
7- useValidateUsers ,
7+ useValidateUsers , useScopes , useOrganizations , useManagedScopeOrgs ,
88} from './hooks' ;
99
1010jest . mock ( '@edx/frontend-platform/auth' , ( ) => ( {
@@ -290,6 +290,175 @@ describe('useValidateUsers', () => {
290290 } ) ;
291291} ) ;
292292
293+ describe ( 'useScopes' , ( ) => {
294+ beforeEach ( ( ) => {
295+ jest . clearAllMocks ( ) ;
296+ } ) ;
297+
298+ const makeScopesResponse = ( next : string | null = null ) => ( {
299+ results : [ { id : 'lib:123' , name : 'Test Library' , org : 'testorg' , contextType : 'library' } ] ,
300+ count : 1 ,
301+ next,
302+ previous : null ,
303+ } ) ;
304+
305+ it ( 'returns pages data on success' , async ( ) => {
306+ getAuthenticatedHttpClient . mockReturnValue ( {
307+ get : jest . fn ( ) . mockResolvedValue ( { data : makeScopesResponse ( ) } ) ,
308+ } ) ;
309+
310+ const { result } = renderHook ( ( ) => useScopes ( { } ) , { wrapper : createWrapper ( ) } ) ;
311+
312+ await waitFor ( ( ) => expect ( result . current . isSuccess ) . toBe ( true ) ) ;
313+
314+ expect ( result . current . data ?. pages ) . toHaveLength ( 1 ) ;
315+ expect ( result . current . data ?. pages [ 0 ] . results ) . toHaveLength ( 1 ) ;
316+ } ) ;
317+
318+ it ( 'hasNextPage is false when next is null' , async ( ) => {
319+ getAuthenticatedHttpClient . mockReturnValue ( {
320+ get : jest . fn ( ) . mockResolvedValue ( { data : makeScopesResponse ( null ) } ) ,
321+ } ) ;
322+
323+ const { result } = renderHook ( ( ) => useScopes ( { } ) , { wrapper : createWrapper ( ) } ) ;
324+
325+ await waitFor ( ( ) => expect ( result . current . isSuccess ) . toBe ( true ) ) ;
326+ expect ( result . current . hasNextPage ) . toBe ( false ) ;
327+ } ) ;
328+
329+ it ( 'hasNextPage is true when next URL has page param' , async ( ) => {
330+ getAuthenticatedHttpClient . mockReturnValue ( {
331+ get : jest . fn ( ) . mockResolvedValue ( {
332+ data : makeScopesResponse ( 'http://localhost:8000/api/authz/v1/scopes/?page=2' ) ,
333+ } ) ,
334+ } ) ;
335+
336+ const { result } = renderHook ( ( ) => useScopes ( { } ) , { wrapper : createWrapper ( ) } ) ;
337+
338+ await waitFor ( ( ) => expect ( result . current . isSuccess ) . toBe ( true ) ) ;
339+ expect ( result . current . hasNextPage ) . toBe ( true ) ;
340+ } ) ;
341+
342+ it ( 'hasNextPage is false when next URL has no page param' , async ( ) => {
343+ getAuthenticatedHttpClient . mockReturnValue ( {
344+ get : jest . fn ( ) . mockResolvedValue ( {
345+ data : makeScopesResponse ( 'http://localhost:8000/api/authz/v1/scopes/' ) ,
346+ } ) ,
347+ } ) ;
348+
349+ const { result } = renderHook ( ( ) => useScopes ( { } ) , { wrapper : createWrapper ( ) } ) ;
350+
351+ await waitFor ( ( ) => expect ( result . current . isSuccess ) . toBe ( true ) ) ;
352+ expect ( result . current . hasNextPage ) . toBe ( false ) ;
353+ } ) ;
354+
355+ it ( 'hasNextPage is false when next is an invalid URL' , async ( ) => {
356+ getAuthenticatedHttpClient . mockReturnValue ( {
357+ get : jest . fn ( ) . mockResolvedValue ( {
358+ data : makeScopesResponse ( 'not-a-valid-url' ) ,
359+ } ) ,
360+ } ) ;
361+
362+ const { result } = renderHook ( ( ) => useScopes ( { } ) , { wrapper : createWrapper ( ) } ) ;
363+
364+ await waitFor ( ( ) => expect ( result . current . isSuccess ) . toBe ( true ) ) ;
365+ expect ( result . current . hasNextPage ) . toBe ( false ) ;
366+ } ) ;
367+
368+ it ( 'handles error when API call fails' , async ( ) => {
369+ getAuthenticatedHttpClient . mockReturnValue ( {
370+ get : jest . fn ( ) . mockRejectedValue ( new Error ( 'Network error' ) ) ,
371+ } ) ;
372+
373+ const { result } = renderHook ( ( ) => useScopes ( { } ) , { wrapper : createWrapper ( ) } ) ;
374+
375+ await waitFor ( ( ) => expect ( result . current . isError ) . toBe ( true ) ) ;
376+ expect ( result . current . error ) . toBeDefined ( ) ;
377+ } ) ;
378+ } ) ;
379+
380+ describe ( 'useOrganizations' , ( ) => {
381+ beforeEach ( ( ) => {
382+ jest . clearAllMocks ( ) ;
383+ } ) ;
384+
385+ it ( 'returns organizations on success' , async ( ) => {
386+ const mockOrgs = [ { org : 'org1' , name : 'Org One' } ] ;
387+ getAuthenticatedHttpClient . mockReturnValue ( {
388+ get : jest . fn ( ) . mockResolvedValue ( { data : { results : mockOrgs } } ) ,
389+ } ) ;
390+
391+ const { result } = renderHook ( ( ) => useOrganizations ( 'library' ) , { wrapper : createWrapper ( ) } ) ;
392+
393+ await waitFor ( ( ) => expect ( result . current . isSuccess ) . toBe ( true ) ) ;
394+ expect ( result . current . data ) . toEqual ( mockOrgs ) ;
395+ } ) ;
396+
397+ it ( 'handles error when API fails' , async ( ) => {
398+ getAuthenticatedHttpClient . mockReturnValue ( {
399+ get : jest . fn ( ) . mockRejectedValue ( new Error ( 'Failed' ) ) ,
400+ } ) ;
401+
402+ const { result } = renderHook ( ( ) => useOrganizations ( ) , { wrapper : createWrapper ( ) } ) ;
403+
404+ await waitFor ( ( ) => expect ( result . current . isError ) . toBe ( true ) ) ;
405+ } ) ;
406+ } ) ;
407+
408+ describe ( 'useManagedScopeOrgs' , ( ) => {
409+ beforeEach ( ( ) => {
410+ jest . clearAllMocks ( ) ;
411+ } ) ;
412+
413+ it ( 'does not fetch when contextType is undefined' , async ( ) => {
414+ const mockGet = jest . fn ( ) ;
415+ getAuthenticatedHttpClient . mockReturnValue ( { get : mockGet } ) ;
416+
417+ const { result } = renderHook ( ( ) => useManagedScopeOrgs ( undefined ) , { wrapper : createWrapper ( ) } ) ;
418+
419+ // Query is disabled, so it should not be loading or have fetched
420+ expect ( result . current . isFetching ) . toBe ( false ) ;
421+ expect ( mockGet ) . not . toHaveBeenCalled ( ) ;
422+ } ) ;
423+
424+ it ( 'fetches and returns a Set of orgs when contextType is provided' , async ( ) => {
425+ const mockScopesResponse = {
426+ results : [
427+ { id : 'lib:123' , name : 'Lib 1' , org : 'org1' , contextType : 'library' } ,
428+ { id : 'lib:456' , name : 'Lib 2' , org : 'org2' , contextType : 'library' } ,
429+ { id : 'lib:789' , name : 'Lib 3' , org : '' , contextType : 'library' } ,
430+ ] ,
431+ count : 3 ,
432+ next : null ,
433+ previous : null ,
434+ } ;
435+ getAuthenticatedHttpClient . mockReturnValue ( {
436+ get : jest . fn ( ) . mockResolvedValue ( { data : mockScopesResponse } ) ,
437+ } ) ;
438+
439+ const { result } = renderHook ( ( ) => useManagedScopeOrgs ( 'library' ) , { wrapper : createWrapper ( ) } ) ;
440+
441+ await waitFor ( ( ) => expect ( result . current . isSuccess ) . toBe ( true ) ) ;
442+
443+ const orgs = result . current . data as Set < string > ;
444+ expect ( orgs . has ( 'org1' ) ) . toBe ( true ) ;
445+ expect ( orgs . has ( 'org2' ) ) . toBe ( true ) ;
446+ // empty string org is filtered out
447+ expect ( orgs . has ( '' ) ) . toBe ( false ) ;
448+ expect ( orgs . size ) . toBe ( 2 ) ;
449+ } ) ;
450+
451+ it ( 'handles error when API fails' , async ( ) => {
452+ getAuthenticatedHttpClient . mockReturnValue ( {
453+ get : jest . fn ( ) . mockRejectedValue ( new Error ( 'API error' ) ) ,
454+ } ) ;
455+
456+ const { result } = renderHook ( ( ) => useManagedScopeOrgs ( 'course' ) , { wrapper : createWrapper ( ) } ) ;
457+
458+ await waitFor ( ( ) => expect ( result . current . isError ) . toBe ( true ) ) ;
459+ } ) ;
460+ } ) ;
461+
293462describe ( 'useRevokeUserRoles' , ( ) => {
294463 beforeEach ( ( ) => {
295464 jest . clearAllMocks ( ) ;
0 commit comments