From 5dac62ced6440a48edc5299a53bf1e10ca26ca6d Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Fri, 14 Jul 2023 11:54:54 -0300 Subject: [PATCH] regression: Apply right filters to action buttons and convert `applyButtonFilters` to `useApplyButtonFilters` (#29822) --- apps/meteor/.mocharc.client.js | 2 +- .../actionButtons/lib/applyButtonFilters.ts | 58 --- .../client/hooks/useAppActionButtons.ts | 25 +- .../client/hooks/useApplyButtonFilters.ts | 65 +++ .../providers/AuthorizationProvider.tsx | 5 +- .../actions/hooks/useAppsItems.spec.tsx | 488 ++++++++++++++++++ apps/meteor/jest.client.config.ts | 6 +- .../src/MockedAuthorizationContext.tsx | 19 +- .../src/MockedServerContext.tsx | 1 + .../ui-contexts/src/AuthorizationContext.ts | 8 +- 10 files changed, 599 insertions(+), 78 deletions(-) delete mode 100644 apps/meteor/app/ui-message/client/actionButtons/lib/applyButtonFilters.ts create mode 100644 apps/meteor/client/hooks/useApplyButtonFilters.ts create mode 100644 apps/meteor/client/sidebar/header/actions/hooks/useAppsItems.spec.tsx diff --git a/apps/meteor/.mocharc.client.js b/apps/meteor/.mocharc.client.js index 7eff846f683..6e522ee6736 100644 --- a/apps/meteor/.mocharc.client.js +++ b/apps/meteor/.mocharc.client.js @@ -38,5 +38,5 @@ module.exports = { 'tests/unit/lib/**/*.tests.ts', 'tests/unit/client/**/*.test.ts', ], - exclude: ['client/hooks/*.spec.{ts,tsx}'], + exclude: ['client/hooks/*.spec.{ts,tsx}', 'client/sidebar/header/actions/hooks/*.spec.{ts,tsx}'], }; diff --git a/apps/meteor/app/ui-message/client/actionButtons/lib/applyButtonFilters.ts b/apps/meteor/app/ui-message/client/actionButtons/lib/applyButtonFilters.ts deleted file mode 100644 index be38eaa6a22..00000000000 --- a/apps/meteor/app/ui-message/client/actionButtons/lib/applyButtonFilters.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* Style disabled as having some arrow functions in one-line hurts readability */ -/* eslint-disable arrow-body-style */ - -import { Meteor } from 'meteor/meteor'; -import type { IUIActionButton } from '@rocket.chat/apps-engine/definition/ui'; -import { RoomTypeFilter } from '@rocket.chat/apps-engine/definition/ui'; -import type { IRoom } from '@rocket.chat/core-typings'; -import { - isDirectMessageRoom, - isMultipleDirectMessageRoom, - isOmnichannelRoom, - isPrivateDiscussion, - isPrivateTeamRoom, - isPublicDiscussion, - isPublicTeamRoom, -} from '@rocket.chat/core-typings'; - -import { hasAtLeastOnePermission, hasPermission, hasRole, hasAnyRole } from '../../../../authorization/client'; - -const applyAuthFilter = (button: IUIActionButton, room?: IRoom, ignoreSubscriptions = false): boolean => { - const { hasAllPermissions, hasOnePermission, hasAllRoles, hasOneRole } = button.when || {}; - - const userId = Meteor.userId(); - - const hasAllPermissionsResult = hasAllPermissions ? hasPermission(hasAllPermissions) : true; - const hasOnePermissionResult = hasOnePermission ? hasAtLeastOnePermission(hasOnePermission) : true; - const hasAllRolesResult = hasAllRoles - ? !!userId && hasAllRoles.every((role) => hasRole(userId, role, room?._id, ignoreSubscriptions)) - : true; - const hasOneRoleResult = hasOneRole ? !!userId && hasAnyRole(userId, hasOneRole, room?._id, ignoreSubscriptions) : true; - - return hasAllPermissionsResult && hasOnePermissionResult && hasAllRolesResult && hasOneRoleResult; -}; - -const enumToFilter: { [k in RoomTypeFilter]: (room: IRoom) => boolean } = { - [RoomTypeFilter.PUBLIC_CHANNEL]: (room) => room.t === 'c', - [RoomTypeFilter.PRIVATE_CHANNEL]: (room) => room.t === 'p', - [RoomTypeFilter.PUBLIC_TEAM]: isPublicTeamRoom, - [RoomTypeFilter.PRIVATE_TEAM]: isPrivateTeamRoom, - [RoomTypeFilter.PUBLIC_DISCUSSION]: isPublicDiscussion, - [RoomTypeFilter.PRIVATE_DISCUSSION]: isPrivateDiscussion, - [RoomTypeFilter.DIRECT]: isDirectMessageRoom, - [RoomTypeFilter.DIRECT_MULTIPLE]: isMultipleDirectMessageRoom, - [RoomTypeFilter.LIVE_CHAT]: isOmnichannelRoom, -}; - -const applyRoomFilter = (button: IUIActionButton, room: IRoom): boolean => { - const { roomTypes } = button.when || {}; - return !roomTypes || roomTypes.some((filter): boolean => enumToFilter[filter]?.(room)); -}; - -export const applyButtonFilters = (button: IUIActionButton, room?: IRoom): boolean => { - return applyAuthFilter(button, room) && (!room || applyRoomFilter(button, room)); -}; - -export const applyDropdownActionButtonFilters = (button: IUIActionButton): boolean => { - return applyAuthFilter(button, undefined, true); -}; diff --git a/apps/meteor/client/hooks/useAppActionButtons.ts b/apps/meteor/client/hooks/useAppActionButtons.ts index 1f539c49b00..6d5aac3e92a 100644 --- a/apps/meteor/client/hooks/useAppActionButtons.ts +++ b/apps/meteor/client/hooks/useAppActionButtons.ts @@ -4,13 +4,13 @@ import type { UseQueryResult } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useEffect, useRef, useMemo } from 'react'; -import { applyButtonFilters } from '../../app/ui-message/client/actionButtons/lib/applyButtonFilters'; import type { MessageActionConfig, MessageActionContext } from '../../app/ui-utils/client/lib/MessageAction'; import type { MessageBoxAction } from '../../app/ui-utils/client/lib/messageBox'; import { Utilities } from '../../ee/lib/misc/Utilities'; import type { GenericMenuItemProps } from '../components/GenericMenu/GenericMenuItem'; import { useRoom } from '../views/room/contexts/RoomContext'; import type { ToolboxAction } from '../views/room/lib/Toolbox'; +import { useApplyButtonFilters, useApplyButtonAuthFilter } from './useApplyButtonFilters'; import { useUiKitActionManager } from './useUiKitActionManager'; const getIdForActionButton = ({ appId, actionId }: IUIActionButton): string => `${appId}/${actionId}`; @@ -49,13 +49,14 @@ export const useAppActionButtons = (context?: `${UIActionButtonContext}`) => { export const useMessageboxAppsActionButtons = () => { const result = useAppActionButtons('messageBoxAction'); const actionManager = useUiKitActionManager(); - const room = useRoom(); + + const applyButtonFilters = useApplyButtonFilters(); const data = useMemo( () => result.data ?.filter((action) => { - return applyButtonFilters(action, room); + return applyButtonFilters(action); }) .map((action) => { const item: MessageBoxAction = { @@ -74,7 +75,7 @@ export const useMessageboxAppsActionButtons = () => { return item; }), - [actionManager, result.data, room], + [actionManager, applyButtonFilters, result.data], ); return { ...result, @@ -86,6 +87,8 @@ export const useUserDropdownAppsActionButtons = () => { const result = useAppActionButtons('userDropdownAction'); const actionManager = useUiKitActionManager(); + const applyButtonFilters = useApplyButtonAuthFilter(); + const data = useMemo( () => result.data @@ -106,7 +109,7 @@ export const useUserDropdownAppsActionButtons = () => { }, }; }), - [actionManager, result.data], + [actionManager, applyButtonFilters, result.data], ); return { ...result, @@ -117,6 +120,7 @@ export const useUserDropdownAppsActionButtons = () => { export const useRoomActionAppsActionButtons = (context?: MessageActionContext) => { const result = useAppActionButtons('roomAction'); const actionManager = useUiKitActionManager(); + const applyButtonFilters = useApplyButtonFilters(); const room = useRoom(); const data = useMemo( () => @@ -125,7 +129,7 @@ export const useRoomActionAppsActionButtons = (context?: MessageActionContext) = if (context && ['group', 'channel', 'live', 'team', 'direct', 'direct_multiple'].includes(context)) { return false; } - return applyButtonFilters(action, room); + return applyButtonFilters(action); }) .map((action) => { const item: [string, ToolboxAction] = [ @@ -149,7 +153,7 @@ export const useRoomActionAppsActionButtons = (context?: MessageActionContext) = ]; return item; }), - [actionManager, context, result.data, room], + [actionManager, applyButtonFilters, context, result.data, room._id], ); return { ...result, @@ -160,8 +164,7 @@ export const useRoomActionAppsActionButtons = (context?: MessageActionContext) = export const useMessageActionAppsActionButtons = (context?: MessageActionContext) => { const result = useAppActionButtons('messageAction'); const actionManager = useUiKitActionManager(); - const room = useRoom(); - + const applyButtonFilters = useApplyButtonFilters(); const data = useMemo( () => result.data @@ -172,7 +175,7 @@ export const useMessageActionAppsActionButtons = (context?: MessageActionContext ) { return false; } - return applyButtonFilters(action, room); + return applyButtonFilters(action); }) .map((action) => { const item: MessageActionConfig = { @@ -192,7 +195,7 @@ export const useMessageActionAppsActionButtons = (context?: MessageActionContext return item; }), - [actionManager, context, result.data, room], + [actionManager, applyButtonFilters, context, result.data], ); return { ...result, diff --git a/apps/meteor/client/hooks/useApplyButtonFilters.ts b/apps/meteor/client/hooks/useApplyButtonFilters.ts new file mode 100644 index 00000000000..742f33489de --- /dev/null +++ b/apps/meteor/client/hooks/useApplyButtonFilters.ts @@ -0,0 +1,65 @@ +import type { IUIActionButton } from '@rocket.chat/apps-engine/definition/ui'; +import { RoomTypeFilter } from '@rocket.chat/apps-engine/definition/ui'; +import type { IRoom } from '@rocket.chat/core-typings'; +import { + isDirectMessageRoom, + isMultipleDirectMessageRoom, + isOmnichannelRoom, + isPrivateDiscussion, + isPrivateTeamRoom, + isPublicDiscussion, + isPublicTeamRoom, +} from '@rocket.chat/core-typings'; +import { AuthorizationContext, useUserId } from '@rocket.chat/ui-contexts'; +import { useCallback, useContext } from 'react'; + +import { useRoom } from '../views/room/contexts/RoomContext'; + +const enumToFilter: { [k in RoomTypeFilter]: (room: IRoom) => boolean } = { + [RoomTypeFilter.PUBLIC_CHANNEL]: (room) => room.t === 'c', + [RoomTypeFilter.PRIVATE_CHANNEL]: (room) => room.t === 'p', + [RoomTypeFilter.PUBLIC_TEAM]: isPublicTeamRoom, + [RoomTypeFilter.PRIVATE_TEAM]: isPrivateTeamRoom, + [RoomTypeFilter.PUBLIC_DISCUSSION]: isPublicDiscussion, + [RoomTypeFilter.PRIVATE_DISCUSSION]: isPrivateDiscussion, + [RoomTypeFilter.DIRECT]: isDirectMessageRoom, + [RoomTypeFilter.DIRECT_MULTIPLE]: isMultipleDirectMessageRoom, + [RoomTypeFilter.LIVE_CHAT]: isOmnichannelRoom, +}; + +const applyRoomFilter = (button: IUIActionButton, room: IRoom): boolean => { + const { roomTypes } = button.when || {}; + return !roomTypes || roomTypes.some((filter): boolean => enumToFilter[filter]?.(room)); +}; + +export const useApplyButtonFilters = (): ((button: IUIActionButton) => boolean) => { + const room = useRoom(); + if (!room) { + throw new Error('useApplyButtonFilters must be used inside a room context'); + } + const applyAuthFilter = useApplyButtonAuthFilter(); + return useCallback( + (button: IUIActionButton) => applyAuthFilter(button) && (!room || applyRoomFilter(button, room)), + [applyAuthFilter, room], + ); +}; + +export const useApplyButtonAuthFilter = (): ((button: IUIActionButton) => boolean) => { + const uid = useUserId(); + + const { queryAllPermissions, queryAtLeastOnePermission, queryRole } = useContext(AuthorizationContext); + + return useCallback( + (button: IUIActionButton, room?: IRoom) => { + const { hasAllPermissions, hasOnePermission, hasAllRoles, hasOneRole } = button.when || {}; + + const hasAllPermissionsResult = hasAllPermissions ? queryAllPermissions(hasAllPermissions)[1]() : true; + const hasOnePermissionResult = hasOnePermission ? queryAtLeastOnePermission(hasOnePermission)[1]() : true; + const hasAllRolesResult = hasAllRoles ? !!uid && hasAllRoles.every((role) => queryRole(role, room?._id)) : true; + const hasOneRoleResult = hasOneRole ? !!uid && hasOneRole.some((role) => queryRole(role, room?._id)[1]()) : true; + + return hasAllPermissionsResult && hasOnePermissionResult && hasAllRolesResult && hasOneRoleResult; + }, + [queryAllPermissions, queryAtLeastOnePermission, queryRole, uid], + ); +}; diff --git a/apps/meteor/client/providers/AuthorizationProvider.tsx b/apps/meteor/client/providers/AuthorizationProvider.tsx index 8fb0e69d12a..64d936b5cd6 100644 --- a/apps/meteor/client/providers/AuthorizationProvider.tsx +++ b/apps/meteor/client/providers/AuthorizationProvider.tsx @@ -20,7 +20,10 @@ const contextValue = { queryPermission: createReactiveSubscriptionFactory((permission, scope, scopeRoles) => hasPermission(permission, scope, scopeRoles)), queryAtLeastOnePermission: createReactiveSubscriptionFactory((permissions, scope) => hasAtLeastOnePermission(permissions, scope)), queryAllPermissions: createReactiveSubscriptionFactory((permissions, scope) => hasAllPermission(permissions, scope)), - queryRole: createReactiveSubscriptionFactory((role) => !!Meteor.userId() && hasRole(Meteor.userId() as string, role)), + queryRole: createReactiveSubscriptionFactory( + (role, scope?, ignoreSubscriptions = false) => + !!Meteor.userId() && hasRole(Meteor.userId() as string, role, scope, ignoreSubscriptions), + ), roleStore: new RoleStore(), }; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useAppsItems.spec.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useAppsItems.spec.tsx new file mode 100644 index 00000000000..a520444389a --- /dev/null +++ b/apps/meteor/client/sidebar/header/actions/hooks/useAppsItems.spec.tsx @@ -0,0 +1,488 @@ +/* eslint-disable react/no-multi-comp */ +import { MockedAuthorizationContext } from '@rocket.chat/mock-providers/src/MockedAuthorizationContext'; +import { MockedServerContext } from '@rocket.chat/mock-providers/src/MockedServerContext'; +import { MockedSettingsContext } from '@rocket.chat/mock-providers/src/MockedSettingsContext'; +import { MockedUserContext } from '@rocket.chat/mock-providers/src/MockedUserContext'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook } from '@testing-library/react-hooks'; +import React from 'react'; + +import { ActionManagerContext } from '../../../../contexts/ActionManagerContext'; +import { useAppsItems } from './useAppsItems'; + +it('should return and empty array if the user does not have `manage-apps` and `access-marketplace` permission', () => { + const { result } = renderHook( + () => { + return useAppsItems(); + }, + { + wrapper: ({ children }) => ( + + { + if (args.method === 'GET' && args.pathPattern === '/apps/actionButtons') { + return [] as any; + } + }} + > + + + {children} + + + + + ), + }, + ); + + expect(result.all[0]).toEqual([]); +}); + +it('should return `marketplace` and `installed` items if the user has `access-marketplace` permission', () => { + const { result } = renderHook( + () => { + return useAppsItems(); + }, + { + wrapper: ({ children }) => ( + + { + if (args.method === 'GET' && args.pathPattern === '/apps/actionButtons') { + return [] as any; + } + }} + > + + + + {children} + + + + + + ), + }, + ); + + expect(result.current[0]).toEqual( + expect.objectContaining({ + id: 'marketplace', + }), + ); + expect(result.current[1]).toEqual( + expect.objectContaining({ + id: 'installed', + }), + ); +}); + +it('should return `marketplace` and `installed` items if the user has `manage-apps` permission', () => { + const { result } = renderHook( + () => { + return useAppsItems(); + }, + { + wrapper: ({ children }) => ( + + { + if (args.method === 'GET' && args.pathPattern === '/apps/app-request/stats') { + return { + data: { + totalSeen: 0, + totalUnseen: 1, + }, + } as any; + } + if (args.method === 'GET' && args.pathPattern === '/apps/actionButtons') { + return [] as any; + } + + throw new Error('Method not mocked'); + }} + > + + + + {children} + + + + + + ), + }, + ); + + expect(result.current[0]).toEqual( + expect.objectContaining({ + id: 'marketplace', + }), + ); + expect(result.current[1]).toEqual( + expect.objectContaining({ + id: 'installed', + }), + ); + + expect(result.current[2]).toEqual( + expect.objectContaining({ + id: 'requested-apps', + }), + ); +}); + +it('should return one action from the server with no conditions', async () => { + const { result, waitForValueToChange } = renderHook( + () => { + return useAppsItems(); + }, + { + wrapper: ({ children }) => ( + + { + if (args.method === 'GET' && args.pathPattern === '/apps/app-request/stats') { + return { + data: { + totalSeen: 0, + totalUnseen: 1, + }, + } as any; + } + if (args.method === 'GET' && args.pathPattern === '/apps/actionButtons') { + return [ + { + appId: 'APP_ID', + actionId: 'ACTION_ID', + labelI18n: 'LABEL_I18N', + context: 'userDropdownAction', + }, + ] as any; + } + + throw new Error('Method not mocked'); + }} + > + + + + {children} + + + + + + ), + }, + ); + + expect(result.current[0]).toEqual( + expect.objectContaining({ + id: 'marketplace', + }), + ); + expect(result.current[1]).toEqual( + expect.objectContaining({ + id: 'installed', + }), + ); + + await waitForValueToChange(() => result.current[3]); + + expect(result.current[3]).toEqual( + expect.objectContaining({ + id: 'APP_ID_ACTION_ID', + }), + ); +}); + +describe('User Dropdown actions with role conditions', () => { + it('should return the action if the user has admin role', async () => { + const { result, waitForValueToChange } = renderHook( + () => { + return useAppsItems(); + }, + { + wrapper: ({ children }) => ( + + { + if (args.method === 'GET' && args.pathPattern === '/apps/app-request/stats') { + return { + data: { + totalSeen: 0, + totalUnseen: 1, + }, + } as any; + } + if (args.method === 'GET' && args.pathPattern === '/apps/actionButtons') { + return [ + { + appId: 'APP_ID', + actionId: 'ACTION_ID', + labelI18n: 'LABEL_I18N', + context: 'userDropdownAction', + when: { + hasOneRole: ['admin'], + }, + }, + ] as any; + } + + throw new Error('Method not mocked'); + }} + > + + + + {children} + + + + + + ), + }, + ); + + await waitForValueToChange(() => { + return queryClient.isFetching(); + }); + + expect(result.current[0]).toEqual( + expect.objectContaining({ + id: 'marketplace', + }), + ); + expect(result.current[1]).toEqual( + expect.objectContaining({ + id: 'installed', + }), + ); + + expect(result.current[3]).toEqual( + expect.objectContaining({ + id: 'APP_ID_ACTION_ID', + }), + ); + }); + + it('should return filter the action if the user doesn`t have admin role', async () => { + const { result, waitForValueToChange } = renderHook( + () => { + return useAppsItems(); + }, + { + wrapper: ({ children }) => ( + + { + if (args.method === 'GET' && args.pathPattern === '/apps/app-request/stats') { + return { + data: { + totalSeen: 0, + totalUnseen: 1, + }, + } as any; + } + if (args.method === 'GET' && args.pathPattern === '/apps/actionButtons') { + return [ + { + appId: 'APP_ID', + actionId: 'ACTION_ID', + labelI18n: 'LABEL_I18N', + context: 'userDropdownAction', + when: { + hasOneRole: ['admin'], + }, + }, + ] as any; + } + + throw new Error('Method not mocked'); + }} + > + + + + {children} + + + + + + ), + }, + ); + + await waitForValueToChange(() => { + return queryClient.isFetching(); + }); + + expect(result.current[0]).toEqual( + expect.objectContaining({ + id: 'marketplace', + }), + ); + expect(result.current[1]).toEqual( + expect.objectContaining({ + id: 'installed', + }), + ); + + expect(result.current[2]).toEqual( + expect.objectContaining({ + id: 'requested-apps', + }), + ); + + expect(result.current[3]).toEqual(undefined); + }); +}); + +describe('User Dropdown actions with permission conditions', () => { + it('should return the action if the user has manage-apps permission', async () => { + const { result, waitForValueToChange } = renderHook( + () => { + return useAppsItems(); + }, + { + wrapper: ({ children }) => ( + + { + if (args.method === 'GET' && args.pathPattern === '/apps/app-request/stats') { + return { + data: { + totalSeen: 0, + totalUnseen: 1, + }, + } as any; + } + if (args.method === 'GET' && args.pathPattern === '/apps/actionButtons') { + return [ + { + appId: 'APP_ID', + actionId: 'ACTION_ID', + labelI18n: 'LABEL_I18N', + context: 'userDropdownAction', + when: { + hasOnePermission: ['manage-apps'], + }, + }, + ] as any; + } + + throw new Error('Method not mocked'); + }} + > + + + + {children} + + + + + + ), + }, + ); + + await waitForValueToChange(() => { + return queryClient.isFetching(); + }); + + expect(result.current[0]).toEqual( + expect.objectContaining({ + id: 'marketplace', + }), + ); + expect(result.current[1]).toEqual( + expect.objectContaining({ + id: 'installed', + }), + ); + + expect(result.current[3]).toEqual( + expect.objectContaining({ + id: 'APP_ID_ACTION_ID', + }), + ); + }); + + it('should return filter the action if the user doesn`t have `any` permission', async () => { + const { result, waitForValueToChange } = renderHook( + () => { + return useAppsItems(); + }, + { + wrapper: ({ children }) => ( + + { + if (args.method === 'GET' && args.pathPattern === '/apps/app-request/stats') { + return { + data: { + totalSeen: 0, + totalUnseen: 1, + }, + } as any; + } + if (args.method === 'GET' && args.pathPattern === '/apps/actionButtons') { + return [ + { + appId: 'APP_ID', + actionId: 'ACTION_ID', + labelI18n: 'LABEL_I18N', + context: 'userDropdownAction', + when: { + hasOnePermission: ['any'], + }, + }, + ] as any; + } + + throw new Error('Method not mocked'); + }} + > + + + + {children} + + + + + + ), + }, + ); + + await waitForValueToChange(() => { + return queryClient.isFetching(); + }); + + expect(result.current[3]).toEqual(undefined); + }); +}); + +export const MockedUiKitActionManager = ({ children }: { children: React.ReactNode }) => { + return {children}; +}; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + // ✅ turns retries off + retry: false, + }, + }, +}); +afterEach(() => { + queryClient.clear(); +}); diff --git a/apps/meteor/jest.client.config.ts b/apps/meteor/jest.client.config.ts index db3a03ecf03..bcd9f8f5432 100644 --- a/apps/meteor/jest.client.config.ts +++ b/apps/meteor/jest.client.config.ts @@ -3,7 +3,11 @@ export default { testEnvironment: 'jsdom', modulePathIgnorePatterns: ['/dist/'], - testMatch: ['/client/hooks/**.spec.[jt]s?(x)', '/client/components/**.spec.[jt]s?(x)'], + testMatch: [ + '/client/hooks/**.spec.[jt]s?(x)', + '/client/components/**.spec.[jt]s?(x)', + '/client/sidebar/header/actions/hooks/**/**.spec.[jt]s?(x)', + ], transform: { '^.+\\.(t|j)sx?$': '@swc/jest', }, diff --git a/packages/mock-providers/src/MockedAuthorizationContext.tsx b/packages/mock-providers/src/MockedAuthorizationContext.tsx index 18f791f1dd9..4d2a5c05a47 100644 --- a/packages/mock-providers/src/MockedAuthorizationContext.tsx +++ b/packages/mock-providers/src/MockedAuthorizationContext.tsx @@ -1,14 +1,25 @@ import React from 'react'; import { AuthorizationContext } from '@rocket.chat/ui-contexts'; -export const MockedAuthorizationContext = ({ permissions = [], children }: { permissions: string[]; children: React.ReactNode }) => { +export const MockedAuthorizationContext = ({ + permissions = [], + roles = [], + children, +}: { + permissions: string[]; + roles?: string[]; + children: React.ReactNode; +}) => { return ( [() => (): void => undefined, (): boolean => permissions.includes(id)], - queryAtLeastOnePermission: () => [() => (): void => undefined, (): boolean => false], - queryAllPermissions: () => [() => (): void => undefined, (): boolean => false], - queryRole: () => [() => (): void => undefined, (): boolean => false], + queryAtLeastOnePermission: (ids: string[]) => [ + () => (): void => undefined, + (): boolean => ids.some((id) => permissions.includes(id)), + ], + queryAllPermissions: (ids: string[]) => [() => (): void => undefined, (): boolean => ids.every((id) => permissions.includes(id))], + queryRole: (id: string) => [() => (): void => undefined, (): boolean => roles.includes(id)], roleStore: { roles: {}, emit: (): void => undefined, diff --git a/packages/mock-providers/src/MockedServerContext.tsx b/packages/mock-providers/src/MockedServerContext.tsx index f3c13004ed7..6b23664f816 100644 --- a/packages/mock-providers/src/MockedServerContext.tsx +++ b/packages/mock-providers/src/MockedServerContext.tsx @@ -33,6 +33,7 @@ export const MockedServerContext = ({ }) => { return handleRequest(args); }, + getStream: () => () => undefined, } as any } > diff --git a/packages/ui-contexts/src/AuthorizationContext.ts b/packages/ui-contexts/src/AuthorizationContext.ts index a321c408672..d077568e772 100644 --- a/packages/ui-contexts/src/AuthorizationContext.ts +++ b/packages/ui-contexts/src/AuthorizationContext.ts @@ -1,4 +1,4 @@ -import type { IRole } from '@rocket.chat/core-typings'; +import type { IRole, IRoom } from '@rocket.chat/core-typings'; import type { IEmitter } from '@rocket.chat/emitter'; import { createContext } from 'react'; import type { ObjectId } from 'mongodb'; @@ -27,7 +27,11 @@ export type AuthorizationContextValue = { scope?: string | ObjectId, scopedRoles?: IRole['_id'][], ): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => boolean]; - queryRole(role: string | ObjectId): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => boolean]; + queryRole( + role: string | ObjectId, + scope?: IRoom['_id'], + ignoreSubscriptions?: boolean, + ): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => boolean]; roleStore: RoleStore; };