From d8755e67f09495d96f7dc42e6f451affa24f75b4 Mon Sep 17 00:00:00 2001 From: aidynoJ Date: Thu, 13 Jun 2024 18:07:06 +0500 Subject: [PATCH 1/4] SCTOR-834: use self endpoint user permissions if roles interface present, by refactor useUserTenantPermissions hook to achieve that --- src/hooks/useUserSelfTenantPermissions.js | 51 ++++++++++++++++ src/hooks/useUserTenantPermissionNames.js | 59 ++++++++++++++++++ ...s => useUserTenantPermissionNames.test.js} | 4 +- src/hooks/useUserTenantPermissions.js | 61 ++++++------------- 4 files changed, 132 insertions(+), 43 deletions(-) create mode 100644 src/hooks/useUserSelfTenantPermissions.js create mode 100644 src/hooks/useUserTenantPermissionNames.js rename src/hooks/{useUserTenantPermissions.test.js => useUserTenantPermissionNames.test.js} (90%) diff --git a/src/hooks/useUserSelfTenantPermissions.js b/src/hooks/useUserSelfTenantPermissions.js new file mode 100644 index 00000000..7ffde2e8 --- /dev/null +++ b/src/hooks/useUserSelfTenantPermissions.js @@ -0,0 +1,51 @@ +import { useQuery } from 'react-query'; + +import { useStripes } from '../StripesContext'; +import { useNamespace } from '../components'; +import useOkapiKy from '../useOkapiKy'; + +const INITIAL_DATA = []; + +const useUserSelfTenantPermissions = ( + { tenantId }, + options = {}, +) => { + const stripes = useStripes(); + const ky = useOkapiKy(); + const api = ky.extend({ + hooks: { + beforeRequest: [(req) => req.headers.set('X-Okapi-Tenant', tenantId)] + } + }); + const [namespace] = useNamespace({ key: 'user-self-permissions' }); + + const user = stripes.user.user; + + const { + isFetching, + isLoading, + data, + } = useQuery( + [namespace, user?.id, tenantId], + ({ signal }) => { + return api.get( + 'users-keycloak/_self', + { signal }, + ).json(); + }, + { + enabled: Boolean(user?.id && tenantId) && stripes.hasInterface('users-keycloak'), + keepPreviousData: true, + ...options, + }, + ); + + return ({ + isFetching, + isLoading, + userPermissions: data?.permissions.permissions || INITIAL_DATA, + totalRecords: data?.permissions.permissions.length || 0, + }); +}; + +export default useUserSelfTenantPermissions; diff --git a/src/hooks/useUserTenantPermissionNames.js b/src/hooks/useUserTenantPermissionNames.js new file mode 100644 index 00000000..b3c470e3 --- /dev/null +++ b/src/hooks/useUserTenantPermissionNames.js @@ -0,0 +1,59 @@ +import { useQuery } from 'react-query'; + +import { useStripes } from '../StripesContext'; +import { useNamespace } from '../components'; +import useOkapiKy from '../useOkapiKy'; + +const INITIAL_DATA = []; + +const useUserTenantPermissionNames = ( + { tenantId }, + options = {}, +) => { + const stripes = useStripes(); + const ky = useOkapiKy(); + const api = ky.extend({ + hooks: { + beforeRequest: [(req) => req.headers.set('X-Okapi-Tenant', tenantId)] + } + }); + const [namespace] = useNamespace({ key: 'user-affiliation-permissions' }); + + const user = stripes.user.user; + + const searchParams = { + full: 'true', + indexField: 'userId', + }; + + const { + isFetching, + isLoading, + data = {}, + } = useQuery( + [namespace, user?.id, tenantId], + ({ signal }) => { + return api.get( + `perms/users/${user.id}/permissions`, + { + searchParams, + signal, + }, + ).json(); + }, + { + enabled: Boolean(user?.id && tenantId), + keepPreviousData: true, + ...options, + }, + ); + + return ({ + isFetching, + isLoading, + userPermissions: data.permissionNames || INITIAL_DATA, + totalRecords: data.totalRecords, + }); +}; + +export default useUserTenantPermissionNames; diff --git a/src/hooks/useUserTenantPermissions.test.js b/src/hooks/useUserTenantPermissionNames.test.js similarity index 90% rename from src/hooks/useUserTenantPermissions.test.js rename to src/hooks/useUserTenantPermissionNames.test.js index e64b9c44..2e31d6af 100644 --- a/src/hooks/useUserTenantPermissions.test.js +++ b/src/hooks/useUserTenantPermissionNames.test.js @@ -5,7 +5,7 @@ import { } from 'react-query'; import permissions from 'fixtures/permissions'; -import useUserTenantPermissions from './useUserTenantPermissions'; +import useUserTenantPermissionNames from './useUserTenantPermissionNames'; import useOkapiKy from '../useOkapiKy'; jest.mock('../useOkapiKy'); @@ -61,7 +61,7 @@ describe('useUserTenantPermissions', () => { userId: 'userId', tenantId: 'tenantId', }; - const { result } = renderHook(() => useUserTenantPermissions(options), { wrapper }); + const { result } = renderHook(() => useUserTenantPermissionNames(options), { wrapper }); await waitFor(() => !result.current.isLoading); diff --git a/src/hooks/useUserTenantPermissions.js b/src/hooks/useUserTenantPermissions.js index 80030eea..4173160c 100644 --- a/src/hooks/useUserTenantPermissions.js +++ b/src/hooks/useUserTenantPermissions.js @@ -1,58 +1,37 @@ -import { useQuery } from 'react-query'; - import { useStripes } from '../StripesContext'; -import { useNamespace } from '../components'; -import useOkapiKy from '../useOkapiKy'; - -const INITIAL_DATA = []; +import useUserSelfTenantPermissions from './useUserSelfTenantPermissions'; +import useUserTenantPermissionNames from './useUserTenantPermissionNames'; const useUserTenantPermissions = ( { tenantId }, options = {}, ) => { const stripes = useStripes(); - const ky = useOkapiKy(); - const api = ky.extend({ - hooks: { - beforeRequest: [(req) => req.headers.set('X-Okapi-Tenant', tenantId)] - } - }); - const [namespace] = useNamespace({ key: 'user-affiliation-permissions' }); - - const user = stripes.user.user; - const searchParams = { - full: 'true', - indexField: 'userId', - }; + const { + isFetching: isPermissionsFetching, + isLoading: isPermissionsLoading, + userPermissions: permissionsData = {}, + totalRecords: permissionsTotalRecords + } = useUserTenantPermissionNames({ tenantId }, options); const { - isFetching, - isLoading, - data = {}, - } = useQuery( - [namespace, user?.id, tenantId], - ({ signal }) => { - return api.get( - `perms/users/${user.id}/permissions`, - { - searchParams, - signal, - }, - ).json(); - }, - { - enabled: Boolean(user?.id && tenantId), - keepPreviousData: true, - ...options, - }, - ); + isFetching: isSelfPermissionsFetching, + isLoading: isSelfPermissionsLoading, + userPermissions:selfPermissionsData = {}, + totalRecords: selfPermissionsTotalRecords + } = useUserSelfTenantPermissions({ tenantId }, options); + + const isFetching = stripes.hasInterface('roles') ? isSelfPermissionsFetching : isPermissionsFetching; + const isLoading = stripes.hasInterface('roles') ? isSelfPermissionsLoading : isPermissionsLoading; + const userPermissions = stripes.hasInterface('roles') ? selfPermissionsData : permissionsData; + const totalRecords = stripes.hasInterface('roles') ? selfPermissionsTotalRecords : permissionsTotalRecords; return ({ isFetching, isLoading, - userPermissions: data.permissionNames || INITIAL_DATA, - totalRecords: data.totalRecords, + userPermissions, + totalRecords }); }; From db0c8b7b7061672d24797a23a75cdd0230f76bda Mon Sep 17 00:00:00 2001 From: aidynoJ Date: Thu, 13 Jun 2024 18:09:23 +0500 Subject: [PATCH 2/4] add enabled flag to permNames hook --- src/hooks/useUserTenantPermissionNames.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useUserTenantPermissionNames.js b/src/hooks/useUserTenantPermissionNames.js index b3c470e3..dc7c2057 100644 --- a/src/hooks/useUserTenantPermissionNames.js +++ b/src/hooks/useUserTenantPermissionNames.js @@ -42,7 +42,7 @@ const useUserTenantPermissionNames = ( ).json(); }, { - enabled: Boolean(user?.id && tenantId), + enabled: Boolean(user?.id && tenantId) && !stripes.hasInterface('roles'), keepPreviousData: true, ...options, }, From 9d07a1efb14879a3ef2390f5367c8c767a26bb38 Mon Sep 17 00:00:00 2001 From: aidynoJ Date: Thu, 13 Jun 2024 19:58:46 +0500 Subject: [PATCH 3/4] add tests to introduced hooks --- .../useUserSelfTenantPermissions.test.js | 71 +++++++++++++++++++ .../useUserTenantPermissionNames.test.js | 5 +- 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src/hooks/useUserSelfTenantPermissions.test.js diff --git a/src/hooks/useUserSelfTenantPermissions.test.js b/src/hooks/useUserSelfTenantPermissions.test.js new file mode 100644 index 00000000..e8604bab --- /dev/null +++ b/src/hooks/useUserSelfTenantPermissions.test.js @@ -0,0 +1,71 @@ +import { renderHook, waitFor } from '@folio/jest-config-stripes/testing-library/react'; +import { + QueryClient, + QueryClientProvider, +} from 'react-query'; + +import permissions from 'fixtures/permissions'; +import useUserSelfTenantPermissions from './useUserSelfTenantPermissions'; +import useOkapiKy from '../useOkapiKy'; + +jest.mock('../useOkapiKy'); +jest.mock('../components', () => ({ + useNamespace: () => ([]), +})); +jest.mock('../StripesContext', () => ({ + useStripes: () => ({ + user: { + user: { + id: 'userId' + } + }, + hasInterface: () => true + }), +})); + +const queryClient = new QueryClient(); + +// eslint-disable-next-line react/prop-types +const wrapper = ({ children }) => ( + + {children} + +); + +const response = { + permissions: { permissions }, +}; + +describe('useUserSelfTenantPermissions', () => { + const getMock = jest.fn(() => ({ + json: () => Promise.resolve(response), + })); + const setHeaderMock = jest.fn(); + const kyMock = { + extend: jest.fn(({ hooks: { beforeRequest } }) => { + beforeRequest.forEach(handler => handler({ headers: { set: setHeaderMock } })); + + return { + get: getMock, + }; + }), + }; + + beforeEach(() => { + getMock.mockClear(); + useOkapiKy.mockClear().mockReturnValue(kyMock); + }); + + it('should fetch user permissions for specified tenant', async () => { + const options = { + userId: 'userId', + tenantId: 'tenantId', + }; + const { result } = renderHook(() => useUserSelfTenantPermissions(options), { wrapper }); + + await waitFor(() => !result.current.isLoading); + + expect(setHeaderMock).toHaveBeenCalledWith('X-Okapi-Tenant', options.tenantId); + expect(getMock).toHaveBeenCalledWith('users-keycloak/_self', expect.objectContaining({})); + }); +}); diff --git a/src/hooks/useUserTenantPermissionNames.test.js b/src/hooks/useUserTenantPermissionNames.test.js index 2e31d6af..cc15aecd 100644 --- a/src/hooks/useUserTenantPermissionNames.test.js +++ b/src/hooks/useUserTenantPermissionNames.test.js @@ -18,7 +18,8 @@ jest.mock('../StripesContext', () => ({ user: { id: 'userId' } - } + }, + hasInterface: () => false }), })); @@ -36,7 +37,7 @@ const response = { totalRecords: permissions.length, }; -describe('useUserTenantPermissions', () => { +describe('useUserTenantPermissionNames', () => { const getMock = jest.fn(() => ({ json: () => Promise.resolve(response), })); From 4d5a9a0cdc45efd5bf86af5d6b9062efd603ccc3 Mon Sep 17 00:00:00 2001 From: aidynoJ Date: Tue, 9 Jul 2024 11:25:04 +0500 Subject: [PATCH 4/4] add tests for useUserTenantPermissions --- src/hooks/useUserTenantPermissions.test.js | 74 ++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/hooks/useUserTenantPermissions.test.js diff --git a/src/hooks/useUserTenantPermissions.test.js b/src/hooks/useUserTenantPermissions.test.js new file mode 100644 index 00000000..c3f4aa9b --- /dev/null +++ b/src/hooks/useUserTenantPermissions.test.js @@ -0,0 +1,74 @@ +import { renderHook } from '@folio/jest-config-stripes/testing-library/react'; +import { useStripes } from '../StripesContext'; +import useUserSelfTenantPermissions from './useUserSelfTenantPermissions'; +import useUserTenantPermissionNames from './useUserTenantPermissionNames'; +import useUserTenantPermissions from './useUserTenantPermissions'; + +jest.mock('../StripesContext'); +jest.mock('./useUserSelfTenantPermissions'); +jest.mock('./useUserTenantPermissionNames'); + +describe('useUserTenantPermissions', () => { + const tenantId = 'tenant-id'; + const options = {}; + + beforeEach(() => { + useStripes.mockReturnValue({ + hasInterface: jest.fn() + }); + }); + + it('should return _self permissions data when "roles" interface is present', () => { + useStripes().hasInterface.mockReturnValue(true); + + useUserSelfTenantPermissions.mockReturnValue({ + isFetching: true, + isLoading: true, + userPermissions: ['self'], + totalRecords: 1 + }); + + useUserTenantPermissionNames.mockReturnValue({ + isFetching: false, + isLoading: false, + userPermissions: ['permission name'], + totalRecords: 1 + }); + + const { result } = renderHook(() => useUserTenantPermissions({ tenantId }, options)); + + expect(result.current).toStrictEqual({ + isFetching: true, + isLoading: true, + userPermissions: ['self'], + totalRecords: 1 + }); + }); + + it('should return tenant permissions data when "roles" interface is NOT present', () => { + useStripes().hasInterface.mockReturnValue(false); + + useUserSelfTenantPermissions.mockReturnValue({ + isFetching: true, + isLoading: true, + userPermissions: ['self'], + totalRecords: 1 + }); + + useUserTenantPermissionNames.mockReturnValue({ + isFetching: false, + isLoading: false, + userPermissions: ['permission name'], + totalRecords: 1 + }); + + const { result } = renderHook(() => useUserTenantPermissions({ tenantId }, options)); + + expect(result.current).toStrictEqual({ + isFetching: false, + isLoading: false, + userPermissions: ['permission name'], + totalRecords: 1 + }); + }); +});