diff --git a/src/helpers/useValidateUserPermissions.test.tsx b/src/helpers/useValidateUserPermissions.test.tsx new file mode 100644 index 0000000..c063c9b --- /dev/null +++ b/src/helpers/useValidateUserPermissions.test.tsx @@ -0,0 +1,99 @@ +import { act, ReactNode } from 'react'; +import { renderHook, waitFor } from '@testing-library/react'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { useValidateUserPermissions } from './useValidateUserPermissions'; + +jest.mock('@edx/frontend-platform/auth', () => ({ + getAuthenticatedHttpClient: jest.fn(), +})); + +const createWrapper = () => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }); + + const wrapper = ({ children }: { children: ReactNode }) => ( + + {children} + + ); + + return wrapper; +}; +const permissions = [ + { + action: 'act:read', + object: 'lib:test-lib', + scope: 'org:OpenedX', + }, +]; + +const mockValidPermissions = [ + { action: 'act:read', object: 'lib:test-lib', allowed: true }, +]; + +const mockInvalidPermissions = [ + { action: 'act:read', object: 'lib:test-lib', allowed: false }, +]; + +describe('useValidateUserPermissions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns allowed true when permissions are valid', async () => { + getAuthenticatedHttpClient.mockReturnValue({ + post: jest.fn().mockResolvedValueOnce({ data: mockValidPermissions }), + }); + + + const { result } = renderHook(() => useValidateUserPermissions(permissions), { + wrapper: createWrapper(), + }); + + await waitFor(() => expect(result.current).toBeDefined()); + + expect(getAuthenticatedHttpClient).toHaveBeenCalled(); + expect(result.current.data[0].allowed).toBe(true); + }); + + it('returns allowed false when permissions are invalid', async () => { + getAuthenticatedHttpClient.mockReturnValue({ + post: jest.fn().mockResolvedValue({ data: mockInvalidPermissions }), + }); + + const { result } = renderHook(() => useValidateUserPermissions(permissions), { + wrapper: createWrapper(), + }); + + await waitFor(() => expect(result.current).toBeDefined()); + + expect(getAuthenticatedHttpClient).toHaveBeenCalled(); + expect(result.current.data[0].allowed).toBe(false); + }); + + it('handles error when the API call fails', async () => { + const mockError = new Error('API Error'); + + getAuthenticatedHttpClient.mockReturnValue({ + post: jest.fn().mockRejectedValue(new Error('API Error')), + }); + + try { + act(() => { + renderHook(() => useValidateUserPermissions(permissions), { + wrapper: createWrapper(), + }); + }); + + } catch (error) { + expect(error).toEqual(mockError); // Check for the expected error + return; + } + }); +}); diff --git a/src/helpers/useValidateUserPermissions.ts b/src/helpers/useValidateUserPermissions.ts new file mode 100644 index 0000000..9a09604 --- /dev/null +++ b/src/helpers/useValidateUserPermissions.ts @@ -0,0 +1,44 @@ +import { useSuspenseQuery } from "@tanstack/react-query"; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { getApiUrl } from './utils'; + +export interface PermissionValidationRequest { + action: string; + object?: string; + scope?: string; +} + +export interface PermissionValidationResponse extends PermissionValidationRequest{ + allowed: boolean; +} + +const validateUserPermissions = async (validations: PermissionValidationRequest[]): Promise => { + const { data } = await getAuthenticatedHttpClient().post(getApiUrl(`/api/authz/v1/permissions/validate/me`), validations); + return data; +}; + + +/** + * React Query hook to validate if the current user has permissions over a certain object in the instance. + * It helps to: + * - Determine whether the current user can access certain object. + * - Provide role-based rendering logic for UI components. + * + * @param permissions - The array of objects and actions to validate. + * + * @example + * const { data } = useValidateTeamMember([{ + "action": "act:read", + "object": "lib:test-lib", + "scope": "org:OpenedX" + }]); + * if (data[0].allowed) { ... } + * + */ +export const useValidateUserPermissions = (permissions: PermissionValidationRequest[]) => { + return useSuspenseQuery({ + queryKey: ['validate-user-permissions', permissions], + queryFn: () => validateUserPermissions(permissions), + retry: false, + }); +} \ No newline at end of file diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts new file mode 100644 index 0000000..8676ba1 --- /dev/null +++ b/src/helpers/utils.ts @@ -0,0 +1,4 @@ +import { getConfig } from '@edx/frontend-platform'; + +export const getApiUrl = (path: string) => `${getConfig().LMS_BASE_URL}${path || ''}`; +export const getStudioApiUrl = (path: string) => `${getConfig().STUDIO_BASE_URL}${path || ''}`;