diff --git a/.changeset/ninety-carrots-flow.md b/.changeset/ninety-carrots-flow.md new file mode 100644 index 000000000000..3dcfaa8208db --- /dev/null +++ b/.changeset/ninety-carrots-flow.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +test: read receipts diff --git a/apps/meteor/tests/end-to-end/api/24-methods.js b/apps/meteor/tests/end-to-end/api/24-methods.js index 899eb0db7d5f..121a9a76dd7e 100644 --- a/apps/meteor/tests/end-to-end/api/24-methods.js +++ b/apps/meteor/tests/end-to-end/api/24-methods.js @@ -1,11 +1,14 @@ import { expect } from 'chai'; import { after, before, beforeEach, describe, it } from 'mocha'; -import { getCredentials, request, methodCall, api, credentials } from '../../data/api-data.js'; +import { api, credentials, getCredentials, methodCall, request } from '../../data/api-data.js'; +import { sendSimpleMessage } from '../../data/chat.helper.js'; import { CI_MAX_ROOMS_PER_GUEST as maxRoomsPerGuest } from '../../data/constants'; import { updatePermission, updateSetting } from '../../data/permissions.helper'; -import { createRoom } from '../../data/rooms.helper'; +import { createRoom, deleteRoom } from '../../data/rooms.helper'; +import { password } from '../../data/user'; import { createUser, deleteUser, login } from '../../data/users.helper.js'; +import { IS_EE } from '../../e2e/config/constants'; describe('Meteor.methods', function () { this.retries(0); @@ -123,6 +126,322 @@ describe('Meteor.methods', function () { }); }); + describe('[@getReadReceipts]', () => { + it('should fail if not logged in', async () => { + await request + .post(methodCall('getReadReceipts')) + .send({ + message: JSON.stringify({ + method: 'getReadReceipts', + params: [{ messageId: 'test' }], + id: 'id', + msg: 'method', + }), + }) + .expect('Content-Type', 'application/json') + .expect(401) + .expect((res) => { + expect(res.body).to.have.property('status', 'error'); + expect(res.body).to.have.property('message', 'You must be logged in to do this.'); + }); + }); + + (!IS_EE ? describe : describe.skip)('[@getReadReceipts] CE', () => { + it('should fail if there is no enterprise license', async () => { + await request + .post(methodCall('getReadReceipts')) + .set(credentials) + .send({ + message: JSON.stringify({ + method: 'getReadReceipts', + params: [{ messageId: 'test' }], + id: 'id', + msg: 'method', + }), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + const data = JSON.parse(res.body.message); + expect(data).to.have.property('error').that.is.an('object'); + expect(data.error).to.have.property('error', 'error-action-not-allowed'); + expect(data.error).to.have.property('message', 'This is an enterprise feature [error-action-not-allowed]'); + }); + }); + }); + + (IS_EE ? describe : describe.skip)('[@getReadReceipts] EE', () => { + let user = null; + let userCredentials = null; + let room = null; + let firstMessage = null; + let firstThreadMessage = null; + + const roomName = `methods-test-channel-${Date.now()}`; + before(async () => { + await updateSetting('Message_Read_Receipt_Enabled', true); + await updateSetting('Message_Read_Receipt_Store_Users', true); + + user = await createUser(); + userCredentials = await login(user.username, password); + room = (await createRoom({ type: 'p', name: roomName, members: [user.username] })).body.group; + firstMessage = (await sendSimpleMessage({ roomId: room._id })).body.message; + firstThreadMessage = (await sendSimpleMessage({ roomId: room._id, tmid: firstMessage._id })).body.message; + }); + + after(async () => { + await deleteRoom({ type: 'p', roomId: room._id }); + await deleteUser(user); + }); + + describe('simple message and thread that nobody has read yet', () => { + it("should return only the sender's read receipt for a message sent in the main room", async () => { + await request + .post(methodCall('getReadReceipts')) + .set(credentials) + .send({ + message: JSON.stringify({ + method: 'getReadReceipts', + params: [{ messageId: firstMessage._id }], + id: 'id', + msg: 'method', + }), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.a.property('success', true); + expect(res.body).to.have.a.property('message').that.is.a('string'); + + const data = JSON.parse(res.body.message); + expect(data).to.have.a.property('result').that.is.an('array'); + expect(data.result.length).to.equal(1); + expect(data.result[0]).to.have.property('userId', credentials['X-User-Id']); + }); + }); + + it("should return only the sender's read receipt for a message sent in a thread", async () => { + await request + .post(methodCall('getReadReceipts')) + .set(credentials) + .send({ + message: JSON.stringify({ + method: 'getReadReceipts', + params: [{ messageId: firstThreadMessage._id }], + id: 'id', + msg: 'method', + }), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.a.property('success', true); + expect(res.body).to.have.a.property('message').that.is.a('string'); + + const data = JSON.parse(res.body.message); + expect(data).to.have.a.property('result').that.is.an('array'); + expect(data.result.length).to.equal(1); + expect(data.result[0]).to.have.property('userId', credentials['X-User-Id']); + }); + }); + }); + + describe('simple message and thread where the room message was read by the invited user but the thread message was not', () => { + before("should read all main room's messages with the invited user", async () => { + await request + .post(methodCall('readMessages')) + .set(userCredentials) + .send({ + message: JSON.stringify({ + id: 'id', + msg: 'method', + method: 'readMessages', + params: [room._id, true], + }), + }); + }); + + it("should return both the sender's and the invited user's read receipt for a message sent in the main room", async () => { + await request + .post(methodCall('getReadReceipts')) + .set(credentials) + .send({ + message: JSON.stringify({ + method: 'getReadReceipts', + params: [{ messageId: firstMessage._id }], + id: 'id', + msg: 'method', + }), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.a.property('success', true); + expect(res.body).to.have.a.property('message').that.is.a('string'); + + const data = JSON.parse(res.body.message); + expect(data).to.have.a.property('result').that.is.an('array'); + expect(data.result.length).to.equal(2); + + const receiptsUserIds = [data.result[0].userId, data.result[1].userId]; + expect(receiptsUserIds).to.have.members([credentials['X-User-Id'], user._id]); + }); + }); + + it("should return only the sender's read receipt for a message sent in a thread", async () => { + await request + .post(methodCall('getReadReceipts')) + .set(credentials) + .send({ + message: JSON.stringify({ + method: 'getReadReceipts', + params: [{ messageId: firstThreadMessage._id }], + id: 'id', + msg: 'method', + }), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.a.property('success', true); + expect(res.body).to.have.a.property('message').that.is.a('string'); + + const data = JSON.parse(res.body.message); + expect(data).to.have.a.property('result').that.is.an('array'); + expect(data.result.length).to.equal(1); + expect(data.result[0]).to.have.property('userId', credentials['X-User-Id']); + }); + }); + }); + + describe('simple message and thread where both was read by the invited user', () => { + before('should read thread messages with the invited user', async () => { + await request + .post(methodCall('getThreadMessages')) + .set(userCredentials) + .send({ + message: JSON.stringify({ + id: 'id', + msg: 'method', + method: 'getThreadMessages', + params: [ + { + tmid: firstMessage._id, + }, + ], + }), + }); + }); + + it("should return both the sender's and invited user's read receipt for a message sent in a thread", async () => { + await request + .post(methodCall('getReadReceipts')) + .set(credentials) + .send({ + message: JSON.stringify({ + method: 'getReadReceipts', + params: [{ messageId: firstThreadMessage._id }], + id: 'id', + msg: 'method', + }), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.a.property('success', true); + expect(res.body).to.have.a.property('message').that.is.a('string'); + + const data = JSON.parse(res.body.message); + expect(data).to.have.a.property('result').that.is.an('array'); + expect(data.result.length).to.equal(2); + + const receiptsUserIds = [data.result[0].userId, data.result[1].userId]; + expect(receiptsUserIds).to.have.members([credentials['X-User-Id'], user._id]); + }); + }); + }); + + describe('simple message and thread marked as read by the invited user', () => { + let otherMessage = null; + let otherThreadMessage = null; + + before('should send another message and create a thread', async () => { + otherMessage = (await sendSimpleMessage({ roomId: room._id })).body.message; + otherThreadMessage = (await sendSimpleMessage({ roomId: room._id, tmid: otherMessage._id })).body.message; + }); + + before('should mark the thread as read by the invited user', async () => { + await request + .post(methodCall('readThreads')) + .set(userCredentials) + .send({ + message: JSON.stringify({ + method: 'readThreads', + params: [otherMessage._id], + id: 'id', + msg: 'method', + }), + }); + }); + + it("should return both the sender's and invited user's read receipt for a message sent in the main room", async () => { + await request + .post(methodCall('getReadReceipts')) + .set(credentials) + .send({ + message: JSON.stringify({ + method: 'getReadReceipts', + params: [{ messageId: otherThreadMessage._id }], + id: 'id', + msg: 'method', + }), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.a.property('success', true); + expect(res.body).to.have.a.property('message').that.is.a('string'); + + const data = JSON.parse(res.body.message); + expect(data).to.have.a.property('result').that.is.an('array'); + expect(data.result.length).to.equal(2); + + const receiptsUserIds = [data.result[0].userId, data.result[1].userId]; + expect(receiptsUserIds).to.have.members([credentials['X-User-Id'], user._id]); + }); + }); + + it("should return both the sender's and invited user's read receipt for a message sent in a thread", async () => { + await request + .post(methodCall('getReadReceipts')) + .set(credentials) + .send({ + message: JSON.stringify({ + method: 'getReadReceipts', + params: [{ messageId: otherThreadMessage._id }], + id: 'id', + msg: 'method', + }), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.a.property('success', true); + expect(res.body).to.have.a.property('message').that.is.a('string'); + + const data = JSON.parse(res.body.message); + expect(data).to.have.a.property('result').that.is.an('array'); + expect(data.result.length).to.equal(2); + + const receiptsUserIds = [data.result[0].userId, data.result[1].userId]; + expect(receiptsUserIds).to.have.members([credentials['X-User-Id'], user._id]); + }); + }); + }); + }); + }); + describe('[@getMessages]', () => { let rid = false; let firstMessage = false;