import type { Credentials } from '@rocket.chat/api-client'; import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; import { Random } from '@rocket.chat/random'; import { expect } from 'chai'; import { after, before, describe, it } from 'mocha'; import { getCredentials, api, request, credentials, apiUsername, apiEmail, methodCall } from '../../data/api-data'; import { pinMessage, sendMessage, starMessage, updateMessage } from '../../data/chat.helper'; import { updateSetting, updatePermission } from '../../data/permissions.helper'; import { deleteRoom } from '../../data/rooms.helper'; import { testFileUploads } from '../../data/uploads.helper'; import { password, adminUsername } from '../../data/user'; import type { TestUser } from '../../data/users.helper'; import { createUser, deleteUser, login, setUserStatus } from '../../data/users.helper'; describe('[Direct Messages]', () => { let testDM: IRoom & { rid: IRoom['_id'] }; let user: TestUser; let directMessage: { _id: IRoom['_id'] }; before((done) => getCredentials(done)); before(async () => { user = await createUser(); const cred = await login(user.username, password); await setUserStatus(cred); await request .post(api('im.create')) .set(credentials) .send({ username: user.username, }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { testDM = res.body.room; }); }); before('/chat.postMessage', (done) => { void request .post(api('chat.postMessage')) .set(credentials) .send({ roomId: testDM.rid, text: 'This message was sent using the API', }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.nested.property('message.msg', 'This message was sent using the API'); expect(res.body).to.have.nested.property('message.rid'); directMessage = { _id: res.body.message.rid }; }) .end(done); }); after(() => deleteUser(user)); describe('/im.blockUser', () => { const fetchOwnSubscription = async (roomId: IRoom['_id']) => { const res = await request.get(api('subscriptions.getOne')).set(credentials).query({ roomId }).expect(200); return res.body.subscription; }; it('should return unauthorized when not authenticated', async () => { await request.post(api('im.blockUser')).send({ roomId: directMessage._id, block: true }).expect(401); }); it('should return bad request when roomId is missing', async () => { await request.post(api('im.blockUser')).set(credentials).send({ block: true }).expect(400); }); it('should return bad request when block flag is missing', async () => { await request.post(api('im.blockUser')).set(credentials).send({ roomId: directMessage._id }).expect(400); }); it('should block the other DM participant and flip the subscription blocker flag', async () => { await request .post(api('im.blockUser')) .set(credentials) .send({ roomId: directMessage._id, block: true }) .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); }); const subscription = await fetchOwnSubscription(directMessage._id); expect(subscription).to.have.property('blocker', true); }); it('should unblock the other DM participant and clear the subscription blocker flag', async () => { await request .post(api('im.blockUser')) .set(credentials) .send({ roomId: directMessage._id, block: false }) .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); }); const subscription = await fetchOwnSubscription(directMessage._id); expect(Boolean(subscription.blocker)).to.equal(false); }); it('should fail when called on a non-DM room', async () => { await request .post(api('im.blockUser')) .set(credentials) .send({ roomId: 'GENERAL', block: true }) .expect((res) => { expect(res.body).to.have.property('success', false); }); }); }); describe('/im.setTopic', () => { it('should set the topic of the DM with a string', async () => { const res = await request .post(api('im.setTopic')) .set(credentials) .send({ roomId: directMessage._id, topic: `a direct message with ${user.username}`, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); expect(res.body).to.have.nested.property('topic', `a direct message with ${user.username}`); }); it('should set the topic of DM with an empty string(remove the topic)', async () => { const res = await request .post(api('im.setTopic')) .set(credentials) .send({ roomId: directMessage._id, topic: '', }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); expect(res.body).to.have.nested.property('topic', ''); }); }); describe('Testing DM info', () => { let dmMessage: IMessage; it('sending a message...', async () => { const res = await request .post(api('chat.sendMessage')) .set(credentials) .send({ message: { text: 'Sample message', rid: testDM._id, }, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); dmMessage = res.body.message; }); it('REACTing with last message', async () => { const res = await request .post(api('chat.react')) .set(credentials) .send({ emoji: ':squid:', messageId: dmMessage._id, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); }); it('STARring last message', async () => { const res = await request .post(api('chat.starMessage')) .set(credentials) .send({ messageId: dmMessage._id, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); }); it('PINning last message', async () => { const res = await request .post(api('chat.pinMessage')) .set(credentials) .send({ messageId: dmMessage._id, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); }); it('should return all DM messages where the last message of array should have the "star" array with USERS star ONLY', async () => { const res = await request .get(api('im.messages')) .set(credentials) .query({ roomId: testDM._id, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('messages').and.to.be.an('array'); const messages = res.body.messages as IMessage[]; const lastMessage = messages.filter((message) => message._id === dmMessage._id)[0]; expect(lastMessage).to.have.property('starred').and.to.be.an('array'); expect(lastMessage.starred?.[0]._id).to.be.equal(adminUsername); }); }); it('/im.history', async () => { const res = await request .get(api('im.history')) .set(credentials) .query({ roomId: directMessage._id, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('messages'); }); describe('/im.history inclusive parameter', () => { let testDMRoom: IRoom; let testUser2: TestUser; let oldestMessage: IMessage; let middleMessage: IMessage; let latestMessage: IMessage; before(async () => { testUser2 = await createUser(); const dmRes = await request.post(api('im.create')).set(credentials).send({ username: testUser2.username }); testDMRoom = dmRes.body.room; // Send messages with small delays to ensure distinct timestamps const msg1 = await sendMessage({ message: { rid: testDMRoom._id, msg: 'oldest message' } }); oldestMessage = msg1.body.message; // Small delay to ensure timestamps are different await new Promise((resolve) => setTimeout(resolve, 50)); const msg2 = await sendMessage({ message: { rid: testDMRoom._id, msg: 'middle message' } }); middleMessage = msg2.body.message; await new Promise((resolve) => setTimeout(resolve, 50)); const msg3 = await sendMessage({ message: { rid: testDMRoom._id, msg: 'latest message' } }); latestMessage = msg3.body.message; }); after(async () => { if (testDMRoom?._id) { await deleteRoom({ type: 'd', roomId: testDMRoom._id }); } if (testUser2) { await deleteUser(testUser2); } }); it('should include boundary messages when inclusive=true', async () => { const res = await request .get(api('im.history')) .set(credentials) .query({ roomId: testDMRoom._id, oldest: oldestMessage.ts, latest: latestMessage.ts, inclusive: 'true', }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('messages').that.is.an('array'); const messageIds = res.body.messages.map((m: IMessage) => m._id); expect(messageIds).to.include(oldestMessage._id, 'oldest message should be included'); expect(messageIds).to.include(latestMessage._id, 'latest message should be included'); }); it('should exclude boundary messages when inclusive=false', async () => { const res = await request .get(api('im.history')) .set(credentials) .query({ roomId: testDMRoom._id, oldest: oldestMessage.ts, latest: latestMessage.ts, inclusive: 'false', }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('messages').that.is.an('array'); const messageIds = res.body.messages.map((m: IMessage) => m._id); expect(messageIds).to.not.include(oldestMessage._id, 'oldest message should be excluded'); expect(messageIds).to.not.include(latestMessage._id, 'latest message should be excluded'); // Middle message should still be included if it exists in the range expect(messageIds).to.include(middleMessage._id, 'middle message should be included'); }); it('should exclude boundary messages by default (no inclusive param)', async () => { const res = await request .get(api('im.history')) .set(credentials) .query({ roomId: testDMRoom._id, oldest: oldestMessage.ts, latest: latestMessage.ts, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('messages').that.is.an('array'); const messageIds = res.body.messages.map((m: IMessage) => m._id); expect(messageIds).to.not.include(oldestMessage._id, 'oldest message should be excluded by default'); expect(messageIds).to.not.include(latestMessage._id, 'latest message should be excluded by default'); }); }); it('/im.list', async () => { const res = await request.get(api('im.list')).set(credentials).expect('Content-Type', 'application/json').expect(200); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('count'); expect(res.body).to.have.property('total'); expect(res.body).to.have.property('ims').and.to.be.an('array'); const im = (res.body.ims as IRoom[]).find((dm) => dm._id === testDM._id); expect(im).to.have.property('_id'); expect(im).to.have.property('t').and.to.be.eq('d'); expect(im).to.have.property('msgs').and.to.be.a('number'); expect(im).to.have.property('usernames').and.to.be.an('array'); expect(im).to.have.property('lm'); expect(im).to.have.property('_updatedAt'); expect(im).to.have.property('ts'); expect(im).to.have.property('lastMessage'); }); describe('/im.list.everyone', () => { before(async () => { return updatePermission('view-room-administration', ['admin']); }); after(async () => { return updatePermission('view-room-administration', ['admin']); }); it('should succesfully return a list of direct messages', async () => { await request .get(api('im.list.everyone')) .set(credentials) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('count', 1); expect(res.body).to.have.property('total', 1); expect(res.body).to.have.property('ims').and.to.be.an('array'); const im = res.body.ims[0]; expect(im).to.have.property('_id'); expect(im).to.have.property('t').and.to.be.eq('d'); expect(im).to.have.property('msgs').and.to.be.a('number'); expect(im).to.have.property('usernames').and.to.be.an('array'); expect(im).to.have.property('ro'); expect(im).to.have.property('sysMes'); expect(im).to.have.property('_updatedAt'); expect(im).to.have.property('ts'); expect(im).to.have.property('lastMessage'); }); }); it('should fail if user does NOT have the view-room-administration permission', async () => { await updatePermission('view-room-administration', []); await request .get(api('im.list.everyone')) .set(credentials) .expect('Content-Type', 'application/json') .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }); }); }); describe("Setting: 'Use Real Name': true", () => { before(async () => updateSetting('UI_Use_Real_Name', true)); after(async () => updateSetting('UI_Use_Real_Name', false)); it('/im.list', async () => { const res = await request.get(api('im.list')).set(credentials).expect('Content-Type', 'application/json').expect(200); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('count'); expect(res.body).to.have.property('total'); expect(res.body).to.have.property('ims').and.to.be.an('array'); const im = (res.body.ims as IRoom[]).find((dm) => dm._id === testDM._id) as IRoom; expect(im).to.have.property('_id'); expect(im).to.have.property('t').and.to.be.eq('d'); expect(im).to.have.property('msgs').and.to.be.a('number'); expect(im).to.have.property('usernames').and.to.be.an('array'); expect(im).to.have.property('lm'); expect(im).to.have.property('_updatedAt'); expect(im).to.have.property('ts'); expect(im).to.have.property('lastMessage'); const { lastMessage } = im; expect(lastMessage).to.have.nested.property('u.name', 'RocketChat Internal Admin Test'); }); it('/im.list.everyone', async () => { const res = await request.get(api('im.list.everyone')).set(credentials).expect('Content-Type', 'application/json').expect(200); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('count'); expect(res.body).to.have.property('total'); expect(res.body).to.have.property('ims').and.to.be.an('array'); const im = (res.body.ims as IRoom[]).find((dm) => dm._id === testDM._id) as IRoom; expect(im).to.have.property('_id'); expect(im).to.have.property('t').and.to.be.eq('d'); expect(im).to.have.property('msgs').and.to.be.a('number'); expect(im).to.have.property('usernames').and.to.be.an('array'); expect(im).to.have.property('ro'); expect(im).to.have.property('sysMes'); expect(im).to.have.property('_updatedAt'); expect(im).to.have.property('ts'); expect(im).to.have.property('lastMessage'); const { lastMessage } = im; expect(lastMessage).to.have.nested.property('u.name', 'RocketChat Internal Admin Test'); }); }); it('/im.open', async () => { const res = await request .post(api('im.open')) .set(credentials) .send({ roomId: directMessage._id, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); }); describe('/im.counters', () => { it('should require auth', async () => { await request .get(api('im.counters')) .expect('Content-Type', 'application/json') .expect(401) .expect((res) => { expect(res.body).to.have.property('status', 'error'); }); }); it('should require a roomId', async () => { await request .get(api('im.counters')) .set(credentials) .expect('Content-Type', 'application/json') .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); }); }); it('should work with all params right', async () => { const res = await request .get(api('im.counters')) .set(credentials) .query({ roomId: directMessage._id, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('joined', true); expect(res.body).to.have.property('members'); expect(res.body).to.have.property('unreads'); expect(res.body).to.have.property('unreadsFrom'); expect(res.body).to.have.property('msgs'); expect(res.body).to.have.property('latest'); expect(res.body).to.have.property('userMentions'); }); describe('with valid room id', () => { let testDM: IRoom & { rid: IRoom['_id'] }; let user2: TestUser; let userCreds: Credentials; before(async () => { user2 = await createUser(); userCreds = await login(user2.username, password); await request .post(api('im.create')) .set(credentials) .send({ username: user2.username, }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { testDM = res.body.room; }); await request .post(api('chat.sendMessage')) .set(credentials) .send({ message: { text: 'Sample message', rid: testDM._id, }, }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); }); }); after(async () => { await request .post(api('im.delete')) .set(credentials) .send({ roomId: testDM._id, }) .expect(200); await deleteUser(user2); }); it('should properly return counters before opening the dm', async () => { await request .get(api('im.counters')) .set(userCreds) .query({ roomId: testDM._id, }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('joined', true); expect(res.body).to.have.property('members').and.to.be.a('number').and.to.be.eq(2); expect(res.body).to.have.property('unreads').and.to.be.a('number').and.to.be.eq(1); expect(res.body).to.have.property('unreadsFrom'); expect(res.body).to.have.property('msgs').and.to.be.a('number').and.to.be.eq(1); expect(res.body).to.have.property('latest'); expect(res.body).to.have.property('userMentions').and.to.be.a('number').and.to.be.eq(0); }); }); }); describe('with deactived users', async () => { before(() => request.post(api('users.setActiveStatus')).set(credentials).send({ userId: user._id, activeStatus: false })); after(() => request.post(api('users.setActiveStatus')).set(credentials).send({ userId: user._id, activeStatus: true })); it('should not include deactivated users in members count', async () => { // Deactivate the second user const res = await request.get(api('im.counters')).set(credentials).query({ roomId: directMessage._id }); expect(res.status).to.equal(200); expect(res.body.success).to.be.true; // Only admin remain active expect(res.body.members).to.equal(1); }); }); }); describe('[/im.files]', async () => { await testFileUploads('im.files', 'd', 'invalid-channel'); }); describe('/im.messages', () => { let testUser: IUser; let testUser2: IUser; let testUserDMRoom: IRoom; let testUserCredentials: Credentials; let testUser2Credentials: Credentials; let pinnedMessageId: IMessage['_id']; let messages: Pick[] = []; before(async () => { [testUser, testUser2] = await Promise.all([ createUser({ joinDefaultChannels: false, roles: ['admin'], username: `a_${Random.id()}` }), createUser({ joinDefaultChannels: false, username: `b_${Random.id()}` }), ]); [testUserCredentials, testUser2Credentials] = await Promise.all([ login(testUser.username, password), login(testUser2.username, password), ]); await setUserStatus(testUserCredentials); testUserDMRoom = ( await request .post(api('im.create')) .set(testUserCredentials) .send({ username: `${testUser2.username}` }) ).body.room; messages = [ { rid: testUserDMRoom._id, msg: `@${adminUsername} youre being mentioned`, mentions: [{ username: adminUsername, _id: adminUsername, name: adminUsername }], }, { rid: testUserDMRoom._id, msg: `@${testUser.username} youre being mentioned`, mentions: [{ username: testUser.username, _id: testUser._id, name: testUser.name }], }, { rid: testUserDMRoom._id, msg: `A simple message`, }, { rid: testUserDMRoom._id, msg: `A pinned simple message`, }, ]; /** * We are not using `Promise.all` here because we want to ensure that each message is sent sequentially. * This approach helps in maintaining the order of messages by ts. */ const starredMessage = await sendMessage({ message: messages[0], requestCredentials: testUserCredentials }); const pinnedMessage = await sendMessage({ message: messages[1], requestCredentials: testUser2Credentials }); await sendMessage({ message: messages[2], requestCredentials: testUserCredentials }); await sendMessage({ message: messages[3], requestCredentials: testUser2Credentials }); await Promise.all([ starMessage({ messageId: starredMessage.body.message._id, requestCredentials: testUserCredentials }), pinMessage({ messageId: pinnedMessage.body.message._id, requestCredentials: testUserCredentials }), ]); pinnedMessageId = pinnedMessage.body.message._id; }); after(async () => Promise.all([deleteUser(testUser), deleteUser(testUser2)])); it('should return all DM messages that were sent to yourself using your username', async () => { const res = await request .get(api('im.messages')) .set(testUserCredentials) .query({ username: testUser.username, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('messages').and.to.be.an('array'); }); it('should sort by ts by default', async () => { await request .get(api('im.messages')) .set(testUserCredentials) .query({ roomId: testUserDMRoom._id, }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body.messages.map((m: IMessage) => m.u.username)).to.deep.equal( res.body.messages .sort((a: IMessage, b: IMessage) => new Date(b.ts).getTime() - new Date(a.ts).getTime()) .map((m: IMessage) => m.u.username), ); }); }); it('should allow custom sorting', async () => { const { messages } = ( await request .get(api('im.messages')) .set(testUserCredentials) .query({ roomId: testUserDMRoom._id, }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); }) ).body; await request .get(api('im.messages')) .set(testUserCredentials) .query({ roomId: testUserDMRoom._id, sort: '{"u.username":-1}', }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body.messages.map((m: IMessage) => m.u.username)).to.deep.equal( messages.map((m: IMessage) => m.u.username).sort((a: string, b: string) => b.localeCompare(a)), ); }); }); it('should return an error when trying to access a DM that does not belong to the current user', async () => { await request .get(api('im.messages')) .set(credentials) .query({ roomId: testUserDMRoom._id }) .expect('Content-Type', 'application/json') .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); expect(res.body).to.have.property('error', 'unauthorized'); }); }); it('should return messages that mention a single user', async () => { await request .get(api('im.messages')) .set(testUserCredentials) .query({ roomId: testUserDMRoom._id, mentionIds: adminUsername, }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body.messages).to.have.lengthOf(1); expect(res.body.messages[0]).to.have.nested.property('mentions').that.is.an('array').and.to.have.lengthOf(1); expect(res.body.messages[0].mentions[0]).to.have.property('_id', adminUsername); expect(res.body).to.have.property('count', 1); expect(res.body).to.have.property('total', 1); }); }); it('should return messages that mention multiple users', async () => { await request .get(api('im.messages')) .set(testUserCredentials) .query({ roomId: testUserDMRoom._id, mentionIds: `${adminUsername},${testUser._id}`, }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body.messages).to.have.lengthOf(2); expect(res.body).to.have.property('count', 2); expect(res.body).to.have.property('total', 2); const mentionIds = res.body.messages.map((message: any) => message.mentions[0]._id); expect(mentionIds).to.include.members([adminUsername, testUser._id]); }); }); it('should return messages that are starred by a specific user', async () => { await request .get(api('im.messages')) .set(testUserCredentials) .query({ roomId: testUserDMRoom._id, starredIds: testUser._id, }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body.messages).to.have.lengthOf(1); expect(res.body.messages[0]).to.have.nested.property('starred').that.is.an('array').and.to.have.lengthOf(1); expect(res.body).to.have.property('count', 1); expect(res.body).to.have.property('total', 1); }); }); it('should return messages that are pinned', async () => { await request .get(api('im.messages')) .set(testUserCredentials) .query({ roomId: testUserDMRoom._id, pinned: true, }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body.messages).to.have.lengthOf(1); expect(res.body.messages[0]).to.have.nested.property('pinned').that.is.an('boolean').and.to.be.true; expect(res.body.messages[0]).to.have.nested.property('pinnedBy').that.is.an('object'); expect(res.body.messages[0].pinnedBy).to.have.property('_id', testUser._id); expect(res.body).to.have.property('count', 1); expect(res.body).to.have.property('total', 1); }); }); describe('_hidden messages behavior when Message_KeepHistory is enabled', async () => { before(async () => { await updateSetting('Message_KeepHistory', true); await pinMessage({ messageId: pinnedMessageId, unpin: true, requestCredentials: testUserCredentials }); }); after(async () => { await updateSetting('Message_KeepHistory', false); }); it('should return all messages, without any pinned messages', async () => { await request .get(api('im.messages')) .set(testUserCredentials) .query({ roomId: testUserDMRoom._id }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('messages').and.to.be.an('array'); expect(res.body.messages).to.have.lengthOf(5); res.body.messages.forEach((msg: IMessage) => { expect(msg).to.not.have.property('pinned', true); expect(msg).to.not.have.property('_hidden'); }); }); }); it('should return no pinned messages', async () => { await request .get(api('im.messages')) .set(testUserCredentials) .query({ roomId: testUserDMRoom._id, pinned: true, }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body.messages).to.have.lengthOf(0); expect(res.body).to.have.property('count', 0); expect(res.body).to.have.property('total', 0); }); }); it('should not return old message when updating a message', async () => { await updateMessage({ msgId: pinnedMessageId, updatedMessage: 'message was unpinned', roomId: testUserDMRoom._id, requestCredentials: testUser2Credentials, }); await request .get(api('im.messages')) .set(testUserCredentials) .query({ roomId: testUserDMRoom._id }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('messages').and.to.be.an('array'); expect(res.body.messages).to.have.lengthOf(5); const updatedMessage = res.body.messages.find((msg: IMessage) => msg._id === pinnedMessageId); expect(updatedMessage).to.have.property('msg', 'message was unpinned'); expect(updatedMessage).to.have.property('editedAt'); res.body.messages.forEach((msg: IMessage) => { expect(msg).to.not.have.property('_hidden'); }); }); }); }); }); describe('/im.messages.others', () => { it('should fail when the endpoint is disabled and the user has permissions', async () => { await updateSetting('API_Enable_Direct_Message_History_EndPoint', false); await request .get(api('im.messages.others')) .set(credentials) .query({ roomId: directMessage._id, }) .expect('Content-Type', 'application/json') .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); expect(res.body).to.have.property('errorType', 'error-endpoint-disabled'); }); }); it('should fail when the endpoint is disabled and the user doesnt have permission', async () => { await updateSetting('API_Enable_Direct_Message_History_EndPoint', false); await updatePermission('view-room-administration', []); await request .get(api('im.messages.others')) .set(credentials) .query({ roomId: directMessage._id, }) .expect('Content-Type', 'application/json') .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }); }); it('should fail when the endpoint is enabled but the user doesnt have permission', async () => { await updateSetting('API_Enable_Direct_Message_History_EndPoint', true); await updatePermission('view-room-administration', []); await request .get(api('im.messages.others')) .set(credentials) .query({ roomId: directMessage._id, }) .expect('Content-Type', 'application/json') .expect(403) .expect((res) => { expect(res.body).to.have.property('success', false); expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); }); }); it('should succeed when the endpoint is enabled and user has permission', async () => { await updateSetting('API_Enable_Direct_Message_History_EndPoint', true); await updatePermission('view-room-administration', ['admin']); await request .get(api('im.messages.others')) .set(credentials) .query({ roomId: directMessage._id, }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('messages').and.to.be.an('array'); expect(res.body).to.have.property('offset'); expect(res.body).to.have.property('count'); expect(res.body).to.have.property('total'); }); }); }); it('/im.close', async () => { const res = await request .post(api('im.close')) .set(credentials) .send({ roomId: directMessage._id, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); }); describe('fname property', () => { const username = `fname_${apiUsername}`; const name = `Name fname_${apiUsername}`; const updatedName = `Updated Name fname_${apiUsername}`; const email = `fname_${apiEmail}`; let userId: IUser['_id']; let directMessageId: IMessage['_id']; let user: TestUser; before((done) => { void request .post(api('users.create')) .set(credentials) .send({ email, name, username, password, active: true, roles: ['user'], joinDefaultChannels: true, verified: true, }) .expect((res) => { user = res.body.user; userId = res.body.user._id; }) .end(done); }); before((done) => { void request .post(api('chat.postMessage')) .set(credentials) .send({ channel: `@${username}`, text: 'This message was sent using the API', }) .expect('Content-Type', 'application/json') .expect(200) .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.nested.property('message.msg', 'This message was sent using the API'); expect(res.body).to.have.nested.property('message.rid'); directMessageId = res.body.message.rid; }) .end(done); }); after(async () => deleteUser(user)); it('should have fname property', async () => { const res = await request .get(api('subscriptions.getOne')) .set(credentials) .query({ roomId: directMessageId, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); expect(res.body.subscription).to.have.property('name', username); expect(res.body.subscription).to.have.property('fname', name); }); it("should update user's name", async () => { const res = await request .post(api('users.update')) .set(credentials) .send({ userId, data: { name: updatedName, }, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); expect(res.body.user).to.have.property('name', updatedName); }); it('should have fname property updated', async () => { const res = await request .get(api('subscriptions.getOne')) .set(credentials) .query({ roomId: directMessageId, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); expect(res.body.subscription).to.have.property('name', username); expect(res.body.subscription).to.have.property('fname', updatedName); }); }); describe('/im.members', () => { it('should return and array with two members', async () => { const res = await request .get(api('im.members')) .set(credentials) .query({ roomId: directMessage._id, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('count').and.to.be.equal(2); expect(res.body).to.have.property('offset').and.to.be.equal(0); expect(res.body).to.have.property('total').and.to.be.equal(2); expect(res.body).to.have.property('members').and.to.have.lengthOf(2); }); it('should return and array with one member', async () => { const res = await request .get(api('im.members')) .set(credentials) .query({ username: user.username, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('count').and.to.be.equal(2); expect(res.body).to.have.property('offset').and.to.be.equal(0); expect(res.body).to.have.property('total').and.to.be.equal(2); expect(res.body).to.have.property('members').and.to.have.lengthOf(2); }); it('should return and array with one member queried by status', async () => { const res = await request .get(api('im.members')) .set(credentials) .query({ 'roomId': directMessage._id, 'status[]': ['online'], }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('count').and.to.be.equal(1); expect(res.body).to.have.property('offset').and.to.be.equal(0); expect(res.body).to.have.property('total').and.to.be.equal(1); expect(res.body).to.have.property('members').and.to.have.lengthOf(1); }); }); describe('/im.create', () => { let user: TestUser; let userCredentials: Credentials; let otherUser: TestUser; let otherUserCredentials: Credentials; let thirdUser: TestUser; let thirdUserCredentials: Credentials; let roomIds: Record = {}; // Names have to be in alfabetical order so we can test the room's fullname const userFullName = 'User A'; const otherUserFullName = 'User B'; const thirdUserFullName = 'User C'; before(async () => { user = await createUser({ name: userFullName }); otherUser = await createUser({ name: otherUserFullName }); thirdUser = await createUser({ name: thirdUserFullName }); userCredentials = await login(user.username, password); otherUserCredentials = await login(otherUser.username, password); thirdUserCredentials = await login(thirdUser.username, password); }); after(async () => { await Promise.all(Object.values(roomIds).map((roomId) => deleteRoom({ type: 'd', roomId }))); await deleteUser(user); await deleteUser(otherUser); await deleteUser(thirdUser); }); it('creates a DM between two other parties (including self)', async () => { const res = await request .post(api('im.create')) .set(userCredentials) .send({ usernames: [otherUser.username, thirdUser.username].join(','), }) .expect(200) .expect('Content-Type', 'application/json'); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('room').and.to.be.an('object'); expect(res.body.room).to.have.property('usernames').and.to.have.members([thirdUser.username, user.username, otherUser.username]); roomIds = { ...roomIds, multipleDm: res.body.room._id }; }); it('creates a DM between two other parties (excluding self)', async () => { const res = await request .post(api('im.create')) .set(credentials) .send({ usernames: [user.username, otherUser.username].join(','), excludeSelf: true, }) .expect(200) .expect('Content-Type', 'application/json'); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('room').and.to.be.an('object'); expect(res.body.room).to.have.property('usernames').and.to.have.members([user.username, otherUser.username]); roomIds = { ...roomIds, dm: res.body.room._id }; }); it('should create a self-DM', async () => { const res = await request .post(api('im.create')) .set(userCredentials) .send({ username: user.username, }) .expect(200) .expect('Content-Type', 'application/json'); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('room').and.to.be.an('object'); expect(res.body.room).to.have.property('usernames').and.to.have.members([user.username]); roomIds = { ...roomIds, self: res.body.room._id }; }); describe('should create dm with correct notification preferences', () => { let user: TestUser; let userCredentials: Credentials; let userPrefRoomId: IRoom['_id']; before(async () => { user = await createUser(); userCredentials = await login(user.username, password); }); after(async () => { if (userPrefRoomId) { await deleteRoom({ type: 'd', roomId: userPrefRoomId }); } await deleteUser(user); }); it('should save user preferences', async () => { await request .post(methodCall('saveUserPreferences')) .set(userCredentials) .send({ message: JSON.stringify({ id: 'id', msg: 'method', method: 'saveUserPreferences', params: [{ emailNotificationMode: 'nothing' }], }), }) .expect(200); }); it('should create a DM', async () => { const res = await request .post(api('im.create')) .set(userCredentials) .send({ usernames: [user.username, otherUser.username].join(','), }) .expect(200) .expect('Content-Type', 'application/json'); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('room').and.to.be.an('object'); expect(res.body.room).to.have.property('usernames').and.to.have.members([user.username, otherUser.username]); userPrefRoomId = res.body.room._id; }); it('should return the right user notification preferences in the dm', async () => { const res = await request .get(api('subscriptions.getOne')) .set(userCredentials) .query({ roomId: userPrefRoomId, }) .expect('Content-Type', 'application/json') .expect(200); expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('subscription').and.to.be.an('object'); expect(res.body).to.have.nested.property('subscription.emailNotifications').and.to.be.equal('nothing'); }); }); async function testRoomFNameForUser(testCredentials: Credentials, roomId: IRoom['_id'], fullName: string) { return request .get(api('subscriptions.getOne')) .set(testCredentials) .query({ roomId }) .expect(200) .expect('Content-Type', 'application/json') .expect((res) => { expect(res.body).to.have.property('success', true); expect(res.body).to.have.property('subscription').and.to.be.an('object'); expect(res.body.subscription).to.have.property('fname', fullName); }); } describe('Rooms fullName', () => { it("should be own user's name for self DM", async () => { await testRoomFNameForUser(userCredentials, roomIds.self, userFullName); }); it("should be other user's name concatenated for multiple users's DM for every user", async () => { await testRoomFNameForUser(userCredentials, roomIds.multipleDm, [otherUserFullName, thirdUserFullName].join(', ')); await testRoomFNameForUser(otherUserCredentials, roomIds.multipleDm, [userFullName, thirdUserFullName].join(', ')); await testRoomFNameForUser(thirdUserCredentials, roomIds.multipleDm, [userFullName, otherUserFullName].join(', ')); }); it("should be other user's name for DM for both users", async () => { await testRoomFNameForUser(userCredentials, roomIds.dm, otherUserFullName); await testRoomFNameForUser(otherUserCredentials, roomIds.dm, userFullName); }); }); }); describe('/im.delete', () => { let testDM: IRoom; it('/im.create', async () => { const res = await request .post(api('im.create')) .set(credentials) .send({ username: user.username, }) .expect(200) .expect('Content-Type', 'application/json'); testDM = res.body.room; }); it('/im.delete', async () => { const res = await request .post(api('im.delete')) .set(credentials) .send({ username: user.username, }) .expect(200) .expect('Content-Type', 'application/json'); expect(res.body).to.have.property('success', true); }); it('/im.open', async () => { const res = await request .post(api('im.open')) .set(credentials) .send({ roomId: testDM._id, }) .expect(403) .expect('Content-Type', 'application/json'); expect(res.body).to.have.property('success', false); expect(res.body).to.have.property('error', 'unauthorized'); }); describe('when authenticated as a non-admin user', () => { let otherUser: TestUser; let otherCredentials: Credentials; before(async () => { otherUser = await createUser(); otherCredentials = await login(otherUser.username, password); }); after(async () => { await deleteUser(otherUser); }); it('/im.create', async () => { const res = await request .post(api('im.create')) .set(credentials) .send({ username: otherUser.username, }) .expect(200) .expect('Content-Type', 'application/json'); testDM = res.body.room; }); it('/im.delete', async () => { const res = await request .post(api('im.delete')) .set(otherCredentials) .send({ roomId: testDM._id, }) .expect(400) .expect('Content-Type', 'application/json'); expect(res.body).to.have.property('success', false); }); }); }); });