From 4d837ad431ac30b362ab561f3c212d2359ff9997 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Thu, 19 Dec 2024 17:11:59 -0300 Subject: [PATCH 01/21] feat: adds new setting to ignore automatic messages --- apps/meteor/server/settings/omnichannel.ts | 8 ++++++++ packages/i18n/src/locales/en.i18n.json | 1 + 2 files changed, 9 insertions(+) diff --git a/apps/meteor/server/settings/omnichannel.ts b/apps/meteor/server/settings/omnichannel.ts index 37701cea8c417..209a328d0be6b 100644 --- a/apps/meteor/server/settings/omnichannel.ts +++ b/apps/meteor/server/settings/omnichannel.ts @@ -664,6 +664,14 @@ export const createOmniSettings = () => i18nLabel: 'Call_provider', enableQuery: omnichannelEnabledQuery, }); + + await this.add('Omnichannel_Metrics_Ignore_Automatic_Messages', false, { + type: 'boolean', + public: true, + group: 'Omnichannel', + section: 'Analytics', + i18nLabel: 'Omnichannel_Ignore_automatic_responses_for_performance_metrics', + }); }); await settingsRegistry.addGroup('SMS', async function () { await this.add('SMS_Enabled', false, { diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index b23646f9360a9..1bc26a7c26afc 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -3366,6 +3366,7 @@ "Omnichannel_placed_chat_on_hold": "Chat On Hold: {{comment}}", "Omnichannel_hide_conversation_after_closing": "Hide conversation after closing", "Omnichannel_hide_conversation_after_closing_description": "After closing the conversation you will be redirected to Home.", + "Omnichannel_Ignore_automatic_responses_for_performance_metrics": "Ignore bots activities for performance metrics", "Livechat_Block_Unknown_Contacts": "Block unknown contacts", "Livechat_Block_Unknown_Contacts_Description": "Conversations from people who are not on the contact list will not be able to be taken.", "Livechat_Block_Unverified_Contacts": "Block unverified contacts", From 90852def69be75ba2a5a97c40fef66bcb4d3df02 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Thu, 19 Dec 2024 17:13:37 -0300 Subject: [PATCH 02/21] feat: adds logic to skip metrics for automatic messages --- .../app/livechat/server/hooks/markRoomResponded.ts | 9 ++++++++- .../app/livechat/server/hooks/saveAnalyticsData.ts | 9 ++++++++- apps/meteor/app/livechat/server/lib/isMessageFromBot.ts | 7 +++++++ 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 apps/meteor/app/livechat/server/lib/isMessageFromBot.ts diff --git a/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts b/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts index 3720780339fb9..bfc0681d74ac9 100644 --- a/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts +++ b/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts @@ -6,13 +6,20 @@ import moment from 'moment'; import { callbacks } from '../../../../lib/callbacks'; import { notifyOnLivechatInquiryChanged } from '../../../lib/server/lib/notifyListener'; +import { settings } from '../../../settings/server'; +import { isMessageFromBot } from '../lib/isMessageFromBot'; export async function markRoomResponded( message: IMessage, room: IOmnichannelRoom, roomUpdater: Updater, ): Promise { - if (isSystemMessage(message) || isEditedMessage(message) || isMessageFromVisitor(message)) { + if ( + isSystemMessage(message) || + isEditedMessage(message) || + isMessageFromVisitor(message) || + ((await isMessageFromBot(message)) && settings.get('')) + ) { return; } diff --git a/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts b/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts index 9553e9fe981b8..79f6d4f1cdf9b 100644 --- a/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts +++ b/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts @@ -3,7 +3,9 @@ import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; import { LivechatRooms } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; +import { settings } from '../../../settings/server'; import { normalizeMessageFileUpload } from '../../../utils/server/functions/normalizeMessageFileUpload'; +import { isMessageFromBot } from '../lib/isMessageFromBot'; const getMetricValue = (metric: T | undefined, defaultValue: T): T => metric ?? defaultValue; const calculateTimeDifference = (startTime: T, now: Date): number => @@ -62,7 +64,12 @@ const getAnalyticsData = (room: IOmnichannelRoom, now: Date): Record { - if (!message || isEditedMessage(message) || isSystemMessage(message)) { + if ( + !message || + isEditedMessage(message) || + isSystemMessage(message) || + ((await isMessageFromBot(message)) && settings.get('')) + ) { return message; } diff --git a/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts b/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts new file mode 100644 index 0000000000000..5ce08fd3e0c7b --- /dev/null +++ b/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts @@ -0,0 +1,7 @@ +import type { IMessage } from '@rocket.chat/core-typings'; +import { Users } from '@rocket.chat/models'; + +export async function isMessageFromBot(message: IMessage): Promise { + const roles = await Users.findOneById(message.u._id, { projection: { roles: 1 } }); + return !!roles?.include('bot'); +} From 2bb55d268ebe7dc8ed0205eccb6314c42caeddca Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Mon, 30 Dec 2024 16:26:57 -0300 Subject: [PATCH 03/21] refactor: improves logic for isMessageFromBot method --- apps/meteor/app/livechat/server/hooks/markRoomResponded.ts | 2 +- apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts | 2 +- apps/meteor/app/livechat/server/lib/isMessageFromBot.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts b/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts index bfc0681d74ac9..f2649a6f0ff25 100644 --- a/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts +++ b/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts @@ -18,7 +18,7 @@ export async function markRoomResponded( isSystemMessage(message) || isEditedMessage(message) || isMessageFromVisitor(message) || - ((await isMessageFromBot(message)) && settings.get('')) + ((await isMessageFromBot(message)) && settings.get('Omnichannel_Metrics_Ignore_Automatic_Messages')) ) { return; } diff --git a/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts b/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts index 79f6d4f1cdf9b..72e794fc41fd5 100644 --- a/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts +++ b/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts @@ -68,7 +68,7 @@ callbacks.add( !message || isEditedMessage(message) || isSystemMessage(message) || - ((await isMessageFromBot(message)) && settings.get('')) + ((await isMessageFromBot(message)) && settings.get('Omnichannel_Metrics_Ignore_Automatic_Messages')) ) { return message; } diff --git a/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts b/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts index 5ce08fd3e0c7b..bddbf49100c5a 100644 --- a/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts +++ b/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts @@ -1,7 +1,7 @@ -import type { IMessage } from '@rocket.chat/core-typings'; +import type { IMessage, IUser } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; export async function isMessageFromBot(message: IMessage): Promise { - const roles = await Users.findOneById(message.u._id, { projection: { roles: 1 } }); - return !!roles?.include('bot'); + const user = await Users.findOneById>(message.u._id, { projection: { roles: 1 } }); + return !!user?.roles?.includes('bot'); } From ed8facd457165efba1f155b3e1b583333aa0cb54 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Mon, 30 Dec 2024 16:27:43 -0300 Subject: [PATCH 04/21] tests: adds unit test for isMessageFromBot method --- .../server/lib/isMessageFromBot.spec.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 apps/meteor/tests/unit/app/livechat/server/lib/isMessageFromBot.spec.ts diff --git a/apps/meteor/tests/unit/app/livechat/server/lib/isMessageFromBot.spec.ts b/apps/meteor/tests/unit/app/livechat/server/lib/isMessageFromBot.spec.ts new file mode 100644 index 0000000000000..8e64002fe5809 --- /dev/null +++ b/apps/meteor/tests/unit/app/livechat/server/lib/isMessageFromBot.spec.ts @@ -0,0 +1,37 @@ +import { expect } from 'chai'; +import p from 'proxyquire'; +import sinon from 'sinon'; + +import { createFakeMessage, createFakeUser } from '../../../../../mocks/data'; + +const modelsMock = { + Users: { + findOneById: sinon.stub(), + }, +}; + +const { isMessageFromBot } = p.noCallThru().load('../../../../../../app/livechat/server/lib/isMessageFromBot', { + '@rocket.chat/models': modelsMock, +}); + +describe('isMessageFromBot', () => { + const mockUser = createFakeUser({ roles: ['bot'] }); + const mockMessage = createFakeMessage(); + + beforeEach(() => { + modelsMock.Users.findOneById.reset(); + }); + + it('Should return true if user has bot role', async () => { + modelsMock.Users.findOneById.resolves(mockUser.roles); + const result = await isMessageFromBot(mockMessage); + expect(result).to.be.true; + }); + + it('Should return false if user does not have bot role', async () => { + mockUser.roles = ['user']; + modelsMock.Users.findOneById.resolves(mockUser.roles); + const result = await isMessageFromBot(mockMessage); + expect(result).to.be.false; + }); +}); From e0fff075b0167775c2e6899288d09da81b0f73d1 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Tue, 7 Jan 2025 14:32:32 -0300 Subject: [PATCH 05/21] refactor: inverts order of if statement validation to avoid unnecessary calls to db --- apps/meteor/app/livechat/server/hooks/markRoomResponded.ts | 2 +- apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts b/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts index f2649a6f0ff25..300dbe5784812 100644 --- a/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts +++ b/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts @@ -18,7 +18,7 @@ export async function markRoomResponded( isSystemMessage(message) || isEditedMessage(message) || isMessageFromVisitor(message) || - ((await isMessageFromBot(message)) && settings.get('Omnichannel_Metrics_Ignore_Automatic_Messages')) + (settings.get('Omnichannel_Metrics_Ignore_Automatic_Messages') && (await isMessageFromBot(message))) ) { return; } diff --git a/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts b/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts index 72e794fc41fd5..88144870b4513 100644 --- a/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts +++ b/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts @@ -68,7 +68,7 @@ callbacks.add( !message || isEditedMessage(message) || isSystemMessage(message) || - ((await isMessageFromBot(message)) && settings.get('Omnichannel_Metrics_Ignore_Automatic_Messages')) + (settings.get('Omnichannel_Metrics_Ignore_Automatic_Messages') && (await isMessageFromBot(message))) ) { return message; } From 3d1a78521ff90b24bfaee10fd1b9aabb14e22510 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Tue, 7 Jan 2025 16:09:51 -0300 Subject: [PATCH 06/21] refactor: adds validation for null returns from Users.findOneById --- apps/meteor/app/livechat/server/lib/isMessageFromBot.ts | 7 ++++++- .../unit/app/livechat/server/lib/isMessageFromBot.spec.ts | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts b/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts index bddbf49100c5a..05a2e0bab15ef 100644 --- a/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts +++ b/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts @@ -3,5 +3,10 @@ import { Users } from '@rocket.chat/models'; export async function isMessageFromBot(message: IMessage): Promise { const user = await Users.findOneById>(message.u._id, { projection: { roles: 1 } }); - return !!user?.roles?.includes('bot'); + + if (!user) { + throw new Error('User not found'); + } + + return !!user.roles.includes('bot'); } diff --git a/apps/meteor/tests/unit/app/livechat/server/lib/isMessageFromBot.spec.ts b/apps/meteor/tests/unit/app/livechat/server/lib/isMessageFromBot.spec.ts index 8e64002fe5809..08134cb50dc53 100644 --- a/apps/meteor/tests/unit/app/livechat/server/lib/isMessageFromBot.spec.ts +++ b/apps/meteor/tests/unit/app/livechat/server/lib/isMessageFromBot.spec.ts @@ -34,4 +34,10 @@ describe('isMessageFromBot', () => { const result = await isMessageFromBot(mockMessage); expect(result).to.be.false; }); + + it('Should throw error when user is not found', async () => { + modelsMock.Users.findOneById.resolves(null); + + expect(isMessageFromBot(mockMessage)).to.throw(Error('User mot found')); + }); }); From 15f46d104f3e21c4aa9734e4b43c219ab975086b Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Wed, 8 Jan 2025 11:24:18 -0300 Subject: [PATCH 07/21] test: adds e2e test to validate bot messages are ignored when setting is enabled --- .../end-to-end/api/livechat/04-dashboards.ts | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts index 351abecff21c2..8096beb46d307 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts @@ -16,7 +16,7 @@ import { sendMessage, startANewLivechatRoomAndTakeIt, } from '../../../data/livechat/rooms'; -import { createAnOnlineAgent } from '../../../data/livechat/users'; +import { createAnOnlineAgent, createBotAgent } from '../../../data/livechat/users'; import { sleep } from '../../../data/livechat/utils'; import { removePermissionFromAllRoles, restorePermissionToRoles, updateEESetting, updateSetting } from '../../../data/permissions.helper'; import { deleteUser } from '../../../data/users.helper'; @@ -923,6 +923,7 @@ describe('LIVECHAT - dashboards', function () { describe('[livechat/analytics/agent-overview] - Average first response time', () => { let agent: { credentials: Credentials; user: IUser & { username: string } }; let forwardAgent: { credentials: Credentials; user: IUser & { username: string } }; + let botAgent: { credentials: Credentials; user: IUser }; let originalFirstResponseTimeInSeconds: number; let roomId: string; const firstDelayInSeconds = 4; @@ -931,6 +932,7 @@ describe('LIVECHAT - dashboards', function () { before(async () => { agent = await createAnOnlineAgent(); forwardAgent = await createAnOnlineAgent(); + botAgent = await createBotAgent(); }); after(async () => Promise.all([deleteUser(agent.user), deleteUser(forwardAgent.user)])); @@ -984,6 +986,39 @@ describe('LIVECHAT - dashboards', function () { expect(originalFirstResponseTimeInSeconds).to.be.greaterThanOrEqual(firstDelayInSeconds); }); + it("should not consider bot messages in agent's first response time metric if setting is enabled", async () => { + await updateSetting('Omnichannel_Metrics_Ignore_Automatic_Messages', true); + + const response = await startANewLivechatRoomAndTakeIt({ agent: botAgent.credentials }); + + roomId = response.room._id; + + await sleep(firstDelayInSeconds * 1000); + + await sendAgentMessage(roomId, 'first response from agent', agent.credentials); + + const today = moment().startOf('day').format('YYYY-MM-DD'); + const result = await request + .get(api('livechat/analytics/agent-overview')) + .query({ from: today, to: today, name: 'Avg_first_response_time' }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(result.body).to.have.property('success', true); + expect(result.body).to.have.property('head'); + expect(result.body).to.have.property('data'); + expect(result.body.data).to.be.an('array'); + + const agentData = result.body.data.find( + (agentOverviewData: { name: string; value: string }) => agentOverviewData.name === agent.user.username, + ); + expect(agentData).to.be.undefined; + + originalFirstResponseTimeInSeconds = moment.duration(agentData.value).asSeconds(); + expect(originalFirstResponseTimeInSeconds).to.be.greaterThanOrEqual(firstDelayInSeconds); + }); + it('should correctly associate the first response time to the first agent who responded the room', async () => { const response = await startANewLivechatRoomAndTakeIt({ agent: forwardAgent.credentials }); roomId = response.room._id; From 6eb89ab3333d9526987bcb3e9ac01b560e339584 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Wed, 8 Jan 2025 11:26:45 -0300 Subject: [PATCH 08/21] test: adds unit test to check for setting and bot message on markRoomResponded --- .../livechat/hooks/markRoomResponded.spec.ts | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/meteor/tests/unit/server/livechat/hooks/markRoomResponded.spec.ts b/apps/meteor/tests/unit/server/livechat/hooks/markRoomResponded.spec.ts index d8372f6282c37..9b503774f91df 100644 --- a/apps/meteor/tests/unit/server/livechat/hooks/markRoomResponded.spec.ts +++ b/apps/meteor/tests/unit/server/livechat/hooks/markRoomResponded.spec.ts @@ -3,6 +3,8 @@ import { describe, it, beforeEach } from 'mocha'; import proxyquire from 'proxyquire'; import Sinon from 'sinon'; +import { createFakeMessage, createFakeUser } from '../../../../mocks/data'; + const models = { LivechatVisitors: { isVisitorActiveOnPeriod: Sinon.stub(), markVisitorActiveForPeriod: Sinon.stub() }, LivechatInquiry: { markInquiryActiveForPeriod: Sinon.stub() }, @@ -13,20 +15,27 @@ const models = { }, }; +const settingsGetMock = { + get: Sinon.stub(), +}; + const { markRoomResponded } = proxyquire.load('../../../../../app/livechat/server/hooks/markRoomResponded.ts', { '../../../../lib/callbacks': { callbacks: { add: Sinon.stub(), priority: { HIGH: 'high' } } }, '../../../lib/server/lib/notifyListener': { notifyOnLivechatInquiryChanged: Sinon.stub() }, '@rocket.chat/models': models, + '../../../settings/server': { settings: settingsGetMock }, }); describe('markRoomResponded', () => { - beforeEach(() => { + beforeEach(async () => { models.LivechatVisitors.isVisitorActiveOnPeriod.reset(); models.LivechatVisitors.markVisitorActiveForPeriod.reset(); models.LivechatInquiry.markInquiryActiveForPeriod.reset(); models.LivechatRooms.getVisitorActiveForPeriodUpdateQuery.reset(); models.LivechatRooms.getAgentLastMessageTsUpdateQuery.reset(); models.LivechatRooms.getResponseByRoomIdUpdateQuery.reset(); + + settingsGetMock.get.reset(); }); it('should return void if message is system message', async () => { @@ -66,6 +75,18 @@ describe('markRoomResponded', () => { expect(res).to.be.undefined; }); + it('should return void if message is from bot and setting is enabled', async () => { + settingsGetMock.get.withArgs('Omnichannel_Metrics_Ignore_Automatic_Messages').returns(true); + + const user = createFakeUser({ roles: ['bot'] }); + const message = createFakeMessage(); + const room = {}; + + const res = await markRoomResponded(message, room, user); + + expect(res).to.be.undefined; + }); + it('should try to mark visitor as active for current period', async () => { const message = {}; const room = { v: { _id: '1234' } }; From 297a649c1fc4522d999ad33238b48c76bcc1f769 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Mon, 20 Jan 2025 15:31:02 -0300 Subject: [PATCH 09/21] test: fixes e2e and unit tests --- apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts | 7 ++----- .../unit/server/livechat/hooks/markRoomResponded.spec.ts | 6 +++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts index 8096beb46d307..8072077080f7e 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts @@ -995,7 +995,7 @@ describe('LIVECHAT - dashboards', function () { await sleep(firstDelayInSeconds * 1000); - await sendAgentMessage(roomId, 'first response from agent', agent.credentials); + await sendAgentMessage(roomId, 'first response from agent', botAgent.credentials); const today = moment().startOf('day').format('YYYY-MM-DD'); const result = await request @@ -1011,12 +1011,9 @@ describe('LIVECHAT - dashboards', function () { expect(result.body.data).to.be.an('array'); const agentData = result.body.data.find( - (agentOverviewData: { name: string; value: string }) => agentOverviewData.name === agent.user.username, + (agentOverviewData: { name: string; value: string }) => agentOverviewData.name === botAgent.user.username, ); expect(agentData).to.be.undefined; - - originalFirstResponseTimeInSeconds = moment.duration(agentData.value).asSeconds(); - expect(originalFirstResponseTimeInSeconds).to.be.greaterThanOrEqual(firstDelayInSeconds); }); it('should correctly associate the first response time to the first agent who responded the room', async () => { diff --git a/apps/meteor/tests/unit/server/livechat/hooks/markRoomResponded.spec.ts b/apps/meteor/tests/unit/server/livechat/hooks/markRoomResponded.spec.ts index 9b503774f91df..b59d90def006d 100644 --- a/apps/meteor/tests/unit/server/livechat/hooks/markRoomResponded.spec.ts +++ b/apps/meteor/tests/unit/server/livechat/hooks/markRoomResponded.spec.ts @@ -19,7 +19,7 @@ const settingsGetMock = { get: Sinon.stub(), }; -const { markRoomResponded } = proxyquire.load('../../../../../app/livechat/server/hooks/markRoomResponded.ts', { +const { markRoomResponded } = proxyquire.noCallThru().load('../../../../../app/livechat/server/hooks/markRoomResponded.ts', { '../../../../lib/callbacks': { callbacks: { add: Sinon.stub(), priority: { HIGH: 'high' } } }, '../../../lib/server/lib/notifyListener': { notifyOnLivechatInquiryChanged: Sinon.stub() }, '@rocket.chat/models': models, @@ -27,7 +27,7 @@ const { markRoomResponded } = proxyquire.load('../../../../../app/livechat/serve }); describe('markRoomResponded', () => { - beforeEach(async () => { + beforeEach(() => { models.LivechatVisitors.isVisitorActiveOnPeriod.reset(); models.LivechatVisitors.markVisitorActiveForPeriod.reset(); models.LivechatInquiry.markInquiryActiveForPeriod.reset(); @@ -76,7 +76,7 @@ describe('markRoomResponded', () => { }); it('should return void if message is from bot and setting is enabled', async () => { - settingsGetMock.get.withArgs('Omnichannel_Metrics_Ignore_Automatic_Messages').returns(true); + settingsGetMock.get.withArgs('Omnichannel_Metrics_Ignore_Automatic_Messages').resolves(true); const user = createFakeUser({ roles: ['bot'] }); const message = createFakeMessage(); From b47ad58eceba732ab2e2cac3fc6b9355e3e2d088 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Mon, 20 Jan 2025 16:27:06 -0300 Subject: [PATCH 10/21] test: fixes mocks used in the unit tests --- .../unit/app/livechat/server/lib/isMessageFromBot.spec.ts | 6 +++--- .../unit/server/livechat/hooks/markRoomResponded.spec.ts | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/meteor/tests/unit/app/livechat/server/lib/isMessageFromBot.spec.ts b/apps/meteor/tests/unit/app/livechat/server/lib/isMessageFromBot.spec.ts index 08134cb50dc53..52f71f9998e5d 100644 --- a/apps/meteor/tests/unit/app/livechat/server/lib/isMessageFromBot.spec.ts +++ b/apps/meteor/tests/unit/app/livechat/server/lib/isMessageFromBot.spec.ts @@ -23,14 +23,14 @@ describe('isMessageFromBot', () => { }); it('Should return true if user has bot role', async () => { - modelsMock.Users.findOneById.resolves(mockUser.roles); + modelsMock.Users.findOneById.resolves(mockUser); const result = await isMessageFromBot(mockMessage); expect(result).to.be.true; }); it('Should return false if user does not have bot role', async () => { mockUser.roles = ['user']; - modelsMock.Users.findOneById.resolves(mockUser.roles); + modelsMock.Users.findOneById.resolves(mockUser); const result = await isMessageFromBot(mockMessage); expect(result).to.be.false; }); @@ -38,6 +38,6 @@ describe('isMessageFromBot', () => { it('Should throw error when user is not found', async () => { modelsMock.Users.findOneById.resolves(null); - expect(isMessageFromBot(mockMessage)).to.throw(Error('User mot found')); + await expect(isMessageFromBot(mockMessage)).to.be.rejectedWith(Error, 'User not found'); }); }); diff --git a/apps/meteor/tests/unit/server/livechat/hooks/markRoomResponded.spec.ts b/apps/meteor/tests/unit/server/livechat/hooks/markRoomResponded.spec.ts index b59d90def006d..b266862f17e94 100644 --- a/apps/meteor/tests/unit/server/livechat/hooks/markRoomResponded.spec.ts +++ b/apps/meteor/tests/unit/server/livechat/hooks/markRoomResponded.spec.ts @@ -19,11 +19,14 @@ const settingsGetMock = { get: Sinon.stub(), }; +const isMessageFromBotMock = { isMessageFromBot: Sinon.stub() }; + const { markRoomResponded } = proxyquire.noCallThru().load('../../../../../app/livechat/server/hooks/markRoomResponded.ts', { '../../../../lib/callbacks': { callbacks: { add: Sinon.stub(), priority: { HIGH: 'high' } } }, '../../../lib/server/lib/notifyListener': { notifyOnLivechatInquiryChanged: Sinon.stub() }, '@rocket.chat/models': models, '../../../settings/server': { settings: settingsGetMock }, + '../lib/isMessageFromBot': isMessageFromBotMock, }); describe('markRoomResponded', () => { @@ -79,6 +82,9 @@ describe('markRoomResponded', () => { settingsGetMock.get.withArgs('Omnichannel_Metrics_Ignore_Automatic_Messages').resolves(true); const user = createFakeUser({ roles: ['bot'] }); + + isMessageFromBotMock.isMessageFromBot.resolves(user); + const message = createFakeMessage(); const room = {}; From 1f77cd8414bcbfbab13abf521f81b1a82a24f70c Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Mon, 20 Jan 2025 17:22:43 -0300 Subject: [PATCH 11/21] docs: adds changeset --- .changeset/funny-turtles-hunt.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/funny-turtles-hunt.md diff --git a/.changeset/funny-turtles-hunt.md b/.changeset/funny-turtles-hunt.md new file mode 100644 index 0000000000000..ceebec3390a52 --- /dev/null +++ b/.changeset/funny-turtles-hunt.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/i18n": patch +--- + +Adds a new setting that if enabled, will not count bot messages in the average response time metrics From 67ee91fa912c1b62e2b7a4eb8d1001a6f596a2c5 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Mon, 20 Jan 2025 17:34:38 -0300 Subject: [PATCH 12/21] chore: updates number of total conversations testing --- apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts index 8072077080f7e..865f281b2c3b9 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts @@ -1316,7 +1316,7 @@ describe('LIVECHAT - dashboards', function () { expect(result.body).to.be.an('array'); const expectedResult = [ - { title: 'Total_conversations', value: 15 }, + { title: 'Total_conversations', value: 16 }, { title: 'Open_conversations', value: 12 }, { title: 'On_Hold_conversations', value: 1 }, // { title: 'Total_messages', value: 6 }, From 3ef8969dc467cfe567a1916e85f3a3011141f151 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Tue, 21 Jan 2025 16:14:29 -0300 Subject: [PATCH 13/21] tests: fixes setting logic at saveAnalyticsData --- .../app/livechat/server/hooks/saveAnalyticsData.ts | 11 +++++------ .../tests/end-to-end/api/livechat/04-dashboards.ts | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts b/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts index 88144870b4513..ce420afa1cb86 100644 --- a/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts +++ b/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts @@ -64,12 +64,7 @@ const getAnalyticsData = (room: IOmnichannelRoom, now: Date): Record { - if ( - !message || - isEditedMessage(message) || - isSystemMessage(message) || - (settings.get('Omnichannel_Metrics_Ignore_Automatic_Messages') && (await isMessageFromBot(message))) - ) { + if (!message || isEditedMessage(message) || isSystemMessage(message)) { return message; } @@ -80,6 +75,10 @@ callbacks.add( if (isMessageFromVisitor(message)) { LivechatRooms.getAnalyticsUpdateQueryBySentByVisitor(room, message, roomUpdater); } else { + if (settings.get('Omnichannel_Metrics_Ignore_Automatic_Messages') && (await isMessageFromBot(message))) { + return message; + } + const analyticsData = getAnalyticsData(room, new Date()); LivechatRooms.getAnalyticsUpdateQueryBySentByAgent(room, message, analyticsData, roomUpdater); } diff --git a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts index 865f281b2c3b9..7206855ef8062 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts @@ -1317,7 +1317,7 @@ describe('LIVECHAT - dashboards', function () { const expectedResult = [ { title: 'Total_conversations', value: 16 }, - { title: 'Open_conversations', value: 12 }, + { title: 'Open_conversations', value: 11 }, { title: 'On_Hold_conversations', value: 1 }, // { title: 'Total_messages', value: 6 }, // { title: 'Busiest_day', value: moment().format('dddd') }, From 9fe3e3bfc2e94756e3a6b3b958800ed96ec1e3b2 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Tue, 21 Jan 2025 16:59:49 -0300 Subject: [PATCH 14/21] fix: updates Open_conversations expected values --- apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts index 7206855ef8062..d8137b075aca9 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts @@ -1317,7 +1317,7 @@ describe('LIVECHAT - dashboards', function () { const expectedResult = [ { title: 'Total_conversations', value: 16 }, - { title: 'Open_conversations', value: 11 }, + { title: 'Open_conversations', value: 13 }, { title: 'On_Hold_conversations', value: 1 }, // { title: 'Total_messages', value: 6 }, // { title: 'Busiest_day', value: moment().format('dddd') }, From cfc9ddb90c3a537291e0161fe503295f77757df8 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Wed, 22 Jan 2025 11:13:42 -0300 Subject: [PATCH 15/21] fix: updates Conversations_per_day value --- apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts index d8137b075aca9..4143b04debe90 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts @@ -1321,7 +1321,7 @@ describe('LIVECHAT - dashboards', function () { { title: 'On_Hold_conversations', value: 1 }, // { title: 'Total_messages', value: 6 }, // { title: 'Busiest_day', value: moment().format('dddd') }, - { title: 'Conversations_per_day', value: '7.50' }, + { title: 'Conversations_per_day', value: '8.00' }, // { title: 'Busiest_time', value: '' }, ]; From 58158b0c7ed3bb154724cf8beff724e7028ef247 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Wed, 22 Jan 2025 11:32:03 -0300 Subject: [PATCH 16/21] chore: updates en.i18n with setting label --- packages/i18n/src/locales/en.i18n.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 82edac6ad6bfe..6f9650586d72a 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -3374,6 +3374,7 @@ "Omnichannel_placed_chat_on_hold": "Chat On Hold: {{comment}}", "Omnichannel_hide_conversation_after_closing": "Hide conversation after closing", "Omnichannel_hide_conversation_after_closing_description": "After closing the conversation you will be redirected to Home.", + "Omnichannel_Ignore_automatic_responses_for_performance_metrics": "Ignore bots activities for performance metrics", "Livechat_Block_Unknown_Contacts": "Block unknown contacts", "Livechat_Block_Unknown_Contacts_Description": "Conversations from people who are not on the contact list will not be able to be taken.", "Livechat_Block_Unverified_Contacts": "Block unverified contacts", From c70ec88ffa7c1d07bc5e0fe97cb0883f97da8ca0 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Thu, 23 Jan 2025 14:52:44 -0300 Subject: [PATCH 17/21] chore: updates .changeset/funny-turtles-hunt.md Co-authored-by: Marcos Spessatto Defendi --- .changeset/funny-turtles-hunt.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/funny-turtles-hunt.md b/.changeset/funny-turtles-hunt.md index ceebec3390a52..982dc542325ff 100644 --- a/.changeset/funny-turtles-hunt.md +++ b/.changeset/funny-turtles-hunt.md @@ -1,6 +1,6 @@ --- -"@rocket.chat/meteor": patch -"@rocket.chat/i18n": patch +"@rocket.chat/meteor": minor +"@rocket.chat/i18n": minor --- Adds a new setting that if enabled, will not count bot messages in the average response time metrics From bc84edf398ef444b59460c02b672cd42e3e7a7ca Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Thu, 23 Jan 2025 15:01:26 -0300 Subject: [PATCH 18/21] fix: readds labels mistakenly removed --- packages/i18n/src/locales/en.i18n.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 6f9650586d72a..2b96602266829 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -3374,6 +3374,9 @@ "Omnichannel_placed_chat_on_hold": "Chat On Hold: {{comment}}", "Omnichannel_hide_conversation_after_closing": "Hide conversation after closing", "Omnichannel_hide_conversation_after_closing_description": "After closing the conversation you will be redirected to Home.", + "Omnichannel_allow_force_close_conversations": "Force close conversation API", + "Omnichannel_allow_force_close_conversations_Description": "Allow agents and managers to force close conversations via API.", + "Omnichannel_allow_force_close_conversations_alert": "Only enable if your workspace has issues with rooms with invalid states.", "Omnichannel_Ignore_automatic_responses_for_performance_metrics": "Ignore bots activities for performance metrics", "Livechat_Block_Unknown_Contacts": "Block unknown contacts", "Livechat_Block_Unknown_Contacts_Description": "Conversations from people who are not on the contact list will not be able to be taken.", @@ -5517,7 +5520,9 @@ "This_is_a_desktop_notification": "This is a desktop notification", "This_is_a_deprecated_feature_alert": "This is a deprecated feature. It may not work as expected and will not get new updates.", "Zapier_integration_has_been_deprecated": "The Zapier integration has been deprecated, may not work as expected and will not receive updates", + "Zapier_integration_is_not_available": "The Zapier integration has been deprecated and is no longer available for new Rocket.Chat workspaces", "Install_Zapier_from_marketplace": "Install the Zapier app from Marketplace to avoid disruptions", + "Install_Zapier_from_marketplace_new_workspaces": "Install the Zapier app from Marketplace to configure new integrations", "Input": "Input", "This_is_a_push_test_messsage": "This is a push test message", "This_message_was_rejected_by__peer__peer": "This message was rejected by {{peer}} peer.", From 803da6614db0fa324f39e978ba44115bd6769cb6 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Thu, 23 Jan 2025 15:01:26 -0300 Subject: [PATCH 19/21] fix: readds labels mistakenly removed --- packages/i18n/src/locales/en.i18n.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 6f9650586d72a..faf7d5f659db9 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -407,8 +407,8 @@ "Agree": "Agree", "AI_Actions": "AI actions", "AirGapped_Restriction_Warning": "**Your air-gapped workspace will enter read-only mode in {{remainingDays}} days.** \n Users will still be able to access rooms and read existing messages but will be unable to send new messages. \n Reconnect it to the internet or [upgrade to a premium license](https://go.rocket.chat/i/air-gapped) to prevent this.", - "Airgapped_workspace_warning": "This air-gapped workspace will enter read-only mode in {{remainingDays}} days. <2>Connect it to the internet or upgrade to a premium plan to prevent this.", - "Airgapped_workspace_restriction": "This air-gapped workspace is in read-only mode. <2>Connect it to the internet or upgrade to a premium plan to restore full functionality.", + "Airgapped_workspace_warning": "This air-gapped workspace will enter read-only mode in {{remainingDays}} days. <1>Connect it to the internet or upgrade to a premium plan to prevent this.", + "Airgapped_workspace_restriction": "This air-gapped workspace is in read-only mode. <1>Connect it to the internet or upgrade to a premium plan to restore full functionality.", "Alerts": "Alerts", "Alias": "Alias", "Alias_Format": "Alias Format", @@ -3374,6 +3374,9 @@ "Omnichannel_placed_chat_on_hold": "Chat On Hold: {{comment}}", "Omnichannel_hide_conversation_after_closing": "Hide conversation after closing", "Omnichannel_hide_conversation_after_closing_description": "After closing the conversation you will be redirected to Home.", + "Omnichannel_allow_force_close_conversations": "Force close conversation API", + "Omnichannel_allow_force_close_conversations_Description": "Allow agents and managers to force close conversations via API.", + "Omnichannel_allow_force_close_conversations_alert": "Only enable if your workspace has issues with rooms with invalid states.", "Omnichannel_Ignore_automatic_responses_for_performance_metrics": "Ignore bots activities for performance metrics", "Livechat_Block_Unknown_Contacts": "Block unknown contacts", "Livechat_Block_Unknown_Contacts_Description": "Conversations from people who are not on the contact list will not be able to be taken.", @@ -5517,7 +5520,9 @@ "This_is_a_desktop_notification": "This is a desktop notification", "This_is_a_deprecated_feature_alert": "This is a deprecated feature. It may not work as expected and will not get new updates.", "Zapier_integration_has_been_deprecated": "The Zapier integration has been deprecated, may not work as expected and will not receive updates", + "Zapier_integration_is_not_available": "The Zapier integration has been deprecated and is no longer available for new Rocket.Chat workspaces", "Install_Zapier_from_marketplace": "Install the Zapier app from Marketplace to avoid disruptions", + "Install_Zapier_from_marketplace_new_workspaces": "Install the Zapier app from Marketplace to configure new integrations", "Input": "Input", "This_is_a_push_test_messsage": "This is a push test message", "This_message_was_rejected_by__peer__peer": "This message was rejected by {{peer}} peer.", From 10abc1ab98c376dc80489db935278314a8d4e59a Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Thu, 23 Jan 2025 16:58:23 -0300 Subject: [PATCH 20/21] refactor: moves setting update to before/after tests --- .../tests/end-to-end/api/livechat/04-dashboards.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts index 4143b04debe90..5facfbe965a7a 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts @@ -933,9 +933,17 @@ describe('LIVECHAT - dashboards', function () { agent = await createAnOnlineAgent(); forwardAgent = await createAnOnlineAgent(); botAgent = await createBotAgent(); + + await updateSetting('Omnichannel_Metrics_Ignore_Automatic_Messages', true); }); - after(async () => Promise.all([deleteUser(agent.user), deleteUser(forwardAgent.user)])); + after(async () => + Promise.all([ + deleteUser(agent.user), + deleteUser(forwardAgent.user), + updateSetting('Omnichannel_Metrics_Ignore_Automatic_Messages', false), + ]), + ); it('should return no average response time for an agent if no response has been sent in the period', async () => { await startANewLivechatRoomAndTakeIt({ agent: agent.credentials }); @@ -987,8 +995,6 @@ describe('LIVECHAT - dashboards', function () { }); it("should not consider bot messages in agent's first response time metric if setting is enabled", async () => { - await updateSetting('Omnichannel_Metrics_Ignore_Automatic_Messages', true); - const response = await startANewLivechatRoomAndTakeIt({ agent: botAgent.credentials }); roomId = response.room._id; From 4221773ada0c24d22ad9e045978310f032fed941 Mon Sep 17 00:00:00 2001 From: Lucas Pelegrino Date: Fri, 24 Jan 2025 13:08:56 -0300 Subject: [PATCH 21/21] refactor: replaces Users.findOne for Users.isUserInRole --- .../livechat/server/lib/isMessageFromBot.ts | 10 ++-------- .../server/lib/isMessageFromBot.spec.ts | 18 +++++------------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts b/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts index 05a2e0bab15ef..8c53bf7c555d0 100644 --- a/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts +++ b/apps/meteor/app/livechat/server/lib/isMessageFromBot.ts @@ -1,12 +1,6 @@ -import type { IMessage, IUser } from '@rocket.chat/core-typings'; +import type { IMessage } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; export async function isMessageFromBot(message: IMessage): Promise { - const user = await Users.findOneById>(message.u._id, { projection: { roles: 1 } }); - - if (!user) { - throw new Error('User not found'); - } - - return !!user.roles.includes('bot'); + return Users.isUserInRole(message.u._id, 'bot'); } diff --git a/apps/meteor/tests/unit/app/livechat/server/lib/isMessageFromBot.spec.ts b/apps/meteor/tests/unit/app/livechat/server/lib/isMessageFromBot.spec.ts index 52f71f9998e5d..6449f3a52cb72 100644 --- a/apps/meteor/tests/unit/app/livechat/server/lib/isMessageFromBot.spec.ts +++ b/apps/meteor/tests/unit/app/livechat/server/lib/isMessageFromBot.spec.ts @@ -2,11 +2,11 @@ import { expect } from 'chai'; import p from 'proxyquire'; import sinon from 'sinon'; -import { createFakeMessage, createFakeUser } from '../../../../../mocks/data'; +import { createFakeMessage } from '../../../../../mocks/data'; const modelsMock = { Users: { - findOneById: sinon.stub(), + isUserInRole: sinon.stub(), }, }; @@ -15,29 +15,21 @@ const { isMessageFromBot } = p.noCallThru().load('../../../../../../app/livechat }); describe('isMessageFromBot', () => { - const mockUser = createFakeUser({ roles: ['bot'] }); const mockMessage = createFakeMessage(); beforeEach(() => { - modelsMock.Users.findOneById.reset(); + modelsMock.Users.isUserInRole.reset(); }); it('Should return true if user has bot role', async () => { - modelsMock.Users.findOneById.resolves(mockUser); + modelsMock.Users.isUserInRole.resolves(true); const result = await isMessageFromBot(mockMessage); expect(result).to.be.true; }); it('Should return false if user does not have bot role', async () => { - mockUser.roles = ['user']; - modelsMock.Users.findOneById.resolves(mockUser); + modelsMock.Users.isUserInRole.resolves(false); const result = await isMessageFromBot(mockMessage); expect(result).to.be.false; }); - - it('Should throw error when user is not found', async () => { - modelsMock.Users.findOneById.resolves(null); - - await expect(isMessageFromBot(mockMessage)).to.be.rejectedWith(Error, 'User not found'); - }); });