fix: show only relevant userInfoActions for mentioned non-members (#31525)

pull/32891/head
Abhinav Kumar 1 year ago committed by GitHub
parent 58c0efc732
commit b764c415dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      .changeset/ninety-hounds-exist.md
  2. 40
      apps/meteor/app/api/server/v1/rooms.ts
  3. 10
      apps/meteor/client/views/hooks/useMemberExists.ts
  4. 15
      apps/meteor/client/views/room/UserCard/UserCardWithData.tsx
  5. 2
      apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersActions.tsx
  6. 22
      apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoActions.tsx
  7. 95
      apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx
  8. 20
      apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts
  9. 6
      apps/meteor/client/views/room/lib/getRoomDirectives.ts
  10. 90
      apps/meteor/tests/e2e/user-card-info-actions-by-member.spec.ts
  11. 90
      apps/meteor/tests/e2e/user-card-info-actions-by-room-owner.spec.ts
  12. 282
      apps/meteor/tests/end-to-end/api/rooms.ts
  13. 1
      packages/i18n/src/locales/en.i18n.json
  14. 19
      packages/rest-typings/src/v1/rooms.ts

@ -0,0 +1,7 @@
---
'@rocket.chat/rest-typings': patch
'@rocket.chat/meteor': patch
'@rocket.chat/i18n': patch
---
Fix: Show correct user info actions for non-members in channels.

@ -1,8 +1,14 @@
import { Media } from '@rocket.chat/core-services';
import type { IRoom, IUpload } from '@rocket.chat/core-typings';
import { Messages, Rooms, Users, Uploads } from '@rocket.chat/models';
import { Messages, Rooms, Users, Uploads, Subscriptions } from '@rocket.chat/models';
import type { Notifications } from '@rocket.chat/rest-typings';
import { isGETRoomsNameExists, isRoomsImagesProps, isRoomsMuteUnmuteUserProps, isRoomsExportProps } from '@rocket.chat/rest-typings';
import {
isGETRoomsNameExists,
isRoomsImagesProps,
isRoomsMuteUnmuteUserProps,
isRoomsExportProps,
isRoomsIsMemberProps,
} from '@rocket.chat/rest-typings';
import { Meteor } from 'meteor/meteor';
import { isTruthy } from '../../../../lib/isTruthy';
@ -783,6 +789,36 @@ API.v1.addRoute(
},
);
API.v1.addRoute(
'rooms.isMember',
{
authRequired: true,
validateParams: isRoomsIsMemberProps,
},
{
async get() {
const { roomId, userId, username } = this.queryParams;
const [room, user] = await Promise.all([
findRoomByIdOrName({
params: { roomId },
}) as Promise<IRoom>,
Users.findOneByIdOrUsername(userId || username),
]);
if (!user?._id) {
return API.v1.failure('error-user-not-found');
}
if (await canAccessRoomAsync(room, { _id: this.user._id })) {
return API.v1.success({
isMember: (await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) > 0,
});
}
return API.v1.unauthorized();
},
},
);
API.v1.addRoute(
'rooms.muteUser',
{ authRequired: true, validateParams: isRoomsMuteUnmuteUserProps },

@ -0,0 +1,10 @@
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
type UseMemberExistsProps = { roomId: string; username: string };
export const useMemberExists = ({ roomId, username }: UseMemberExistsProps) => {
const checkMember = useEndpoint('GET', '/v1/rooms.isMember');
return useQuery(['rooms/isMember', roomId, username], () => checkMember({ roomId, username }));
};

@ -10,6 +10,7 @@ import LocalTime from '../../../components/LocalTime';
import { UserCard, UserCardAction, UserCardRole, UserCardSkeleton } from '../../../components/UserCard';
import { ReactiveUserStatus } from '../../../components/UserStatus';
import { useUserInfoQuery } from '../../../hooks/useUserInfoQuery';
import { useMemberExists } from '../../hooks/useMemberExists';
import { useUserInfoActions } from '../hooks/useUserInfoActions';
type UserCardWithDataProps = {
@ -24,7 +25,16 @@ const UserCardWithData = ({ username, rid, onOpenUserInfo, onClose }: UserCardWi
const getRoles = useRolesDescription();
const showRealNames = Boolean(useSetting('UI_Use_Real_Name'));
const { data, isLoading } = useUserInfoQuery({ username });
const { data, isLoading: isUserInfoLoading } = useUserInfoQuery({ username });
const {
data: isMemberData,
refetch,
isSuccess: membershipCheckSuccess,
isLoading: isMembershipStatusLoading,
} = useMemberExists({ roomId: rid, username });
const isLoading = isUserInfoLoading || isMembershipStatusLoading;
const isMember = membershipCheckSuccess && isMemberData?.isMember;
const user = useMemo(() => {
const defaultValue = isLoading ? undefined : null;
@ -62,6 +72,9 @@ const UserCardWithData = ({ username, rid, onOpenUserInfo, onClose }: UserCardWi
const { actions: actionsDefinition, menuActions: menuOptions } = useUserInfoActions(
{ _id: user._id ?? '', username: user.username, name: user.name },
rid,
refetch,
undefined,
isMember,
);
const menu = useMemo(() => {

@ -16,7 +16,7 @@ type RoomMembersActionsProps = {
const RoomMembersActions = ({ username, _id, name, rid, reload }: RoomMembersActionsProps): ReactElement | null => {
const t = useTranslation();
const { menuActions: menuOptions } = useUserInfoActions({ _id, username, name }, rid, reload, 0);
const { menuActions: menuOptions } = useUserInfoActions({ _id, username, name }, rid, reload, 0, true);
if (!menuOptions) {
return null;
}

@ -1,12 +1,13 @@
/* eslint-disable react/display-name, react/no-multi-comp */
import type { IRoom, IUser } from '@rocket.chat/core-typings';
import { ButtonGroup, IconButton } from '@rocket.chat/fuselage';
import { ButtonGroup, IconButton, Skeleton } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { useMemo } from 'react';
import GenericMenu from '../../../../components/GenericMenu/GenericMenu';
import { UserInfoAction } from '../../../../components/UserInfo';
import { useMemberExists } from '../../../hooks/useMemberExists';
import { useUserInfoActions } from '../../hooks/useUserInfoActions';
type UserInfoActionsProps = {
@ -17,10 +18,24 @@ type UserInfoActionsProps = {
const UserInfoActions = ({ user, rid, backToList }: UserInfoActionsProps): ReactElement => {
const t = useTranslation();
const {
data: isMemberData,
refetch,
isSuccess: membershipCheckSuccess,
isLoading,
} = useMemberExists({ roomId: rid, username: user.username as string });
const isMember = membershipCheckSuccess && isMemberData?.isMember;
const { actions: actionsDefinition, menuActions: menuOptions } = useUserInfoActions(
{ _id: user._id, username: user.username, name: user.name },
rid,
backToList,
() => {
backToList?.();
refetch();
},
undefined,
isMember,
);
const menu = useMemo(() => {
@ -51,6 +66,9 @@ const UserInfoActions = ({ user, rid, backToList }: UserInfoActionsProps): React
return [...actionsDefinition.map(mapAction), menu].filter(Boolean);
}, [actionsDefinition, menu]);
if (isLoading) {
return <Skeleton w='full' />;
}
return <ButtonGroup align='center'>{actions}</ButtonGroup>;
};

@ -0,0 +1,95 @@
import type { IRoom, IUser } from '@rocket.chat/core-typings';
import { isRoomFederated } from '@rocket.chat/core-typings';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import {
useTranslation,
useUser,
useUserRoom,
useUserSubscription,
useToastMessageDispatch,
useAtLeastOnePermission,
useEndpoint,
} from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';
import * as Federation from '../../../../../lib/federation/Federation';
import { useAddMatrixUsers } from '../../../contextualBar/RoomMembers/AddUsers/AddMatrixUsers/useAddMatrixUsers';
import { getRoomDirectives } from '../../../lib/getRoomDirectives';
import type { UserInfoAction } from '../useUserInfoActions';
const inviteUserEndpoints = {
c: '/v1/channels.invite',
p: '/v1/groups.invite',
} as const;
export const useAddUserAction = (
user: Pick<IUser, '_id' | 'username'>,
rid: IRoom['_id'],
reload?: () => void,
): UserInfoAction | undefined => {
const t = useTranslation();
const room = useUserRoom(rid);
const currentUser = useUser();
const subscription = useUserSubscription(rid);
const dispatchToastMessage = useToastMessageDispatch();
const { username, _id: uid } = user;
if (!room) {
throw Error('Room not provided');
}
const hasPermissionToAddUsers = useAtLeastOnePermission(
useMemo(() => [room?.t === 'p' ? 'add-user-to-any-p-room' : 'add-user-to-any-c-room', 'add-user-to-joined-room'], [room?.t]),
rid,
);
const userCanAdd =
room && user && isRoomFederated(room)
? Federation.isEditableByTheUser(currentUser || undefined, room, subscription)
: hasPermissionToAddUsers;
const { roomCanInvite } = getRoomDirectives({ room, showingUserId: uid, userSubscription: subscription });
const inviteUser = useEndpoint('POST', inviteUserEndpoints[room.t === 'p' ? 'p' : 'c']);
const handleAddUser = useEffectEvent(async ({ users }) => {
const [username] = users;
await inviteUser({ roomId: rid, username });
reload?.();
});
const addClickHandler = useAddMatrixUsers();
const addUserOptionAction = useEffectEvent(async () => {
try {
const users = [username as string];
if (isRoomFederated(room)) {
addClickHandler.mutate({
users,
handleSave: handleAddUser,
});
} else {
await handleAddUser({ users });
}
dispatchToastMessage({ type: 'success', message: t('User_added') });
} catch (error) {
dispatchToastMessage({ type: 'error', message: error as Error });
}
});
const addUserOption = useMemo(
() =>
roomCanInvite && userCanAdd && room.archived !== true
? {
content: t('add-to-room'),
icon: 'user-plus' as const,
onClick: addUserOptionAction,
type: 'management' as const,
}
: undefined,
[roomCanInvite, userCanAdd, room.archived, t, addUserOptionAction],
);
return addUserOption;
};

@ -6,6 +6,7 @@ import { useMemo } from 'react';
import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem';
import { useEmbeddedLayout } from '../../../../hooks/useEmbeddedLayout';
import { useAddUserAction } from './actions/useAddUserAction';
import { useBlockUserAction } from './actions/useBlockUserAction';
import { useCallAction } from './actions/useCallAction';
import { useChangeLeaderAction } from './actions/useChangeLeaderAction';
@ -39,7 +40,9 @@ export const useUserInfoActions = (
rid: IRoom['_id'],
reload?: () => void,
size = 2,
isMember?: boolean,
): { actions: [string, UserInfoAction][]; menuActions: any | undefined } => {
const addUser = useAddUserAction(user, rid, reload);
const blockUser = useBlockUserAction(user, rid);
const changeLeader = useChangeLeaderAction(user, rid);
const changeModerator = useChangeModeratorAction(user, rid);
@ -58,15 +61,16 @@ export const useUserInfoActions = (
() => ({
...(openDirectMessage && !isLayoutEmbedded && { openDirectMessage }),
...(call && { call }),
...(changeOwner && { changeOwner }),
...(changeLeader && { changeLeader }),
...(changeModerator && { changeModerator }),
...(openModerationConsole && { openModerationConsole }),
...(ignoreUser && { ignoreUser }),
...(muteUser && { muteUser }),
...(!isMember && addUser && { addUser }),
...(isMember && changeOwner && { changeOwner }),
...(isMember && changeLeader && { changeLeader }),
...(isMember && changeModerator && { changeModerator }),
...(isMember && openModerationConsole && { openModerationConsole }),
...(isMember && ignoreUser && { ignoreUser }),
...(isMember && muteUser && { muteUser }),
...(blockUser && { toggleBlock: blockUser }),
...(reportUserOption && { reportUser: reportUserOption }),
...(removeUser && { removeUser }),
...(isMember && removeUser && { removeUser }),
}),
[
openDirectMessage,
@ -81,6 +85,8 @@ export const useUserInfoActions = (
removeUser,
reportUserOption,
openModerationConsole,
addUser,
isMember,
],
);

@ -11,6 +11,7 @@ type getRoomDirectiesType = {
roomCanBlock: boolean;
roomCanMute: boolean;
roomCanRemove: boolean;
roomCanInvite: boolean;
};
export const getRoomDirectives = ({
@ -24,7 +25,7 @@ export const getRoomDirectives = ({
}): getRoomDirectiesType => {
const roomDirectives = room?.t && roomCoordinator.getRoomDirectives(room.t);
const [roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove] = [
const [roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove, roomCanInvite] = [
...((roomDirectives && [
roomDirectives.allowMemberAction(room, RoomMemberActions.SET_AS_OWNER, showingUserId, userSubscription),
roomDirectives.allowMemberAction(room, RoomMemberActions.SET_AS_LEADER, showingUserId, userSubscription),
@ -33,9 +34,10 @@ export const getRoomDirectives = ({
roomDirectives.allowMemberAction(room, RoomMemberActions.BLOCK, showingUserId, userSubscription),
roomDirectives.allowMemberAction(room, RoomMemberActions.MUTE, showingUserId, userSubscription),
roomDirectives.allowMemberAction(room, RoomMemberActions.REMOVE_USER, showingUserId, userSubscription),
roomDirectives.allowMemberAction(room, RoomMemberActions.INVITE, showingUserId, userSubscription),
]) ??
[]),
];
return { roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove };
return { roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove, roomCanInvite };
};

@ -0,0 +1,90 @@
import { Users } from './fixtures/userStates';
import { HomeChannel } from './page-objects';
import { createTargetChannel, deleteChannel } from './utils';
import { expect, test } from './utils/test';
test.use({ storageState: Users.user3.state });
test.describe.parallel('Mention User Card [To Member]', () => {
let poHomeChannel: HomeChannel;
let targetChannel: string;
test.beforeAll(async ({ api }) => {
targetChannel = await createTargetChannel(api, { members: [Users.user1.data.username, Users.user3.data.username] });
await api.post(`/chat.postMessage`, {
text: `Hello @${Users.user1.data.username} @${Users.user2.data.username}`,
channel: targetChannel,
});
});
test.beforeEach(async ({ page }) => {
poHomeChannel = new HomeChannel(page);
await page.goto('/home');
});
test.afterAll(({ api }) => deleteChannel(api, targetChannel));
test('should show correct userinfo actions for a member of the room to a non-privileged member', async ({ page }) => {
await poHomeChannel.sidenav.openChat(targetChannel);
const mentionSpan = page.locator(`span[title="Mentions user"][data-uid="${Users.user1.data.username}"]`);
await mentionSpan.click();
await expect(page.locator('div[aria-label="User card actions"]')).toBeVisible();
const moreButton = await page.locator('div[aria-label="User card actions"] button[title="More"]');
if (await moreButton.isVisible()) {
await moreButton.click();
}
const isAddToRoomVisible =
(await page.locator('button[title="Add to room"]').isVisible()) || (await page.locator('label[data-key="Add to room"]').isVisible());
await expect(isAddToRoomVisible).toBeFalsy();
const isRemoveFromRoomVisible =
(await page.locator('button[title="Remove from room"]').isVisible()) ||
(await page.locator('label[data-key="Remove from room"]').isVisible());
await expect(isRemoveFromRoomVisible).toBeFalsy();
const isSetAsLeaderVisible =
(await page.locator('button[title="Set as leader"]').isVisible()) ||
(await page.locator('label[data-key="Set as leader"]').isVisible());
await expect(isSetAsLeaderVisible).toBeFalsy();
const isSetAsModeratorVisible =
(await page.locator('button[title="Set as moderator"]').isVisible()) ||
(await page.locator('label[data-key="Set as moderator"]').isVisible());
await expect(isSetAsModeratorVisible).toBeFalsy();
});
test('should show correct userinfo actions for a non-member of the room to a non-privileged member', async ({ page }) => {
await poHomeChannel.sidenav.openChat(targetChannel);
const mentionSpan = page.locator(`span[title="Mentions user"][data-uid="${Users.user2.data.username}"]`);
await mentionSpan.click();
await expect(page.locator('div[aria-label="User card actions"]')).toBeVisible();
const moreButton = await page.locator('div[aria-label="User card actions"] button[title="More"]');
if (await moreButton.isVisible()) {
await moreButton.click();
}
const isAddToRoomVisible =
(await page.locator('button[title="Add to room"]').isVisible()) || (await page.locator('label[data-key="Add to room"]').isVisible());
await expect(isAddToRoomVisible).toBeFalsy();
const isRemoveFromRoomVisible =
(await page.locator('button[title="Remove from room"]').isVisible()) ||
(await page.locator('label[data-key="Remove from room"]').isVisible());
await expect(isRemoveFromRoomVisible).toBeFalsy();
const isSetAsLeaderVisible =
(await page.locator('button[title="Set as leader"]').isVisible()) ||
(await page.locator('label[data-key="Set as leader"]').isVisible());
await expect(isSetAsLeaderVisible).toBeFalsy();
const isSetAsModeratorVisible =
(await page.locator('button[title="Set as moderator"]').isVisible()) ||
(await page.locator('label[data-key="Set as moderator"]').isVisible());
await expect(isSetAsModeratorVisible).toBeFalsy();
});
});

@ -0,0 +1,90 @@
import { Users } from './fixtures/userStates';
import { HomeChannel } from './page-objects';
import { createTargetChannel, deleteChannel } from './utils';
import { expect, test } from './utils/test';
test.use({ storageState: Users.admin.state });
test.describe.parallel('Mention User Card [To Room Owner]', () => {
let poHomeChannel: HomeChannel;
let targetChannel: string;
test.beforeAll(async ({ api }) => {
targetChannel = await createTargetChannel(api, { members: [Users.user1.data.username] });
await api.post(`/chat.postMessage`, {
text: `Hello @${Users.user1.data.username} @${Users.user2.data.username}`,
channel: targetChannel,
});
});
test.beforeEach(async ({ page }) => {
poHomeChannel = new HomeChannel(page);
await page.goto('/home');
});
test.afterAll(({ api }) => deleteChannel(api, targetChannel));
test('should show correct userinfo actions for a member of the room to the room owner', async ({ page }) => {
await poHomeChannel.sidenav.openChat(targetChannel);
const mentionSpan = page.locator(`span[title="Mentions user"][data-uid="${Users.user1.data.username}"]`);
await mentionSpan.click();
await expect(page.locator('div[aria-label="User card actions"]')).toBeVisible();
const moreButton = await page.locator('div[aria-label="User card actions"] button[title="More"]');
if (await moreButton.isVisible()) {
await moreButton.click();
}
const isAddToRoomVisible =
(await page.locator('button[title="Add to room"]').isVisible()) || (await page.locator('label[data-key="Add to room"]').isVisible());
await expect(isAddToRoomVisible).toBeFalsy();
const isRemoveFromRoomVisible =
(await page.locator('button[title="Remove from room"]').isVisible()) ||
(await page.locator('label[data-key="Remove from room"]').isVisible());
await expect(isRemoveFromRoomVisible).toBeTruthy();
const isSetAsLeaderVisible =
(await page.locator('button[title="Set as leader"]').isVisible()) ||
(await page.locator('label[data-key="Set as leader"]').isVisible());
await expect(isSetAsLeaderVisible).toBeTruthy();
const isSetAsModeratorVisible =
(await page.locator('button[title="Set as moderator"]').isVisible()) ||
(await page.locator('label[data-key="Set as moderator"]').isVisible());
await expect(isSetAsModeratorVisible).toBeTruthy();
});
test('should show correct userinfo actions for a non-member of the room to the room owner', async ({ page }) => {
await poHomeChannel.sidenav.openChat(targetChannel);
const mentionSpan = page.locator(`span[title="Mentions user"][data-uid="${Users.user2.data.username}"]`);
await mentionSpan.click();
await expect(page.locator('div[aria-label="User card actions"]')).toBeVisible();
const moreButton = await page.locator('div[aria-label="User card actions"] button[title="More"]');
if (await moreButton.isVisible()) {
await moreButton.click();
}
const isAddToRoomVisible =
(await page.locator('button[title="Add to room"]').isVisible()) || (await page.locator('label[data-key="Add to room"]').isVisible());
await expect(isAddToRoomVisible).toBeTruthy();
const isRemoveFromRoomVisible =
(await page.locator('button[title="Remove from room"]').isVisible()) ||
(await page.locator('label[data-key="Remove from room"]').isVisible());
await expect(isRemoveFromRoomVisible).toBeFalsy();
const isSetAsLeaderVisible =
(await page.locator('button[title="Set as leader"]').isVisible()) ||
(await page.locator('label[data-key="Set as leader"]').isVisible());
await expect(isSetAsLeaderVisible).toBeFalsy();
const isSetAsModeratorVisible =
(await page.locator('button[title="Set as moderator"]').isVisible()) ||
(await page.locator('label[data-key="Set as moderator"]').isVisible());
await expect(isSetAsModeratorVisible).toBeFalsy();
});
});

@ -3032,4 +3032,286 @@ describe('[Rooms]', () => {
});
});
});
describe('/rooms.isMember', () => {
let testChannel: IRoom;
let testGroup: IRoom;
let testDM: IRoom;
const fakeRoomId = `room.test.${Date.now()}-${Math.random()}`;
const fakeUserId = `user.test.${Date.now()}-${Math.random()}`;
const testChannelName = `channel.test.${Date.now()}-${Math.random()}`;
const testGroupName = `group.test.${Date.now()}-${Math.random()}`;
let testUser1: TestUser<IUser>;
let testUser2: TestUser<IUser>;
let testUserNonMember: TestUser<IUser>;
let testUser1Credentials: Credentials;
let testUserNonMemberCredentials: Credentials;
before(async () => {
testUser1 = await createUser();
testUser1Credentials = await login(testUser1.username, password);
});
before(async () => {
testUser2 = await createUser();
});
before(async () => {
testUserNonMember = await createUser();
testUserNonMemberCredentials = await login(testUserNonMember.username, password);
});
before(async () => {
const response = await createRoom({
type: 'c',
name: testChannelName,
members: [testUser1.username, testUser2.username],
});
testChannel = response.body.channel;
});
before(async () => {
const response = await createRoom({
type: 'p',
name: testGroupName,
members: [testUser1.username, testUser2.username],
});
testGroup = response.body.group;
});
before(async () => {
const response = await createRoom({
type: 'd',
username: testUser2.username,
credentials: testUser1Credentials,
});
testDM = response.body.room;
});
after(() =>
Promise.all([
deleteRoom({ type: 'c', roomId: testChannel._id }),
deleteRoom({ type: 'p', roomId: testGroup._id }),
deleteRoom({ type: 'd', roomId: testDM._id }),
deleteUser(testUser1),
deleteUser(testUser2),
deleteUser(testUserNonMember),
]),
);
it('should return error if room not found', () => {
return request
.get(api('rooms.isMember'))
.set(testUser1Credentials)
.query({
roomId: fakeRoomId,
userId: testUser1._id,
})
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property(
'error',
'The required "roomId" or "roomName" param provided does not match any channel [error-room-not-found]',
);
});
});
it('should return error if user not found with the given userId', () => {
return request
.get(api('rooms.isMember'))
.set(testUser1Credentials)
.query({
roomId: testChannel._id,
userId: fakeUserId,
})
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'error-user-not-found');
});
});
it('should return error if user not found with the given username', () => {
return request
.get(api('rooms.isMember'))
.set(testUser1Credentials)
.query({
roomId: testChannel._id,
username: fakeUserId,
})
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'error-user-not-found');
});
});
it('should return success with isMember=true if given userId is a member of the channel', () => {
return request
.get(api('rooms.isMember'))
.set(testUser1Credentials)
.query({
roomId: testChannel._id,
userId: testUser2._id,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('isMember', true);
});
});
it('should return success with isMember=true if given username is a member of the channel', () => {
return request
.get(api('rooms.isMember'))
.set(testUser1Credentials)
.query({
roomId: testChannel._id,
username: testUser2.username,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('isMember', true);
});
});
it('should return success with isMember=false if user is not a member of the channel', () => {
return request
.get(api('rooms.isMember'))
.set(testUser1Credentials)
.query({
roomId: testChannel._id,
userId: testUserNonMember._id,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('isMember', false);
});
});
it('should return success with isMember=true if given userId is a member of the group', () => {
return request
.get(api('rooms.isMember'))
.set(testUser1Credentials)
.query({
roomId: testGroup._id,
userId: testUser2._id,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('isMember', true);
});
});
it('should return success with isMember=true if given username is a member of the group', () => {
return request
.get(api('rooms.isMember'))
.set(testUser1Credentials)
.query({
roomId: testGroup._id,
username: testUser2.username,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('isMember', true);
});
});
it('should return success with isMember=false if user is not a member of the group', () => {
return request
.get(api('rooms.isMember'))
.set(testUser1Credentials)
.query({
roomId: testGroup._id,
userId: testUserNonMember._id,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('isMember', false);
});
});
it('should return unauthorized if caller cannot access the group', () => {
return request
.get(api('rooms.isMember'))
.set(testUserNonMemberCredentials)
.query({
roomId: testGroup._id,
userId: testUser1._id,
})
.expect(403)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'unauthorized');
});
});
it('should return success with isMember=true if given userId is a member of the DM', () => {
return request
.get(api('rooms.isMember'))
.set(testUser1Credentials)
.query({
roomId: testDM._id,
userId: testUser2._id,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('isMember', true);
});
});
it('should return success with isMember=true if given username is a member of the DM', () => {
return request
.get(api('rooms.isMember'))
.set(testUser1Credentials)
.query({
roomId: testDM._id,
username: testUser2.username,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('isMember', true);
});
});
it('should return success with isMember=false if user is not a member of the DM', () => {
return request
.get(api('rooms.isMember'))
.set(testUser1Credentials)
.query({
roomId: testDM._id,
userId: testUserNonMember._id,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('isMember', false);
});
});
it('should return unauthorized if caller cannot access the DM', () => {
return request
.get(api('rooms.isMember'))
.set(testUserNonMemberCredentials)
.query({
roomId: testDM._id,
userId: testUser1._id,
})
.expect(403)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'unauthorized');
});
});
});
});

@ -331,6 +331,7 @@
"Add_User": "Add User",
"Add_users": "Add users",
"Add_members": "Add Members",
"add-to-room": "Add to room",
"add-all-to-room": "Add all users to a room",
"add-all-to-room_description": "Permission to add all users to a room",
"add-livechat-department-agents": "Add Omnichannel Agents to Departments",

@ -447,6 +447,21 @@ const GETRoomsNameExistsSchema = {
export const isGETRoomsNameExists = ajv.compile<GETRoomsNameExists>(GETRoomsNameExistsSchema);
type RoomsIsMemberProps = { roomId: string } & ({ username: string } | { userId: string });
const RoomsIsMemberPropsSchema = {
type: 'object',
properties: {
roomId: { type: 'string', minLength: 1 },
userId: { type: 'string', minLength: 1 },
username: { type: 'string', minLength: 1 },
},
oneOf: [{ required: ['roomId', 'userId'] }, { required: ['roomId', 'username'] }],
additionalProperties: false,
};
export const isRoomsIsMemberProps = ajv.compile<RoomsIsMemberProps>(RoomsIsMemberPropsSchema);
export type Notifications = {
disableNotifications: string;
muteGroupMentions: string;
@ -685,6 +700,10 @@ export type RoomsEndpoints = {
}>;
};
'/v1/rooms.isMember': {
GET: (params: RoomsIsMemberProps) => { isMember: boolean };
};
'/v1/rooms.muteUser': {
POST: (params: RoomsMuteUnmuteUser) => void;
};

Loading…
Cancel
Save