From df5c496487acd331e2c90e48d8a24a52a5bb8bee Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Fri, 6 Oct 2023 22:35:17 +0400 Subject: [PATCH] test: Improve tests for legacy omnichannel analytics (#30379) --- apps/meteor/tests/data/livechat/department.ts | 26 +- apps/meteor/tests/data/livechat/rooms.ts | 3 + apps/meteor/tests/data/livechat/users.ts | 23 +- apps/meteor/tests/data/livechat/utils.ts | 4 + .../end-to-end/api/livechat/04-dashboards.ts | 499 +++++++++++++++++- packages/random/src/NodeRandomGenerator.ts | 10 + 6 files changed, 523 insertions(+), 42 deletions(-) diff --git a/apps/meteor/tests/data/livechat/department.ts b/apps/meteor/tests/data/livechat/department.ts index e11324a47a4..8aba28addfc 100644 --- a/apps/meteor/tests/data/livechat/department.ts +++ b/apps/meteor/tests/data/livechat/department.ts @@ -2,9 +2,9 @@ import { faker } from '@faker-js/faker'; import { expect } from 'chai'; import type { ILivechatDepartment, IUser, LivechatDepartmentDTO } from '@rocket.chat/core-typings'; import { api, credentials, methodCall, request } from '../api-data'; -import { IUserCredentialsHeader, password } from '../user'; -import { login } from '../users.helper'; -import { createAgent, makeAgentAvailable } from './rooms'; +import { IUserCredentialsHeader } from '../user'; +import { createAnOnlineAgent } from './users'; +import { WithRequiredProperty } from './utils'; export const NewDepartmentData = ((): Partial => ({ enabled: true, @@ -59,29 +59,19 @@ new Promise((resolve, reject) => { export const createDepartmentWithAnOnlineAgent = async (): Promise<{department: ILivechatDepartment, agent: { credentials: IUserCredentialsHeader; - user: IUser; + user: WithRequiredProperty; }}> => { - // TODO moving here for tests - const username = `user.test.${Date.now()}`; - const email = `${username}@rocket.chat`; - const { body } = await request - .post(api('users.create')) - .set(credentials) - .send({ email, name: username, username, password }); - const agent = body.user; - const createdUserCredentials = await login(agent.username, password); - await createAgent(agent.username); - await makeAgentAvailable(createdUserCredentials); + const { user, credentials } = await createAnOnlineAgent(); const department = await createDepartmentWithMethod() as ILivechatDepartment; - await addOrRemoveAgentFromDepartment(department._id, {agentId: agent._id, username: (agent.username as string)}, true); + await addOrRemoveAgentFromDepartment(department._id, {agentId: user._id, username: user.username}, true); return { department, agent: { - credentials: createdUserCredentials, - user: agent, + credentials, + user, } }; }; diff --git a/apps/meteor/tests/data/livechat/rooms.ts b/apps/meteor/tests/data/livechat/rooms.ts index c2658c73af8..5efb279dcb1 100644 --- a/apps/meteor/tests/data/livechat/rooms.ts +++ b/apps/meteor/tests/data/livechat/rooms.ts @@ -185,6 +185,9 @@ export const getLivechatRoomInfo = (roomId: string): Promise = }); }; +/** + * @summary Sends message as visitor +*/ export const sendMessage = (roomId: string, message: string, visitorToken: string): Promise => { return new Promise((resolve, reject) => { request diff --git a/apps/meteor/tests/data/livechat/users.ts b/apps/meteor/tests/data/livechat/users.ts index 7a5dc23b4cc..38fb176faaa 100644 --- a/apps/meteor/tests/data/livechat/users.ts +++ b/apps/meteor/tests/data/livechat/users.ts @@ -1,6 +1,6 @@ import { faker } from "@faker-js/faker"; import type { IUser } from "@rocket.chat/core-typings"; -import { password } from "../user"; +import { IUserCredentialsHeader, password } from "../user"; import { createUser, login } from "../users.helper"; import { createAgent, makeAgentAvailable } from "./rooms"; import { api, credentials, request } from "../api-data"; @@ -29,3 +29,24 @@ export const removeAgent = async (userId: string): Promise => { .set(credentials) .expect(200); } + +export const createAnOnlineAgent = async (): Promise<{ + credentials: IUserCredentialsHeader; + user: IUser & { username: string }; +}> => { + const username = `user.test.${Date.now()}`; + const email = `${username}@rocket.chat`; + const { body } = await request + .post(api('users.create')) + .set(credentials) + .send({ email, name: username, username, password }); + const agent = body.user; + const createdUserCredentials = await login(agent.username, password); + await createAgent(agent.username); + await makeAgentAvailable(createdUserCredentials); + + return { + credentials: createdUserCredentials, + user: agent, + }; +} diff --git a/apps/meteor/tests/data/livechat/utils.ts b/apps/meteor/tests/data/livechat/utils.ts index 89b6af709fb..b6fd3a4bf6b 100644 --- a/apps/meteor/tests/data/livechat/utils.ts +++ b/apps/meteor/tests/data/livechat/utils.ts @@ -1,6 +1,10 @@ export type DummyResponse = E extends 'wrapped' ? { body: { [k: string]: T } } : { body: T }; +export type WithRequiredProperty = Type & { + [Property in Key]-?: Type[Property]; +}; + export const sleep = (ms: number) => { return new Promise((resolve) => setTimeout(resolve, ms)); } 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 61a2719d9cb..c12b875783a 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 @@ -1,12 +1,30 @@ +import { faker } from '@faker-js/faker'; +import type { ILivechatDepartment, IUser } from '@rocket.chat/core-typings'; +import { Random } from '@rocket.chat/random'; import { expect } from 'chai'; import { before, describe, it } from 'mocha'; +import moment from 'moment'; import type { Response } from 'supertest'; import { getCredentials, api, request, credentials } from '../../../data/api-data'; -import { updatePermission, updateSetting } from '../../../data/permissions.helper'; +import { addOrRemoveAgentFromDepartment, createDepartmentWithAnOnlineAgent } from '../../../data/livechat/department'; +import { + closeOmnichannelRoom, + placeRoomOnHold, + sendAgentMessage, + sendMessage, + startANewLivechatRoomAndTakeIt, +} from '../../../data/livechat/rooms'; +import { createAnOnlineAgent } from '../../../data/livechat/users'; +import { sleep } from '../../../data/livechat/utils'; +import { removePermissionFromAllRoles, restorePermissionToRoles, updateSetting } from '../../../data/permissions.helper'; +import type { IUserCredentialsHeader } from '../../../data/user'; +import { IS_EE } from '../../../e2e/config/constants'; describe('LIVECHAT - dashboards', function () { this.retries(0); + // This test is expected to take more time since we're simulating real time conversations to verify analytics + this.timeout(60000); before((done) => getCredentials(done)); @@ -14,6 +32,106 @@ describe('LIVECHAT - dashboards', function () { await updateSetting('Livechat_enabled', true); }); + let department: ILivechatDepartment; + const agents: { + credentials: IUserCredentialsHeader; + user: IUser & { username: string }; + }[] = []; + let avgClosedRoomChatDuration = 0; + + const inactivityTimeout = 3; + + const TOTAL_MESSAGES = { + min: 5, + max: 10, + }; + const DELAY_BETWEEN_MESSAGES = { + min: 1000, + max: (inactivityTimeout - 1) * 1000, + }; + const TOTAL_ROOMS = 7; + + const simulateRealtimeConversation = async (chatInfo: Awaited>[]) => { + const promises = chatInfo.map(async (info) => { + const { room, visitor } = info; + + // send a few messages + const numberOfMessages = Random.between(TOTAL_MESSAGES.min, TOTAL_MESSAGES.max); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + for await (const _ of Array(numberOfMessages - 1).keys()) { + // flip a coin to decide who will send the message + const willSendFromAgent = Random.between(0, 1) === 1; + + if (willSendFromAgent) { + await sendAgentMessage(room._id); + } else { + await sendMessage(room._id, faker.lorem.sentence(), visitor.token); + } + + const delay = Random.between(DELAY_BETWEEN_MESSAGES.min, DELAY_BETWEEN_MESSAGES.max); + await sleep(delay); + } + + // Last message is always from visitor so that the chat doesn't get abandoned due to + // "Livechat_visitor_inactivity_timeout" setting + await sendMessage(room._id, faker.lorem.sentence(), visitor.token); + }); + + await Promise.all(promises); + }; + + before(async () => { + if (!IS_EE) { + return; + } + + await updateSetting('Livechat_visitor_inactivity_timeout', inactivityTimeout); + await updateSetting('Livechat_enable_business_hours', false); + + // create dummy test data for further tests + const { department: createdDept, agent: agent1 } = await createDepartmentWithAnOnlineAgent(); + department = createdDept; + + console.log('department', department.name); + + const agent2 = await createAnOnlineAgent(); + await addOrRemoveAgentFromDepartment(department._id, { agentId: agent2.user._id, username: agent2.user.username }, true); + agents.push(agent1); + agents.push(agent2); + + const roomCreationStart = moment(); + // start a few chats + const promises = Array.from(Array(TOTAL_ROOMS).keys()).map((i) => { + // 2 rooms by agent 1 + if (i < 2) { + return startANewLivechatRoomAndTakeIt({ departmentId: department._id, agent: agent1.credentials }); + } + return startANewLivechatRoomAndTakeIt({ departmentId: department._id, agent: agent2.credentials }); + }); + + const results = await Promise.all(promises); + + const chatInfo = results.map((result) => ({ room: result.room, visitor: result.visitor })); + + // simulate messages being exchanged between agents and visitors + await simulateRealtimeConversation(chatInfo); + + // put a chat on hold + await sendAgentMessage(chatInfo[1].room._id); + await placeRoomOnHold(chatInfo[1].room._id); + // close a chat + await closeOmnichannelRoom(chatInfo[4].room._id); + const room5ChatDuration = moment().diff(roomCreationStart, 'seconds'); + // close an abandoned chat + await sendAgentMessage(chatInfo[5].room._id); + await sleep(inactivityTimeout * 1000); // wait for the chat to be considered abandoned + await closeOmnichannelRoom(chatInfo[5].room._id); + const room6ChatDuration = moment().diff(roomCreationStart, 'seconds'); + + avgClosedRoomChatDuration = (room5ChatDuration + room6ChatDuration) / 2; + }); + describe('livechat/analytics/dashboards/conversation-totalizers', () => { const expectedMetrics = [ 'Total_conversations', @@ -25,7 +143,7 @@ describe('LIVECHAT - dashboards', function () { 'Total_visitors', ]; it('should return an "unauthorized error" when the user does not have the necessary permission', async () => { - await updatePermission('view-livechat-manager', []); + await removePermissionFromAllRoles('view-livechat-manager'); await request .get(api('livechat/analytics/dashboards/conversation-totalizers?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) .set(credentials) @@ -33,7 +151,7 @@ describe('LIVECHAT - dashboards', function () { .expect(403); }); it('should return an array of conversation totalizers', async () => { - await updatePermission('view-livechat-manager', ['admin']); + await restorePermissionToRoles('view-livechat-manager'); await request .get(api('livechat/analytics/dashboards/conversation-totalizers?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) .set(credentials) @@ -47,12 +165,51 @@ describe('LIVECHAT - dashboards', function () { ); }); }); + (IS_EE ? it : it.skip)('should return data with correct values', async () => { + const start = moment().subtract(1, 'days').toISOString(); + const end = moment().toISOString(); + + const result = await request + .get(api('livechat/analytics/dashboards/conversation-totalizers')) + .query({ start, end, departmentId: department._id }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(result.body).to.have.property('success', true); + expect(result.body).to.have.property('totalizers'); + expect(result.body.totalizers).to.be.an('array'); + expect(result.body.totalizers).to.have.lengthOf(5); + + const expectedResult = [ + { title: 'Total_conversations', value: 7 }, + { title: 'Open_conversations', value: 4 }, + { title: 'On_Hold_conversations', value: 1 }, + // { title: 'Total_messages', value: 60 }, + { title: 'Total_visitors', value: 7 }, + ]; + + expectedResult.forEach((expected) => { + const resultItem = result.body.totalizers.find((item: any) => item.title === expected.title); + expect(resultItem).to.not.be.undefined; + expect(resultItem).to.have.property('value', expected.value); + }); + + const minMessages = TOTAL_MESSAGES.min * TOTAL_ROOMS; + const maxMessages = TOTAL_MESSAGES.max * TOTAL_ROOMS; + + const totalMessages = result.body.totalizers.find((item: any) => item.title === 'Total_messages'); + expect(totalMessages).to.not.be.undefined; + const totalMessagesValue = parseInt(totalMessages.value); + expect(totalMessagesValue).to.be.greaterThanOrEqual(minMessages); + expect(totalMessagesValue).to.be.lessThanOrEqual(maxMessages); + }); }); describe('livechat/analytics/dashboards/productivity-totalizers', () => { const expectedMetrics = ['Avg_response_time', 'Avg_first_response_time', 'Avg_reaction_time', 'Avg_of_waiting_time']; it('should return an "unauthorized error" when the user does not have the necessary permission', async () => { - await updatePermission('view-livechat-manager', []); + await removePermissionFromAllRoles('view-livechat-manager'); await request .get(api('livechat/analytics/dashboards/productivity-totalizers?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) .set(credentials) @@ -60,7 +217,7 @@ describe('LIVECHAT - dashboards', function () { .expect(403); }); it('should return an array of productivity totalizers', async () => { - await updatePermission('view-livechat-manager', ['admin']); + await restorePermissionToRoles('view-livechat-manager'); await request .get(api('livechat/analytics/dashboards/productivity-totalizers?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) .set(credentials) @@ -74,12 +231,41 @@ describe('LIVECHAT - dashboards', function () { ); }); }); + (IS_EE ? it : it.skip)('should return data with correct values', async () => { + const start = moment().subtract(1, 'days').toISOString(); + const end = moment().toISOString(); + + const result = await request + .get(api('livechat/analytics/dashboards/productivity-totalizers')) + .query({ start, end, departmentId: department._id }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(result.body).to.have.property('success', true); + + // const expected = [ + // // There's a bug in the code for calculation of these 3 values. + // // Due to which it always return 0 + // { title: 'Avg_response_time', value: '00:00:00' }, + // { title: 'Avg_first_response_time', value: '00:00:00' }, + // { title: 'Avg_reaction_time', value: '00:00:00' }, + + // { title: 'Avg_of_waiting_time', value: '00:00:03' }, // approx 3, 5 delta + // ]; + + const avgWaitingTime = result.body.totalizers.find((item: any) => item.title === 'Avg_of_waiting_time'); + expect(avgWaitingTime).to.not.be.undefined; + + const avgWaitingTimeValue = moment.duration(avgWaitingTime.value).asSeconds(); + expect(avgWaitingTimeValue).to.be.closeTo(DELAY_BETWEEN_MESSAGES.max / 1000, 5); + }); }); describe('livechat/analytics/dashboards/chats-totalizers', () => { const expectedMetrics = ['Total_abandoned_chats', 'Avg_of_abandoned_chats', 'Avg_of_chat_duration_time']; it('should return an "unauthorized error" when the user does not have the necessary permission', async () => { - await updatePermission('view-livechat-manager', []); + await removePermissionFromAllRoles('view-livechat-manager'); await request .get(api('livechat/analytics/dashboards/chats-totalizers?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) .set(credentials) @@ -87,7 +273,7 @@ describe('LIVECHAT - dashboards', function () { .expect(403); }); it('should return an array of chats totalizers', async () => { - await updatePermission('view-livechat-manager', ['admin']); + await restorePermissionToRoles('view-livechat-manager'); await request .get(api('livechat/analytics/dashboards/chats-totalizers?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) .set(credentials) @@ -101,12 +287,45 @@ describe('LIVECHAT - dashboards', function () { ); }); }); + (IS_EE ? it : it.skip)('should return data with correct values', async () => { + const start = moment().subtract(1, 'days').toISOString(); + const end = moment().toISOString(); + + const result = await request + .get(api('livechat/analytics/dashboards/chats-totalizers')) + .query({ start, end, departmentId: department._id }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + const expected = [ + { title: 'Total_abandoned_chats', value: 1 }, + { title: 'Avg_of_abandoned_chats', value: '14%' }, + // { title: 'Avg_of_chat_duration_time', value: '00:00:01' }, + ]; + + expect(result.body).to.have.property('success', true); + expect(result.body).to.have.property('totalizers'); + expect(result.body.totalizers).to.be.an('array'); + + expected.forEach((expected) => { + const resultItem = result.body.totalizers.find((item: any) => item.title === expected.title); + expect(resultItem).to.not.be.undefined; + expect(resultItem).to.have.property('value', expected.value); + }); + + const resultAverageChatDuration = result.body.totalizers.find((item: any) => item.title === 'Avg_of_chat_duration_time'); + expect(resultAverageChatDuration).to.not.be.undefined; + + const resultAverageChatDurationValue = moment.duration(resultAverageChatDuration.value).asSeconds(); + expect(resultAverageChatDurationValue).to.be.closeTo(avgClosedRoomChatDuration, 5); // Keep a margin of 3 seconds + }); }); describe('livechat/analytics/dashboards/agents-productivity-totalizers', () => { const expectedMetrics = ['Busiest_time', 'Avg_of_available_service_time', 'Avg_of_service_time']; it('should return an "unauthorized error" when the user does not have the necessary permission', async () => { - await updatePermission('view-livechat-manager', []); + await removePermissionFromAllRoles('view-livechat-manager'); await request .get( api('livechat/analytics/dashboards/agents-productivity-totalizers?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z'), @@ -116,7 +335,7 @@ describe('LIVECHAT - dashboards', function () { .expect(403); }); it('should return an array of agents productivity totalizers', async () => { - await updatePermission('view-livechat-manager', ['admin']); + await restorePermissionToRoles('view-livechat-manager'); await request .get( api('livechat/analytics/dashboards/agents-productivity-totalizers?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z'), @@ -132,11 +351,40 @@ describe('LIVECHAT - dashboards', function () { ); }); }); + (IS_EE ? it : it.skip)('should return data with correct values', async () => { + const start = moment().subtract(1, 'days').toISOString(); + const end = moment().toISOString(); + + const result = await request + .get(api('livechat/analytics/dashboards/agents-productivity-totalizers')) + .query({ start, end, departmentId: department._id }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + // [ + // { title: 'Busiest_time', value: '- -' }, + // { title: 'Avg_of_available_service_time', value: '00:00:00' }, + // { title: 'Avg_of_service_time', value: '00:00:16' } approx 17, 6 delta + // ], + + expect(result.body).to.have.property('success', true); + expect(result.body).to.have.property('totalizers'); + expect(result.body.totalizers).to.be.an('array'); + + const avgServiceTime = result.body.totalizers.find((item: any) => item.title === 'Avg_of_service_time'); + + expect(avgServiceTime).to.not.be.undefined; + const avgServiceTimeValue = moment.duration(avgServiceTime.value).asSeconds(); + const minChatDuration = (DELAY_BETWEEN_MESSAGES.min * TOTAL_MESSAGES.min) / 1000; + const maxChatDuration = (DELAY_BETWEEN_MESSAGES.max * TOTAL_MESSAGES.max) / 1000; + expect(avgServiceTimeValue).to.be.closeTo((minChatDuration + maxChatDuration) / 2, 10); + }); }); describe('livechat/analytics/dashboards/charts/chats', () => { it('should return an "unauthorized error" when the user does not have the necessary permission', async () => { - await updatePermission('view-livechat-manager', []); + await removePermissionFromAllRoles('view-livechat-manager'); await request .get(api('livechat/analytics/dashboards/charts/chats?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) .set(credentials) @@ -144,7 +392,7 @@ describe('LIVECHAT - dashboards', function () { .expect(403); }); it('should return an array of productivity totalizers', async () => { - await updatePermission('view-livechat-manager', ['admin']); + await restorePermissionToRoles('view-livechat-manager'); await request .get(api('livechat/analytics/dashboards/charts/chats?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) .set(credentials) @@ -157,11 +405,35 @@ describe('LIVECHAT - dashboards', function () { expect(res.body).to.have.property('queued'); }); }); + (IS_EE ? it : it.skip)('should return data with correct values', async () => { + const start = moment().subtract(1, 'days').toISOString(); + const end = moment().toISOString(); + + const result = await request + .get(api('livechat/analytics/dashboards/charts/chats')) + .query({ start, end, departmentId: department._id }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + const expected = { + open: 4, + closed: 2, + queued: 0, + onhold: 1, + }; + + expect(result.body).to.have.property('success', true); + + Object.entries(expected).forEach(([key, value]) => { + expect(result.body).to.have.property(key, value); + }); + }); }); describe('livechat/analytics/dashboards/charts/chats-per-agent', () => { it('should return an "unauthorized error" when the user does not have the necessary permission', async () => { - await updatePermission('view-livechat-manager', []); + await removePermissionFromAllRoles('view-livechat-manager'); await request .get(api('livechat/analytics/dashboards/charts/chats-per-agent?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) .set(credentials) @@ -169,7 +441,7 @@ describe('LIVECHAT - dashboards', function () { .expect(403); }); it('should return an object with open and closed chats by agent', async () => { - await updatePermission('view-livechat-manager', ['admin']); + await restorePermissionToRoles('view-livechat-manager'); await request .get(api('livechat/analytics/dashboards/charts/chats-per-agent?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) .set(credentials) @@ -179,11 +451,39 @@ describe('LIVECHAT - dashboards', function () { expect(res.body).to.have.property('success', true); }); }); + (IS_EE ? it : it.skip)('should return data with correct values', async () => { + const start = moment().subtract(1, 'days').toISOString(); + const end = moment().toISOString(); + + const result = await request + .get(api('livechat/analytics/dashboards/charts/chats-per-agent')) + .query({ start, end, departmentId: department._id }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + const expected = { + agent0: { open: 1, closed: 0, onhold: 1 }, + agent1: { open: 3, closed: 2 }, + }; + + expect(result.body).to.have.property('success', true); + + const agent0 = result.body[agents[0].user.username as string]; + const agent1 = result.body[agents[1].user.username as string]; + + Object.entries(expected.agent0).forEach(([key, value]) => { + expect(agent0).to.have.property(key, value); + }); + Object.entries(expected.agent1).forEach(([key, value]) => { + expect(agent1).to.have.property(key, value); + }); + }); }); describe('livechat/analytics/dashboards/charts/agents-status', () => { it('should return an "unauthorized error" when the user does not have the necessary permission', async () => { - await updatePermission('view-livechat-manager', []); + await removePermissionFromAllRoles('view-livechat-manager'); await request .get(api('livechat/analytics/dashboards/charts/agents-status')) .set(credentials) @@ -191,7 +491,7 @@ describe('LIVECHAT - dashboards', function () { .expect(403); }); it('should return an object with agents status metrics', async () => { - await updatePermission('view-livechat-manager', ['admin']); + await restorePermissionToRoles('view-livechat-manager'); await request .get(api('livechat/analytics/dashboards/charts/agents-status')) .set(credentials) @@ -205,11 +505,36 @@ describe('LIVECHAT - dashboards', function () { expect(res.body).to.have.property('available'); }); }); + (IS_EE ? it : it.skip)('should return data with correct values', async () => { + const start = moment().subtract(1, 'days').toISOString(); + const end = moment().toISOString(); + + const result = await request + .get(api('livechat/analytics/dashboards/charts/agents-status')) + .query({ start, end, departmentId: department._id }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + // TODO: We can improve tests further by creating some agents with different status + const expected = { + offline: 0, + away: 0, + busy: 0, + available: 2, + }; + + expect(result.body).to.have.property('success', true); + + Object.entries(expected).forEach(([key, value]) => { + expect(result.body).to.have.property(key, value); + }); + }); }); describe('livechat/analytics/dashboards/charts/chats-per-department', () => { it('should return an "unauthorized error" when the user does not have the necessary permission', async () => { - await updatePermission('view-livechat-manager', []); + await removePermissionFromAllRoles('view-livechat-manager'); await request .get(api('livechat/analytics/dashboards/charts/chats-per-department?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) .set(credentials) @@ -217,7 +542,7 @@ describe('LIVECHAT - dashboards', function () { .expect(403); }); it('should return an object with open and closed chats by department', async () => { - await updatePermission('view-livechat-manager', ['admin']); + await restorePermissionToRoles('view-livechat-manager'); await request .get(api('livechat/analytics/dashboards/charts/chats-per-department?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) .set(credentials) @@ -227,11 +552,34 @@ describe('LIVECHAT - dashboards', function () { expect(res.body).to.have.property('success', true); }); }); + (IS_EE ? it : it.skip)('should return data with correct values', async () => { + const start = moment().subtract(1, 'days').toISOString(); + const end = moment().toISOString(); + + const result = await request + .get(api('livechat/analytics/dashboards/charts/chats-per-department')) + .query({ start, end, departmentId: department._id }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + const expected = { + department0: { open: 5, closed: 2 }, + }; + + expect(result.body).to.have.property('success', true); + + const department0 = result.body[department.name]; + + Object.entries(expected.department0).forEach(([key, value]) => { + expect(department0).to.have.property(key, value); + }); + }); }); describe('livechat/analytics/dashboards/charts/timings', () => { it('should return an "unauthorized error" when the user does not have the necessary permission', async () => { - await updatePermission('view-livechat-manager', []); + await removePermissionFromAllRoles('view-livechat-manager'); await request .get(api('livechat/analytics/dashboards/charts/timings?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) .set(credentials) @@ -239,7 +587,7 @@ describe('LIVECHAT - dashboards', function () { .expect(403); }); it('should return an object with open and closed chats by department', async () => { - await updatePermission('view-livechat-manager', ['admin']); + await restorePermissionToRoles('view-livechat-manager'); await request .get(api('livechat/analytics/dashboards/charts/timings?start=2019-10-25T15:08:17.248Z&end=2019-12-08T15:08:17.248Z')) .set(credentials) @@ -258,11 +606,52 @@ describe('LIVECHAT - dashboards', function () { expect(res.body.chatDuration).to.have.property('longest'); }); }); + (IS_EE ? it : it.skip)('should return data with correct values', async () => { + const start = moment().subtract(1, 'days').toISOString(); + const end = moment().toISOString(); + + const result = await request + .get(api('livechat/analytics/dashboards/charts/timings')) + .query({ start, end, departmentId: department._id }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(result.body).to.have.property('success', true); + + // const expected = { + // response: { avg: 0, longest: 0.207 }, // avg between delayBetweenMessage.min and delayBetweenMessage.max + // reaction: { avg: 0, longest: 0.221 }, // avg between delayBetweenMessage.min and delayBetweenMessage.max + // chatDuration: { avg: 0, longest: 0.18 }, // avg should be about avgClosedRoomChatDuration, and longest should be greater than avgClosedRoomChatDuration and within delta of 20 + // success: true, + // }; + + const maxChatDuration = (DELAY_BETWEEN_MESSAGES.max * TOTAL_MESSAGES.max) / 1000; + + const responseValues = result.body.response; + expect(responseValues).to.have.property('avg'); + expect(responseValues).to.have.property('longest'); + expect(responseValues.avg).to.be.closeTo((DELAY_BETWEEN_MESSAGES.min + DELAY_BETWEEN_MESSAGES.max) / 2000, 5); + expect(responseValues.longest).to.be.lessThan(maxChatDuration); + + const reactionValues = result.body.reaction; + expect(reactionValues).to.have.property('avg'); + expect(reactionValues).to.have.property('longest'); + expect(reactionValues.avg).to.be.closeTo((DELAY_BETWEEN_MESSAGES.min + DELAY_BETWEEN_MESSAGES.max) / 2000, 5); + expect(reactionValues.longest).to.be.lessThan(maxChatDuration); + + const chatDurationValues = result.body.chatDuration; + expect(chatDurationValues).to.have.property('avg'); + expect(chatDurationValues).to.have.property('longest'); + expect(chatDurationValues.avg).to.be.closeTo(avgClosedRoomChatDuration, 5); + expect(chatDurationValues.longest).to.be.greaterThan(avgClosedRoomChatDuration); + expect(chatDurationValues.longest).to.be.lessThan(avgClosedRoomChatDuration + 20); + }); }); describe('livechat/analytics/agent-overview', () => { it('should return an "unauthorized error" when the user does not have the necessary permission', async () => { - await updatePermission('view-livechat-manager', []); + await removePermissionFromAllRoles('view-livechat-manager'); await request .get(api('livechat/analytics/agent-overview')) .query({ from: '2020-01-01', to: '2020-01-02', name: 'Total_conversations' }) @@ -271,7 +660,7 @@ describe('LIVECHAT - dashboards', function () { .expect(403); }); it('should return an "invalid-chart-name error" when the chart name is empty', async () => { - await updatePermission('view-livechat-manager', ['admin']); + await restorePermissionToRoles('view-livechat-manager'); await request .get(api('livechat/analytics/agent-overview')) .query({ from: '2020-01-01', to: '2020-01-02', name: '' }) @@ -305,11 +694,37 @@ describe('LIVECHAT - dashboards', function () { expect(result.body.head).to.be.an('array'); expect(result.body.data).to.be.an('array'); }); + (IS_EE ? it : it.skip)('should return agent overview data with correct values', async () => { + const yesterday = moment().subtract(1, 'days').format('YYYY-MM-DD'); + const today = moment().startOf('day').format('YYYY-MM-DD'); + + const result = await request + .get(api('livechat/analytics/agent-overview')) + .query({ from: yesterday, to: today, name: 'Total_conversations', departmentId: department._id }) + .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'); + expect(result.body.data).to.have.lengthOf(2); + + const user1Data = result.body.data.find((data: any) => data.name === agents[0].user.username); + const user2Data = result.body.data.find((data: any) => data.name === agents[1].user.username); + + expect(user1Data).to.not.be.undefined; + expect(user2Data).to.not.be.undefined; + + expect(user1Data).to.have.property('value', '28.57%'); + expect(user2Data).to.have.property('value', '71.43%'); + }); }); describe('livechat/analytics/overview', () => { it('should return an "unauthorized error" when the user does not have the necessary permission', async () => { - await updatePermission('view-livechat-manager', []); + await removePermissionFromAllRoles('view-livechat-manager'); await request .get(api('livechat/analytics/overview')) .query({ from: '2020-01-01', to: '2020-01-02', name: 'Conversations' }) @@ -318,7 +733,7 @@ describe('LIVECHAT - dashboards', function () { .expect(403); }); it('should return an "invalid-chart-name error" when the chart name is empty', async () => { - await updatePermission('view-livechat-manager', ['admin']); + await restorePermissionToRoles('view-livechat-manager'); await request .get(api('livechat/analytics/overview')) .query({ from: '2020-01-01', to: '2020-01-02', name: '' }) @@ -351,5 +766,43 @@ describe('LIVECHAT - dashboards', function () { expect(result.body[0]).to.have.property('title', 'Total_conversations'); expect(result.body[0]).to.have.property('value', 0); }); + (IS_EE ? it : it.skip)('should return analytics overview data with correct values', async () => { + const yesterday = moment().subtract(1, 'days').format('YYYY-MM-DD'); + const today = moment().startOf('day').format('YYYY-MM-DD'); + + const result = await request + .get(api('livechat/analytics/overview')) + .query({ from: yesterday, to: today, name: 'Conversations', departmentId: department._id }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(result.body).to.be.an('array'); + + const expectedResult = [ + { title: 'Total_conversations', value: 7 }, + { title: 'Open_conversations', value: 4 }, + { title: 'On_Hold_conversations', value: 1 }, + // { title: 'Total_messages', value: 6 }, + // { title: 'Busiest_day', value: moment().format('dddd') }, // TODO: need to check y this return a day before + { title: 'Conversations_per_day', value: '3.50' }, + { title: 'Busiest_time', value: '- -' }, + ]; + + expectedResult.forEach((expected) => { + const resultItem = result.body.find((item: any) => item.title === expected.title); + expect(resultItem).to.not.be.undefined; + expect(resultItem).to.have.property('value', expected.value); + }); + + const minMessages = TOTAL_MESSAGES.min * TOTAL_ROOMS; + const maxMessages = TOTAL_MESSAGES.max * TOTAL_ROOMS; + + const totalMessages = result.body.find((item: any) => item.title === 'Total_messages'); + expect(totalMessages).to.not.be.undefined; + const totalMessagesValue = parseInt(totalMessages.value); + expect(totalMessagesValue).to.be.greaterThanOrEqual(minMessages); + expect(totalMessagesValue).to.be.lessThanOrEqual(maxMessages); + }); }); }); diff --git a/packages/random/src/NodeRandomGenerator.ts b/packages/random/src/NodeRandomGenerator.ts index b9d556c6ac0..8c9f239413c 100644 --- a/packages/random/src/NodeRandomGenerator.ts +++ b/packages/random/src/NodeRandomGenerator.ts @@ -38,6 +38,16 @@ export class NodeRandomGenerator extends RandomGenerator { return result.substring(0, digits); } + /** + * @name Random.between Returns a random integer between min and max, inclusive. + * @param min Minimum value (inclusive) + * @param max Maximum value (inclusive) + * @returns A random integer between min and max, inclusive. + */ + between(min: number, max: number) { + return Math.floor(this.fraction() * (max - min + 1)) + min; + } + protected safelyCreateWithSeeds(...seeds: readonly unknown[]) { return new AleaRandomGenerator({ seeds }); }