55 initializeMocks , render , waitFor , fireEvent , screen ,
66} from '@src/testUtils' ;
77
8+ import { useUserPermissions } from '@src/authz/data/apiHooks' ;
9+
10+ import * as apiHooks from '@src/data/apiHooks' ;
811import {
912 getCourseUpdatesApiUrl ,
1013 getCourseHandoutApiUrl ,
@@ -25,6 +28,19 @@ let store;
2528const mockPathname = '/foo-bar' ;
2629const courseId = '123' ;
2730
31+ jest . mock ( '@src/authz/data/apiHooks' , ( ) => ( {
32+ ...jest . requireActual ( '@src/authz/data/apiHooks' ) ,
33+ useUserPermissions : jest . fn ( ( ) => ( {
34+ isLoading : false ,
35+ data : { canManageCourseUpdates : false , canViewCourseUpdates : true } ,
36+ } ) ) ,
37+ } ) ) ;
38+
39+ jest . mock ( '@src/data/apiHooks' , ( ) => ( {
40+ ...jest . requireActual ( '@src/data/apiHooks' ) ,
41+ useWaffleFlags : jest . fn ( ( ) => ( { enableAuthzCourseAuthoring : false } ) ) ,
42+ } ) ) ;
43+
2844jest . mock ( 'react-router-dom' , ( ) => ( {
2945 ...jest . requireActual ( 'react-router-dom' ) ,
3046 useLocation : ( ) => ( {
@@ -342,4 +358,149 @@ describe('<CourseUpdates />', () => {
342358 expect ( await screen . findByText ( messages . savingHandoutsErrorDescription . defaultMessage ) ) ;
343359 } ) ;
344360 } ) ;
361+
362+ describe ( 'Authorization and permissions' , ( ) => {
363+ describe ( 'when user has permission to manage course updates' , ( ) => {
364+ beforeEach ( ( ) => {
365+ const mocks = initializeMocks ( ) ;
366+ store = mocks . reduxStore ;
367+ axiosMock = mocks . axiosMock ;
368+
369+ ( apiHooks . useWaffleFlags as jest . Mock ) . mockReturnValue ( { enableAuthzCourseAuthoring : true } ) ;
370+ ( useUserPermissions as jest . Mock ) . mockReturnValue ( {
371+ isLoading : false ,
372+ data : { canManageCourseUpdates : true } ,
373+ } ) ;
374+
375+ axiosMock
376+ . onGet ( getCourseUpdatesApiUrl ( courseId ) )
377+ . reply ( 200 , courseUpdatesMock ) ;
378+ axiosMock
379+ . onGet ( getCourseHandoutApiUrl ( courseId ) )
380+ . reply ( 200 , courseHandoutsMock ) ;
381+ } ) ;
382+
383+ it ( 'should render the "New update" button' , async ( ) => {
384+ render ( < RootWrapper /> ) ;
385+
386+ expect ( await screen . findByRole ( 'button' , {
387+ name : messages . newUpdateButton . defaultMessage ,
388+ } ) ) . toBeInTheDocument ( ) ;
389+ } ) ;
390+
391+ it ( 'should render edit and delete buttons for course updates' , async ( ) => {
392+ const { container } = render ( < RootWrapper /> ) ;
393+ await waitFor ( ( ) => {
394+ expect ( container . querySelectorAll ( '.course-update' ) ) . toHaveLength ( 3 ) ;
395+ } ) ;
396+
397+ expect ( await screen . findAllByRole ( 'button' , { name : / e d i t / i } ) ) . toHaveLength ( 4 ) ; // 3 for course updates and 1 for handouts
398+ expect ( await screen . findAllByRole ( 'button' , { name : / d e l e t e / i } ) ) . toHaveLength ( 3 ) ;
399+ } ) ;
400+ } ) ;
401+
402+ describe ( 'when user does NOT have permission to manage course updates and enableAuthzCourseAuthoring is enabled' , ( ) => {
403+ beforeEach ( ( ) => {
404+ const mocks = initializeMocks ( ) ;
405+ store = mocks . reduxStore ;
406+ axiosMock = mocks . axiosMock ;
407+
408+ ( apiHooks . useWaffleFlags as jest . Mock ) . mockReturnValue ( { enableAuthzCourseAuthoring : true } ) ;
409+ ( useUserPermissions as jest . Mock ) . mockReturnValue ( {
410+ isLoading : false ,
411+ data : { canManageCourseUpdates : false } ,
412+ } ) ;
413+
414+ axiosMock
415+ . onGet ( getCourseUpdatesApiUrl ( courseId ) )
416+ . reply ( 200 , courseUpdatesMock ) ;
417+ axiosMock
418+ . onGet ( getCourseHandoutApiUrl ( courseId ) )
419+ . reply ( 200 , courseHandoutsMock ) ;
420+ } ) ;
421+
422+ it ( 'should NOT render the "New update" button' , async ( ) => {
423+ render ( < RootWrapper /> ) ;
424+
425+ await waitFor ( ( ) => {
426+ expect ( screen . getByText ( messages . headingTitle . defaultMessage ) ) . toBeInTheDocument ( ) ;
427+ } ) ;
428+
429+ const newUpdateButton = screen . queryByRole ( 'button' , { name : / N e w u p d a t e / } ) ;
430+
431+ expect ( newUpdateButton ) . not . toBeInTheDocument ( ) ;
432+ } ) ;
433+
434+ it ( 'should NOT render edit and delete buttons for course updates' , async ( ) => {
435+ const { container } = render ( < RootWrapper /> ) ;
436+
437+ await waitFor ( ( ) => {
438+ expect ( container . querySelectorAll ( '.course-update' ) ) . toHaveLength ( 3 ) ;
439+ expect ( screen . queryByRole ( 'button' , { name : / e d i t / i } ) ) . not . toBeInTheDocument ( ) ;
440+ expect ( screen . queryByRole ( 'button' , { name : / d e l e t e / i } ) ) . not . toBeInTheDocument ( ) ;
441+ } ) ;
442+ } ) ;
443+ } ) ;
444+
445+ describe ( 'when enableAuthzCourseAuthoring is disabled' , ( ) => {
446+ beforeEach ( ( ) => {
447+ const mocks = initializeMocks ( ) ;
448+ store = mocks . reduxStore ;
449+ axiosMock = mocks . axiosMock ;
450+
451+ ( apiHooks . useWaffleFlags as jest . Mock ) . mockReturnValue ( { enableAuthzCourseAuthoring : false } ) ;
452+ ( useUserPermissions as jest . Mock ) . mockReturnValue ( {
453+ isLoading : false ,
454+ data : { canManageCourseUpdates : false } ,
455+ } ) ;
456+
457+ axiosMock
458+ . onGet ( getCourseUpdatesApiUrl ( courseId ) )
459+ . reply ( 200 , courseUpdatesMock ) ;
460+ axiosMock
461+ . onGet ( getCourseHandoutApiUrl ( courseId ) )
462+ . reply ( 200 , courseHandoutsMock ) ;
463+ } ) ;
464+
465+ it ( 'should render the "New update" button (defaults to true when authz disabled)' , async ( ) => {
466+ render ( < RootWrapper /> ) ;
467+
468+ expect ( await screen . findByRole ( 'button' , {
469+ name : messages . newUpdateButton . defaultMessage ,
470+ } ) ) . toBeInTheDocument ( ) ;
471+ } ) ;
472+ } ) ;
473+
474+ describe ( 'when user does NOT have permission to view course updates' , ( ) => {
475+ beforeEach ( ( ) => {
476+ const mocks = initializeMocks ( ) ;
477+ store = mocks . reduxStore ;
478+ axiosMock = mocks . axiosMock ;
479+
480+ ( apiHooks . useWaffleFlags as jest . Mock ) . mockReturnValue ( { enableAuthzCourseAuthoring : true } ) ;
481+ ( useUserPermissions as jest . Mock ) . mockReturnValue ( {
482+ isLoading : false ,
483+ data : { canManageCourseUpdates : false , canViewCourseUpdates : false } ,
484+ } ) ;
485+
486+ axiosMock
487+ . onGet ( getCourseUpdatesApiUrl ( courseId ) )
488+ . reply ( 200 , courseUpdatesMock ) ;
489+ axiosMock
490+ . onGet ( getCourseHandoutApiUrl ( courseId ) )
491+ . reply ( 200 , courseHandoutsMock ) ;
492+ } ) ;
493+
494+ it ( 'should render PermissionDeniedAlert instead of course updates content' , async ( ) => {
495+ render ( < RootWrapper /> ) ;
496+
497+ expect ( await screen . findByText ( / Y o u a r e n o t a u t h o r i z e d t o v i e w t h i s p a g e / ) ) . toBeInTheDocument ( ) ;
498+ expect ( screen . queryByText ( messages . headingTitle . defaultMessage ) ) . not . toBeInTheDocument ( ) ;
499+ expect ( screen . queryByText ( messages . headingSubtitle . defaultMessage ) ) . not . toBeInTheDocument ( ) ;
500+ expect ( screen . queryByRole ( 'button' , {
501+ name : messages . newUpdateButton . defaultMessage ,
502+ } ) ) . not . toBeInTheDocument ( ) ;
503+ } ) ;
504+ } ) ;
505+ } ) ;
345506} ) ;
0 commit comments