snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,788 @@
import type { Credentials } from '@rocket.chat/api-client';
import { UserStatus } from '@rocket.chat/core-typings';
import type { ILivechatVisitor, IOmnichannelRoom, ILivechatAgent, ILivechatDepartment, 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 type { Response } from 'supertest';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { disableDefaultBusinessHour, makeDefaultBusinessHourActiveAndClosed } from '../../../data/livechat/businessHours';
import { createDepartment, deleteDepartment } from '../../../data/livechat/department';
import {
createAgent,
createManager,
createVisitor,
createLivechatRoom,
takeInquiry,
fetchInquiry,
makeAgentAvailable,
startANewLivechatRoomAndTakeIt,
moveBackToQueue,
closeOmnichannelRoom,
deleteVisitor,
} from '../../../data/livechat/rooms';
import {
removePermissionFromAllRoles,
restorePermissionToRoles,
updateEESetting,
updatePermission,
updateSetting,
} from '../../../data/permissions.helper';
import { password } from '../../../data/user';
import { createUser, deleteUser, getMe, login, setUserStatus } from '../../../data/users.helper';
import { IS_EE } from '../../../e2e/config/constants';
describe('LIVECHAT - Agents', () => {
let agent: ILivechatAgent;
let manager: ILivechatAgent;
let agent2: { user: IUser; credentials: Credentials };
before((done) => getCredentials(done));
before(async () => {
await updateSetting('Livechat_enabled', true);
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
await updateSetting('Omnichannel_enable_department_removal', true);
agent = await createAgent();
manager = await createManager();
});
before(async () => {
const user = await createUser();
const userCredentials = await login(user.username, password);
await createAgent(user.username);
await makeAgentAvailable(userCredentials);
agent2 = {
user,
credentials: userCredentials,
};
});
after(async () => {
await updateSetting('Omnichannel_enable_department_removal', false);
await deleteUser(agent2.user);
});
// TODO: missing test cases for POST method
describe('GET livechat/users/:type', () => {
describe('with no permissions', () => {
before(async () => {
await updatePermission('edit-omnichannel-contact', []);
await updatePermission('transfer-livechat-guest', []);
await updatePermission('manage-livechat-agents', []);
});
after(async () => {
await updatePermission('edit-omnichannel-contact', ['admin']);
await updatePermission('transfer-livechat-guest', ['admin']);
await updatePermission('manage-livechat-agents', ['admin']);
});
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await request.get(api('livechat/users/agent')).set(credentials).expect('Content-Type', 'application/json').expect(403);
});
});
it('should throw an error when the type is invalid', async () => {
await request
.get(api('livechat/users/invalid-type'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.be.equal('Invalid type');
});
});
it('should return an array of agents', async () => {
await request
.get(api('livechat/users/agent'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.users).to.be.an('array');
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('count');
const agentRecentlyCreated = (res.body.users as ILivechatAgent[]).find((user) => agent._id === user._id);
expect(agentRecentlyCreated?._id).to.be.equal(agent._id);
});
});
it('should return an array of available agents', async () => {
await request
.get(api('livechat/users/agent'))
.set(credentials)
.expect('Content-Type', 'application/json')
.query({ onlyAvailable: true })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.users).to.be.an('array');
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('count');
expect(res.body.users.every((u: { statusLivechat: string }) => u.statusLivechat === 'available')).to.be.true;
});
});
it('should return an array of available/unavailable agents when onlyAvailable is false', async () => {
await request
.get(api('livechat/users/agent'))
.set(credentials)
.expect('Content-Type', 'application/json')
.query({ onlyAvailable: false })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.users).to.be.an('array');
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('count');
expect(
res.body.users.every(
(u: { statusLivechat: string }) => !u.statusLivechat || ['available', 'not-available'].includes(u.statusLivechat),
),
).to.be.true;
});
});
it('should return offline agents when showIdleAgents is true', async () => {
await setUserStatus(agent2.credentials, UserStatus.OFFLINE);
await request
.get(api('livechat/users/agent'))
.set(credentials)
.expect('Content-Type', 'application/json')
.query({ showIdleAgents: true })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.users).to.be.an('array');
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('count');
expect(
res.body.users.every(
(u: { status: UserStatus }) =>
!u.status || [UserStatus.ONLINE, UserStatus.OFFLINE, UserStatus.AWAY, UserStatus.BUSY].includes(u.status),
),
).to.be.true;
});
});
it('should return only online agents when showIdleAgents is false', async () => {
await setUserStatus(agent2.credentials, UserStatus.ONLINE);
await request
.get(api('livechat/users/agent'))
.set(credentials)
.expect('Content-Type', 'application/json')
.query({ showIdleAgents: false })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.users).to.be.an('array');
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('count');
expect(res.body.users.every((u: { status: UserStatus }) => u.status !== UserStatus.OFFLINE)).to.be.true;
});
});
it('should return an array of managers', async () => {
await request
.get(api('livechat/users/manager'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.users).to.be.an('array');
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('count');
const managerRecentlyCreated = (res.body.users as ILivechatAgent[]).find((user) => manager._id === user._id);
expect(managerRecentlyCreated?._id).to.be.equal(manager._id);
});
});
});
describe('POST livechat/users/:type', () => {
describe('with no permission', () => {
before(async () => {
await updatePermission('view-livechat-manager', []);
});
after(async () => {
await updatePermission('view-livechat-manager', ['admin']);
});
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await request
.post(api('livechat/users/agent'))
.set(credentials)
.send({
username: 'test-agent',
})
.expect('Content-Type', 'application/json')
.expect(403);
});
});
it('should return an error when type is invalid', async () => {
await request
.post(api('livechat/users/invalid-type'))
.set(credentials)
.send({
username: 'test-agent',
})
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should return an error when username is invalid', async () => {
await request
.post(api('livechat/users/agent'))
.set(credentials)
.send({
username: 'mr-not-valid',
})
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should return a valid user when all goes fine', async () => {
const user = await createUser();
await request
.post(api('livechat/users/agent'))
.set(credentials)
.send({
username: user.username,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('user');
expect(res.body.user).to.have.property('_id');
expect(res.body.user).to.have.property('username');
});
// cleanup
await deleteUser(user);
});
it('should properly create a manager', async () => {
const user = await createUser();
await request
.post(api('livechat/users/manager'))
.set(credentials)
.send({
username: user.username,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('user');
expect(res.body.user).to.have.property('_id');
expect(res.body.user).to.have.property('username');
});
// cleanup
await deleteUser(user);
});
});
describe('GET livechat/users/:type/:_id', () => {
describe('with no permission', () => {
before(async () => {
await updatePermission('view-livechat-manager', []);
});
after(async () => {
await updatePermission('view-livechat-manager', ['admin']);
});
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await request
.get(api(`livechat/users/agent/id${agent._id}`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(403);
});
});
describe('with new user', () => {
let user: IUser;
before(async () => {
user = await createUser();
});
after(async () => {
await deleteUser(user);
});
it('should return an error when type is invalid', async () => {
await request
.get(api(`livechat/users/invalid-type/${agent._id}`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(400);
}).timeout(5000);
it('should return a valid user when all goes fine', async () => {
await request
.get(api(`livechat/users/agent/${agent._id}`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('user');
expect(res.body.user).to.have.property('_id');
expect(res.body.user).to.have.property('username');
expect(res.body.user).to.not.have.property('roles');
});
});
it('should return { user: null } when user is not an agent', async () => {
await request
.get(api(`livechat/users/agent/${user._id}`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('user');
expect(res.body.user).to.be.null;
});
});
it('should return { user: null } when user is not a manager', async () => {
await request
.get(api(`livechat/users/manager/${user._id}`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('user');
expect(res.body.user).to.be.null;
});
});
});
});
describe('DELETE livechat/users/:type/:_id', () => {
describe('with no permission', () => {
before(async () => {
await updatePermission('view-livechat-manager', []);
});
after(async () => {
await updatePermission('view-livechat-manager', ['admin']);
});
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await request.delete(api(`livechat/users/agent/id`)).set(credentials).expect('Content-Type', 'application/json').expect(403);
}).timeout(5000);
});
it('should return an error when type is invalid', async () => {
await request.delete(api(`livechat/users/invalid-type/id`)).set(credentials).expect('Content-Type', 'application/json').expect(400);
}).timeout(5000);
it('should return an error when _id is invalid', async () => {
await request.delete(api('livechat/users/agent/invalid-id')).set(credentials).expect('Content-Type', 'application/json').expect(400);
}).timeout(5000);
it('should return a valid user when all goes fine', async () => {
const agent = await createAgent();
await request
.delete(api(`livechat/users/agent/${agent._id}`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
});
});
(IS_EE ? describe : describe.skip)('livechat/agents/:agentId/departments', () => {
let dep1: ILivechatDepartment;
let dep2: ILivechatDepartment;
before(async () => {
dep1 = await createDepartment(
{
enabled: true,
name: Random.id(),
showOnRegistration: true,
email: `${Random.id()}@example.com`,
showOnOfflineForm: true,
},
[{ agentId: credentials['X-User-Id'] }],
);
dep2 = await createDepartment(
{
enabled: false,
name: Random.id(),
email: `${Random.id()}@example.com`,
showOnRegistration: true,
showOnOfflineForm: true,
},
[{ agentId: credentials['X-User-Id'] }],
);
});
after(async () => {
await deleteDepartment(dep1._id);
await deleteDepartment(dep2._id);
});
describe('with no permission', () => {
before(async () => {
await updatePermission('view-l-room', []);
});
after(async () => {
await updatePermission('view-l-room', ['livechat-manager', 'livechat-agent', 'admin']);
});
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await request
.get(api(`livechat/agents/${agent._id}/departments`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(403);
});
});
it('should return an empty array of departments when the agentId is invalid', async () => {
await request
.get(api('livechat/agents/invalid-id/departments'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('departments').and.to.be.an('array');
});
});
it('should return an array of departments when the agentId is valid', async () => {
await request
.get(api(`livechat/agents/${agent._id}/departments`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('departments').and.to.be.an('array');
expect(res.body.departments.length).to.be.equal(2);
(res.body.departments as ILivechatDepartment[]).forEach((department) => {
expect(department.agentId).to.be.equal(agent._id);
expect(department).to.have.property('departmentName').that.is.a('string');
});
});
});
it('should return only enabled departments when param `enabledDepartmentsOnly` is true ', async () => {
await request
.get(api(`livechat/agents/${agent._id}/departments`))
.set(credentials)
.query({ enabledDepartmentsOnly: true })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('departments').and.to.be.an('array');
expect(res.body.departments.length).to.be.equal(1);
expect(res.body.departments[0].departmentEnabled).to.be.true;
});
});
});
describe('livechat/agent.info/:rid/:token', () => {
let room: IOmnichannelRoom;
let visitor: ILivechatVisitor;
before(async () => {
visitor = await createVisitor();
room = await createLivechatRoom(visitor.token);
});
after(async () => {
await closeOmnichannelRoom(room._id);
await deleteVisitor(visitor.token);
});
it('should fail when token in url params is not valid', async () => {
await request.get(api(`livechat/agent.info/soemthing/invalid-token`)).expect(400);
});
it('should fail when token is valid but rid isnt', async () => {
const visitor = await createVisitor();
await request.get(api(`livechat/agent.info/invalid-rid/${visitor.token}`)).expect(400);
await deleteVisitor(visitor.token);
});
/* it('should fail when room is not being served by any agent', async () => {
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await request.get(api(`livechat/agent.info/${room._id}/${visitor.token}`)).expect(400);
}); */
it('should return a valid agent when the room is being served and the room belongs to visitor', async () => {
const inq = await fetchInquiry(room._id);
await takeInquiry(inq._id, agent2.credentials);
const { body } = await request.get(api(`livechat/agent.info/${room._id}/${visitor.token}`));
expect(body).to.have.property('success', true);
expect(body).to.have.property('agent');
expect(body.agent).to.have.property('_id', agent2.user._id);
});
});
describe('livechat/agent.next/:token', () => {
let visitor: ILivechatVisitor;
let room: IOmnichannelRoom;
before(async () => {
visitor = await createVisitor();
room = await createLivechatRoom(visitor.token);
});
after(async () => {
await closeOmnichannelRoom(room._id);
await deleteVisitor(visitor.token);
});
it('should fail when token in url params is not valid', async () => {
await request.get(api(`livechat/agent.next/invalid-token`)).expect(400);
});
it('should return success when visitor with token has an open room', async () => {
await request.get(api(`livechat/agent.next/${visitor.token}`)).expect(200);
});
// TODO: test cases when algo is Auto_Selection
});
describe('livechat/agent.next/:token - with manual selection', () => {
before(async () => {
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
});
after(async () => {
await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
});
it('should fail if theres no open room for visitor and algo is manual selection', async () => {
const visitor = await createVisitor();
await request.get(api(`livechat/agent.next/${visitor.token}`)).expect(400);
await deleteVisitor(visitor.token);
});
});
describe('livechat/agent.status', () => {
describe('with no view-l permission', () => {
before(async () => {
await removePermissionFromAllRoles('view-l-room');
});
after(async () => {
await restorePermissionToRoles('view-l-room');
});
it('should return an "unauthorized error" when the user does not have the necessary permission to change its own status', async () => {
await request.post(api('livechat/agent.status')).set(credentials).send({ status: 'not-available' }).expect(403);
});
});
describe('with no manage-agent permission', () => {
before(async () => {
await request
.post(api('livechat/agent.status'))
.set(credentials)
.send({ status: 'not-available', agentId: agent2.user._id })
.expect(200);
await removePermissionFromAllRoles('manage-livechat-agents');
});
after(async () => {
await restorePermissionToRoles('manage-livechat-agents');
});
it('should return an "unauthorized error" when the user does not have the necessary permission to change other status', async () => {
await request
.post(api('livechat/agent.status'))
.set(credentials)
.send({ status: 'available', agentId: agent2.user._id })
.expect(403);
});
});
it('should return an error if user is not an agent', async () => {
const user = await createUser({ roles: ['livechat-manager'] });
const userCredentials = await login(user.username, password);
await request
.post(api('livechat/agent.status'))
.set(userCredentials)
.send({ status: 'available', agentId: user._id })
.expect(404)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Agent not found');
});
// cleanup
await deleteUser(user);
});
it('should return an error if status is not valid', async () => {
await request
.post(api('livechat/agent.status'))
.set(agent2.credentials)
.send({ status: 'invalid-status', agentId: agent2.user._id })
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
});
});
it('should return an error if agentId param is not valid', async () => {
await request
.post(api('livechat/agent.status'))
.set(agent2.credentials)
.send({ status: 'available', agentId: 'invalid-agent-id' })
.expect(404)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Agent not found');
});
});
describe('cases for valid agents', () => {
let currentUser: ILivechatAgent;
before(async () => {
currentUser = await getMe(agent2.credentials);
});
afterEach(async () => {
await request
.post(api('livechat/agent.status'))
.set(agent2.credentials)
.send({ status: 'not-available', agentId: currentUser._id })
.expect(200);
});
it('should be able to set a logged in users status to available', async () => {
await request
.post(api('livechat/agent.status'))
.set(agent2.credentials)
.send({ status: 'available', agentId: currentUser._id })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('status', 'available');
});
});
it('should allow managers to set other agents status to available', async () => {
await request
.post(api('livechat/agent.status'))
.set(credentials)
.send({ status: 'available', agentId: currentUser._id })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('status', 'available');
});
});
describe('outside of business hours', () => {
before(async () => {
await makeDefaultBusinessHourActiveAndClosed();
await request
.post(api('livechat/agent.status'))
.set(agent2.credentials)
.send({ status: 'not-available', agentId: currentUser._id })
.expect(200);
});
after(async () => {
await disableDefaultBusinessHour();
});
it('should throw an error if agent tries to make themselves available outside of Business hour', async () => {
await request
.post(api('livechat/agent.status'))
.set(agent2.credentials)
.send({ status: 'available', agentId: currentUser._id })
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'error-business-hours-are-closed');
});
});
it('should return success but not change the agent when admin make try other agents available outside business hour', async () => {
await request
.post(api('livechat/agent.status'))
.set(credentials)
.send({ status: 'available', agentId: currentUser._id })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('status', 'not-available');
});
});
});
});
});
describe('Agent sidebar', () => {
let testUser: { user: IUser; credentials: Credentials };
before(async () => {
const user = await createUser();
await createAgent(user.username);
const credentials2 = await login(user.username, password);
await makeAgentAvailable(credentials2);
testUser = {
user,
credentials: credentials2,
};
});
after(async () => {
await deleteUser(testUser.user);
});
it('should return an empty list of rooms for a newly created agent', async () => {
const { body } = await request.get(api('rooms.get')).set(testUser.credentials).send({}).expect(200);
expect(body).to.have.property('success', true);
expect(body.update.filter((r: IRoom) => r.t === 'l')).to.have.lengthOf(0);
});
describe('with a room', () => {
let room: IOmnichannelRoom;
let visitor: ILivechatVisitor;
before(async () => {
const { room: r, visitor: v } = await startANewLivechatRoomAndTakeIt({ agent: testUser.credentials });
room = r;
visitor = v;
});
after(async () => {
await closeOmnichannelRoom(room._id);
await deleteVisitor(visitor.token);
});
it('should have a new room in his sidebar after taking a conversation from the queue', async () => {
const { body } = await request.get(api('rooms.get')).set(testUser.credentials).send({}).expect(200);
expect(body).to.have.property('success', true);
const livechatRooms = body.update.filter((r: IRoom) => r.t === 'l');
expect(livechatRooms).to.have.lengthOf(1);
expect(body.update.find((r: { _id: string }) => r._id === room._id)).to.be.an('object');
expect(body.update.find((r: { _id: string }) => r._id === 'GENERAL')).to.be.an('object');
});
it('should not have the room if user moves room back to queue', async () => {
await moveBackToQueue(room._id, testUser.credentials);
const { body } = await request
.get(api('rooms.get'))
.set(testUser.credentials)
.query({ updatedSince: new Date(new Date().getTime() - 2000) })
.expect(200);
expect(body).to.have.property('success', true);
expect(body.update.find((r: { _id: string }) => r._id === room._id)).to.be.undefined;
});
});
it('should not have the room if the user closes the room', async () => {
const { room, visitor } = await startANewLivechatRoomAndTakeIt({ agent: testUser.credentials });
await closeOmnichannelRoom(room._id);
const { body } = await request.get(api('rooms.get')).set(testUser.credentials).expect(200);
expect(body.update.find((r: { _id: string }) => r._id === room._id)).to.be.undefined;
await deleteVisitor(visitor.token);
});
});
});

View File

@@ -0,0 +1,265 @@
import { expect } from 'chai';
import { before, describe, it } from 'mocha';
import type { Response } from 'supertest';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { sleep } from '../../../data/livechat/utils';
import { removePermissionFromAllRoles, restorePermissionToRoles, updatePermission, updateSetting } from '../../../data/permissions.helper';
import { IS_EE } from '../../../e2e/config/constants';
describe('LIVECHAT - appearance', () => {
before((done) => getCredentials(done));
before(async () => {
await updateSetting('Livechat_enabled', true);
});
describe('livechat/appearance', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-manager', []);
await request.get(api('livechat/appearance')).set(credentials).expect('Content-Type', 'application/json').expect(403);
});
it('should return an array of settings', async () => {
await updatePermission('view-livechat-manager', ['admin']);
await request
.get(api('livechat/appearance'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.appearance).to.be.an('array');
});
});
});
describe('POST livechat/appearance', () => {
it('should fail if user is not logged in', async () => {
await request.post(api('livechat/appearance')).send({}).expect(401);
});
it('should fail if body is not an array', async () => {
await request.post(api('livechat/appearance')).set(credentials).send({}).expect(400);
});
it('should fail if body is an empty array', async () => {
await request.post(api('livechat/appearance')).set(credentials).send([]).expect(400);
});
it('should fail if body does not contain value', async () => {
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([{ name: 'Livechat_title' }])
.expect(400);
});
it('should fail if body does not contain name', async () => {
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([{ value: 'test' }])
.expect(400);
});
it('should fail if user does not have the necessary permission', async () => {
await removePermissionFromAllRoles('view-livechat-manager');
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([{ _id: 'invalid', value: 'test' }])
.expect(403);
});
it('should fail if body contains invalid _id', async () => {
await restorePermissionToRoles('view-livechat-manager');
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([{ _id: 'invalid', value: 'test' }])
.expect(400);
});
it('should update the settings', async () => {
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([{ _id: 'Livechat_title', value: 'test' }])
.expect(200);
});
// Test for: https://github.com/ajv-validator/ajv/issues/1140
it('should update a boolean setting and keep it as boolean', async () => {
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([{ _id: 'Livechat_registration_form', value: true }])
.expect(200);
// Just enough to get the stream to update cached settings
await sleep(500);
// Get data from livechat/config
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
expect(body.config.settings.registrationForm).to.be.true;
});
it('should update a boolean setting and keep it as boolean', async () => {
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([{ _id: 'Livechat_registration_form', value: false }])
.expect(200);
// Just enough to get the stream to update cached settings
await sleep(500);
// Get data from livechat/config
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
expect(body.config.settings.registrationForm).to.be.false;
});
it('should update a number setting and keep it as number', async () => {
await updateSetting('Livechat_enable_message_character_limit', true);
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([{ _id: 'Livechat_message_character_limit', value: 100 }])
.expect(200);
// Just enough to get the stream to update cached settings
await sleep(500);
// Get data from livechat/config
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
expect(body.config.settings.limitTextLength).to.be.equal(100);
await updateSetting('Livechat_enable_message_character_limit', false);
});
it('should coerce the value of a setting based on its stored datatype (int)', async () => {
await updateSetting('Livechat_enable_message_character_limit', true);
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([{ _id: 'Livechat_message_character_limit', value: '100' }])
.expect(200);
// Just enough to get the stream to update cached settings
await sleep(500);
// Get data from livechat/config
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
expect(body.config.settings.limitTextLength).to.be.equal(100);
await updateSetting('Livechat_enable_message_character_limit', false);
});
it('should coerce the value of a setting based on its stored datatype (boolean)', async () => {
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([{ _id: 'Livechat_registration_form', value: 'true' }])
.expect(200);
// Just enough to get the stream to update cached settings
await sleep(500);
// Get data from livechat/config
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
expect(body.config.settings.registrationForm).to.be.true;
});
it('should coerce an invalid number value to zero', async () => {
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([
{ _id: 'Livechat_message_character_limit', value: 'xxxx' },
{ _id: 'Livechat_enable_message_character_limit', value: true },
])
.expect(200);
// Just enough to get the stream to update cached settings
await sleep(500);
// Get data from livechat/config
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
// When setting is 0, we default to Message_MaxAllowedSize value
expect(body.config.settings.limitTextLength).to.be.equal(5000);
});
it('should coerce a boolean value on an int setting to 0', async () => {
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([
{ _id: 'Livechat_message_character_limit', value: true },
{ _id: 'Livechat_enable_message_character_limit', value: true },
])
.expect(200);
// Just enough to get the stream to update cached settings
await sleep(500);
// Get data from livechat/config
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
expect(body.config.settings.limitTextLength).to.be.equal(5000);
});
it('should coerce a non boolean value on a boolean setting to false', async () => {
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([{ _id: 'Livechat_enable_message_character_limit', value: 'xxxx' }])
.expect(200);
// Just enough to get the stream to update cached settings
await sleep(500);
// Get data from livechat/config
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
expect(body.config.settings.limitTextLength).to.be.false;
});
(IS_EE ? it : it.skip)('should accept an array setting', async () => {
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([{ _id: 'Livechat_hide_system_messages', value: ['livechat-started'] }])
.expect(200);
await sleep(500);
// Get data from livechat/config
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
expect(body.config.settings.hiddenSystemMessages).to.be.an('array');
expect(body.config.settings.hiddenSystemMessages).to.include('livechat-started');
});
(IS_EE ? it : it.skip)('should accept an array setting with multiple values', async () => {
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([{ _id: 'Livechat_hide_system_messages', value: ['uj', 'livechat_transfer_history'] }])
.expect(200);
await sleep(500);
// Get data from livechat/config
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
expect(body.config.settings.hiddenSystemMessages).to.be.an('array');
expect(body.config.settings.hiddenSystemMessages).to.include('uj');
expect(body.config.settings.hiddenSystemMessages).to.include('livechat_transfer_history');
expect(body.config.settings.hiddenSystemMessages).not.to.include('livechat-started');
});
(IS_EE ? it : it.skip)('should not update an array setting with a value other than array', async () => {
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([{ _id: 'Livechat_hide_system_messages', value: 'uj' }])
.expect(200);
await sleep(500);
// Get data from livechat/config
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
expect(body.config.settings.hiddenSystemMessages).to.be.an('array');
expect(body.config.settings.hiddenSystemMessages).to.include('uj');
expect(body.config.settings.hiddenSystemMessages).to.include('livechat_transfer_history');
});
(IS_EE ? it : it.skip)('should not update an array setting with values that are not valid setting values', async () => {
await request
.post(api('livechat/appearance'))
.set(credentials)
.send([{ _id: 'Livechat_hide_system_messages', value: ['livechat-started', 'invalid'] }])
.expect(200);
await sleep(500);
// Get data from livechat/config
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
expect(body.config.settings.hiddenSystemMessages).to.be.an('array');
expect(body.config.settings.hiddenSystemMessages).to.include('uj');
expect(body.config.settings.hiddenSystemMessages).to.include('livechat_transfer_history');
expect(body.config.settings.hiddenSystemMessages).to.not.include('livechat-started');
expect(body.config.settings.hiddenSystemMessages).to.not.include('invalid');
});
});
});

View File

@@ -0,0 +1,746 @@
import type { ILivechatVisitor, IOmnichannelRoom } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';
import type { Response } from 'supertest';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { createCustomField, deleteCustomField } from '../../../data/livechat/custom-fields';
import { closeOmnichannelRoom, createLivechatRoom, createVisitor, deleteVisitor, getLivechatRoomInfo } from '../../../data/livechat/rooms';
import { updatePermission, updateSetting } from '../../../data/permissions.helper';
describe('LIVECHAT - custom fields', () => {
before((done) => getCredentials(done));
before(async () => {
await updateSetting('Livechat_enabled', true);
});
describe('livechat/custom-fields', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-l-room', []);
await request.get(api('livechat/custom-fields')).set(credentials).expect('Content-Type', 'application/json').expect(403);
});
it('should return an array of custom fields', async () => {
await updatePermission('view-l-room', ['admin']);
await request
.get(api('livechat/custom-fields'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.customFields).to.be.an('array');
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('count');
});
});
it('should return an array of custom fields even requested with count and offset params', async () => {
await updatePermission('view-l-room', ['admin']);
await request
.get(api('livechat/custom-fields'))
.set(credentials)
.query({
count: 5,
offset: 0,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.customFields).to.be.an('array');
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('count');
});
});
});
describe('livechat/custom-fields/id', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-l-room', []);
await request.get(api('livechat/custom-fields/invalid-id')).set(credentials).expect('Content-Type', 'application/json').expect(403);
await updatePermission('view-l-room', ['admin']);
});
});
describe('livechat/custom.field', () => {
it('should fail when token is not on body params', async () => {
await request.post(api('livechat/custom.field')).expect(400);
});
it('should fail when key is not on body params', async () => {
await request.post(api('livechat/custom.field')).send({ token: 'invalid-token' }).expect(400);
});
it('should fail when value is not on body params', async () => {
await request.post(api('livechat/custom.field')).send({ token: 'invalid-token', key: 'invalid-key' }).expect(400);
});
it('should fail when overwrite is not on body params', async () => {
await request
.post(api('livechat/custom.field'))
.send({ token: 'invalid-token', key: 'invalid-key', value: 'invalid-value' })
.expect(400);
});
it('should fail when token is invalid', async () => {
await request
.post(api('livechat/custom.field'))
.send({ token: 'invalid-token', key: 'invalid-key', value: 'invalid-value', overwrite: true })
.expect(400);
});
it('should fail when key is invalid', async () => {
const visitor = await createVisitor();
await request
.post(api('livechat/custom.field'))
.send({ token: visitor.token, key: 'invalid-key', value: 'invalid-value', overwrite: true })
.expect(400);
await deleteVisitor(visitor.token);
});
it('should save a custom field on visitor', async () => {
const visitor = await createVisitor();
const customFieldName = `new_custom_field_${Date.now()}`;
await createCustomField({
searchable: true,
field: customFieldName,
label: customFieldName,
defaultValue: 'test_default_address',
scope: 'visitor',
visibility: 'public',
regexp: '',
});
const { body } = await request
.post(api('livechat/custom.field'))
.send({ token: visitor.token, key: customFieldName, value: 'test_address', overwrite: true })
.expect(200);
expect(body).to.have.property('success', true);
expect(body).to.have.property('field');
expect(body.field).to.have.property('value', 'test_address');
await deleteVisitor(visitor.token);
});
});
describe('livechat/custom.fields', () => {
const customFieldName = `new_custom_field_${Date.now()}_1`;
const customFieldName2 = `new_custom_field_${Date.now()}_2`;
const customFieldName3 = `new_custom_field_${Date.now()}_3`;
const roomCustomField = `new_custom_field_${Date.now()}_4`;
let visitor: ILivechatVisitor;
let visitorRoom: IOmnichannelRoom;
before(async () => {
await updateSetting('Livechat_accept_chats_with_no_agents', true);
await createCustomField({
searchable: true,
field: customFieldName,
label: customFieldName,
defaultValue: 'test_default_address',
scope: 'visitor',
visibility: 'public',
regexp: '',
});
await createCustomField({
searchable: true,
field: customFieldName2,
label: customFieldName2,
defaultValue: 'test_default_address',
scope: 'visitor',
visibility: 'public',
regexp: '',
});
await createCustomField({
searchable: true,
field: customFieldName3,
label: customFieldName3,
defaultValue: 'test_default_address',
scope: 'visitor',
visibility: 'public',
regexp: '',
});
await createCustomField({
searchable: true,
field: roomCustomField,
label: roomCustomField,
defaultValue: 'test_default_address',
scope: 'room',
visibility: 'public',
regexp: '',
});
visitor = await createVisitor();
// start a room for visitor2
visitorRoom = await createLivechatRoom(visitor.token);
});
after(async () => {
await Promise.all([
deleteCustomField(customFieldName),
deleteCustomField(customFieldName2),
deleteCustomField(customFieldName3),
deleteCustomField(roomCustomField),
closeOmnichannelRoom(visitorRoom._id),
deleteVisitor(visitor.token),
updateSetting('Livechat_accept_chats_with_no_agents', false),
]);
});
it('should fail when token is not on body params', async () => {
await request.post(api('livechat/custom.fields')).expect(400);
});
it('should fail if customFields is not on body params', async () => {
await request.post(api('livechat/custom.fields')).send({ token: 'invalid-token' }).expect(400);
});
it('should fail if customFields is not an array', async () => {
await request
.post(api('livechat/custom.fields'))
.send({
token: 'invalid-token',
customFields: 'invalid-custom-fields',
})
.expect(400);
});
it('should fail if customFields is an empty array', async () => {
await request.post(api('livechat/custom.fields')).send({ token: 'invalid-token', customFields: [] }).expect(400);
});
it('should fail if customFields is an array with invalid objects', async () => {
await request
.post(api('livechat/custom.fields'))
.send({ token: 'invalid-token', customFields: [{}] })
.expect(400);
});
it('should fail if token is not a valid token', async () => {
await request
.post(api('livechat/custom.fields'))
.send({
token: 'invalid-token',
customFields: [{ key: 'invalid-key', value: 'invalid-value', overwrite: true }],
})
.expect(400);
});
it('should fail when customFields.key is invalid', async () => {
await request
.post(api('livechat/custom.fields'))
.send({
token: visitor.token,
customFields: [{ key: 'invalid-key', value: 'invalid-value', overwrite: true }],
})
.expect(400);
});
it('should save a custom field on visitor', async () => {
const visitor = await createVisitor();
const { body } = await request
.post(api('livechat/custom.fields'))
.send({
token: visitor.token,
customFields: [{ key: customFieldName, value: 'test_address', overwrite: true }],
})
.expect(200);
expect(body).to.have.property('success', true);
expect(body).to.have.property('fields');
expect(body.fields).to.be.an('array');
expect(body.fields).to.have.lengthOf(1);
expect(body.fields[0]).to.have.property('value', 'test_address');
await deleteVisitor(visitor.token);
});
it('should save multiple custom fields on a visitor', async () => {
const visitor = await createVisitor();
const { body } = await request
.post(api('livechat/custom.fields'))
.send({
token: visitor.token,
customFields: [
{ key: customFieldName, value: 'test_address', overwrite: true },
{ key: customFieldName2, value: 'test_address2', overwrite: true },
{ key: customFieldName3, value: 'test_address3', overwrite: true },
],
})
.expect(200);
expect(body).to.have.property('success', true);
expect(body).to.have.property('fields');
expect(body.fields).to.be.an('array');
expect(body.fields).to.have.lengthOf(3);
expect(body.fields[0]).to.have.property('value', 'test_address');
expect(body.fields[1]).to.have.property('value', 'test_address2');
expect(body.fields[2]).to.have.property('value', 'test_address3');
await deleteVisitor(visitor.token);
});
it('should save multiple custom fields on contact when visitor already has custom fields and an update with multiple fields is issued', async () => {
const { body } = await request
.post(api('livechat/custom.fields'))
.send({
token: visitor.token,
customFields: [{ key: customFieldName, value: 'test_address', overwrite: true }],
})
.expect(200);
expect(body).to.have.property('success', true);
expect(body).to.have.property('fields');
expect(body.fields).to.be.an('array');
expect(body.fields).to.have.lengthOf(1);
expect(body.fields[0]).to.have.property('value', 'test_address');
await request
.post(api('livechat/custom.fields'))
.send({
token: visitor.token,
customFields: [
{ key: customFieldName2, value: 'test_address2', overwrite: true },
{ key: customFieldName3, value: 'test_address3', overwrite: true },
],
})
.expect(200);
await request
.get(api(`omnichannel/contacts.get`))
.set(credentials)
.query({ contactId: visitorRoom.contactId })
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('contact');
expect(res.body.contact).to.have.property('customFields');
expect(res.body.contact.customFields).to.have.property(customFieldName, 'test_address');
expect(res.body.contact.customFields).to.have.property(customFieldName2, 'test_address2');
expect(res.body.contact.customFields).to.have.property(customFieldName3, 'test_address3');
});
});
it('should mark a conflict on a contact custom fields when overwrite is true and visitor already has the custom field set', async () => {
await request
.post(api('livechat/custom.fields'))
.send({
token: visitor.token,
customFields: [{ key: customFieldName, value: 'test_address_conflict', overwrite: false }],
})
.expect(200);
await request
.get(api(`omnichannel/contacts.get`))
.set(credentials)
.query({ contactId: visitorRoom.contactId })
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('contact');
expect(res.body.contact).to.have.property('customFields');
expect(res.body.contact.customFields).to.have.property(customFieldName, 'test_address');
expect(res.body.contact.customFields).to.have.property(customFieldName2, 'test_address2');
expect(res.body.contact.customFields).to.have.property(customFieldName3, 'test_address3');
expect(res.body.contact).to.have.property('conflictingFields').that.is.an('array');
expect(res.body.contact.conflictingFields[0]).to.deep.equal({
field: `customFields.${customFieldName}`,
value: 'test_address_conflict',
});
});
});
it('should overwrite the contact custom field when overwrite is true', async () => {
await request
.post(api('livechat/custom.fields'))
.send({
token: visitor.token,
customFields: [{ key: customFieldName2, value: 'test_new_add', overwrite: true }],
})
.expect(200);
await request
.get(api(`omnichannel/contacts.get`))
.set(credentials)
.query({ contactId: visitorRoom.contactId })
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('contact');
expect(res.body.contact).to.have.property('customFields');
expect(res.body.contact.customFields).to.have.property(customFieldName, 'test_address');
expect(res.body.contact.customFields).to.have.property(customFieldName2, 'test_new_add');
expect(res.body.contact.customFields).to.have.property(customFieldName3, 'test_address3');
expect(res.body.contact).to.have.property('conflictingFields').that.is.an('array');
expect(res.body.contact.conflictingFields[0]).to.deep.equal({
field: `customFields.${customFieldName}`,
value: 'test_address_conflict',
});
});
});
describe('with a room', () => {
let visitor: ILivechatVisitor;
let room: IOmnichannelRoom;
before(async () => {
visitor = await createVisitor();
room = await createLivechatRoom(visitor.token);
});
after(async () => {
await closeOmnichannelRoom(room._id);
await deleteVisitor(visitor.token);
});
it('should save both room & vistor custom fields on one call', async () => {
await request
.post(api('livechat/custom.fields'))
.send({
token: visitor.token,
customFields: [
{ key: customFieldName, value: 'test_address', overwrite: true },
{ key: roomCustomField, value: 'test_address2', overwrite: true },
{ key: customFieldName3, value: 'test_address3', overwrite: true },
],
})
.expect(200);
await request
.get(api(`omnichannel/contacts.get`))
.set(credentials)
.query({ contactId: visitorRoom.contactId })
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('contact');
expect(res.body.contact).to.have.property('customFields');
expect(res.body.contact.customFields).to.have.property(customFieldName, 'test_address');
expect(res.body.contact.customFields).to.have.property(customFieldName3, 'test_address3');
});
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo).to.have.property('livechatData').that.is.an('object');
expect(roomInfo.livechatData).to.have.property(roomCustomField, 'test_address2');
});
it('should ignore a room custom field when room already has a value for it and overwrite is false', async () => {
await request
.post(api('livechat/custom.fields'))
.send({
token: visitor.token,
customFields: [{ key: roomCustomField, value: 'test_value_overriden', overwrite: false }],
})
.expect(200);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo).to.have.property('livechatData').that.is.an('object');
expect(roomInfo.livechatData).to.have.property(roomCustomField, 'test_address2');
});
});
});
describe('livechat/custom.field [with Contacts]', () => {
let visitor: ILivechatVisitor;
let contactId: string;
let room: IOmnichannelRoom;
const customFieldName = `custom_field_${Date.now()}`;
const customFieldValue = 'custom-field-value';
before(async () => {
await updatePermission('create-livechat-contact', ['admin']);
await updatePermission('view-livechat-contact', ['admin']);
await updateSetting('Livechat_accept_chats_with_no_agents', true);
// Create a Visitor
visitor = await createVisitor();
// Create a Contact and store id on var
await request
.post(api('omnichannel/contacts'))
.set(credentials)
.send({
name: visitor.name,
emails: [visitor.visitorEmails?.[0].address],
phones: [visitor.phone?.[0].phoneNumber],
})
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('contactId');
contactId = res.body.contactId;
});
room = await createLivechatRoom(visitor.token);
// Create Custom Field
await createCustomField({
searchable: true,
field: customFieldName,
label: customFieldName,
defaultValue: 'test_default_address',
scope: 'visitor',
visibility: 'public',
regexp: '',
});
await createCustomField({
searchable: true,
field: `${customFieldName}_2`,
label: `${customFieldName}_2`,
defaultValue: 'test_default_address',
scope: 'visitor',
visibility: 'public',
regexp: '',
});
});
after(async () => {
// TODO: add clean up for contacts, visitors, etc
await Promise.all([
deleteCustomField(customFieldName),
deleteCustomField(`${customFieldName}_2`),
deleteCustomField(`${customFieldName}_3`),
deleteVisitor(visitor.token),
closeOmnichannelRoom(room._id),
updateSetting('Livechat_accept_chats_with_no_agents', false),
]);
});
it('should save the custom field on Contact when available', async () => {
// Save the custom field on Visitor/Contact
await request
.post(api('livechat/custom.field'))
.send({ token: visitor.token, key: customFieldName, value: customFieldValue, overwrite: true })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
});
// Fetch the visitor to validate custom fields are properly set.
await request
.get(api(`livechat/visitor/${visitor.token}`))
.set(credentials)
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.visitor).to.have.property('livechatData');
expect(res.body.visitor.livechatData).to.have.property(customFieldName, customFieldValue);
});
// Fetch the visitor's contact to validate custom fields are properly set.
await request
.get(api(`omnichannel/contacts.get`))
.set(credentials)
.query({ contactId })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('contact');
expect(res.body.contact).to.have.property('customFields');
expect(res.body.contact.customFields).to.have.property(customFieldName, customFieldValue);
});
});
it('Should save multiple custom fields on Contact when available', async () => {
// Save the custom field on Visitor/Contact
await request
.post(api('livechat/custom.field'))
.send({ token: visitor.token, key: customFieldName, value: customFieldValue, overwrite: true })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
});
await request
.post(api('livechat/custom.field'))
.send({ token: visitor.token, key: `${customFieldName}_2`, value: customFieldValue, overwrite: true })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
});
// Fetch the visitor to validate custom fields are properly set.
await request
.get(api(`livechat/visitor/${visitor.token}`))
.set(credentials)
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.visitor).to.have.property('livechatData');
expect(res.body.visitor.livechatData).to.have.property(customFieldName, customFieldValue);
expect(res.body.visitor.livechatData).to.have.property(`${customFieldName}_2`, customFieldValue);
});
// Fetch the visitor's contact to validate custom fields are properly set.
await request
.get(api(`omnichannel/contacts.get`))
.set(credentials)
.query({ contactId })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('contact');
expect(res.body.contact).to.have.property('customFields');
expect(res.body.contact.customFields).to.have.property(customFieldName, customFieldValue);
expect(res.body.contact.customFields).to.have.property(`${customFieldName}_2`, customFieldValue);
});
});
it('should add the custom field as conflict on Contact when overwrite is false', async () => {
const conflictingFieldValue = 'conflicting-custom-field-value';
// Save the custom field on Contact
await request
.post(api('livechat/custom.field'))
.send({ token: visitor.token, key: customFieldName, value: conflictingFieldValue, overwrite: false })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
});
// Fetch the visitor to validate custom fields are properly set.
await request
.get(api(`livechat/visitor/${visitor.token}`))
.set(credentials)
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.visitor).to.have.property('livechatData');
expect(res.body.visitor.livechatData).to.have.property(customFieldName, customFieldValue);
});
// Fetch the visitor's contact to validate custom fields are properly set.
await request
.get(api(`omnichannel/contacts.get`))
.set(credentials)
.query({ contactId })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('contact');
expect(res.body.contact).to.have.property('customFields');
expect(res.body.contact.customFields).to.have.property(customFieldName, customFieldValue);
// Validate custom fields contain both entries, indicating conflict criteria
expect(res.body.contact.conflictingFields).to.have.lengthOf(1);
expect(res.body.contact.conflictingFields[0]).to.have.property('field', `customFields.${customFieldName}`);
expect(res.body.contact.conflictingFields[0]).to.have.property('value', conflictingFieldValue);
});
});
it('should not save the custom field as a conflict on Contact when overwrite is false but custom field is not registered yet', async () => {
await createCustomField({
searchable: true,
field: `${customFieldName}_3`,
label: `${customFieldName}_3`,
defaultValue: 'test_default_address',
scope: 'visitor',
visibility: 'public',
regexp: '',
});
// Save the custom field on Contact
await request
.post(api('livechat/custom.field'))
.send({ token: visitor.token, key: `${customFieldName}_3`, value: customFieldValue, overwrite: false })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
});
// Fetch the visitor to validate custom fields are properly set.
await request
.get(api(`livechat/visitor/${visitor.token}`))
.set(credentials)
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.visitor).to.have.property('livechatData');
expect(res.body.visitor.livechatData).to.have.property(`${customFieldName}_3`, customFieldValue);
});
// Fetch the visitor's contact to validate custom fields are properly set.
await request
.get(api(`omnichannel/contacts.get`))
.set(credentials)
.query({ contactId })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('contact');
expect(res.body.contact).to.have.property('customFields');
expect(res.body.contact.customFields).to.have.property(`${customFieldName}_3`, customFieldValue);
// Validate custom fields contain both entries, indicating conflict criteria
expect(res.body.contact.conflictingFields).to.have.lengthOf(1);
expect(res.body.contact.conflictingFields[0]).to.not.have.property('field', `customFields.${customFieldName}_3`);
});
});
describe('conflictingField not nullish', () => {
let room: IOmnichannelRoom;
let visitor: ILivechatVisitor;
let contactId2: string | undefined;
before(async () => {
visitor = await createVisitor();
await request
.post(api('omnichannel/contacts'))
.set(credentials)
.send({
name: visitor.name,
emails: [visitor.visitorEmails?.[0].address],
phones: [visitor.phone?.[0].phoneNumber],
})
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('contactId');
contactId2 = res.body.contactId;
});
room = await createLivechatRoom(visitor.token);
});
after(async () => {
await closeOmnichannelRoom(room._id);
await deleteVisitor(visitor.token);
});
it('should not save contact conflictingFields as nullish if not modified', async () => {
await request
.post(api('livechat/custom.field'))
.send({ token: visitor.token, key: `${customFieldName}`, value: customFieldValue, overwrite: true })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
});
// Fetch the visitor's contact to validate custom fields are properly set.
await request
.get(api(`omnichannel/contacts.get`))
.set(credentials)
.query({ contactId: contactId2 })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('contact');
expect(res.body.contact).to.have.property('customFields');
expect(res.body.contact.customFields).to.have.property(`${customFieldName}`, customFieldValue);
// Validate conflictingFields was not saved as null
expect(res.body.contact).to.not.have.property('conflictingFields');
});
});
it('should not save contact conflictingFields as nullish if not modified (through visitor update)', async () => {
await request
.post(api('livechat/visitor'))
.set(credentials)
.send({
visitor: {
token: visitor.token,
customFields: [{ key: `${customFieldName}`, value: customFieldValue, overwrite: true }],
},
});
// Fetch the visitor's contact to validate custom fields are properly set.
await request
.get(api(`omnichannel/contacts.get`))
.set(credentials)
.query({ contactId: contactId2 })
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('contact');
expect(res.body.contact).to.have.property('customFields');
expect(res.body.contact.customFields).to.have.property(`${customFieldName}`, customFieldValue);
// Validate conflictingFields was not saved as null
expect(res.body.contact).to.not.have.property('conflictingFields');
});
});
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,611 @@
import type { Credentials } from '@rocket.chat/api-client';
import type { ILivechatDepartment, ILivechatInquiryRecord, ILivechatVisitor, IOmnichannelRoom, IUser } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { before, describe, it, after } from 'mocha';
import type { Response } from 'supertest';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { deleteDepartment } from '../../../data/livechat/department';
import {
closeOmnichannelRoom,
createAgent,
createDepartment,
createLivechatRoom,
createVisitor,
deleteVisitor,
fetchInquiry,
getLivechatRoomInfo,
makeAgentAvailable,
startANewLivechatRoomAndTakeIt,
takeInquiry,
} from '../../../data/livechat/rooms';
import {
removePermissionFromAllRoles,
restorePermissionToRoles,
updateEESetting,
updatePermission,
updateSetting,
} from '../../../data/permissions.helper';
import { password } from '../../../data/user';
import { createUser, login, deleteUser } from '../../../data/users.helper';
import { IS_EE } from '../../../e2e/config/constants';
import { retry } from '../helpers/retry';
describe('LIVECHAT - inquiries', () => {
before((done) => getCredentials(done));
before(async () => {
await updateSetting('Livechat_enabled', true);
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
});
describe('livechat/inquiries.list', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-manager', []);
await request.get(api('livechat/inquiries.list')).set(credentials).expect('Content-Type', 'application/json').expect(403);
});
it('should return an array of inquiries', async () => {
await updatePermission('view-livechat-manager', ['admin']);
await request
.get(api('livechat/inquiries.list'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.inquiries).to.be.an('array');
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('count');
});
});
});
describe('livechat/inquiries.getOne', () => {
let room: IOmnichannelRoom;
let visitor: ILivechatVisitor;
before(async () => {
await createAgent();
await makeAgentAvailable();
visitor = await createVisitor();
room = await createLivechatRoom(visitor.token);
});
after(async () => {
await closeOmnichannelRoom(room._id);
await deleteVisitor(visitor.token);
});
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-l-room', []);
await request
.get(api('livechat/inquiries.getOne'))
.query({ roomId: 'room-id' })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(403);
});
it('should return a inquiry', async () => {
await updatePermission('view-l-room', ['admin']);
await request
.get(api('livechat/inquiries.getOne'))
.query({ roomId: 'room-id' })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('inquiry');
});
});
it('should get an inquiry by room id', async () => {
const inquiry = await fetchInquiry(room._id);
await request
.get(api(`livechat/inquiries.getOne`))
.query({ roomId: room._id })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('inquiry');
expect(res.body.inquiry).to.have.property('_id', inquiry._id);
expect(res.body.inquiry).to.have.property('rid', room._id);
expect(res.body.inquiry).to.have.property('ts');
expect(res.body.inquiry.ts).to.be.a('string');
expect(res.body.inquiry).to.have.property('status', 'queued');
expect(res.body.inquiry).to.have.property('name', visitor.name);
expect(res.body.inquiry).to.have.property('t', 'l');
expect(res.body.inquiry).to.have.property('priorityWeight');
expect(res.body.inquiry).to.have.property('estimatedWaitingTimeQueue');
expect(res.body.inquiry.source).to.have.property('type', 'api');
expect(res.body.inquiry).to.have.property('_updatedAt');
expect(res.body.inquiry).to.have.property('v').and.be.an('object');
expect(res.body.inquiry.v).to.have.property('_id', visitor._id);
});
});
});
describe('POST livechat/inquiries.take', () => {
let takenRoom: IOmnichannelRoom;
let servedByRoom: IOmnichannelRoom;
let visitor: ILivechatVisitor;
let visitor2: ILivechatVisitor;
before(async () => {
visitor = await createVisitor();
visitor2 = await createVisitor();
takenRoom = await createLivechatRoom(visitor.token);
servedByRoom = await createLivechatRoom(visitor2.token);
});
after(async () => {
await closeOmnichannelRoom(takenRoom._id);
await closeOmnichannelRoom(servedByRoom._id);
await deleteVisitor(visitor.token);
await deleteVisitor(visitor2.token);
});
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-l-room', []);
await request
.post(api('livechat/inquiries.take'))
.set(credentials)
.send({ inquiryId: 'room-id' })
.expect('Content-Type', 'application/json')
.expect(403);
});
it('should throw an error when userId is provided but is invalid', async () => {
await updatePermission('view-l-room', ['admin', 'livechat-agent']);
await request
.post(api('livechat/inquiries.take'))
.set(credentials)
.send({ inquiryId: 'room-id', userId: 'invalid-user-id' })
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
it('should throw an error if inquiryId is not an string', async () => {
await updatePermission('view-l-room', ['admin', 'livechat-agent']);
await request
.post(api('livechat/inquiries.take'))
.set(credentials)
.send({ inquiryId: { regexxxx: 'bla' }, userId: 'user-id' })
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
it('should take an inquiry if all params are good', async () => {
await updatePermission('view-l-room', ['admin', 'livechat-agent']);
const agent = await createAgent();
await makeAgentAvailable();
const inquiry = await fetchInquiry(takenRoom._id);
await request
.post(api('livechat/inquiries.take'))
.set(credentials)
.send({
inquiryId: inquiry._id,
userId: agent._id,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
});
const inquiry2 = (await fetchInquiry(takenRoom._id)) as ILivechatInquiryRecord;
expect(inquiry2.source?.type).to.equal('api');
expect(inquiry2.status).to.equal('taken');
});
it('should mark a taken room as servedBy me', async () => {
const agent = await createAgent();
await makeAgentAvailable();
const inquiry = await fetchInquiry(servedByRoom._id);
await request
.post(api('livechat/inquiries.take'))
.set(credentials)
.send({
inquiryId: inquiry._id,
userId: agent._id,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
});
const roomInfo = await getLivechatRoomInfo(servedByRoom._id);
expect(roomInfo).to.have.property('servedBy').that.is.an('object');
expect(roomInfo.servedBy).to.have.property('_id', 'rocketchat.internal.admin.test');
});
});
describe('livechat/inquiries.queuedForUser', () => {
let testUser: { user: IUser; credentials: { [key: string]: string } };
const departmentsToDelete: ILivechatDepartment[] = [];
const visitorsToDelete: ILivechatVisitor[] = [];
const roomsToClose: IOmnichannelRoom[] = [];
before(async () => {
await updateSetting('Livechat_accept_chats_with_no_agents', true);
await updateSetting('Omnichannel_enable_department_removal', true);
const user = await createUser();
await createAgent(user.username);
const credentials2 = await login(user.username, password);
await makeAgentAvailable(credentials2);
testUser = {
user,
credentials: credentials2,
};
});
after(async () => {
await Promise.allSettled(roomsToClose.map((room) => closeOmnichannelRoom(room._id)));
await Promise.allSettled(visitorsToDelete.map((visitor) => deleteVisitor(visitor.token)));
await Promise.allSettled(departmentsToDelete.map((dep) => deleteDepartment(dep._id)));
await updateSetting('Livechat_accept_chats_with_no_agents', false);
await updateSetting('Omnichannel_enable_department_removal', false);
await deleteUser(testUser.user);
});
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-l-room', []);
await request.get(api('livechat/inquiries.queuedForUser')).set(credentials).expect('Content-Type', 'application/json').expect(403);
});
it('should return an array of inquiries', async () => {
await restorePermissionToRoles('view-l-room');
await request
.get(api('livechat/inquiries.queuedForUser'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.inquiries).to.be.an('array');
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('count');
});
});
it('should validate all returned inquiries are queued', async () => {
await request
.get(api('livechat/inquiries.queuedForUser'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect(async (res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.inquiries).to.be.an('array');
for (const inquiry of res.body.inquiries) {
expect(inquiry).to.have.property('status', 'queued');
}
});
});
it('should return only public inquiries for a user with no departments', async () => {
const { body } = await request
.get(api('livechat/inquiries.queuedForUser'))
.set(testUser.credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body.inquiries).to.be.an('array');
for (const inq of body.inquiries) {
expect(inq).to.not.have.property('department');
expect(inq).to.have.property('status', 'queued');
}
});
(IS_EE ? it : it.skip)('should return inquiries of the same department as the user', async () => {
const dep = await createDepartment([{ agentId: testUser.user._id }]);
departmentsToDelete.push(dep);
const visitor = await createVisitor(dep._id);
visitorsToDelete.push(visitor);
const room = await createLivechatRoom(visitor.token);
roomsToClose.push(room);
const { body } = await request
.get(api('livechat/inquiries.queuedForUser'))
.set(testUser.credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body.inquiries).to.be.an('array');
const depInq = body.inquiries.filter((inq: { department: string }) => inq.department === dep._id);
expect(depInq.length).to.be.equal(1);
});
(IS_EE ? it : it.skip)('should not return an inquiry of a department the user is not part of', async () => {
const dep = await createDepartment();
departmentsToDelete.push(dep);
const visitor = await createVisitor(dep._id);
visitorsToDelete.push(visitor);
const room = await createLivechatRoom(visitor.token);
roomsToClose.push(room);
const { body } = await request
.get(api('livechat/inquiries.queuedForUser'))
.set(testUser.credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body.inquiries).to.be.an('array');
const depInq = body.inquiries.filter((inq: { department: string }) => inq.department === dep._id);
expect(depInq.length).to.be.equal(0);
});
});
describe('livechat/inquiries.returnAsInquiry', () => {
let testUser: { user: IUser; credentials: Credentials };
before(async () => {
const user = await createUser();
await createAgent(user.username);
const credentials2 = await login(user.username, password);
await makeAgentAvailable(credentials2);
testUser = {
user,
credentials: credentials2,
};
});
after(async () => {
await deleteUser(testUser.user);
});
it('should throw an error if user doesnt have view-l-room permission', async () => {
await removePermissionFromAllRoles('view-l-room');
const { body } = await request
.post(api('livechat/inquiries.returnAsInquiry'))
.set(credentials)
.send({ roomId: 'test' })
.expect('Content-Type', 'application/json')
.expect(403);
expect(body).to.have.property('success', false);
expect(body.error).to.have.equal('User does not have the permissions required for this action [error-unauthorized]');
});
it('should fail if provided room doesnt exists', async () => {
await restorePermissionToRoles('view-l-room');
const { body } = await request
.post(api('livechat/inquiries.returnAsInquiry'))
.set(credentials)
.send({
roomId: 'test',
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(body).to.have.property('success', false);
expect(body).to.have.property('error', 'error-room-not-found');
});
it('should fail if room is not a livechat room', async () => {
const { body } = await request
.post(api('livechat/inquiries.returnAsInquiry'))
.set(credentials)
.send({ roomId: 'GENERAL' })
.expect('Content-Type', 'application/json')
.expect(400);
expect(body).to.have.property('success', false);
expect(body).to.have.property('error', 'error-room-not-found');
});
it('should fail if room is closed', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await closeOmnichannelRoom(room._id);
const { body } = await request
.post(api('livechat/inquiries.returnAsInquiry'))
.set(credentials)
.send({ roomId: room._id })
.expect('Content-Type', 'application/json')
.expect(400);
expect(body).to.have.property('success', false);
expect(body).to.have.property('error', 'room-closed');
await deleteVisitor(visitor.token);
});
describe('no serving', () => {
let room: IOmnichannelRoom;
let visitor: ILivechatVisitor;
before(async () => {
visitor = await createVisitor();
room = await createLivechatRoom(visitor.token);
});
after(async () => {
await closeOmnichannelRoom(room._id);
await deleteVisitor(visitor.token);
});
it('should fail if no one is serving the room', async () => {
const { body } = await request
.post(api('livechat/inquiries.returnAsInquiry'))
.set(credentials)
.send({ roomId: room._id })
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body).to.have.property('result', false);
});
});
let inquiry: ILivechatInquiryRecord;
let room: IOmnichannelRoom;
let returnAsInquiryDep: ILivechatDepartment;
let returnAsInquiryVisitor: ILivechatVisitor;
(IS_EE ? it : it.skip)('should move a room back to queue', async () => {
await updateSetting('Omnichannel_enable_department_removal', true);
returnAsInquiryDep = await createDepartment([{ agentId: testUser.user._id }]);
returnAsInquiryVisitor = await createVisitor(returnAsInquiryDep._id);
room = await createLivechatRoom(returnAsInquiryVisitor.token);
const inq = await fetchInquiry(room._id);
inquiry = inq;
await takeInquiry(inq._id, testUser.credentials);
const { body } = await request
.post(api('livechat/inquiries.returnAsInquiry'))
.set(testUser.credentials)
.send({ roomId: room._id })
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body).to.have.property('result', true);
});
(IS_EE ? it : it.skip)('should appear on users queued elements', async () => {
const { body } = await request
.get(api('livechat/inquiries.queuedForUser'))
.set(testUser.credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(body.inquiries).to.be.an('array');
const depInq = body.inquiries.filter((inq: { _id: string }) => inq._id === inquiry._id);
expect(depInq.length).to.be.equal(1);
await closeOmnichannelRoom(room._id);
await deleteVisitor(returnAsInquiryVisitor.token);
await deleteDepartment(returnAsInquiryDep._id);
await updateSetting('Omnichannel_enable_department_removal', false);
});
});
describe('keep inquiry last message updated', () => {
let room: any;
let visitor: any;
let agent: any;
before(async () => {
agent = await createAgent();
visitor = await createVisitor();
await makeAgentAvailable();
room = await createLivechatRoom(visitor.token);
});
after(async () => {
await closeOmnichannelRoom(room._id);
await deleteVisitor(visitor.token);
});
it('should update inquiry last message', async () => {
const msgText = `update inquiry ${Date.now()}`;
await request.post(api('livechat/message')).send({ token: visitor.token, rid: room._id, msg: msgText }).expect(200);
const inquiry = await fetchInquiry(room._id);
expect(inquiry).to.have.property('_id', inquiry._id);
expect(inquiry).to.have.property('rid', room._id);
expect(inquiry).to.have.property('lastMessage');
expect(inquiry.lastMessage).to.have.property('msg', msgText);
});
it('should update room last message after inquiry is taken', async () => {
const msgText = `update room ${Date.now()}`;
const inquiry = await fetchInquiry(room._id);
await request
.post(api('livechat/inquiries.take'))
.set(credentials)
.send({
inquiryId: inquiry._id,
userId: agent._id,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
});
await request.post(api('livechat/message')).send({ token: visitor.token, rid: room._id, msg: msgText }).expect(200);
// check room
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo).to.have.property('lastMessage');
expect(roomInfo.lastMessage).to.have.property('msg', msgText);
});
it('should have the correct last message when room is returned to queue', async () => {
const msgText = `return to queue ${Date.now()}`;
await request.post(api('livechat/message')).send({ token: visitor.token, rid: room._id, msg: msgText }).expect(200);
await request
.post(api('livechat/inquiries.returnAsInquiry'))
.set(credentials)
.send({ roomId: room._id })
.expect('Content-Type', 'application/json')
.expect(200);
const inquiry = await fetchInquiry(room._id);
expect(inquiry).to.have.property('_id', inquiry._id);
expect(inquiry).to.have.property('rid', room._id);
expect(inquiry).to.have.property('lastMessage');
expect(inquiry.lastMessage).to.have.property('msg', msgText);
});
});
(IS_EE ? describe : describe.skip)('Auto Transfer Scheduler - Manual_Selection', () => {
let testRoom: IOmnichannelRoom;
let testDepartment: ILivechatDepartment;
let testVisitor: ILivechatVisitor;
before(async () => {
// seconds
await Promise.all([
updateSetting('Livechat_auto_transfer_chat_timeout', 3),
createAgent(),
updateSetting('Omnichannel_enable_department_removal', true),
]);
testDepartment = await createDepartment([{ agentId: 'rocketchat.internal.admin.test' }]);
});
after(async () => {
await deleteVisitor(testVisitor.token);
await deleteDepartment(testDepartment._id);
await Promise.all([
updateSetting('Livechat_auto_transfer_chat_timeout', 0),
updateSetting('Omnichannel_enable_department_removal', false),
]);
});
it('should create a room and schedule it for transfer', async () => {
const { room, visitor } = await startANewLivechatRoomAndTakeIt({ departmentId: testDepartment._id });
testVisitor = visitor;
// The room returned is not updated :(
const updatedRoom = await getLivechatRoomInfo(room._id);
expect(updatedRoom).to.have.property('servedBy').that.is.an('object');
testRoom = updatedRoom;
});
it('should return a chat to the queue when not answered after 3 seconds', async () => {
await retry(
`The agenda has a 'heartbeat' of 3 seconds (for testing environment), so its likely that the first fetch will not have the room updated`,
async () => {
const inquiry = await fetchInquiry(testRoom._id);
const room = await getLivechatRoomInfo(testRoom._id);
expect(room).to.not.have.property('servedBy');
expect(inquiry).to.have.property('status', 'queued');
await closeOmnichannelRoom(testRoom._id);
},
{
delayMs: 3000,
},
);
});
});
});

View File

@@ -0,0 +1,300 @@
import type { ILivechatVisitor, IOmnichannelRoom, ISetting } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import {
closeOmnichannelRoom,
createAgent,
createLivechatRoom,
createVisitor,
deleteVisitor,
getLivechatRoomInfo,
startANewLivechatRoomAndTakeIt,
} from '../../../data/livechat/rooms';
import { sleep } from '../../../data/livechat/utils';
import { getLivechatVisitorByToken } from '../../../data/livechat/visitor';
import { updatePermission, updateSetting } from '../../../data/permissions.helper';
describe('LIVECHAT - Integrations', () => {
before((done) => getCredentials(done));
before(async () => {
await updateSetting('Livechat_enabled', true);
});
describe('livechat/integrations.settings', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-manager', []);
await request.get(api('livechat/integrations.settings')).set(credentials).expect('Content-Type', 'application/json').expect(403);
});
it('should return an array of settings', (done) => {
updatePermission('view-livechat-manager', ['admin'])
.then(async () => {
const res = await request
.get(api('livechat/integrations.settings'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body.settings).to.be.an('array');
const settingIds = res.body.settings.map((setting: ISetting) => setting._id);
expect(settingIds).to.include.members([
'Livechat_webhookUrl',
'Livechat_secret_token',
'Livechat_http_timeout',
'Livechat_webhook_on_start',
'Livechat_webhook_on_close',
'Livechat_webhook_on_chat_taken',
'Livechat_webhook_on_chat_queued',
'Livechat_webhook_on_forward',
'Livechat_webhook_on_offline_msg',
'Livechat_webhook_on_visitor_message',
'Livechat_webhook_on_agent_message',
]);
})
.then(done)
.catch(done);
});
});
describe('Incoming SMS', () => {
let smsVisitor: ILivechatVisitor;
let smsRoom: string;
before(async () => {
await updateSetting('SMS_Enabled', true);
await updateSetting('SMS_Service', '');
});
after(async () => {
await closeOmnichannelRoom(smsRoom);
await deleteVisitor(smsVisitor.token);
await updateSetting('SMS_Default_Omnichannel_Department', '');
await updateSetting('SMS_Service', 'twilio');
});
// Twilio sends the body as a x-www-form-urlencoded, the tests should do the same
describe('POST livechat/sms-incoming/:service', () => {
it('should throw an error if SMS is disabled', async () => {
await updateSetting('SMS_Enabled', false);
await request
.post(api('livechat/sms-incoming/twilio'))
.set(credentials)
.send('from=%2B123456789&body=Hello')
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should return an error when SMS service is not configured', async () => {
await updateSetting('SMS_Enabled', true);
await request
.post(api('livechat/sms-incoming/twilio'))
.set(credentials)
.send('From=%2B123456789&To=%2B123456789&Body=Hello')
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should throw an error if SMS_Default_Omnichannel_Department does not exists', async () => {
await updateSetting('SMS_Default_Omnichannel_Department', '123');
await request
.post(api('livechat/sms-incoming/twilio'))
.set(credentials)
.send('From=%2B123456789&To=%2B123456789&Body=Hello')
.expect('Content-Type', 'application/json')
.expect(400);
});
it('should return headers and <Response> as body on success', async () => {
await updateSetting('SMS_Default_Omnichannel_Department', '');
await updateSetting('SMS_Service', 'twilio');
// Create visitor with the phone number that will be used in SMS
smsVisitor = await createVisitor(undefined, 'sms-visitor', undefined, '+12345678910');
const response = await request
.post(api('livechat/sms-incoming/twilio'))
.set(credentials)
.send('From=%2B12345678910&To=%2B123456789&Body=Hello')
.expect('Content-Type', 'text/xml')
.expect(200);
expect(response).to.have.property('text', '<Response></Response>');
const lastChat = (await getLivechatVisitorByToken(smsVisitor.token)).lastChat?._id;
expect(lastChat).to.exist;
smsRoom = lastChat as string;
});
});
});
describe('Livechat - Webhooks', () => {
const webhookUrl = process.env.WEBHOOK_TEST_URL || 'https://httpbin.org';
describe('livechat/webhook.test', () => {
it('should fail when user doesnt have view-livechat-webhooks permission', async () => {
await updatePermission('view-livechat-webhooks', []);
const response = await request.post(api('livechat/webhook.test')).set(credentials).expect(403);
expect(response.body).to.have.property('success', false);
});
it('should fail if setting Livechat_webhookUrl is not set', async () => {
await updateSetting('Livechat_webhookUrl', '');
await updatePermission('view-livechat-webhooks', ['admin', 'livechat-manager']);
const response = await request.post(api('livechat/webhook.test')).set(credentials).expect(400);
expect(response.body).to.have.property('success', false);
});
it('should return true if webhook test went good', async () => {
await updateSetting('Livechat_webhookUrl', `${webhookUrl}/status/200`);
const response = await request.post(api('livechat/webhook.test')).set(credentials).expect(200);
expect(response.body.success).to.be.true;
});
it('should fail if webhook test went bad', async () => {
await updateSetting('Livechat_webhookUrl', `${webhookUrl}/status/400`);
await request.post(api('livechat/webhook.test')).set(credentials).expect(400);
});
});
describe('Webhook notifications', () => {
const visitorsToDelete: ILivechatVisitor[] = [];
const roomsToClose: IOmnichannelRoom[] = [];
before(async () => {
await updateSetting('Livechat_webhookUrl', `${webhookUrl}/anything`);
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
await createAgent();
});
after(async () => {
await Promise.allSettled(roomsToClose.map((room) => closeOmnichannelRoom(room._id)));
await Promise.allSettled(visitorsToDelete.map((visitor) => deleteVisitor(visitor.token)));
await updateSetting('Livechat_webhookUrl', '');
await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
await updateSetting('Livechat_webhook_on_start', false);
await updateSetting('Livechat_webhook_on_close', false);
await updateSetting('Livechat_webhook_on_chat_taken', false);
await updateSetting('Livechat_webhook_on_chat_queued', false);
});
it('should send a notification on chat start', async () => {
await updateSetting('Livechat_webhook_on_start', true);
const { room, visitor } = await startANewLivechatRoomAndTakeIt();
roomsToClose.push(room);
visitorsToDelete.push(visitor);
await sleep(1000);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.crmData).to.be.an('string');
expect(JSON.parse(roomInfo.crmData as string))
.to.have.property('json')
.that.has.property('type', 'LivechatSessionStart');
await updateSetting('Livechat_webhook_on_start', false);
});
it('should send a notification on chat taken', async () => {
await updateSetting('Livechat_webhook_on_chat_taken', true);
const { room, visitor } = await startANewLivechatRoomAndTakeIt();
roomsToClose.push(room);
visitorsToDelete.push(visitor);
await sleep(1000);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.crmData).to.be.an('string');
expect(JSON.parse(roomInfo.crmData as string))
.to.have.property('json')
.that.has.property('type', 'LivechatSessionTaken');
await updateSetting('Livechat_webhook_on_chat_taken', false);
});
let queuedRoom: IOmnichannelRoom;
let queuedVisitor: ILivechatVisitor;
it('should send a notification on chat queued', async () => {
await updateSetting('Livechat_webhook_on_chat_queued', true);
queuedVisitor = await createVisitor();
queuedRoom = await createLivechatRoom(queuedVisitor.token);
roomsToClose.push(queuedRoom);
visitorsToDelete.push(queuedVisitor);
await sleep(1000);
const roomInfo = await getLivechatRoomInfo(queuedRoom._id);
expect(roomInfo.crmData).to.be.an('string');
expect(JSON.parse(roomInfo.crmData as string))
.to.have.property('json')
.that.has.property('type', 'LivechatSessionQueued');
await updateSetting('Livechat_webhook_on_chat_queued', false);
});
it('should send a notification on chat close', async () => {
await updateSetting('Livechat_webhook_on_close', true);
await closeOmnichannelRoom(queuedRoom._id);
const idx = roomsToClose.indexOf(queuedRoom);
if (idx > -1) {
roomsToClose.splice(idx, 1);
}
await sleep(1000);
const roomInfo = await getLivechatRoomInfo(queuedRoom._id);
expect(roomInfo.crmData).to.be.an('string');
expect(JSON.parse(roomInfo.crmData as string))
.to.have.property('json')
.that.has.property('type', 'LivechatSession');
await updateSetting('Livechat_webhook_on_close', false);
});
});
});
describe('omnichannel/integrations', () => {
describe('POST', () => {
it('should update the integration settings if the required parameters are provided', async () => {
const response = await request
.post(api('omnichannel/integrations'))
.set(credentials)
.send({
LivechatWebhookUrl: 'http://localhost:8080',
LivechatSecretToken: 'asdfasdf',
LivechatHttpTimeout: 3000,
LivechatWebhookOnStart: false,
LivechatWebhookOnClose: false,
LivechatWebhookOnChatTaken: false,
LivechatWebhookOnChatQueued: false,
LivechatWebhookOnForward: false,
LivechatWebhookOnOfflineMsg: false,
LivechatWebhookOnVisitorMessage: false,
LivechatWebhookOnAgentMessage: false,
})
.expect(200);
expect(response.body).to.have.property('success', true);
});
it('should fail if a wrong type is provided', async () => {
const response = await request
.post(api('omnichannel/integrations'))
.set(credentials)
.send({
LivechatWebhookUrl: 8000,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
});
it('should fail if a wrong setting is provided', async () => {
const response = await request
.post(api('omnichannel/integrations'))
.set(credentials)
.send({
LivechatWebhook_url: 'http://localhost:8000',
})
.expect(400);
expect(response.body).to.have.property('success', false);
});
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,426 @@
import { expect } from 'chai';
import { before, describe, it, after } from 'mocha';
import type { Response } from 'supertest';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { createTrigger, fetchTriggers } from '../../../data/livechat/triggers';
import { removePermissionFromAllRoles, restorePermissionToRoles, updatePermission, updateSetting } from '../../../data/permissions.helper';
import { IS_EE } from '../../../e2e/config/constants';
describe('LIVECHAT - triggers', () => {
before((done) => getCredentials(done));
before(async () => {
await updateSetting('Livechat_enabled', true);
});
describe('livechat/triggers', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await removePermissionFromAllRoles('view-livechat-manager');
await request.get(api('livechat/triggers')).set(credentials).expect('Content-Type', 'application/json').expect(403);
});
it('should return an array of triggers', async () => {
await restorePermissionToRoles('view-livechat-manager');
await createTrigger(`test${Date.now()}`);
await request
.get(api('livechat/triggers'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.triggers).to.be.an('array');
expect(res.body).to.have.property('count').to.be.greaterThan(0);
expect(res.body.triggers[0]).to.have.property('_id');
expect(res.body.triggers[0]).to.have.property('name');
expect(res.body.triggers[0]).to.have.property('description');
expect(res.body.triggers[0]).to.have.property('enabled', true);
expect(res.body.triggers[0]).to.have.property('runOnce').that.is.a('boolean');
expect(res.body.triggers[0]).to.have.property('conditions').that.is.an('array').with.lengthOf.greaterThan(0);
expect(res.body.triggers[0]).to.have.property('actions').that.is.an('array').with.lengthOf.greaterThan(0);
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
});
});
});
describe('POST livechat/triggers', () => {
it('should fail if user is not logged in', async () => {
await request.post(api('livechat/triggers')).send({}).expect(401);
});
it('should fail if no data is sent', async () => {
await request.post(api('livechat/triggers')).set(credentials).send({}).expect(400);
});
it('should fail if invalid data is sent', async () => {
await request.post(api('livechat/triggers')).set(credentials).send({ name: 'test' }).expect(400);
});
it('should fail if name is not an string', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({ name: 1, description: 'test', enabled: true, runOnce: true, conditions: [], actions: [] })
.expect(400);
});
it('should fail if description is not an string', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({ name: 'test', description: 1, enabled: true, runOnce: true, conditions: [], actions: [] })
.expect(400);
});
it('should fail if enabled is not an boolean', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({ name: 'test', description: 'test', enabled: 1, runOnce: true, conditions: [], actions: [] })
.expect(400);
});
it('should fail if runOnce is not an boolean', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({ name: 'test', description: 'test', enabled: true, runOnce: 1, conditions: [], actions: [] })
.expect(400);
});
it('should fail if conditions is not an array', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({ name: 'test', description: 'test', enabled: true, runOnce: true, conditions: 1, actions: [] })
.expect(400);
});
it('should fail if actions is not an array', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({ name: 'test', description: 'test', enabled: true, runOnce: true, conditions: [], actions: 1 })
.expect(400);
});
it('should fail if conditions is an array with invalid data', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({ name: 'test', description: 'test', enabled: true, runOnce: true, conditions: [1], actions: [] })
.expect(400);
});
it('should fail if conditions is an array of objects, but name is not a valid value', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({ name: 'test', description: 'test', enabled: true, runOnce: true, conditions: [{ name: 'invalid' }], actions: [] })
.expect(400);
});
it('should fail if actions is an array of invalid values', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({ name: 'test', description: 'test', enabled: true, runOnce: true, conditions: [{ name: 'page-url' }], actions: [1] })
.expect(400);
});
it('should fail if actions is an array of objects, but name is not a valid value', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({
name: 'test',
description: 'test',
enabled: true,
runOnce: true,
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
actions: [{ name: 'invalid' }],
})
.expect(400);
});
it('should fail if actions is an array of objects, but sender is not a valid value', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({
name: 'test',
description: 'test',
enabled: true,
runOnce: true,
conditions: [{ name: 'page-url' }],
actions: [{ name: 'send-message', params: { sender: 'invalid' } }],
})
.expect(400);
});
it('should fail if actions is an array of objects, but msg is not a valid value', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({
name: 'test',
description: 'test',
enabled: true,
runOnce: true,
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
actions: [{ name: 'send-message', params: { sender: 'custom' } }],
})
.expect(400);
});
it('should fail if actions is an array of objects, but name is not a valid value', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({
name: 'test',
description: 'test',
enabled: true,
runOnce: true,
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
actions: [{ name: 'send-message', params: { sender: 'custom', msg: 'test', name: {} } }],
})
.expect(400);
});
it('should fail if user doesnt have view-livechat-manager permission', async () => {
await removePermissionFromAllRoles('view-livechat-manager');
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({
name: 'test',
description: 'test',
enabled: true,
runOnce: true,
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
actions: [{ name: 'send-message', params: { sender: 'custom', msg: 'test', name: 'test' } }],
})
.expect(403);
});
it('should save a new trigger of type send-message', async () => {
await restorePermissionToRoles('view-livechat-manager');
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({
name: 'test',
description: 'test',
enabled: true,
runOnce: true,
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
actions: [{ name: 'send-message', params: { sender: 'custom', msg: 'test', name: 'test' } }],
})
.expect(200);
});
it('should fail if type is use-external-service but serviceUrl is not a present', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({
name: 'test2',
description: 'test2',
enabled: true,
runOnce: true,
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
actions: [
{
name: 'use-external-service',
params: {
serviceTimeout: 5000,
serviceFallbackMessage: 'Were sorry, we cannot complete your request',
},
},
],
})
.expect(400);
});
it('should fail if type is use-external-service but serviceTimeout is not a present', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({
name: 'test2',
description: 'test2',
enabled: true,
runOnce: true,
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
actions: [
{
name: 'use-external-service',
params: {
serviceUrl: 'http://localhost:3000/api/vX',
serviceFallbackMessage: 'Were sorry, we cannot complete your request',
},
},
],
})
.expect(400);
});
it('should fail if type is use-external-service but serviceFallbackMessage is not a present', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({
name: 'test2',
description: 'test2',
enabled: true,
runOnce: true,
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
actions: [
{
name: 'use-external-service',
params: {
serviceUrl: 'http://localhost:3000/api/vX',
serviceTimeout: 5000,
},
},
],
})
.expect(400);
});
it('should save a new trigger of type use-external-service', async () => {
await request
.post(api('livechat/triggers'))
.set(credentials)
.send({
name: 'test3',
description: 'test3',
enabled: true,
runOnce: true,
conditions: [{ name: 'page-url', value: 'http://localhost:3000' }],
actions: [
{
name: 'use-external-service',
params: {
serviceUrl: 'http://localhost:3000/api/vX',
serviceTimeout: 5000,
serviceFallbackMessage: 'Were sorry, we cannot complete your request',
},
},
],
})
.expect(200);
});
});
(IS_EE ? describe : describe.skip)('POST livechat/triggers/external-service/test', () => {
const webhookUrl = process.env.WEBHOOK_TEST_URL || 'https://httpbin.org';
after(() => Promise.all([updateSetting('Livechat_secret_token', ''), restorePermissionToRoles('view-livechat-manager')]));
it('should fail if user is not logged in', async () => {
await request.post(api('livechat/triggers/external-service/test')).send({}).expect(401);
});
it('should fail if no data is sent', async () => {
await request.post(api('livechat/triggers/external-service/test')).set(credentials).send({}).expect(400);
});
it('should fail if invalid data is sent', async () => {
await request.post(api('livechat/triggers/external-service/test')).set(credentials).send({ webhookUrl: 'test' }).expect(400);
});
it('should fail if webhookUrl is not an string', async () => {
await request
.post(api('livechat/triggers/external-service/test'))
.set(credentials)
.send({ webhookUrl: 1, timeout: 1000, fallbackMessage: 'test', extraData: [] })
.expect(400);
});
it('should fail if timeout is not an number', async () => {
await request
.post(api('livechat/triggers/external-service/test'))
.set(credentials)
.send({ webhookUrl: 'test', timeout: '1000', fallbackMessage: 'test', extraData: [] })
.expect(400);
});
it('should fail if fallbackMessage is not an string', async () => {
await request
.post(api('livechat/triggers/external-service/test'))
.set(credentials)
.send({ webhookUrl: 'test', timeout: 1000, fallbackMessage: 1, extraData: [] })
.expect(400);
});
it('should fail if params is not an array', async () => {
await request
.post(api('livechat/triggers/external-service/test'))
.set(credentials)
.send({ webhookUrl: 'test', timeout: 1000, fallbackMessage: 'test', extraData: 1 })
.expect(400);
});
it('should fail if user doesnt have view-livechat-webhooks permission', async () => {
await removePermissionFromAllRoles('view-livechat-manager');
await request
.post(api('livechat/triggers/external-service/test'))
.set(credentials)
.send({ webhookUrl: 'test', timeout: 1000, fallbackMessage: 'test', extraData: [] })
.expect(403);
});
it('should fail if Livechat_secret_token setting is empty', async () => {
await restorePermissionToRoles('view-livechat-manager');
await updateSetting('Livechat_secret_token', '');
await request
.post(api('livechat/triggers/external-service/test'))
.set(credentials)
.send({ webhookUrl: 'test', timeout: 1000, fallbackMessage: 'test', extraData: [] })
.expect(400);
});
it('should return error when webhook returns error', async () => {
await updateSetting('Livechat_secret_token', 'test');
await request
.post(api('livechat/triggers/external-service/test'))
.set(credentials)
.send({ webhookUrl: `${webhookUrl}/status/500`, timeout: 5000, fallbackMessage: 'test', extraData: [] })
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'error-invalid-external-service-response');
expect(res.body).to.have.property('response').to.be.a('string');
});
});
it('should return error when webhook times out', async () => {
await request
.post(api('livechat/triggers/external-service/test'))
.set(credentials)
.send({ webhookUrl: `${webhookUrl}/delay/2`, timeout: 1000, fallbackMessage: 'test', extraData: [] })
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'error-timeout');
expect(res.body).to.have.property('response').to.be.a('string');
});
});
it('should fail when webhook returns an answer that doesnt match the format', async () => {
await request
.post(api('livechat/triggers/external-service/test'))
.set(credentials)
.send({ webhookUrl: `${webhookUrl}/anything`, timeout: 5000, fallbackMessage: 'test', extraData: [] })
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'error-invalid-external-service-response');
});
});
});
describe('livechat/triggers/:id', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-manager', []);
await request.get(api('livechat/triggers/invalid-id')).set(credentials).expect('Content-Type', 'application/json').expect(403);
await updatePermission('view-livechat-manager', ['admin']);
});
it('should return null when trigger does not exist', async () => {
await updatePermission('view-livechat-manager', ['admin']);
const response = await request
.get(api('livechat/triggers/invalid-id'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.trigger).to.be.null;
});
it('should return the trigger', async () => {
const triggerName = `test${Date.now()}`;
await createTrigger(triggerName);
const trigger = (await fetchTriggers()).find((t) => t.name === triggerName);
const response = await request
.get(api(`livechat/triggers/${trigger?._id}`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.trigger).to.be.an('object');
expect(response.body.trigger).to.have.property('_id', trigger?._id);
});
});
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,215 @@
import type { IEmailInbox } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';
import type { Response } from 'supertest';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { createEmailInbox } from '../../../data/livechat/inboxes';
import { updatePermission } from '../../../data/permissions.helper';
describe('Email inbox', () => {
before((done) => getCredentials(done));
let testInbox = '';
before(async () => {
await request
.post(api('email-inbox'))
.set(credentials)
.send({
active: true,
name: 'test-email-inbox##',
email: 'test-email@example.com',
description: 'test email inbox',
senderInfo: 'test email inbox',
smtp: {
server: 'smtp.example.com',
port: 587,
username: 'example@example.com',
password: 'not-a-real-password',
secure: true,
},
imap: {
server: 'imap.example.com',
port: 993,
username: 'example@example.com',
password: 'not-a-real-password',
secure: true,
maxRetries: 10,
},
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success');
if (res.body.success === true) {
testInbox = res.body._id;
} else {
expect(res.body).to.have.property('error');
expect(res.body.error.includes('E11000')).to.be.eq(true);
}
});
});
after(async () => {
if (testInbox) {
await request
.delete(api(`email-inbox/${testInbox}`))
.set(credentials)
.send()
.expect(200);
}
});
describe('GET email-inbox.list', () => {
it('should fail if user doesnt have manage-email-inbox permission', async () => {
await updatePermission('manage-email-inbox', []);
await request.get(api('email-inbox.list')).set(credentials).expect(403);
});
it('should return a list of email inboxes', async () => {
await updatePermission('manage-email-inbox', ['admin']);
const res = await request.get(api('email-inbox.list')).set(credentials).send().expect('Content-Type', 'application/json').expect(200);
expect(res.body).to.have.property('emailInboxes');
expect(res.body.emailInboxes).to.be.an('array');
expect(res.body.emailInboxes).to.have.length.of.at.least(1);
expect(res.body.emailInboxes.filter((ibx: IEmailInbox) => ibx.email === 'test-email@example.com')).to.have.length.gte(1);
// make sure we delete the test inbox, even if creation failed on this test run
testInbox = res.body.emailInboxes.filter((ibx: IEmailInbox) => ibx.email === 'test-email@example.com')[0]._id;
expect(res.body).to.have.property('total');
expect(res.body.total).to.be.a('number');
expect(res.body).to.have.property('count');
expect(res.body.count).to.be.a('number');
expect(res.body).to.have.property('offset');
expect(res.body.offset).to.be.a('number');
});
});
describe('POST email-inbox', () => {
let inboxId: string;
const mockedPayload = {
name: 'test',
active: false,
email: `test${new Date().getTime()}@test.com`,
description: 'Updated test description',
senderInfo: 'test',
smtp: {
server: 'smtp.example.com',
port: 587,
username: 'xxxx',
password: 'xxxx',
secure: true,
},
imap: {
server: 'imap.example.com',
port: 993,
username: 'xxxx',
password: 'xxxx',
secure: true,
maxRetries: 10,
},
};
it('should fail if user doesnt have manage-email-inbox permission', async () => {
await updatePermission('manage-email-inbox', []);
await request.post(api('email-inbox')).set(credentials).send(mockedPayload).expect(403);
});
it('should fail if smtp config is not on body params', async () => {
await updatePermission('manage-email-inbox', ['admin']);
await request.post(api('email-inbox')).set(credentials).send({}).expect(400);
});
it('should fail if imap config is not on body params', async () => {
const { imap, ...payload } = mockedPayload;
await request.post(api('email-inbox')).set(credentials).send(payload).expect(400);
});
it('should fail if name is not on body params', async () => {
await updatePermission('manage-email-inbox', ['admin']);
const { name, ...payload } = mockedPayload;
await request.post(api('email-inbox')).set(credentials).send(payload).expect(400);
});
it('should fail if active is not on body params', async () => {
await updatePermission('manage-email-inbox', ['admin']);
const { active, ...payload } = mockedPayload;
await request.post(api('email-inbox')).set(credentials).send(payload).expect(400);
});
it('should fail if email is not on body params', async () => {
await updatePermission('manage-email-inbox', ['admin']);
const { email, ...payload } = mockedPayload;
await request.post(api('email-inbox')).set(credentials).send(payload).expect(400);
});
it('should save an email inbox', async () => {
await updatePermission('manage-email-inbox', ['admin']);
const { email, ...payload } = mockedPayload;
const { body } = await request
.post(api('email-inbox'))
.set(credentials)
.send({
...payload,
email: `test${new Date().getTime()}@test.com`,
})
.expect(200);
expect(body).to.have.property('_id');
inboxId = body._id;
});
it('should update an email inbox when _id is passed in the object', async () => {
await updatePermission('manage-email-inbox', ['admin']);
const { body } = await request
.post(api('email-inbox'))
.set(credentials)
.send({
...mockedPayload,
_id: inboxId,
description: 'Updated test description',
})
.expect(200);
expect(body).to.have.property('_id');
});
});
describe('GET email-inbox/:_id', () => {
it('should fail if user doesnt have manage-email-inbox permission', async () => {
await updatePermission('manage-email-inbox', []);
await request.get(api('email-inbox/123')).set(credentials).expect(403);
});
it('should fail when email inbox does not exist', async () => {
await updatePermission('manage-email-inbox', ['admin']);
await request.get(api('email-inbox/123')).set(credentials).expect(404);
});
it('should return an email inbox', async () => {
const inbox = await createEmailInbox();
const { body } = await request
.get(api(`email-inbox/${inbox._id}`))
.set(credentials)
.expect(200);
expect(body).to.have.property('_id');
expect(body).to.have.property('name', 'test');
});
});
describe('DELETE email-inbox/:_id', () => {
it('should fail if user doesnt have manage-email-inbox permission', async () => {
await updatePermission('manage-email-inbox', []);
await request.delete(api('email-inbox/123')).set(credentials).expect(403);
});
it('should return nothing when email inbox does not exist', async () => {
await updatePermission('manage-email-inbox', ['admin']);
await request.delete(api('email-inbox/123')).set(credentials).expect(404);
});
it('should delete an email inbox', async () => {
const inbox = await createEmailInbox();
const { body } = await request
.delete(api(`email-inbox/${inbox._id}`))
.set(credentials)
.expect(200);
expect(body).to.have.property('success', true);
});
});
describe('GET email-inbox.search', () => {
it('should fail if user doesnt have manage-email-inbox permission', async () => {
await updatePermission('manage-email-inbox', []);
await request.get(api('email-inbox.search')).set(credentials).expect(403);
});
it('should return an email inbox matching email', async () => {
await createEmailInbox();
await updatePermission('manage-email-inbox', ['admin']);
await request.get(api('email-inbox.search')).query({ email: 'test' }).set(credentials).expect(200);
});
});
});

View File

@@ -0,0 +1,844 @@
import type { Credentials } from '@rocket.chat/api-client';
import type { ILivechatDepartment, IUser } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';
import { sleep } from '../../../../lib/utils/sleep';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { createCustomField, deleteCustomField } from '../../../data/livechat/custom-fields';
import {
addOrRemoveAgentFromDepartment,
createDepartmentWithAnOfflineAgent,
createDepartmentWithAnOnlineAgent,
deleteDepartment,
} from '../../../data/livechat/department';
import {
createVisitor,
createLivechatRoom,
makeAgentUnavailable,
closeOmnichannelRoom,
sendMessage,
deleteVisitor,
createDepartment,
} from '../../../data/livechat/rooms';
import { createBotAgent, getRandomVisitorToken } from '../../../data/livechat/users';
import type { WithRequiredProperty } from '../../../data/livechat/utils';
import { removePermissionFromAllRoles, restorePermissionToRoles, updatePermission, updateSetting } from '../../../data/permissions.helper';
import { deleteUser } from '../../../data/users.helper';
import { IS_EE } from '../../../e2e/config/constants';
describe('LIVECHAT - Utils', () => {
before((done) => getCredentials(done));
before(async () => {
await updateSetting('Omnichannel_enable_department_removal', true);
});
after(async () => {
await updateSetting('Livechat_enabled', true);
await updateSetting('Livechat_offline_email', '');
await updateSetting('Omnichannel_enable_department_removal', false);
});
describe('livechat/offline.message', () => {
it('should fail if name is not sent as body parameter', async () => {
const { body } = await request.post(api('livechat/offline.message')).set(credentials).send({});
expect(body).to.have.property('success', false);
});
it('should fail if email is not sent as body parameter', async () => {
const { body } = await request.post(api('livechat/offline.message')).set(credentials).send({ name: 'test' });
expect(body).to.have.property('success', false);
});
it('should fail if message is not sent as body parameter', async () => {
await request.post(api('livechat/offline.message')).set(credentials).send({ name: 'test', email: '' }).expect(400);
});
it('should fail if setting Livechat_display_offline_form is disabled', async () => {
await updateSetting('Livechat_display_offline_form', false);
await request.post(api('livechat/offline.message')).set(credentials).send({ name: 'test', email: '', message: 'test' }).expect(400);
});
it('should fail if Livechat_validate_offline_email is enabled and email passed is not resolvable', async () => {
await updateSetting('Livechat_display_offline_form', true);
await updateSetting('Livechat_validate_offline_email', true);
await request
.post(api('livechat/offline.message'))
.set(credentials)
.send({ name: 'test', email: 'test@fadsjfldasjfd.com', message: 'test' })
.expect(400);
});
it('should fail if setting Livechat_offline_email is not setup or is invalid', async () => {
await updateSetting('Livechat_validate_offline_email', false);
await updateSetting('Livechat_validate_offline_email', 'afsdxcvxc');
await request
.post(api('livechat/offline.message'))
.set(credentials)
.send({ name: 'test', email: 'test@email.com', message: 'this is a test :)' })
.expect(400);
});
it('should send an offline email', async () => {
await updateSetting('Livechat_validate_offline_email', false);
await updateSetting('Livechat_offline_email', 'test-email@rocket.chat');
await request
.post(api('livechat/offline.message'))
.set(credentials)
.send({ name: 'test', email: 'test@email.com', message: 'this is a test :)' })
.expect(200);
});
});
describe('livechat/config', () => {
let emptyDepartment: ILivechatDepartment;
let forwardDepartment: ILivechatDepartment;
let testDepartment: ILivechatDepartment;
let agent: { user: WithRequiredProperty<IUser, 'username'>; credentials: Credentials };
let agent2: { user: WithRequiredProperty<IUser, 'username'>; credentials: Credentials };
before(async () => {
if (!IS_EE) {
return;
}
emptyDepartment = await createDepartment();
({ department: forwardDepartment, agent } = await createDepartmentWithAnOnlineAgent());
({ department: testDepartment, agent: agent2 } = await createDepartmentWithAnOfflineAgent({
departmentsAllowedToForward: [forwardDepartment._id],
}));
});
after(() => {
if (!IS_EE) {
return;
}
return Promise.all([
deleteDepartment(emptyDepartment._id),
deleteDepartment(forwardDepartment._id),
deleteDepartment(testDepartment._id),
deleteUser(agent.user),
deleteUser(agent2.user),
]);
});
it('should return enabled: false if livechat is disabled', async () => {
await updateSetting('Livechat_enabled', false);
const { body } = await request.get(api('livechat/config')).set(credentials);
expect(body).to.have.property('config');
expect(body.config).to.have.property('enabled', false);
});
it('should return basic livechat config when enabled', async () => {
await updateSetting('Livechat_enabled', true);
const { body } = await request.get(api('livechat/config')).set(credentials);
expect(body).to.have.property('config');
expect(body.config).to.have.property('enabled', true);
expect(body.config).to.have.property('settings');
expect(body.config).to.have.property('departments').that.is.an('array');
});
it('should have custom fields data', async () => {
const customFieldName = `new_custom_field_${Date.now()}`;
await createCustomField({
searchable: true,
field: customFieldName,
label: customFieldName,
defaultValue: 'test_default_address',
scope: 'visitor',
visibility: 'visible',
regexp: '',
public: true,
required: false,
options: '',
});
const { body } = await request.get(api('livechat/config')).set(credentials);
expect(body).to.have.property('config');
expect(body.config).to.have.property('customFields').that.is.an('array');
expect(body.config.customFields).to.have.length.greaterThan(0);
const customField = body.config.customFields.find((field: any) => field._id === customFieldName);
expect(customField).to.be.an('object');
expect(customField).to.have.property('label', customFieldName);
await deleteCustomField(customFieldName);
});
(IS_EE ? it : it.skip)('should return online as true if there is at least one agent online', async () => {
const { department } = await createDepartmentWithAnOnlineAgent();
const { body } = await request.get(api('livechat/config')).query({ department: department._id }).set(credentials);
expect(body).to.have.property('config');
expect(body.config).to.have.property('online', true);
});
(IS_EE ? it : it.skip)('should return online as false if there is no agent online', async () => {
const { department, agent } = await createDepartmentWithAnOnlineAgent();
await makeAgentUnavailable(agent.credentials);
const { body } = await request.get(api('livechat/config')).query({ department: department._id }).set(credentials);
expect(body).to.have.property('config');
expect(body.config).to.have.property('online', false);
});
(IS_EE ? it : it.skip)('should return online as true if bot is online and there is no agent online', async () => {
await updateSetting('Livechat_assign_new_conversation_to_bot', true);
const { department, agent } = await createDepartmentWithAnOnlineAgent();
await makeAgentUnavailable(agent.credentials);
const botUser = await createBotAgent();
await addOrRemoveAgentFromDepartment(department._id, { agentId: botUser.user._id, username: botUser.user.username as string }, true);
const { body } = await request.get(api('livechat/config')).query({ department: department._id }).set(credentials);
expect(body).to.have.property('config');
await updateSetting('Livechat_assign_new_conversation_to_bot', false);
await makeAgentUnavailable(botUser.credentials);
});
it('should return a guest if there exists a guest with the same token', async () => {
const guest = await createVisitor();
const { body } = await request.get(api('livechat/config')).query({ token: guest.token }).set(credentials);
expect(body).to.have.property('config');
expect(body.config).to.have.property('guest');
expect(body.config.guest).to.have.property('name', guest.name);
});
it('should not return a guest if there exists a guest with the same token but the guest is not online', async () => {
const token = getRandomVisitorToken();
const { body } = await request.get(api('livechat/config')).query({ token }).set(credentials);
expect(body).to.have.property('config');
expect(body.config).to.not.have.property('guest');
});
it('should return no online room if visitor is not chatting with an agent', async () => {
const visitor = await createVisitor();
const { body } = await request.get(api('livechat/config')).query({ token: visitor.token }).set(credentials);
expect(body).to.have.property('config');
expect(body.config).to.not.have.property('room');
});
it('should return online room if visitor is already chatting with an agent', async () => {
const newVisitor = await createVisitor();
const newRoom = await createLivechatRoom(newVisitor.token);
const { body } = await request.get(api('livechat/config')).query({ token: newVisitor.token }).set(credentials);
expect(body).to.have.property('config');
expect(body.config).to.have.property('room');
expect(body.config.room).to.have.property('_id', newRoom._id);
});
(IS_EE ? it : it.skip)('should return list of departments with at least one agent', async () => {
const { body } = await request.get(api('livechat/config')).set(credentials);
expect(body).to.have.property('success', true);
expect(body).to.have.property('config');
expect(body.config).to.have.property('departments');
expect(body.config.departments).to.be.an('array').with.lengthOf.at.least(2);
expect(body.config.departments).to.not.deep.include({
_id: emptyDepartment._id,
name: emptyDepartment.name,
showOnRegistration: emptyDepartment.showOnRegistration,
showOnOfflineForm: emptyDepartment.showOnOfflineForm,
});
expect(body.config.departments).to.deep.include({
_id: forwardDepartment._id,
name: forwardDepartment.name,
showOnRegistration: forwardDepartment.showOnRegistration,
showOnOfflineForm: forwardDepartment.showOnOfflineForm,
});
expect(body.config.departments).to.deep.include({
_id: testDepartment._id,
name: testDepartment.name,
showOnRegistration: testDepartment.showOnRegistration,
showOnOfflineForm: testDepartment.showOnOfflineForm,
departmentsAllowedToForward: [forwardDepartment._id],
});
});
});
describe('livechat/page.visited', () => {
it('should fail if token is not sent as body param', async () => {
const { body } = await request.post(api('livechat/page.visited')).set(credentials).send({});
expect(body).to.have.property('success', false);
});
it('should fail if rid is not an string', async () => {
const { body } = await request.post(api('livechat/page.visited')).set(credentials).send({ token: 'test', rid: {} });
expect(body).to.have.property('success', false);
});
it('should fail if pageInfo is not sent as body param', async () => {
const { body } = await request.post(api('livechat/page.visited')).set(credentials).send({ token: 'test', rid: 'test' });
expect(body).to.have.property('success', false);
});
it('should fail if pageInfo is not of the right format', async () => {
const { body } = await request.post(api('livechat/page.visited')).set(credentials).send({ token: 'test', rid: 'test', pageInfo: {} });
expect(body).to.have.property('success', false);
});
it('should return empty if pageInfo.change is not equal to Livechat_history_monitor_type', async () => {
const { body } = await request
.post(api('livechat/page.visited'))
.set(credentials)
.send({ token: 'test', rid: 'test', pageInfo: { change: 'test', title: 'test', location: { href: 'test' } } });
expect(body).to.have.property('success', true);
});
it('should store values for 1 month if visitor has no room yet', async () => {
const { body } = await request
.post(api('livechat/page.visited'))
.set(credentials)
.send({ token: 'test', rid: 'test', pageInfo: { change: 'url', title: 'Rocket.Chat', location: { href: 'https://rocket.chat' } } });
expect(body).to.have.property('success', true);
expect(body).to.have.property('page');
expect(body.page).to.have.property('navigation');
expect(body.page).to.have.property('msg');
});
it('should store values correctly on visitor room when room is valid', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
const { body } = await request
.post(api('livechat/page.visited'))
.set(credentials)
.send({
token: visitor.token,
rid: room._id,
pageInfo: { change: 'url', title: 'Rocket.Chat', location: { href: 'https://rocket.chat' } },
});
expect(body).to.have.property('success', true);
expect(body).to.have.property('page');
expect(body.page).to.have.property('navigation');
expect(body.page).to.have.property('msg');
});
});
describe('livechat/transcript', () => {
it('should fail if token is not in body params', async () => {
const { body } = await request.post(api('livechat/transcript')).set(credentials).send({});
expect(body).to.have.property('success', false);
});
it('should fail if rid is not in body params', async () => {
const { body } = await request.post(api('livechat/transcript')).set(credentials).send({ token: 'test' });
expect(body).to.have.property('success', false);
});
it('should fail if email is not in body params', async () => {
const { body } = await request.post(api('livechat/transcript')).set(credentials).send({ token: 'test', rid: 'test' });
expect(body).to.have.property('success', false);
});
it('should fail if token is not a valid guest token', async () => {
const { body } = await request.post(api('livechat/transcript')).set(credentials).send({ token: 'test', rid: 'test', email: '' });
expect(body).to.have.property('success', false);
});
it('should fail if rid is not a valid room id', async () => {
const visitor = await createVisitor();
const { body } = await request
.post(api('livechat/transcript'))
.set(credentials)
.send({ token: visitor.token, rid: 'test', email: '' });
expect(body).to.have.property('success', false);
});
it('should fail if requesting a transcript of another visitors room', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
const visitor2 = await createVisitor();
const { body } = await request
.post(api('livechat/transcript'))
.set(credentials)
.send({ token: visitor2.token, rid: room._id, email: '' });
expect(body).to.have.property('success', false);
});
it('should fail if email is not a valid email', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
const { body } = await request
.post(api('livechat/transcript'))
.set(credentials)
.send({ token: visitor.token, rid: room._id, email: 'test' });
expect(body).to.have.property('success', false);
});
it('should send a transcript if all is good', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
const { body } = await request
.post(api('livechat/transcript'))
.set(credentials)
.send({ token: visitor.token, rid: room._id, email: 'visitor@notadomain.com' });
expect(body).to.have.property('success', true);
});
it('should allow a visitor to get a transcript even if token changed by using an old token that matches room.v', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await closeOmnichannelRoom(room._id);
const visitor2 = await createVisitor(undefined, undefined, visitor.visitorEmails?.[0].address);
const room2 = await createLivechatRoom(visitor2.token);
await closeOmnichannelRoom(room2._id);
expect(visitor.token !== visitor2.token).to.be.true;
const { body } = await request
.post(api('livechat/transcript'))
.set(credentials)
.send({ token: visitor.token, rid: room._id, email: 'visitor@notadomain.com' });
expect(body).to.have.property('success', true);
const { body: body2 } = await request
.post(api('livechat/transcript'))
.set(credentials)
.send({ token: visitor2.token, rid: room2._id, email: 'visitor@notadomain.com' });
expect(body2).to.have.property('success', true);
});
});
describe('livechat/transcript/:rid', () => {
it('should fail if user is not authenticated', async () => {
await request.delete(api('livechat/transcript/rid')).send({}).expect(401);
});
it('should fail if user doesnt have "send-omnichannel-chat-transcript" permission', async () => {
const user = await createVisitor();
const room = await createLivechatRoom(user.token);
await removePermissionFromAllRoles('send-omnichannel-chat-transcript');
await request
.delete(api(`livechat/transcript/${room._id}`))
.set(credentials)
.send({})
.expect(403);
});
it('should fail if rid is not a valid room id', async () => {
await restorePermissionToRoles('send-omnichannel-chat-transcript');
await request.delete(api('livechat/transcript/rid')).set(credentials).send({}).expect(400);
});
it('should fail if room is not open', async () => {
const user = await createVisitor();
const room = await createLivechatRoom(user.token);
await closeOmnichannelRoom(room._id);
await request
.delete(api(`livechat/transcript/${room._id}`))
.set(credentials)
.send({})
.expect(400);
});
it('should fail if room doesnt have transcript requested', async () => {
const user = await createVisitor();
const room = await createLivechatRoom(user.token);
await request
.delete(api(`livechat/transcript/${room._id}`))
.set(credentials)
.send({})
.expect(400);
});
it('should remove transcript if all good', async () => {
const user = await createVisitor();
const room = await createLivechatRoom(user.token);
await request
.post(api(`livechat/transcript/${room._id}`))
.set(credentials)
.send({ email: 'abc@abc.com', subject: 'test' })
.expect(200);
await request
.delete(api(`livechat/transcript/${room._id}`))
.set(credentials)
.send({})
.expect(200);
});
});
describe('POST livechat/transcript/:rid', () => {
it('should fail if user is not authenticated', async () => {
await request.post(api('livechat/transcript/rid')).send({}).expect(401);
});
it('should fail if "email" param is not sent', async () => {
const user = await createVisitor();
const room = await createLivechatRoom(user.token);
await request
.post(api(`livechat/transcript/${room._id}`))
.set(credentials)
.send({})
.expect(400);
});
it('should fail if "subject" param is not sent', async () => {
const user = await createVisitor();
const room = await createLivechatRoom(user.token);
await request
.post(api(`livechat/transcript/${room._id}`))
.set(credentials)
.send({ email: 'abc@abc.xmz' })
.expect(400);
});
it('should fail if user doesnt have "send-omnichannel-chat-transcript" permission', async () => {
const user = await createVisitor();
const room = await createLivechatRoom(user.token);
await updatePermission('send-omnichannel-chat-transcript', []);
await request
.post(api(`livechat/transcript/${room._id}`))
.set(credentials)
.send({ email: 'abc@abc.com', subject: 'test' })
.expect(403);
});
it('should fail if rid is not a valid room id', async () => {
await updatePermission('send-omnichannel-chat-transcript', ['livechat-manager', 'admin']);
await request.post(api('livechat/transcript/rid')).set(credentials).send({ email: 'abc@abc.com', subject: 'test' }).expect(400);
});
it('should fail if room is not open', async () => {
const user = await createVisitor();
const room = await createLivechatRoom(user.token);
await closeOmnichannelRoom(room._id);
await request
.post(api(`livechat/transcript/${room._id}`))
.set(credentials)
.send({ email: 'abc@abc.com', subject: 'test' })
.expect(400);
});
it('should fail if room already has transcript requested', async () => {
const user = await createVisitor();
const room = await createLivechatRoom(user.token);
await request
.post(api(`livechat/transcript/${room._id}`))
.set(credentials)
.send({ email: 'abc@abc.com', subject: 'test' })
.expect(200);
await request
.post(api(`livechat/transcript/${room._id}`))
.set(credentials)
.send({ email: 'abc@abc.com', subject: 'test' })
.expect(400);
});
it('should request transcript if all good', async () => {
const user = await createVisitor();
const room = await createLivechatRoom(user.token);
await request
.post(api(`livechat/transcript/${room._id}`))
.set(credentials)
.send({ email: 'abc@abc.com', subject: 'test' })
.expect(200);
});
});
describe('livechat/visitors.search', () => {
it('should bring sorted data by last chat time', async () => {
const visitor1 = await createVisitor(undefined, 'VisitorInPast');
const room1 = await createLivechatRoom(visitor1.token);
const visitor2 = await createVisitor(undefined, 'VisitorInPresent');
const room2 = await createLivechatRoom(visitor2.token);
const { body: result1 } = await request
.get(api('livechat/visitors.search'))
.query({ term: 'VisitorIn', sort: '{"lastChat.ts":1}' })
.set(credentials)
.send();
expect(result1).to.have.property('visitors').that.is.an('array');
expect(result1.visitors[0]).to.have.property('name');
expect(result1.visitors[0].name).to.be.eq('VisitorInPast');
const { body: result2 } = await request
.get(api('livechat/visitors.search'))
.query({ term: 'VisitorIn', sort: '{"lastChat.ts":-1}' })
.set(credentials)
.send();
expect(result2).to.have.property('visitors').that.is.an('array');
expect(result2.visitors[0]).to.have.property('name');
expect(result2.visitors[0].name).to.be.eq('VisitorInPresent');
await closeOmnichannelRoom(room1._id);
await closeOmnichannelRoom(room2._id);
await deleteVisitor(visitor1.token);
await deleteVisitor(visitor2.token);
});
});
describe('livechat/message', () => {
const visitorTokens: string[] = [];
after(() => Promise.all(visitorTokens.map((token) => deleteVisitor(token))));
it('should fail if no token', async () => {
await request.post(api('livechat/message')).set(credentials).send({}).expect(400);
});
it('should fail if no rid', async () => {
await request.post(api('livechat/message')).set(credentials).send({ token: 'test' }).expect(400);
});
it('should fail if no msg', async () => {
await request.post(api('livechat/message')).set(credentials).send({ token: 'test', rid: 'test' }).expect(400);
});
it('should fail if token is invalid', async () => {
await request.post(api('livechat/message')).set(credentials).send({ token: 'test', rid: 'test', msg: 'test' }).expect(400);
});
it('should fail if rid is invalid', async () => {
const visitor = await createVisitor();
visitorTokens.push(visitor.token);
await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: 'test', msg: 'test' }).expect(400);
});
it('should fail if rid belongs to another visitor', async () => {
const visitor = await createVisitor();
const visitor2 = await createVisitor();
visitorTokens.push(visitor.token, visitor2.token);
const room = await createLivechatRoom(visitor2.token);
await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: room._id, msg: 'test' }).expect(400);
});
it('should fail if room is closed', async () => {
const visitor = await createVisitor();
visitorTokens.push(visitor.token);
const room = await createLivechatRoom(visitor.token);
await closeOmnichannelRoom(room._id);
await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: room._id, msg: 'test' }).expect(400);
});
it('should fail if message is greater than Livechat_enable_message_character_limit setting', async () => {
const visitor = await createVisitor();
visitorTokens.push(visitor.token);
const room = await createLivechatRoom(visitor.token);
await updateSetting('Livechat_enable_message_character_limit', true);
await updateSetting('Livechat_message_character_limit', 1);
await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: room._id, msg: 'test' }).expect(400);
await updateSetting('Livechat_enable_message_character_limit', false);
});
it('should send a message', async () => {
const visitor = await createVisitor();
visitorTokens.push(visitor.token);
const room = await createLivechatRoom(visitor.token);
await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: room._id, msg: 'test' }).expect(200);
});
});
describe('[GET] livechat/message/:_id', () => {
it('should fail if no token is provided', async () => {
await request.get(api('livechat/message/rid')).set(credentials).expect(400);
});
it('should fail if no rid is provided', async () => {
await request.get(api('livechat/message/mid')).query({ token: 'test' }).expect(400);
});
it('should fail if token points to an invalid visitor', async () => {
await request.get(api('livechat/message/mid')).query({ token: 'test', rid: 'test' }).expect(400);
});
it('should fail if room points to an invalid room', async () => {
const visitor = await createVisitor();
await request.get(api('livechat/message/mid')).query({ token: visitor.token, rid: 'test' }).expect(400);
});
it('should fail if _id points to an invalid message', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await request.get(api('livechat/message/mid')).query({ token: visitor.token, rid: room._id }).expect(400);
});
it('should return a message', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
const message = await sendMessage(room._id, 'test', visitor.token);
await request
.get(api(`livechat/message/${message._id}`))
.query({ token: visitor.token, rid: room._id })
.expect(200);
});
});
describe('[PUT] livechat/message/:_id', () => {
it('should fail if no token is provided', async () => {
await request.put(api('livechat/message/rid')).set(credentials).expect(400);
});
it('should fail if no rid is provided', async () => {
await request.put(api('livechat/message/mid')).query({ token: 'test' }).expect(400);
});
it('should fail if token points to an invalid visitor', async () => {
await request.put(api('livechat/message/mid')).query({ token: 'test', rid: 'test' }).expect(400);
});
it('should fail if room points to an invalid room', async () => {
const visitor = await createVisitor();
await request.put(api('livechat/message/mid')).query({ token: visitor.token, rid: 'test' }).expect(400);
});
it('should fail if _id points to an invalid message', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await request.put(api('livechat/message/mid')).query({ token: visitor.token, rid: room._id }).expect(400);
});
it('should update a message', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
const message = await sendMessage(room._id, 'test', visitor.token);
const { body } = await request
.put(api(`livechat/message/${message._id}`))
.set(credentials)
.send({ msg: 'test2', token: visitor.token, rid: room._id })
.expect(200);
expect(body).to.have.property('message');
expect(body.message).to.have.property('_id', message._id);
expect(body.message).to.have.property('msg', 'test2');
});
});
describe('[DELETE] livechat/message/:_id', () => {
it('should fail if no token is provided', async () => {
await request.delete(api('livechat/message/rid')).set(credentials).expect(400);
});
it('should fail if no rid is provided', async () => {
await request.delete(api('livechat/message/mid')).query({ token: 'test' }).expect(400);
});
it('should fail if token points to an invalid visitor', async () => {
await request.delete(api('livechat/message/mid')).query({ token: 'test', rid: 'test' }).expect(400);
});
it('should fail if room points to an invalid room', async () => {
const visitor = await createVisitor();
await request.delete(api('livechat/message/mid')).query({ token: visitor.token, rid: 'test' }).expect(400);
});
it('should fail if _id points to an invalid message', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await request.delete(api('livechat/message/mid')).query({ token: visitor.token, rid: room._id }).expect(400);
});
it('should delete a message', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
const message = await sendMessage(room._id, 'test', visitor.token);
const { body } = await request
.delete(api(`livechat/message/${message._id}`))
.send({ token: visitor.token, rid: room._id })
.expect(200);
expect(body).to.have.property('message');
expect(body.message).to.have.property('_id', message._id);
});
});
describe('livechat/messages.history/:rid', () => {
it('should fail if no token is provided', async () => {
await request.get(api('livechat/messages.history/rid')).set(credentials).expect(400);
});
it('should fail if token points to an invalid visitor', async () => {
await request.get(api('livechat/messages.history/rid')).query({ token: 'test' }).expect(400);
});
it('should fail if room points to an invalid room', async () => {
const visitor = await createVisitor();
await request.get(api('livechat/messages.history/rid')).query({ token: visitor.token }).expect(400);
});
it('should fail if room points to a room of another visitor', async () => {
const visitor = await createVisitor();
const visitor2 = await createVisitor();
const room = await createLivechatRoom(visitor2.token);
await request
.get(api(`livechat/messages.history/${room._id}`))
.query({ token: visitor.token })
.expect(400);
});
it('should return a list of messages', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await sendMessage(room._id, 'test', visitor.token);
const { body } = await request
.get(api(`livechat/messages.history/${room._id}`))
.query({ token: visitor.token })
.expect(200);
expect(body).to.have.property('messages').that.is.an('array');
expect(body.messages).to.have.lengthOf(2);
expect(body.messages[0]).to.have.property('msg', 'test');
});
it('should return a list of messages with offset and count', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await sendMessage(room._id, 'test', visitor.token);
await sendMessage(room._id, 'test2', visitor.token);
const { body } = await request
.get(api(`livechat/messages.history/${room._id}`))
.query({ token: visitor.token, offset: 1, limit: 1 })
.expect(200);
expect(body).to.have.property('messages').that.is.an('array');
expect(body.messages).to.have.lengthOf(1);
expect(body.messages[0]).to.have.property('msg', 'test');
});
it('should return a list of unseen messages', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await sendMessage(room._id, 'test', visitor.token);
await sendMessage(room._id, 'test2', visitor.token);
const { body } = await request
.get(api(`livechat/messages.history/${room._id}`))
.query({ token: visitor.token, ls: new Date() })
.expect(200);
expect(body).to.have.property('messages').that.is.an('array');
expect(body.messages).to.have.lengthOf(3);
expect(body.messages[0]).to.have.property('msg', 'test2');
});
it('should return a list of messages up to a specific date', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
const msg = await sendMessage(room._id, 'test', visitor.token);
const tsPlusSomeMillis = new Date(new Date(msg.ts).getTime() + 500);
await sleep(1000);
await sendMessage(room._id, 'test2', visitor.token);
const { body } = await request
.get(api(`livechat/messages.history/${room._id}`))
.query({ token: visitor.token, end: tsPlusSomeMillis })
.expect(200);
expect(body).to.have.property('messages').that.is.an('array');
expect(body.messages).to.have.lengthOf(2);
expect(body.messages[0]).to.have.property('msg', 'test');
});
it('should return message history for a valid room with pagination', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await sendMessage(room._id, 'Hello', visitor.token);
const { body } = await request
.get(api(`livechat/messages.history/${room._id}`))
.set(credentials)
.query({ token: visitor.token, limit: 1 })
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body).to.have.property('messages').of.length(1);
expect(body.messages[0]).to.have.property('msg', 'Hello');
});
it('should return message history for a valid room with pagination and offset', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await sendMessage(room._id, 'Hello', visitor.token);
const { body } = await request
.get(api(`livechat/messages.history/${room._id}`))
.set(credentials)
.query({ token: visitor.token, limit: 1, offset: 1 })
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body).to.have.property('messages').of.length(1);
expect(body.messages[0]).to.have.property('t');
});
});
(IS_EE ? describe : describe.skip)('[EE] livechat widget', () => {
it('should include additional css when provided via Livechat_WidgetLayoutClasses setting', async () => {
await updateSetting('Livechat_WidgetLayoutClasses', 'http://my.css.com/my.css');
const x = await request.get('/livechat').expect(200);
expect(x.text.includes('http://my.css.com/my.css')).to.be.true;
});
it('should remove additional css when setting Livechat_WidgetLayoutClasses is empty', async () => {
await updateSetting('Livechat_WidgetLayoutClasses', '');
const x = await request.get('/livechat').expect(200);
expect(x.text.includes('http://my.css.com/my.css')).to.be.false;
});
it('should include additional js when provided via Livechat_AdditionalWidgetScripts setting', async () => {
await updateSetting('Livechat_AdditionalWidgetScripts', 'http://my.js.com/my.js');
const x = await request.get('/livechat').expect(200);
expect(x.text.includes('http://my.js.com/my.js')).to.be.true;
});
it('should remove additional js when setting Livechat_AdditionalWidgetScripts is empty', async () => {
await updateSetting('Livechat_AdditionalWidgetScripts', '');
const x = await request.get('/livechat').expect(200);
expect(x.text.includes('http://my.js.com/my.js')).to.be.false;
});
});
});

View File

@@ -0,0 +1,135 @@
import { expect } from 'chai';
import { before, after, describe, it } from 'mocha';
import type { Response } from 'supertest';
import { api, request, credentials, getCredentials } from '../../../data/api-data';
import { updatePermission } from '../../../data/permissions.helper';
describe('Mailer', () => {
before((done) => getCredentials(done));
describe('POST mailer', async () => {
before(async () => {
return updatePermission('send-mail', ['admin']);
});
after(async () => {
return updatePermission('send-mail', ['admin']);
});
it('should send an email if the payload is correct', async () => {
await request
.post(api('mailer'))
.set(credentials)
.send({
from: 'rocketchat.internal.admin.test@rocket.chat',
subject: 'Test email subject',
body: 'Test email body [unsubscribe]',
dryrun: true,
query: '',
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
});
});
it('should throw an error if the request is incorrect', async () => {
await request
.post(api('mailer'))
.set(credentials)
.send({
from: 12345,
subject: {},
body: 'Test email body',
dryrun: true,
query: '',
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
it('should throw an error if the "from" param is missing', async () => {
await request
.post(api('mailer'))
.set(credentials)
.send({
subject: 'Test email subject',
body: 'Test email body',
dryrun: true,
query: '',
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
it('should throw an error if user does NOT have the send-mail permission', async () => {
await updatePermission('send-mail', []);
await request
.post(api('mailer'))
.set(credentials)
.send({
from: 'test-mail@test.com',
subject: 'Test email subject',
body: 'Test email body',
dryrun: true,
query: '',
})
.expect('Content-Type', 'application/json')
.expect(403)
.expect((res: Response) => {
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('Mailer Unsubscribe', () => {
describe('POST mailer unsubscribe', () => {
it('should unsubscribe to mailer if the request is correct', async () => {
await request
.post(api('mailer.unsubscribe'))
.set(credentials)
.send({
_id: credentials['X-User-Id'],
createdAt: new Date().getTime().toString(),
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
});
});
it('should throw an error if the "_id" param is missing', async () => {
await request
.post(api('mailer.unsubscribe'))
.set(credentials)
.send({
createdAt: new Date().getTime().toString(),
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
it('should throw an error if the "createdAt" param is missing', async () => {
await request
.post(api('mailer.unsubscribe'))
.set(credentials)
.send({
_id: credentials['X-User-Id'],
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
});
});
});
});

View File

@@ -0,0 +1,936 @@
import { faker } from '@faker-js/faker';
import type {
ILivechatInquiryRecord,
ILivechatPriority,
IOmnichannelRoom,
IOmnichannelServiceLevelAgreements,
} from '@rocket.chat/core-typings';
import { OmnichannelSortingMechanismSettingType } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { createDepartmentWithAnOnlineAgent } from '../../../data/livechat/department';
import { fetchAllInquiries } from '../../../data/livechat/inquiries';
import { createSLA, deleteSLA, bulkCreateSLA, deleteAllSLA } from '../../../data/livechat/priorities';
import {
createAgent,
createVisitor,
createLivechatRoom,
bulkCreateLivechatRooms,
startANewLivechatRoomAndTakeIt,
makeAgentAvailable,
deleteAgent,
} from '../../../data/livechat/rooms';
import {
addPermissions,
removePermissionFromAllRoles,
removePermissions,
restorePermissionToRoles,
updateEESetting,
updatePermission,
updateSetting,
} from '../../../data/permissions.helper';
import { IS_EE } from '../../../e2e/config/constants';
import { generateRandomSLAData } from '../../../e2e/utils/omnichannel/sla';
(IS_EE ? describe : describe.skip)('[EE] LIVECHAT - Priorities & SLAs', () => {
before((done) => getCredentials(done));
before(async () => {
await updateSetting('Livechat_enabled', true);
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
});
after(async () => {
await addPermissions({
'manage-livechat-priorities': ['admin', 'livechat-manager'],
'manage-livechat-sla': ['admin', 'livechat-manager'],
'view-l-room': ['admin', 'livechat-manager', 'livechat-agent'],
});
});
describe('livechat/sla', () => {
// GET
it('should return an "unauthorized error" when the user does not have the necessary permission for [GET] livechat/sla endpoint', async () => {
await removePermissions(['manage-livechat-sla', 'view-l-room']);
const response = await request.get(api('livechat/sla')).set(credentials).expect('Content-Type', 'application/json').expect(403);
expect(response.body).to.have.property('success', false);
});
it('should return an array of slas', async () => {
await addPermissions({
'manage-livechat-sla': ['admin', 'livechat-manager'],
'view-l-room': ['livechat-agent', 'admin', 'livechat-manager'],
});
const sla = await createSLA();
expect(sla).to.not.be.undefined;
expect(sla).to.have.property('_id');
const response = await request.get(api('livechat/sla')).set(credentials).expect('Content-Type', 'application/json').expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.sla).to.be.an('array').with.lengthOf.greaterThan(0);
const current = response.body.sla.find((p: IOmnichannelServiceLevelAgreements) => p && p?._id === sla._id);
expect(current).to.be.an('object');
expect(current).to.have.property('name', sla.name);
expect(current).to.have.property('description', sla.description);
expect(current).to.have.property('dueTimeInMinutes', sla.dueTimeInMinutes);
await deleteSLA(sla._id);
});
// POST
it('should return an "unauthorized error" when the user does not have the necessary permission for [POST] livechat/sla endpoint', async () => {
await updatePermission('manage-livechat-sla', []);
const response = await request
.post(api('livechat/sla'))
.set(credentials)
.send(generateRandomSLAData())
.expect('Content-Type', 'application/json')
.expect(403);
expect(response.body).to.have.property('success', false);
});
it('should create a new sla', async () => {
await restorePermissionToRoles('manage-livechat-sla');
const sla = generateRandomSLAData();
const response = await request
.post(api('livechat/sla'))
.set(credentials)
.send(sla)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.sla).to.be.an('object');
expect(response.body.sla).to.have.property('_id');
expect(response.body.sla).to.have.property('name', sla.name);
expect(response.body.sla).to.have.property('description', sla.description);
expect(response.body.sla).to.have.property('dueTimeInMinutes', sla.dueTimeInMinutes);
await deleteSLA(response.body.sla._id);
});
it('should throw an error when trying to create a duplicate sla with same dueTimeInMinutes', async () => {
const firstSla = generateRandomSLAData();
const response = await request
.post(api('livechat/sla'))
.set(credentials)
.send(firstSla)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
const secondSla = generateRandomSLAData();
secondSla.dueTimeInMinutes = firstSla.dueTimeInMinutes;
const secondResponse = await request
.post(api('livechat/sla'))
.set(credentials)
.send(secondSla)
.expect('Content-Type', 'application/json')
.expect(400);
expect(secondResponse.body).to.have.property('success', false);
expect(secondResponse.body).to.have.property('error');
expect(secondResponse.body?.error).to.contain('error-duplicated-sla');
await deleteSLA(response.body.sla._id);
});
it('should throw an error when trying to create a duplicate sla with same name', async () => {
const firstSla = generateRandomSLAData();
const response = await request
.post(api('livechat/sla'))
.set(credentials)
.send(firstSla)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
const secondSla = generateRandomSLAData();
secondSla.name = firstSla.name;
const secondResponse = await request
.post(api('livechat/sla'))
.set(credentials)
.send(secondSla)
.expect('Content-Type', 'application/json')
.expect(400);
expect(secondResponse.body).to.have.property('success', false);
expect(secondResponse.body).to.have.property('error');
expect(secondResponse.body?.error).to.contain('error-duplicated-sla');
await deleteSLA(response.body.sla._id);
});
});
describe('livechat/sla/:slaId', () => {
// GET
it('should return an "unauthorized error" when the user does not have the necessary permission for [GET] livechat/sla/:slaId endpoint', async () => {
await Promise.all([removePermissionFromAllRoles('manage-livechat-sla'), removePermissionFromAllRoles('view-l-room')]);
const response = await request.get(api('livechat/sla/123')).set(credentials).expect('Content-Type', 'application/json').expect(403);
expect(response.body).to.have.property('success', false);
});
it('should create, find and delete an sla', async () => {
await Promise.all([restorePermissionToRoles('manage-livechat-sla'), restorePermissionToRoles('view-l-room')]);
const sla = await createSLA();
const response = await request
.get(api(`livechat/sla/${sla._id}`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body).to.be.an('object');
expect(response.body._id).to.be.equal(sla._id);
await deleteSLA(sla._id);
});
// PUT
it('should return an "unauthorized error" when the user does not have the necessary permission for [PUT] livechat/sla/:slaId endpoint', async () => {
await removePermissionFromAllRoles('manage-livechat-sla');
const response = await request
.put(api('livechat/sla/123'))
.set(credentials)
.send(generateRandomSLAData())
.expect('Content-Type', 'application/json')
.expect(403);
expect(response.body).to.have.property('success', false);
});
it('should update an sla', async () => {
await restorePermissionToRoles('manage-livechat-sla');
const sla = await createSLA();
const newSlaData = generateRandomSLAData();
const response = await request
.put(api(`livechat/sla/${sla._id}`))
.set(credentials)
.send(newSlaData)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.sla).to.be.an('object');
expect(response.body.sla).to.have.property('_id');
expect(response.body.sla).to.have.property('name', newSlaData.name);
expect(response.body.sla).to.have.property('description', newSlaData.description);
expect(response.body.sla).to.have.property('dueTimeInMinutes', newSlaData.dueTimeInMinutes);
await deleteSLA(response.body.sla._id);
});
it('should throw an error when trying to update a sla with a duplicate name', async () => {
const firstSla = await createSLA();
const secondSla = await createSLA();
secondSla.name = firstSla.name;
const response = await request
.put(api(`livechat/sla/${secondSla._id}`))
.set(credentials)
.send({
name: secondSla.name,
description: secondSla.description,
dueTimeInMinutes: secondSla.dueTimeInMinutes,
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(response.body).to.have.property('success', false);
expect(response.body).to.have.property('error');
expect(response.body?.error).to.contain('error-duplicated-sla');
await deleteSLA(firstSla._id);
await deleteSLA(secondSla._id);
});
it('should throw an error when trying to update a sla with a duplicate dueTimeInMinutes', async () => {
const firstSla = await createSLA();
const secondSla = await createSLA();
secondSla.dueTimeInMinutes = firstSla.dueTimeInMinutes;
const response = await request
.put(api(`livechat/sla/${secondSla._id}`))
.set(credentials)
.send({
name: secondSla.name,
description: secondSla.description,
dueTimeInMinutes: secondSla.dueTimeInMinutes,
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(response.body).to.have.property('success', false);
expect(response.body).to.have.property('error');
expect(response.body?.error).to.contain('error-duplicated-sla');
await deleteSLA(firstSla._id);
await deleteSLA(secondSla._id);
});
// DELETE
it('should return an "unauthorized error" when the user does not have the necessary permission for [DELETE] livechat/sla/:slaId endpoint', async () => {
await updatePermission('manage-livechat-sla', []);
const response = await request
.delete(api('livechat/sla/123'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(403);
expect(response.body).to.have.property('success', false);
});
it('should delete an sla', async () => {
await updatePermission('manage-livechat-sla', ['admin', 'livechat-manager']);
const sla = await createSLA();
const response = await request
.delete(api(`livechat/sla/${sla._id}`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
});
});
describe('livechat/inquiry.setSLA', () => {
before(async () => {
await createAgent();
await makeAgentAvailable();
});
after(async () => {
await deleteAgent();
});
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await removePermissions(['manage-livechat-sla', 'view-l-room', 'manage-livechat-priorities']);
const response = await request
.put(api('livechat/inquiry.setSLA'))
.set(credentials)
.send({
roomId: '123',
sla: '123',
})
.expect('Content-Type', 'application/json')
.expect(403);
expect(response.body).to.have.property('success', false);
});
it('should fail if roomId is not in request body', async () => {
await addPermissions({
'manage-livechat-sla': ['admin', 'livechat-manager'],
'manage-livechat-priorities': ['admin', 'livechat-manager'],
'view-l-room': ['livechat-agent', 'admin', 'livechat-manager'],
});
const response = await request
.put(api('livechat/inquiry.setSLA'))
.set(credentials)
.send({
sla: '123',
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if roomId is invalid', async () => {
const response = await request
.put(api('livechat/inquiry.setSLA'))
.set(credentials)
.send({
roomId: '123',
sla: '123',
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if sla is not in request body', async () => {
const response = await request
.put(api('livechat/inquiry.setSLA'))
.set(credentials)
.send({
roomId: '123',
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if sla is not valid', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
const response = await request
.put(api('livechat/inquiry.setSLA'))
.set(credentials)
.send({
roomId: room._id,
sla: '123',
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if inquiry is not queued', async () => {
const {
room: { _id: roomId },
} = await startANewLivechatRoomAndTakeIt();
const response = await request
.put(api('livechat/inquiry.setSLA'))
.set(credentials)
.send({
roomId,
sla: '123',
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should prioritize an inquiry', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
const sla = await createSLA();
const response = await request
.put(api('livechat/inquiry.setSLA'))
.set(credentials)
.send({
roomId: room._id,
sla: sla._id,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
});
});
describe('livechat/priorities', () => {
let priority: ILivechatPriority;
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await removePermissions(['manage-livechat-priorities', 'view-l-room']);
const response = await request
.get(api('livechat/priorities'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(403);
expect(response.body).to.have.property('success', false);
});
it('should return an array of priorities', async () => {
await addPermissions({
'manage-livechat-priorities': ['admin', 'livechat-manager'],
'view-l-room': ['livechat-agent', 'admin', 'livechat-manager'],
});
const response = await request
.get(api('livechat/priorities'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.priorities).to.be.an('array');
expect(response.body.priorities).to.have.lengthOf(5);
expect(response.body.priorities[0]).to.have.property('_id');
expect(response.body.priorities[0]).to.have.property('i18n');
expect(response.body.priorities[0]).to.have.property('dirty');
priority = response.body.priorities[0];
});
it('should return an array of priorities matching text param', async () => {
const response = await request
.get(api('livechat/priorities'))
.set(credentials)
.query({
text: priority.name,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.priorities).to.be.an('array');
expect(response.body.priorities).to.have.length.greaterThan(0);
expect(response.body.priorities[0]).to.have.property('_id');
});
});
describe('livechat/priorities/:priorityId', () => {
let priority: ILivechatPriority;
const name = `${faker.lorem.word()}${faker.git.commitSha({ length: 6 })}`;
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await removePermissions(['manage-livechat-priorities', 'view-l-room']);
const response = await request.get(api('livechat/priorities/123')).set(credentials).expect(403);
expect(response.body).to.have.property('success', false);
expect(response.body).to.have.property('error');
expect(response.body?.error).to.contain('error-unauthorized');
});
it('should return a priority by id', async () => {
await addPermissions({
'manage-livechat-priorities': ['admin', 'livechat-manager'],
'view-l-room': ['livechat-agent', 'admin', 'livechat-manager'],
});
const {
body: { priorities },
} = await request.get(api('livechat/priorities')).set(credentials).expect('Content-Type', 'application/json').expect(200);
priority = priorities[0];
const response = await request
.get(api(`livechat/priorities/${priority._id}`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body).to.be.an('object');
expect(response.body._id).to.be.equal(priority._id);
});
it('should throw 404 when priority does not exist', async () => {
const response = await request.get(api('livechat/priorities/123')).set(credentials).expect(404);
expect(response.body).to.have.property('success', false);
});
it('should update a priority when using PUT', async () => {
const response = await request
.put(api(`livechat/priorities/${priority._id}`))
.set(credentials)
.send({
name,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
});
it('should return dirty: true after a priority has been updated', async () => {
const response = await request
.get(api(`livechat/priorities/${priority._id}`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body).to.have.property('dirty', true);
});
it('should return an array of priorities matching text param', async () => {
const response = await request
.get(api('livechat/priorities'))
.set(credentials)
.query({
text: name,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.priorities).to.be.an('array');
expect(response.body.priorities).to.have.length.greaterThan(0);
const pos = response.body.priorities.findIndex((p: ILivechatPriority) => p._id === priority._id);
expect(pos).to.be.greaterThan(-1);
expect(response.body.priorities[pos]).to.have.property('_id');
expect(response.body.priorities[pos]).to.have.property('i18n', priority.i18n);
});
it('should edit a priority with a PUT', async () => {
const newName = `${faker.lorem.word()}${faker.git.commitSha({ length: 6 })}`;
const response = await request
.put(api(`livechat/priorities/${priority._id}`))
.set(credentials)
.send({
name: newName,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
const newPriorityResponse = await request
.get(api(`livechat/priorities/${priority._id}`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(newPriorityResponse.body).to.have.property('success', true);
expect(newPriorityResponse.body).to.have.property('dirty', true);
expect(newPriorityResponse.body).to.have.property('name', newName);
});
it('should fail to edit a priority with a PUT if using too many parameters', async () => {
const newName = faker.lorem.word();
const response = await request
.put(api(`livechat/priorities/${priority._id}`))
.set(credentials)
.send({
name: newName,
reset: true,
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(response.body).to.have.property('success', false);
expect(response.body).to.have.property('error');
expect(response.body?.error).to.contain('invalid-params');
});
it('should fail to edit a priority with a PUT if using an object as name', async () => {
const newName = faker.lorem.word();
const response = await request
.put(api(`livechat/priorities/${priority._id}`))
.set(credentials)
.send({
name: { name: newName },
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(response.body).to.have.property('success', false);
expect(response.body).to.have.property('error');
expect(response.body?.error).to.contain('invalid-params');
});
it('should not fail to edit a priority with a PUT if using a boolean as name (it becomes a string)', async () => {
const response = await request
.put(api(`livechat/priorities/${priority._id}`))
.set(credentials)
.send({
name: false,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
});
it('should fail to update a non-existing priority', async () => {
const response = await request
.put(api('livechat/priorities/123'))
.set(credentials)
.send({
name: faker.lorem.word(),
})
.expect('Content-Type', 'application/json')
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should reset a single priority with a reset:true PUT parameter', async () => {
const response = await request
.put(api(`livechat/priorities/${priority._id}`))
.set(credentials)
.send({
reset: true,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
const newPriorityResponse = await request
.get(api(`livechat/priorities/${priority._id}`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(newPriorityResponse.body).to.have.property('success', true);
expect(newPriorityResponse.body).to.have.property('dirty', false);
expect(newPriorityResponse.body).to.not.have.property('name');
});
it('should throw a duplicate error incase there is already a priority with same name', async () => {
const {
body: { priorities },
} = await request.get(api('livechat/priorities')).set(credentials).expect('Content-Type', 'application/json').expect(200);
// change name of first priority to a random name
const newName = `${faker.lorem.word()}${faker.git.commitSha({ length: 6 })}`;
await request
.put(api(`livechat/priorities/${priorities[0]._id}`))
.set(credentials)
.send({ name: newName })
.expect('Content-Type', 'application/json')
.expect(200);
// change name of second priority to the name of first priority and expect error
const response = await request
.put(api(`livechat/priorities/${priorities[1]._id}`))
.set(credentials)
.send({ name: newName })
.expect('Content-Type', 'application/json')
.expect(400);
expect(response.body).to.have.property('success', false);
expect(response.body).to.have.property('error', 'error-duplicate-priority-name');
});
it('should throw a duplicate error incase there is already a priority with same name (case insensitive)', async () => {
const {
body: { priorities },
} = await request.get(api('livechat/priorities')).set(credentials).expect('Content-Type', 'application/json').expect(200);
// change name of first priority to a random name
const newNameLowercase = `${faker.lorem.word()}${faker.git.commitSha({ length: 6 })}`.toLowerCase();
await request
.put(api(`livechat/priorities/${priorities[0]._id}`))
.set(credentials)
.send({ name: newNameLowercase })
.expect('Content-Type', 'application/json')
.expect(200);
// change name of second priority to the name of first priority in different case and expect error
const response = await request
.put(api(`livechat/priorities/${priorities[1]._id}`))
.set(credentials)
.send({ name: newNameLowercase.toUpperCase() })
.expect('Content-Type', 'application/json')
.expect(400);
expect(response.body).to.have.property('success', false);
expect(response.body).to.have.property('error', 'error-duplicate-priority-name');
});
});
describe('livechat/priorities.reset', () => {
let priority: ILivechatPriority;
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('manage-livechat-priorities', []);
const response = await request.post(api('livechat/priorities.reset')).set(credentials).expect(403);
expect(response.body).to.have.property('success', false);
expect(response.body).to.have.property('error');
expect(response.body?.error).to.contain('error-unauthorized');
});
it('should respond reset:true when a priority has been changed', async () => {
await updatePermission('manage-livechat-priorities', ['admin', 'livechat-manager']);
const {
body: { priorities },
} = await request.get(api('livechat/priorities')).set(credentials).expect('Content-Type', 'application/json').expect(200);
priority = priorities[0];
priority.name = `${faker.lorem.word()}${faker.git.commitSha({ length: 6 })}`;
const responseChange = await request
.put(api(`livechat/priorities/${priority._id}`))
.set(credentials)
.send({
name: priority.name,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(responseChange.body).to.have.property('success', true);
const response = await request
.get(api('livechat/priorities.reset'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body).to.have.property('reset', true);
});
it('should reset all priorities', async () => {
const resetRespose = await request
.post(api('livechat/priorities.reset'))
.set(credentials)
.send()
.expect('Content-Type', 'application/json')
.expect(200);
expect(resetRespose.body).to.have.property('success', true);
});
it('should return reset: false after all priorities have been reset', async () => {
const response = await request
.get(api('livechat/priorities.reset'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body).to.have.property('reset', false);
});
it('should change the priority name and dirty status when reset', async () => {
const response = await request
.get(api(`livechat/priorities/${priority._id}`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body).to.have.property('dirty', false);
expect(response.body).to.not.have.property('name');
});
it('should change all priorities to their default', async () => {
const response = await request
.get(api('livechat/priorities'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.priorities).to.be.an('array');
response.body.priorities.forEach((priority: ILivechatPriority) => {
expect(priority).to.not.have.property('name');
expect(priority).to.have.property('dirty', false);
});
});
it('should fail to reset when lacking permissions', async () => {
await updatePermission('manage-livechat-priorities', []);
const response = await request.post(api('livechat/priorities.reset')).set(credentials).expect(403);
expect(response.body).to.have.property('success', false);
expect(response.body).to.have.property('error');
expect(response.body?.error).to.contain('error-unauthorized');
});
});
describe('Inquiry queue sorting mechanism', () => {
let omniRooms: IOmnichannelRoom[];
let departmentWithAgent: Awaited<ReturnType<typeof createDepartmentWithAnOnlineAgent>>;
let priorities: ILivechatPriority[];
let slas: Awaited<ReturnType<typeof bulkCreateSLA>>;
const hasSlaProps = (inquiry: ILivechatInquiryRecord): inquiry is ILivechatInquiryRecord & { slaId: string } =>
!!inquiry.estimatedWaitingTimeQueue && !!inquiry.slaId;
const hasPriorityProps = (inquiry: ILivechatInquiryRecord): inquiry is ILivechatInquiryRecord & { priorityId: string } =>
inquiry.priorityId !== undefined;
const getPriorityOrderById = (id: string) => {
const priority = priorities.find((priority) => priority._id === id);
return priority?.sortItem || 0;
};
const sortBySLAProps = (inquiry1: ILivechatInquiryRecord, inquiry2: ILivechatInquiryRecord): number => {
if (hasSlaProps(inquiry1) || hasSlaProps(inquiry2)) {
if (hasSlaProps(inquiry1) && hasSlaProps(inquiry2)) {
// if both inquiries have sla props, then sort by estimatedWaitingTimeQueue: 1
const estimatedWaitingTimeQueue1 = inquiry1.estimatedWaitingTimeQueue;
const estimatedWaitingTimeQueue2 = inquiry2.estimatedWaitingTimeQueue;
if (estimatedWaitingTimeQueue1 !== estimatedWaitingTimeQueue2) {
return estimatedWaitingTimeQueue1 - estimatedWaitingTimeQueue2;
}
}
if (hasSlaProps(inquiry1)) {
return -1;
}
if (hasSlaProps(inquiry2)) {
return 1;
}
}
return 0;
};
const sortByPriorityProps = (inquiry1: ILivechatInquiryRecord, inquiry2: ILivechatInquiryRecord): number => {
if (hasPriorityProps(inquiry1) || hasPriorityProps(inquiry2)) {
if (hasPriorityProps(inquiry1) && hasPriorityProps(inquiry2)) {
// if both inquiries have priority props, then sort by priorityId: 1
return getPriorityOrderById(inquiry1.priorityId) - getPriorityOrderById(inquiry2.priorityId);
}
if (hasPriorityProps(inquiry1)) {
return -1;
}
if (hasPriorityProps(inquiry2)) {
return 1;
}
}
return 0;
};
// this should sort using logic - { ts: 1 }
const sortByTimestamp = (inquiry1: ILivechatInquiryRecord, inquiry2: ILivechatInquiryRecord) => {
return new Date(inquiry1.ts).getTime() - new Date(inquiry2.ts).getTime();
};
// this should sort using logic - { priorityWeight: 1, ts: 1 }
const sortByPriority = (inquiry1: ILivechatInquiryRecord, inquiry2: ILivechatInquiryRecord) => {
const priorityPropsSort = sortByPriorityProps(inquiry1, inquiry2);
if (priorityPropsSort !== 0) {
return priorityPropsSort;
}
return sortByTimestamp(inquiry1, inquiry2);
};
// this should sort using logic - { estimatedWaitingTimeQueue: 1, ts: 1 }
const sortBySLA = (inquiry1: ILivechatInquiryRecord, inquiry2: ILivechatInquiryRecord) => {
const slaPropsSort = sortBySLAProps(inquiry1, inquiry2);
if (slaPropsSort !== 0) {
return slaPropsSort;
}
return sortByTimestamp(inquiry1, inquiry2);
};
const filterUnnecessaryProps = (inquiries: ILivechatInquiryRecord[]) =>
inquiries.map((inquiry) => {
return {
_id: inquiry._id,
rid: inquiry.rid,
...(hasPriorityProps(inquiry) && {
priority: {
priorityWeight: getPriorityOrderById(inquiry.priorityId),
id: inquiry.priorityWeight,
},
}),
...(hasSlaProps(inquiry) && {
sla: {
estimatedWaitingTimeQueue: inquiry.estimatedWaitingTimeQueue,
},
}),
ts: inquiry.ts,
};
});
const compareInquiries = (inquiries1: ILivechatInquiryRecord[], inquiries2: ILivechatInquiryRecord[]) => {
const filteredInquiries1 = filterUnnecessaryProps(inquiries1);
const filteredInquiries2 = filterUnnecessaryProps(inquiries2);
expect(filteredInquiries1).to.deep.equal(filteredInquiries2);
};
it('it should create all the data required for further testing', async () => {
departmentWithAgent = await createDepartmentWithAnOnlineAgent();
const {
body: { priorities: prioritiesResponse },
} = (await request.get(api('livechat/priorities')).set(credentials).expect('Content-Type', 'application/json').expect(200)) as {
body: { priorities: ILivechatPriority[] };
};
priorities = prioritiesResponse;
await deleteAllSLA();
slas = await bulkCreateSLA(5);
type RoomParamsReturnType = { priority: string } | { sla: string } | { priority: string; sla: string } | undefined;
// create 20 rooms, 5 with only priority, 5 with only SLA, 5 with both, 5 without priority or SLA
omniRooms = await bulkCreateLivechatRooms(20, departmentWithAgent.department._id, (index): RoomParamsReturnType => {
if (index < 5) {
return {
priority: priorities[index]._id,
};
}
if (index < 10) {
return {
sla: slas[index - 5]._id,
};
}
if (index < 15) {
// random number between 0 and 4
const randomPriorityIndex = Math.floor(Math.random() * 5);
const randomSlaIndex = Math.floor(Math.random() * 5);
return {
priority: priorities[randomPriorityIndex]._id,
sla: slas[randomSlaIndex]._id,
};
}
});
});
it('it should sort the queue based on priority', async () => {
await updateEESetting('Omnichannel_sorting_mechanism', OmnichannelSortingMechanismSettingType.Priority);
const origInquiries = await fetchAllInquiries(departmentWithAgent.agent.credentials, departmentWithAgent.department._id);
expect(origInquiries.length).to.be.greaterThanOrEqual(omniRooms.length);
const expectedSortedInquiries: ILivechatInquiryRecord[] = JSON.parse(JSON.stringify(origInquiries));
expectedSortedInquiries.sort(sortByPriority);
compareInquiries(origInquiries, expectedSortedInquiries);
});
it('it should sort the queue based on slas', async () => {
await updateEESetting('Omnichannel_sorting_mechanism', OmnichannelSortingMechanismSettingType.SLAs);
const origInquiries = await fetchAllInquiries(departmentWithAgent.agent.credentials, departmentWithAgent.department._id);
expect(origInquiries.length).to.be.greaterThanOrEqual(omniRooms.length);
const expectedSortedInquiries = origInquiries.sort(sortBySLA);
compareInquiries(origInquiries, expectedSortedInquiries);
});
it('it should sort the queue based on timestamp', async () => {
await updateEESetting('Omnichannel_sorting_mechanism', OmnichannelSortingMechanismSettingType.Timestamp);
const origInquiries = await fetchAllInquiries(departmentWithAgent.agent.credentials, departmentWithAgent.department._id);
expect(origInquiries.length).to.be.greaterThanOrEqual(omniRooms.length);
const expectedSortedInquiries = origInquiries.sort(sortByTimestamp);
compareInquiries(origInquiries, expectedSortedInquiries);
});
});
});

View File

@@ -0,0 +1,242 @@
import type { ILivechatDepartment, ILivechatTag } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { createDepartmentWithAnOnlineAgent } from '../../../data/livechat/department';
import { saveTags, removeAllTags } from '../../../data/livechat/tags';
import { createMonitor, createUnit } from '../../../data/livechat/units';
import { removePermissionFromAllRoles, restorePermissionToRoles, updatePermission, updateSetting } from '../../../data/permissions.helper';
import type { IUserWithCredentials } from '../../../data/user';
import { password } from '../../../data/user';
import { createUser, deleteUser, login } from '../../../data/users.helper';
import { IS_EE } from '../../../e2e/config/constants';
(IS_EE ? describe : describe.skip)('[EE] Livechat - Tags', () => {
before((done) => getCredentials(done));
before(async () => {
await updateSetting('Livechat_enabled', true);
});
describe('livechat/tags', () => {
let tagsData: {
caseA: { department: ILivechatDepartment; tag: ILivechatTag; agent: IUserWithCredentials };
caseB: { department: ILivechatDepartment; tag: ILivechatTag; agent: IUserWithCredentials };
casePublic: { tag: ILivechatTag };
};
let monitor: IUserWithCredentials;
before(async () => {
// create monitor
const monitorUser = await createUser();
const monitorCredentials = await login(monitorUser.username, password);
await createMonitor(monitorUser.username);
monitor = {
user: monitorUser,
credentials: monitorCredentials,
};
// remove all existing tags
await removeAllTags();
// should add 3 tags
const { department: dA, agent: agentA } = await createDepartmentWithAnOnlineAgent();
const tagA = await saveTags([dA._id]);
const { department: dB, agent: agentB } = await createDepartmentWithAnOnlineAgent();
const tagB = await saveTags([dB._id]);
const publicTag = await saveTags();
tagsData = {
caseA: { department: dA, tag: tagA, agent: agentA },
caseB: { department: dB, tag: tagB, agent: agentB },
casePublic: { tag: publicTag },
};
});
after(async () => {
await deleteUser(monitor.user);
await removeAllTags();
});
it('should throw unauthorized error when the user does not have the necessary permission', async () => {
await removePermissionFromAllRoles('manage-livechat-tags');
await removePermissionFromAllRoles('view-l-room');
const response = await request.get(api('livechat/tags')).set(credentials).expect('Content-Type', 'application/json').expect(403);
expect(response.body).to.have.property('success', false);
await restorePermissionToRoles('manage-livechat-tags');
await restorePermissionToRoles('view-l-room');
});
it('should return an array of tags', async () => {
const { tag } = tagsData.caseA;
const response = await request
.get(api('livechat/tags'))
.set(credentials)
.query({ text: tag.name, viewAll: 'true' })
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.tags).to.be.an('array').with.lengthOf(1);
expect(response.body.tags[0]).to.have.property('_id', tag._id);
});
it('[Manager role] show return all tags when "viewAll" param is true and user has access to all tags', async () => {
const response = await request
.get(api('livechat/tags'))
.set(credentials)
.query({ viewAll: 'true' })
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.tags).to.be.an('array').with.lengthOf(3);
const expectedTags = [tagsData.caseA.tag, tagsData.caseB.tag, tagsData.casePublic.tag].map((tag) => tag._id).sort();
const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort();
expect(actualTags).to.deep.equal(expectedTags);
});
it('[Monitor role] show return only public tags when "viewAll" param is true and user does not have access to all tags', async () => {
const response = await request
.get(api('livechat/tags'))
.set(monitor.credentials)
.query({ viewAll: 'true' })
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.tags).to.be.an('array').with.lengthOf(1);
const expectedTags = [tagsData.casePublic.tag].map((tag) => tag._id).sort();
const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort();
expect(actualTags).to.deep.equal(expectedTags);
});
it('[Manager Role] should return department tags and public tags when "departmentId" param is provided', async () => {
const { department } = tagsData.caseA;
const response = await request
.get(api('livechat/tags'))
.set(credentials)
.query({ department: department._id })
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.tags).to.be.an('array').with.lengthOf(2);
const expectedTags = [tagsData.caseA.tag, tagsData.casePublic.tag].map((tag) => tag._id).sort();
const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort();
expect(actualTags).to.deep.equal(expectedTags);
});
it('[Manager role] should return public tags when "departmentId" param is not provided', async () => {
const response = await request.get(api('livechat/tags')).set(credentials).expect('Content-Type', 'application/json').expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.tags).to.be.an('array').with.lengthOf(1);
expect(response.body.tags[0]).to.have.property('_id', tagsData.casePublic.tag._id);
});
it('[Monitor role] should return only public tags when user does not have access to department', async () => {
const { department } = tagsData.caseA;
const response = await request
.get(api('livechat/tags'))
.set(monitor.credentials)
.query({ department: department._id })
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.tags).to.be.an('array').with.lengthOf(1);
const expectedTags = [tagsData.casePublic.tag].map((tag) => tag._id).sort();
const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort();
expect(actualTags).to.deep.equal(expectedTags);
});
it('[Monitor Role] should return department tags and public tags when "departmentId" param is provided', async () => {
const { department } = tagsData.caseA;
await createUnit(monitor.user._id, monitor.user.username || '', [department._id]);
const response = await request
.get(api('livechat/tags'))
.set(monitor.credentials)
.query({ department: department._id })
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.tags).to.be.an('array').with.lengthOf(2);
const expectedTags = [tagsData.caseA.tag, tagsData.casePublic.tag].map((tag) => tag._id).sort();
const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort();
expect(actualTags).to.deep.equal(expectedTags);
});
it('[Agent Role] should return department tags and public tags when "departmentId" param is provided', async () => {
const { department, agent } = tagsData.caseA;
const response = await request
.get(api('livechat/tags'))
.set(agent.credentials)
.query({ department: department._id })
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.tags).to.be.an('array').with.lengthOf(2);
const expectedTags = [tagsData.caseA.tag, tagsData.casePublic.tag].map((tag) => tag._id).sort();
const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort();
expect(actualTags).to.deep.equal(expectedTags);
});
it('[Monitor role] should return public tags and department tags which they are part of, when "departmentId" param is not provided', async () => {
const response = await request
.get(api('livechat/tags'))
.set(monitor.credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.tags).to.be.an('array').with.lengthOf(2);
const expectedTags = [tagsData.caseA.tag, tagsData.casePublic.tag].map((tag) => tag._id).sort();
const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort();
expect(actualTags).to.deep.equal(expectedTags);
});
it('[Agent role] should return public tags when "departmentId" param is not provided', async () => {
const response = await request
.get(api('livechat/tags'))
.set(monitor.credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.tags).to.be.an('array').with.lengthOf(2);
const expectedTags = [tagsData.caseA.tag, tagsData.casePublic.tag].map((tag) => tag._id).sort();
const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort();
expect(actualTags).to.deep.equal(expectedTags);
});
});
describe('livechat/tags/:tagId', () => {
it('should throw unauthorized error when the user does not have the necessary permission', async () => {
await updatePermission('manage-livechat-tags', []);
await updatePermission('view-l-room', []);
const response = await request.get(api('livechat/tags/123')).set(credentials).expect('Content-Type', 'application/json').expect(403);
expect(response.body).to.have.property('success', false);
});
it('should return null when the tag does not exist', async () => {
await updatePermission('manage-livechat-tags', ['admin']);
await updatePermission('view-l-room', ['livechat-agent']);
await request.get(api('livechat/tags/123')).set(credentials).expect('Content-Type', 'application/json').expect(404);
});
it('should return a tag', async () => {
const tag = await saveTags();
const response = await request
.get(api(`livechat/tags/${tag._id}`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body).to.have.property('_id', tag._id);
expect(response.body).to.have.property('name', tag.name);
expect(response.body).to.have.property('numDepartments', 0);
});
});
});

View File

@@ -0,0 +1,739 @@
import type { ILivechatDepartment, IOmnichannelBusinessUnit } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { before, after, describe, it } from 'mocha';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { deleteDepartment, getDepartmentById } from '../../../data/livechat/department';
import { createDepartment, updateDepartment } from '../../../data/livechat/rooms';
import { createMonitor, createUnit, deleteUnit, getUnit } from '../../../data/livechat/units';
import { updatePermission, updateSetting } from '../../../data/permissions.helper';
import { password } from '../../../data/user';
import { createUser, deleteUser, login } from '../../../data/users.helper';
import { IS_EE } from '../../../e2e/config/constants';
(IS_EE ? describe : describe.skip)('[EE] LIVECHAT - Units', () => {
before((done) => getCredentials(done));
before(async () => {
await updateSetting('Livechat_enabled', true);
await updatePermission('manage-livechat-departments', ['livechat-manager', 'livechat-monitor', 'admin']);
await updateSetting('Omnichannel_enable_department_removal', true);
});
after(async () => {
await updateSetting('Omnichannel_enable_department_removal', false);
});
describe('[GET] livechat/units', () => {
it('should return empty if manage-livechat-units permission is missing', async () => {
await updatePermission('manage-livechat-units', []);
return request
.get(api('livechat/units'))
.set(credentials)
.send({
unitData: {
name: 'test',
enabled: true,
},
unitMonitors: [],
unitDepartments: [],
})
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('units').that.is.an('array').with.lengthOf(0);
});
});
it('should return a list of units', async () => {
await updatePermission('manage-livechat-units', ['admin']);
const user = await createUser();
await createMonitor(user.username);
const department = await createDepartment();
const unit = await createUnit(user._id, user.username, [department._id]);
const { body } = await request.get(api('livechat/units')).set(credentials).expect(200);
expect(body.units).to.be.an('array').with.lengthOf.greaterThan(0);
const unitFound = body.units.find((u: IOmnichannelBusinessUnit) => u._id === unit._id);
expect(unitFound).to.have.property('_id', unit._id);
expect(unitFound).to.have.property('name', unit.name);
expect(unitFound).to.have.property('numMonitors', 1);
expect(unitFound).to.have.property('numDepartments', 1);
expect(unitFound).to.have.property('type', 'u');
// cleanup
await deleteUser(user);
});
it('should return a list of units matching the provided filter', async () => {
const user = await createUser();
await createMonitor(user.username);
const department = await createDepartment();
const unit = await createUnit(user._id, user.username, [department._id]);
const { body } = await request.get(api('livechat/units')).query({ text: unit.name }).set(credentials).expect(200);
expect(body.units).to.be.an('array').with.lengthOf(1);
const unitFound = body.units.find((u: IOmnichannelBusinessUnit) => u._id === unit._id);
expect(unitFound).to.have.property('_id', unit._id);
expect(unitFound).to.have.property('name', unit.name);
expect(unitFound).to.have.property('numMonitors', 1);
expect(unitFound).to.have.property('numDepartments', 1);
expect(unitFound).to.have.property('type', 'u');
// cleanup
await deleteUser(user);
});
it('should properly paginate the result set', async () => {
const { body } = await request.get(api('livechat/units')).query({ count: 1 }).set(credentials).expect(200);
expect(body).to.have.property('units').and.to.be.an('array').with.lengthOf(1);
expect(body).to.have.property('count').and.to.be.equal(1);
const unit = body.units[0];
const { body: body2 } = await request.get(api('livechat/units')).query({ count: 1, offset: 1 }).set(credentials).expect(200);
expect(body2).to.have.property('units').and.to.be.an('array').with.lengthOf(1);
const unit2 = body2.units[0];
expect(unit._id).to.not.be.equal(unit2._id);
});
it('should sort the result set based on provided fields', async () => {
const user = await createUser();
await createMonitor(user.username);
const department = await createDepartment();
const unit = await createUnit(user._id, user.username, [department._id], 'A test 1234');
const unit2 = await createUnit(user._id, user.username, [department._id], 'a test 1234');
const { body } = await request
.get(api('livechat/units'))
.query({ sort: JSON.stringify({ name: 1 }), text: 'test', count: 2 })
.set(credentials)
.expect(200);
expect(body).to.have.property('units').and.to.be.an('array').with.lengthOf(2);
expect(body.units[0]._id).to.be.equal(unit._id);
expect(body.units[1]._id).to.be.equal(unit2._id);
await deleteUser(user);
});
});
describe('[POST] livechat/units', () => {
it('should fail if manage-livechat-units permission is missing', async () => {
await updatePermission('manage-livechat-units', []);
return request
.post(api('livechat/units'))
.set(credentials)
.send({
unitData: {
name: 'test',
enabled: true,
},
unitMonitors: [],
unitDepartments: [],
})
.expect(403);
});
it('should return a created unit', async () => {
await updatePermission('manage-livechat-units', ['admin']);
const user = await createUser();
await createMonitor(user.username);
const department = await createDepartment();
const { body } = await request
.post(api('livechat/units'))
.set(credentials)
.send({
unitData: { name: 'test', visibility: 'public', enabled: true, description: 'test' },
unitMonitors: [{ monitorId: user._id, username: user.username }],
unitDepartments: [{ departmentId: department._id }],
})
.expect(200);
expect(body).to.have.property('_id');
expect(body).to.have.property('name', 'test');
expect(body).to.have.property('visibility', 'public');
expect(body).to.have.property('type', 'u');
expect(body).to.have.property('numMonitors', 1);
expect(body).to.have.property('numDepartments', 1);
// cleanup
await deleteUser(user);
});
it('should return a unit with no monitors if a user who is not a monitor is passed', async () => {
await updatePermission('manage-livechat-units', ['admin']);
const user = await createUser();
const department = await createDepartment();
const { body } = await request
.post(api('livechat/units'))
.set(credentials)
.send({
unitData: { name: 'test', visibility: 'public', enabled: true, description: 'test' },
unitMonitors: [{ monitorId: user._id, username: user.username }],
unitDepartments: [{ departmentId: department._id }],
})
.expect(200);
expect(body).to.have.property('numMonitors', 0);
expect(body).to.have.property('name', 'test');
// cleanup
await deleteUser(user);
});
});
describe('[GET] livechat/units/:id', () => {
it('should fail if manage-livechat-units permission is missing', async () => {
await updatePermission('manage-livechat-units', []);
return request.get(api('livechat/units/123')).set(credentials).send().expect(403);
});
it('should return a unit', async () => {
await updatePermission('manage-livechat-units', ['admin']);
const user = await createUser();
await createMonitor(user.username);
const department = await createDepartment();
const unit = await createUnit(user._id, user.username, [department._id]);
const { body } = await request
.get(api(`livechat/units/${unit._id}`))
.set(credentials)
.expect(200);
expect(body).to.have.property('_id', unit._id);
expect(body).to.have.property('name', unit.name);
expect(body).to.have.property('numMonitors', 1);
expect(body).to.have.property('numDepartments', 1);
expect(body).to.have.property('type', 'u');
// cleanup
await deleteUser(user);
});
});
describe('[POST] livechat/units/:id', () => {
it('should fail if manage-livechat-units permission is missing', async () => {
await updatePermission('manage-livechat-units', []);
return request.post(api('livechat/units/123')).set(credentials).expect(403);
});
it('should fail if unit does not exist', async () => {
await updatePermission('manage-livechat-units', ['admin']);
const user = await createUser();
await createMonitor(user.username);
const department = await createDepartment();
const { body } = await request
.post(api('livechat/units/123'))
.set(credentials)
.send({
unitData: { name: 'test', visibility: 'public', enabled: true, description: 'test' },
unitMonitors: [{ monitorId: user._id, username: user.username }],
unitDepartments: [{ departmentId: department._id }],
})
.expect(400);
expect(body).to.have.property('success', false);
// cleanup
await deleteUser(user);
});
it('should return a updated unit', async () => {
const user = await createUser();
await createMonitor(user.username);
const department = await createDepartment();
const unit = await createUnit(user._id, user.username, [department._id]);
const { body } = await request
.post(api(`livechat/units/${unit._id}`))
.set(credentials)
.send({
unitData: { name: 'test', visibility: 'private', enabled: true, description: 'test' },
unitMonitors: [{ monitorId: user._id, username: user.username }],
unitDepartments: [{ departmentId: department._id }],
})
.expect(200);
expect(body).to.have.property('_id');
expect(body).to.have.property('name', 'test');
expect(body).to.have.property('visibility', 'private');
expect(body).to.have.property('type', 'u');
expect(body).to.have.property('numMonitors', 1);
expect(body).to.have.property('numDepartments', 1);
// cleanup
await deleteUser(user);
});
it('should move the department to the latest unit that attempted to assign it', async () => {
const user = await createUser();
await createMonitor(user.username);
const department = await createDepartment();
const unit1 = await createUnit(user._id, user.username, [department._id]);
const unit2 = await createUnit(user._id, user.username, [department._id]);
const { body } = await request
.get(api(`livechat/units/${unit1._id}/departments`))
.set(credentials)
.expect(200);
expect(body).to.have.property('departments');
expect(body.departments).to.have.lengthOf(0);
expect(unit2.numDepartments).to.be.equal(1);
});
it('should remove the department from the unit if it is not passed in the request', async () => {
const user = await createUser();
await createMonitor(user.username);
const department = await createDepartment();
const unit1 = await createUnit(user._id, user.username, [department._id]);
const { body } = await request
.post(api(`livechat/units/${unit1._id}`))
.set(credentials)
.send({
unitData: { name: unit1.name, visibility: unit1.visibility },
unitMonitors: [{ monitorId: user._id, username: user.username }],
unitDepartments: [],
})
.expect(200);
expect(body).to.have.property('_id', unit1._id);
expect(body).to.have.property('numDepartments', 0);
});
it('should remove the monitor from the unit if it is not passed in the request', async () => {
const user = await createUser();
await createMonitor(user.username);
const department = await createDepartment();
const unit1 = await createUnit(user._id, user.username, [department._id]);
const { body } = await request
.post(api(`livechat/units/${unit1._id}`))
.set(credentials)
.send({
unitData: { name: unit1.name, visibility: unit1.visibility },
unitMonitors: [],
unitDepartments: [{ departmentId: department._id }],
})
.expect(200);
expect(body).to.have.property('_id', unit1._id);
expect(body).to.have.property('numMonitors', 0);
});
});
describe('[DELETE] livechat/units/:id', () => {
it('should fail if manage-livechat-units permission is missing', async () => {
await updatePermission('manage-livechat-units', []);
return request.delete(api('livechat/units/123')).set(credentials).expect(403);
});
it('should return a deleted unit', async () => {
await updatePermission('manage-livechat-units', ['admin']);
const user = await createUser();
await createMonitor(user.username);
const department = await createDepartment();
const unit = await createUnit(user._id, user.username, [department._id]);
const { body } = await request
.delete(api(`livechat/units/${unit._id}`))
.set(credentials)
.expect(200);
expect(body).to.have.be.a('number').equal(1);
// cleanup
await deleteUser(user);
});
});
describe('livechat/units/:unitId/departments', () => {
it('should fail if manage-livechat-units permission is missing', async () => {
await updatePermission('manage-livechat-units', []);
return request.get(api('livechat/units/123/departments')).set(credentials).expect(403);
});
it('should return departments associated with a unit', async () => {
await updatePermission('manage-livechat-units', ['admin']);
const user = await createUser();
await createMonitor(user.username);
const department = await createDepartment();
const unit = await createUnit(user._id, user.username, [department._id]);
const { body } = await request
.get(api(`livechat/units/${unit._id}/departments`))
.set(credentials)
.expect(200);
expect(body).to.have.property('departments');
expect(body.departments).to.have.lengthOf(1);
expect(body.departments[0]).to.have.property('_id', department._id);
expect(body.departments[0]).to.have.property('name', department.name);
// cleanup
await deleteUser(user);
});
});
describe('livechat/units/:unitId/departments/available', () => {
it('should fail if manage-livechat-units permission is missing', async () => {
await updatePermission('manage-livechat-units', []);
return request.get(api('livechat/units/123/departments/available')).set(credentials).expect(403);
});
it('should return departments not associated with a unit', async () => {
await updatePermission('manage-livechat-units', ['admin']);
const user = await createUser();
await createMonitor(user.username);
const department = await createDepartment();
const unit = await createUnit(user._id, user.username, [department._id]);
const { body } = await request
.get(api(`livechat/units/${unit._id}/departments/available`))
.set(credentials)
.expect(200);
expect(body).to.have.property('departments');
expect(body.departments).to.have.lengthOf.greaterThan(0);
const myUnit = body.departments.find((d: ILivechatDepartment) => d.parentId === unit._id);
expect(myUnit).to.not.be.undefined.and.not.be.null;
// cleanup
await deleteUser(user);
});
});
describe('livechat/units/:unitId/monitors', () => {
it('should fail if manage-livechat-units permission is missing', async () => {
await updatePermission('manage-livechat-monitors', []);
return request.get(api('livechat/units/123/monitors')).set(credentials).expect(403);
});
it('should return monitors associated with a unit', async () => {
await updatePermission('manage-livechat-monitors', ['admin']);
const user = await createUser();
await createMonitor(user.username);
const department = await createDepartment();
const unit = await createUnit(user._id, user.username, [department._id]);
const { body } = await request
.get(api(`livechat/units/${unit._id}/monitors`))
.set(credentials)
.expect(200);
expect(body).to.have.property('monitors');
expect(body.monitors).to.have.lengthOf(1);
expect(body.monitors[0]).to.have.property('monitorId', user._id);
expect(body.monitors[0]).to.have.property('username', user.username);
// cleanup
await deleteUser(user);
});
});
describe('[POST] livechat/department', () => {
let monitor1: Awaited<ReturnType<typeof createUser>>;
let monitor1Credentials: Awaited<ReturnType<typeof login>>;
let monitor2: Awaited<ReturnType<typeof createUser>>;
let monitor2Credentials: Awaited<ReturnType<typeof login>>;
let unit: IOmnichannelBusinessUnit;
before(async () => {
monitor1 = await createUser();
monitor2 = await createUser();
await createMonitor(monitor1.username);
monitor1Credentials = await login(monitor1.username, password);
await createMonitor(monitor2.username);
monitor2Credentials = await login(monitor2.username, password);
unit = await createUnit(monitor1._id, monitor1.username, []);
});
after(async () => Promise.all([deleteUser(monitor1), deleteUser(monitor2), deleteUnit(unit)]));
it('should fail creating department when providing an invalid property in the department unit object', () => {
return request
.post(api('livechat/department'))
.set(credentials)
.send({
department: { name: 'Fail-Test', enabled: true, showOnOfflineForm: true, showOnRegistration: true, email: 'bla@bla' },
departmentUnit: { invalidProperty: true },
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'invalid-params');
});
});
it('should fail creating a department into an existing unit that a monitor does not supervise', async () => {
const department = await createDepartment(undefined, undefined, undefined, undefined, { _id: unit._id }, monitor2Credentials);
const updatedUnit = await getUnit(unit._id);
expect(updatedUnit).to.have.property('name', unit.name);
expect(updatedUnit).to.have.property('numMonitors', 1);
expect(updatedUnit).to.have.property('numDepartments', 0);
const fullDepartment = await getDepartmentById(department._id);
expect(fullDepartment).to.not.have.property('parentId');
expect(fullDepartment).to.not.have.property('ancestors');
await deleteDepartment(department._id);
});
it('should succesfully create a department into an existing unit as an admin', async () => {
const department = await createDepartment(undefined, undefined, undefined, undefined, { _id: unit._id });
const updatedUnit = await getUnit(unit._id);
expect(updatedUnit).to.have.property('name', unit.name);
expect(updatedUnit).to.have.property('numMonitors', 1);
expect(updatedUnit).to.have.property('numDepartments', 1);
const fullDepartment = await getDepartmentById(department._id);
expect(fullDepartment).to.have.property('parentId', unit._id);
expect(fullDepartment).to.have.property('ancestors').that.is.an('array').with.lengthOf(1);
expect(fullDepartment.ancestors?.[0]).to.equal(unit._id);
await deleteDepartment(department._id);
});
it('should succesfully create a department into an existing unit that a monitor supervises', async () => {
const department = await createDepartment(undefined, undefined, undefined, undefined, { _id: unit._id }, monitor1Credentials);
const updatedUnit = await getUnit(unit._id);
expect(updatedUnit).to.have.property('name', unit.name);
expect(updatedUnit).to.have.property('numMonitors', 1);
expect(updatedUnit).to.have.property('numDepartments', 1);
const fullDepartment = await getDepartmentById(department._id);
expect(fullDepartment).to.have.property('parentId', unit._id);
expect(fullDepartment).to.have.property('ancestors').that.is.an('array').with.lengthOf(1);
expect(fullDepartment.ancestors?.[0]).to.equal(unit._id);
await deleteDepartment(department._id);
});
it('unit should end up with 0 departments after removing all of them', async () => {
const updatedUnit = await getUnit(unit._id);
expect(updatedUnit).to.have.property('name', unit.name);
expect(updatedUnit).to.have.property('numMonitors', 1);
expect(updatedUnit).to.have.property('numDepartments', 0);
});
});
describe('[PUT] livechat/department', () => {
let monitor1: Awaited<ReturnType<typeof createUser>>;
let monitor1Credentials: Awaited<ReturnType<typeof login>>;
let monitor2: Awaited<ReturnType<typeof createUser>>;
let monitor2Credentials: Awaited<ReturnType<typeof login>>;
let unit: IOmnichannelBusinessUnit;
let department: ILivechatDepartment;
let baseDepartment: ILivechatDepartment;
before(async () => {
monitor1 = await createUser();
monitor2 = await createUser();
await createMonitor(monitor1.username);
monitor1Credentials = await login(monitor1.username, password);
await createMonitor(monitor2.username);
monitor2Credentials = await login(monitor2.username, password);
department = await createDepartment();
baseDepartment = await createDepartment();
unit = await createUnit(monitor1._id, monitor1.username, [baseDepartment._id]);
});
after(async () =>
Promise.all([
deleteUser(monitor1),
deleteUser(monitor2),
deleteUnit(unit),
deleteDepartment(department._id),
deleteDepartment(baseDepartment._id),
]),
);
it("should fail updating a department's unit when providing an invalid property in the department unit object", () => {
const updatedName = 'updated-department-name';
return request
.put(api(`livechat/department/${department._id}`))
.set(credentials)
.send({
department: { name: updatedName, enabled: true, showOnOfflineForm: true, showOnRegistration: true, email: 'bla@bla' },
departmentUnit: { invalidProperty: true },
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Match error: Unknown key in field departmentUnit.invalidProperty');
});
});
it("should fail updating a department's unit when providing an invalid _id type in the department unit object", () => {
const updatedName = 'updated-department-name';
return request
.put(api(`livechat/department/${department._id}`))
.set(credentials)
.send({
department: { name: updatedName, enabled: true, showOnOfflineForm: true, showOnRegistration: true, email: 'bla@bla' },
departmentUnit: { _id: true },
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Match error: Expected string, got boolean in field departmentUnit._id');
});
});
it('should fail removing the last department from a unit', () => {
const updatedName = 'updated-department-name';
return request
.put(api(`livechat/department/${baseDepartment._id}`))
.set(credentials)
.send({
department: { name: updatedName, enabled: true, showOnOfflineForm: true, showOnRegistration: true, email: 'bla@bla' },
departmentUnit: {},
})
.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-unit-cant-be-empty');
});
});
it('should succesfully add an existing department to a unit as an admin', async () => {
const updatedName = 'updated-department-name';
const updatedDepartment = await updateDepartment({
userCredentials: credentials,
departmentId: department._id,
name: updatedName,
departmentUnit: { _id: unit._id },
});
expect(updatedDepartment).to.have.property('name', updatedName);
expect(updatedDepartment).to.have.property('type', 'd');
expect(updatedDepartment).to.have.property('_id', department._id);
const updatedUnit = await getUnit(unit._id);
expect(updatedUnit).to.have.property('name', unit.name);
expect(updatedUnit).to.have.property('numMonitors', 1);
expect(updatedUnit).to.have.property('numDepartments', 2);
const fullDepartment = await getDepartmentById(department._id);
expect(fullDepartment).to.have.property('parentId', unit._id);
expect(fullDepartment).to.have.property('ancestors').that.is.an('array').with.lengthOf(1);
expect(fullDepartment.ancestors?.[0]).to.equal(unit._id);
});
it('should succesfully remove an existing department from a unit as an admin', async () => {
const updatedName = 'updated-department-name';
const updatedDepartment = await updateDepartment({
userCredentials: credentials,
departmentId: department._id,
name: updatedName,
departmentUnit: {},
});
expect(updatedDepartment).to.have.property('name', updatedName);
expect(updatedDepartment).to.have.property('type', 'd');
expect(updatedDepartment).to.have.property('_id', department._id);
const updatedUnit = await getUnit(unit._id);
expect(updatedUnit).to.have.property('name', unit.name);
expect(updatedUnit).to.have.property('numMonitors', 1);
expect(updatedUnit).to.have.property('numDepartments', 1);
const fullDepartment = await getDepartmentById(department._id);
expect(fullDepartment).to.not.have.property('parentId');
expect(fullDepartment).to.not.have.property('ancestors');
});
it('should fail adding a department into an existing unit that a monitor does not supervise', async () => {
const updatedName = 'updated-department-name2';
const updatedDepartment = await updateDepartment({
userCredentials: monitor2Credentials,
departmentId: department._id,
name: updatedName,
departmentUnit: { _id: unit._id },
});
expect(updatedDepartment).to.have.property('name', updatedName);
expect(updatedDepartment).to.have.property('type', 'd');
expect(updatedDepartment).to.have.property('_id', department._id);
const updatedUnit = await getUnit(unit._id);
expect(updatedUnit).to.have.property('name', unit.name);
expect(updatedUnit).to.have.property('numMonitors', 1);
expect(updatedUnit).to.have.property('numDepartments', 1);
const fullDepartment = await getDepartmentById(department._id);
expect(fullDepartment).to.not.have.property('parentId');
expect(fullDepartment).to.not.have.property('ancestors');
});
it('should succesfully add a department into an existing unit that a monitor supervises', async () => {
const updatedName = 'updated-department-name3';
const updatedDepartment = await updateDepartment({
userCredentials: monitor1Credentials,
departmentId: department._id,
name: updatedName,
departmentUnit: { _id: unit._id },
});
expect(updatedDepartment).to.have.property('name', updatedName);
expect(updatedDepartment).to.have.property('type', 'd');
expect(updatedDepartment).to.have.property('_id', department._id);
const updatedUnit = await getUnit(unit._id);
expect(updatedUnit).to.have.property('name', unit.name);
expect(updatedUnit).to.have.property('numMonitors', 1);
expect(updatedUnit).to.have.property('numDepartments', 2);
const fullDepartment = await getDepartmentById(department._id);
expect(fullDepartment).to.have.property('name', updatedName);
expect(fullDepartment).to.have.property('parentId', unit._id);
expect(fullDepartment).to.have.property('ancestors').that.is.an('array').with.lengthOf(1);
expect(fullDepartment.ancestors?.[0]).to.equal(unit._id);
});
it('should fail removing a department from a unit that a monitor does not supervise', async () => {
const updatedName = 'updated-department-name4';
const updatedDepartment = await updateDepartment({
userCredentials: monitor2Credentials,
departmentId: department._id,
name: updatedName,
departmentUnit: {},
});
expect(updatedDepartment).to.have.property('name', updatedName);
expect(updatedDepartment).to.have.property('type', 'd');
expect(updatedDepartment).to.have.property('_id', department._id);
const updatedUnit = await getUnit(unit._id);
expect(updatedUnit).to.have.property('name', unit.name);
expect(updatedUnit).to.have.property('numMonitors', 1);
expect(updatedUnit).to.have.property('numDepartments', 2);
const fullDepartment = await getDepartmentById(department._id);
expect(fullDepartment).to.have.property('name', updatedName);
expect(fullDepartment).to.have.property('parentId', unit._id);
expect(fullDepartment).to.have.property('ancestors').that.is.an('array').with.lengthOf(1);
expect(fullDepartment.ancestors?.[0]).to.equal(unit._id);
});
it('should succesfully remove a department from a unit that a monitor supervises', async () => {
const updatedName = 'updated-department-name5';
const updatedDepartment = await updateDepartment({
userCredentials: monitor1Credentials,
departmentId: department._id,
name: updatedName,
departmentUnit: {},
});
expect(updatedDepartment).to.have.property('name', updatedName);
expect(updatedDepartment).to.have.property('type', 'd');
expect(updatedDepartment).to.have.property('_id', department._id);
const updatedUnit = await getUnit(unit._id);
expect(updatedUnit).to.have.property('name', unit.name);
expect(updatedUnit).to.have.property('numMonitors', 1);
expect(updatedUnit).to.have.property('numDepartments', 1);
const fullDepartment = await getDepartmentById(department._id);
expect(fullDepartment).to.have.property('name', updatedName);
expect(fullDepartment).to.not.have.property('parentId');
expect(fullDepartment).to.not.have.property('ancestors');
});
});
});

View File

@@ -0,0 +1,296 @@
import { faker } from '@faker-js/faker';
import type { IOmnichannelCannedResponse } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { before, describe, it } from 'mocha';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { createCannedResponse } from '../../../data/livechat/canned-responses';
import { createAgent, createDepartment } from '../../../data/livechat/rooms';
import { removeTag, saveTags } from '../../../data/livechat/tags';
import { createMonitor, createUnit } from '../../../data/livechat/units';
import { updatePermission, updateSetting } from '../../../data/permissions.helper';
import { password, adminUsername } from '../../../data/user';
import { createUser, login } from '../../../data/users.helper';
import { IS_EE } from '../../../e2e/config/constants';
(IS_EE ? describe : describe.skip)('[EE] LIVECHAT - Canned responses', () => {
before((done) => getCredentials(done));
before(async () => {
await updateSetting('Livechat_enabled', true);
});
describe('canned-responses.get', () => {
it('should throw unauthorized when view-canned-responses permission is not set', async () => {
await updatePermission('view-canned-responses', []);
return request.get(api('canned-responses.get')).set(credentials).expect(403);
});
it('should return an array of canned responses when available', async () => {
await updatePermission('view-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']);
await createCannedResponse();
const { body } = await request.get(api('canned-responses.get')).set(credentials).expect(200);
expect(body).to.have.property('success', true);
expect(body.responses).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.responses[0]).to.have.property('_id');
expect(body.responses[0]).to.have.property('shortcut');
expect(body.responses[0]).to.have.property('scope');
expect(body.responses[0]).to.have.property('tags');
expect(body.responses[0]).to.have.property('text');
expect(body.responses[0]).to.have.property('userId');
});
it('should return canned responses for monitors even if department is disabled', async () => {
const user = await createUser();
const monitor = await createMonitor(user.username);
const creds = await login(user.username, password);
const department = await createDepartment(undefined, undefined, false);
await createUnit(monitor._id, user.username, [department._id]);
// create canned response
const shortcut = faker.string.uuid();
await request
.post(api('canned-responses'))
.set(credentials)
.send({ shortcut, scope: 'department', tags: ['tag'], text: 'text', departmentId: department._id })
.expect(200);
const { body } = await request.get(api('canned-responses.get')).set(creds).expect(200);
expect(body).to.have.property('success', true);
expect(body.responses).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.responses.find((response: IOmnichannelCannedResponse) => response.shortcut === shortcut)).to.be.an('object');
});
it('should return canned respones for scope user when agent is not on any department', async () => {
const user = await createUser();
await createAgent(user.username);
const creds = await login(user.username, password);
// create canned response
const shortcut = faker.string.uuid();
await request
.post(api('canned-responses'))
.set(creds)
.send({ shortcut, scope: 'user', tags: ['tag'], text: 'text' })
.expect(200);
const { body } = await request.get(api('canned-responses.get')).set(creds).expect(200);
expect(body).to.have.property('success', true);
expect(body.responses).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.responses.find((response: IOmnichannelCannedResponse) => response.shortcut === shortcut)).to.be.an('object');
});
it('should return canned responses on the global scope', async () => {
const user = await createUser();
const creds = await login(user.username, password);
await createAgent(user.username);
// create canned response
const shortcut = faker.string.uuid();
await request.post(api('canned-responses')).set(credentials).send({ shortcut, scope: 'global', text: 'text' }).expect(200);
const { body } = await request.get(api('canned-responses.get')).set(creds).expect(200);
expect(body).to.have.property('success', true);
expect(body.responses).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.responses.find((response: IOmnichannelCannedResponse) => response.shortcut === shortcut)).to.be.an('object');
});
it('should return canned responses from the departments user is in', async () => {
const user = await createUser();
const creds = await login(user.username, password);
await createAgent(user.username);
const department = await createDepartment([{ agentId: user._id }], undefined, true);
// create canned response
const shortcut = faker.string.uuid();
await request
.post(api('canned-responses'))
.set(credentials)
.send({ shortcut, scope: 'department', text: 'text', departmentId: department._id })
.expect(200);
const { body } = await request.get(api('canned-responses.get')).set(creds).expect(200);
expect(body).to.have.property('success', true);
expect(body.responses).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.responses.find((response: IOmnichannelCannedResponse) => response.shortcut === shortcut)).to.be.an('object');
});
});
describe('[GET] canned-responses', () => {
it('should fail if user dont have view-canned-responses permission', async () => {
await updatePermission('view-canned-responses', []);
return request.get(api('canned-responses')).set(credentials).expect(403);
});
it('should return an array of canned responses when available', async () => {
await updatePermission('view-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']);
await createCannedResponse();
const { body } = await request.get(api('canned-responses')).query({ sort: '{ "_createdAt": -1 }' }).set(credentials).expect(200);
expect(body).to.have.property('success', true);
expect(body.cannedResponses).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.cannedResponses[0]).to.have.property('_id');
expect(body.cannedResponses[0]).to.have.property('shortcut');
expect(body.cannedResponses[0]).to.have.property('scope');
expect(body.cannedResponses[0]).to.have.property('tags');
expect(body.cannedResponses[0]).to.have.property('text');
expect(body.cannedResponses[0]).to.have.property('userId');
});
it('should return a canned response matching the params provided (shortcut)', async () => {
const response = await createCannedResponse();
const { body } = await request.get(api('canned-responses')).set(credentials).query({ shortcut: response.shortcut }).expect(200);
expect(body).to.have.property('success', true);
expect(body.cannedResponses).to.be.an('array').with.lengthOf(1);
expect(body.cannedResponses[0]).to.have.property('_id');
expect(body.cannedResponses[0]).to.have.property('shortcut', response.shortcut);
});
it('should return a canned response matching the params provided (scope)', async () => {
const response = await createCannedResponse();
const { body } = await request.get(api('canned-responses')).set(credentials).query({ scope: response.scope }).expect(200);
expect(body).to.have.property('success', true);
expect(body.cannedResponses).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.cannedResponses[0]).to.have.property('_id');
expect(body.cannedResponses[0]).to.have.property('scope', response.scope);
});
it('should return a canned response matching the params provided (tags)', async () => {
const response = await createCannedResponse();
const { body } = await request.get(api('canned-responses')).set(credentials).query({ 'tags[]': response.tags?.[0] }).expect(200);
expect(body).to.have.property('success', true);
expect(body.cannedResponses).to.be.an('array').with.lengthOf(1);
expect(body.cannedResponses[0]).to.have.property('_id');
expect(body.cannedResponses[0]).to.have.property('tags').that.is.an('array').which.includes(response.tags?.[0]);
});
it('should return a canned response matching the params provided (text)', async () => {
const response = await createCannedResponse();
const { body } = await request.get(api('canned-responses')).set(credentials).query({ text: response.text }).expect(200);
expect(body).to.have.property('success', true);
expect(body.cannedResponses).to.be.an('array').with.lengthOf(1);
expect(body.cannedResponses[0]).to.have.property('_id');
expect(body.cannedResponses[0]).to.have.property('text', response.text);
});
});
describe('[POST] canned-responses', () => {
const dupshortcut = `shortcut-${faker.string.nanoid(6)}`;
it('should fail if user dont have save-canned-responses permission', async () => {
await updatePermission('save-canned-responses', []);
return request
.post(api('canned-responses'))
.set(credentials)
.send({ shortcut: 'shortcut', scope: 'user', tags: ['tag'], text: 'text' })
.expect(403);
});
it('should fail if shortcut is not on the request', async () => {
await updatePermission('save-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']);
return request.post(api('canned-responses')).set(credentials).expect(400);
});
it('should fail if text is not on the request', async () => {
return request.post(api('canned-responses')).set(credentials).send({ shortcut: 'shortcut' }).expect(400);
});
it('should fail if scope is not on the request', async () => {
return request.post(api('canned-responses')).set(credentials).send({ shortcut: 'shortcut', text: 'text' }).expect(400);
});
it('should fail if tags is not an array of strings', async () => {
return request
.post(api('canned-responses'))
.set(credentials)
.send({ shortcut: 'shortcut', text: 'text', scope: 'department', tags: 'tag' })
.expect(400);
});
it('should create a new canned response', async () => {
const { body } = await request
.post(api('canned-responses'))
.set(credentials)
.send({ shortcut: dupshortcut, scope: 'user', tags: ['tag'], text: 'text' })
.expect(200);
expect(body).to.have.property('success', true);
});
it('should fail if shortcut is already in use', async () => {
return request
.post(api('canned-responses'))
.set(credentials)
.send({ shortcut: dupshortcut, scope: 'user', tags: ['tag'], text: 'text' })
.expect(400);
});
it('should save a canned response related to an EE tag', async () => {
const tag = await saveTags();
const { body } = await request
.post(api('canned-responses'))
.set(credentials)
.send({ shortcut: `eetag-${faker.string.nanoid(6)}`, scope: 'user', tags: [tag.name], text: 'text' })
.expect(200);
expect(body).to.have.property('success', true);
const { body: getResult } = await request.get(api('canned-responses')).set(credentials).query({ 'tags[]': tag.name }).expect(200);
expect(getResult).to.have.property('success', true);
expect(getResult.cannedResponses).to.be.an('array').with.lengthOf(1);
expect(getResult.cannedResponses[0]).to.have.property('_id');
expect(getResult.cannedResponses[0]).to.have.property('tags').that.is.an('array').which.includes(tag.name);
});
it('should not include removed tags in the response', async () => {
const tag = await saveTags();
const { body } = await request
.post(api('canned-responses'))
.set(credentials)
.send({ shortcut: `remove-${faker.string.nanoid(6)}`, scope: 'user', tags: [tag.name], text: 'text' })
.expect(200);
expect(body).to.have.property('success', true);
await removeTag(tag._id);
const { body: getResult } = await request.get(api('canned-responses')).set(credentials).query({ 'tags[]': tag.name }).expect(200);
expect(getResult).to.have.property('success', true);
expect(getResult.cannedResponses).to.be.an('array').with.lengthOf(0);
});
it('should persist userId when updating a canned response', async () => {
const response = await createCannedResponse();
const {
body: { cannedResponses },
} = await request.get(api('canned-responses')).set(credentials).query({}).expect(200);
const targetCannedResponse = cannedResponses.find(
(cannedResponse: IOmnichannelCannedResponse) => cannedResponse.shortcut === response.shortcut,
);
const { body } = await request
.post(api('canned-responses'))
.set(credentials)
.send({ _id: targetCannedResponse._id, shortcut: `${targetCannedResponse.shortcut}-edited`, text: 'edited text', scope: 'user' })
.expect(200);
expect(body).to.have.property('success', true);
const { body: getResult } = await request
.get(api('canned-responses'))
.set(credentials)
.query({ shortcut: `${targetCannedResponse.shortcut}-edited` })
.expect(200);
expect(getResult).to.have.property('success', true);
expect(getResult.cannedResponses[0]).to.have.property('userId', adminUsername);
});
});
describe('[DELETE] canned-responses', () => {
it('should fail if user dont have remove-canned-responses permission', async () => {
await updatePermission('remove-canned-responses', []);
return request.delete(api('canned-responses/sfdads')).set(credentials).expect(403);
});
it('should fail if _id is not on the request', async () => {
await updatePermission('remove-canned-responses', ['livechat-agent', 'livechat-monitor', 'livechat-manager', 'admin']);
return request.delete(api('canned-responses')).set(credentials).expect(400);
});
it('should delete a canned response', async () => {
const response = await createCannedResponse();
const { body: cr } = await request.get(api('canned-responses')).set(credentials).query({ shortcut: response.shortcut }).expect(200);
const { body } = await request
.delete(api(`canned-responses/${cr.cannedResponses[0]._id}`))
.set(credentials)
.expect(200);
expect(body).to.have.property('success', true);
});
});
});

View File

@@ -0,0 +1,821 @@
import type { ILivechatDepartment, ILivechatVisitor } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { before, describe, it, after } from 'mocha';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { createDepartment, deleteDepartment } from '../../../data/livechat/department';
import {
createVisitor,
createLivechatRoom,
takeInquiry,
closeOmnichannelRoom,
makeAgentUnavailable,
makeAgentAvailable,
createAgent,
sendAgentMessage,
sendMessage,
fetchInquiry,
deleteVisitor,
} from '../../../data/livechat/rooms';
import { updateEESetting, updatePermission, updateSetting } from '../../../data/permissions.helper';
import { pagination } from '../../../data/utils';
import { IS_EE } from '../../../e2e/config/constants';
(IS_EE ? describe : describe.skip)('[EE] LIVECHAT - dashboards', () => {
before((done) => getCredentials(done));
before(async () => {
await updateSetting('Livechat_enabled', true);
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
await updateSetting('Omnichannel_enable_department_removal', true);
await createAgent();
await makeAgentAvailable();
});
after(async () => {
await updateSetting('Omnichannel_enable_department_removal', false);
});
describe('livechat/analytics/agents/average-service-time', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-manager', []);
await request
.get(api('livechat/analytics/agents/average-service-time'))
.set(credentials)
.query({ start: '2020-01-01', end: '2020-01-02' })
.expect('Content-Type', 'application/json')
.expect(403);
});
it('should fail if start is not present as query param', async () => {
await updatePermission('view-livechat-manager', ['admin']);
const response = await request
.get(api('livechat/analytics/agents/average-service-time'))
.set(credentials)
.query({ end: '2020-01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not present as query param', async () => {
const response = await request
.get(api('livechat/analytics/agents/average-service-time'))
.set(credentials)
.query({ start: '2020-01-01' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if start is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/agents/average-service-time'))
.set(credentials)
.query({ start: '2020-01-01x', end: date })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/agents/average-service-time'))
.set(credentials)
.query({ start: date, end: '2020-x01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should return an array of agents', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
const inq = await fetchInquiry(room._id);
await takeInquiry(inq._id);
await closeOmnichannelRoom(room._id);
const { body } = await request
.get(api('livechat/analytics/agents/average-service-time'))
.set(credentials)
.query({ start: date, end: new Date().toISOString() })
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body.agents).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.agents[0]).to.have.a.property('_id');
expect(body.agents[0]).to.have.a.property('username');
expect(body.agents[0]).to.have.a.property('averageServiceTimeInSeconds').that.is.a('number');
});
it('should accept pagination', async () => {
await pagination('livechat/analytics/agents/average-service-time', credentials, {
start: new Date().toISOString(),
end: new Date().toISOString(),
});
});
});
describe('livechat/analytics/agents/total-service-time', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-manager', []);
await request
.get(api('livechat/analytics/agents/total-service-time'))
.set(credentials)
.query({ start: '2020-01-01', end: '2020-01-02' })
.expect('Content-Type', 'application/json')
.expect(403);
});
it('should fail if start is not present as query param', async () => {
await updatePermission('view-livechat-manager', ['admin']);
const response = await request
.get(api('livechat/analytics/agents/total-service-time'))
.set(credentials)
.query({ end: '2020-01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not present as query param', async () => {
const response = await request
.get(api('livechat/analytics/agents/total-service-time'))
.set(credentials)
.query({ start: '2020-01-01' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if start is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/agents/total-service-time'))
.set(credentials)
.query({ start: '2020-01-01x', end: date })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/agents/total-service-time'))
.set(credentials)
.query({ start: date, end: '2020-x01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should return an array of agents', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const { body } = await request
.get(api('livechat/analytics/agents/total-service-time'))
.set(credentials)
.query({ start: date, end: new Date().toISOString() })
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body.agents).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.agents[0]).to.have.a.property('_id');
expect(body.agents[0]).to.have.a.property('username');
expect(body.agents[0]).to.have.a.property('serviceTimeDuration').that.is.a('number');
});
it('should accept pagination', async () => {
await pagination('livechat/analytics/agents/total-service-time', credentials, {
start: new Date().toISOString(),
end: new Date().toISOString(),
});
});
});
describe('livechat/analytics/agents/available-for-service-history', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-manager', []);
await request
.get(api('livechat/analytics/agents/available-for-service-history'))
.set(credentials)
.query({ start: '2020-01-01', end: '2020-01-02' })
.expect('Content-Type', 'application/json')
.expect(403);
});
it('should fail if start is not present as query param', async () => {
await updatePermission('view-livechat-manager', ['admin']);
const response = await request
.get(api('livechat/analytics/agents/available-for-service-history'))
.set(credentials)
.query({ end: '2020-01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not present as query param', async () => {
const response = await request
.get(api('livechat/analytics/agents/available-for-service-history'))
.set(credentials)
.query({ start: '2020-01-01' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if start is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/agents/available-for-service-history'))
.set(credentials)
.query({ start: '2020-01-01x', end: date })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/agents/available-for-service-history'))
.set(credentials)
.query({ start: date, end: '2020-x01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should return an array of agents', async () => {
// Toggling to populate agent activity collection
await createAgent();
await makeAgentUnavailable();
await makeAgentAvailable();
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const { body } = await request
.get(api('livechat/analytics/agents/available-for-service-history'))
.set(credentials)
.query({ start: date, end: new Date().toISOString() })
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body.agents).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.agents[0]).to.have.a.property('username');
expect(body.agents[0]).to.have.a.property('availableTimeInSeconds').that.is.a('number');
});
it('should accept pagination', async () => {
await pagination('livechat/analytics/agents/available-for-service-history', credentials, {
start: new Date().toISOString(),
end: new Date().toISOString(),
});
});
});
describe('livechat/analytics/departments/amount-of-chats', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-manager', []);
await request
.get(api('livechat/analytics/departments/amount-of-chats'))
.set(credentials)
.query({ start: '2020-01-01', end: '2020-01-02' })
.expect('Content-Type', 'application/json')
.expect(403);
});
it('should fail if start is not present as query param', async () => {
await updatePermission('view-livechat-manager', ['admin']);
const response = await request
.get(api('livechat/analytics/departments/amount-of-chats'))
.set(credentials)
.query({ end: '2020-01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not present as query param', async () => {
const response = await request
.get(api('livechat/analytics/departments/amount-of-chats'))
.set(credentials)
.query({ start: '2020-01-01' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if start is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/departments/amount-of-chats'))
.set(credentials)
.query({ start: '2020-01-01x', end: date })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/departments/amount-of-chats'))
.set(credentials)
.query({ start: date, end: '2020-x01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should return an array of departments', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const { body } = await request
.get(api('livechat/analytics/departments/amount-of-chats'))
.set(credentials)
.query({ start: date, end: new Date().toISOString() })
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body.departments).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.departments[0]).to.have.a.property('rooms').that.is.a('number');
expect(body.departments[0]).to.have.a.property('_id');
});
it('should accept pagination', async () => {
await pagination('livechat/analytics/departments/amount-of-chats', credentials, {
start: new Date().toISOString(),
end: new Date().toISOString(),
});
});
});
describe('livechat/analytics/departments/average-service-time', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-manager', []);
await request
.get(api('livechat/analytics/departments/average-service-time'))
.set(credentials)
.query({ start: '2020-01-01', end: '2020-01-02' })
.expect('Content-Type', 'application/json')
.expect(403);
});
it('should fail if start is not present as query param', async () => {
await updatePermission('view-livechat-manager', ['admin']);
const response = await request
.get(api('livechat/analytics/departments/average-service-time'))
.set(credentials)
.query({ end: '2020-01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not present as query param', async () => {
const response = await request
.get(api('livechat/analytics/departments/average-service-time'))
.set(credentials)
.query({ start: '2020-01-01' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if start is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/departments/average-service-time'))
.set(credentials)
.query({ start: '2020-01-01x', end: date })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/departments/average-service-time'))
.set(credentials)
.query({ start: date, end: '2020-x01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
describe('with data', () => {
let department: ILivechatDepartment;
let visitor: ILivechatVisitor;
before(async () => {
department = await createDepartment({}, [{ agentId: credentials['X-User-Id'] }]);
visitor = await createVisitor(department._id);
const room = await createLivechatRoom(visitor.token);
const inq = await fetchInquiry(room._id);
await takeInquiry(inq._id);
await sendAgentMessage(room._id);
await closeOmnichannelRoom(room._id);
});
after(async () => {
await deleteVisitor(visitor.token);
await deleteDepartment(department._id);
});
it('should return an array of departments', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const { body } = await request
.get(api('livechat/analytics/departments/average-service-time'))
.set(credentials)
.query({ start: date, end: new Date().toISOString() })
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body.departments).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.departments[0]).to.have.a.property('averageServiceTimeInSeconds').that.is.a('number');
expect(body.departments[0]).to.have.a.property('_id');
});
});
it('should accept pagination', async () => {
await pagination('livechat/analytics/departments/average-service-time', credentials, {
start: new Date().toISOString(),
end: new Date().toISOString(),
});
});
});
describe('livechat/analytics/departments/average-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 request
.get(api('livechat/analytics/departments/average-chat-duration-time'))
.set(credentials)
.query({ start: '2020-01-01', end: '2020-01-02' })
.expect('Content-Type', 'application/json')
.expect(403);
});
it('should fail if start is not present as query param', async () => {
await updatePermission('view-livechat-manager', ['admin']);
const response = await request
.get(api('livechat/analytics/departments/average-chat-duration-time'))
.set(credentials)
.query({ end: '2020-01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not present as query param', async () => {
const response = await request
.get(api('livechat/analytics/departments/average-chat-duration-time'))
.set(credentials)
.query({ start: '2020-01-01' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if start is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/departments/average-chat-duration-time'))
.set(credentials)
.query({ start: '2020-01-01x', end: date })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/departments/average-chat-duration-time'))
.set(credentials)
.query({ start: date, end: '2020-x01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should return an array of departments', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const { body } = await request
.get(api('livechat/analytics/departments/average-chat-duration-time'))
.set(credentials)
.query({ start: date, end: new Date().toISOString() })
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body.departments).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.departments[0]).to.have.a.property('averageChatDurationTimeInSeconds').that.is.a('number');
expect(body.departments[0]).to.have.a.property('_id');
});
it('should accept pagination', async () => {
await pagination('livechat/analytics/departments/average-chat-duration-time', credentials, {
start: new Date().toISOString(),
end: new Date().toISOString(),
});
});
});
describe('livechat/analytics/departments/total-service-time', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-manager', []);
await request
.get(api('livechat/analytics/departments/total-service-time'))
.set(credentials)
.query({ start: '2020-01-01', end: '2020-01-02' })
.expect('Content-Type', 'application/json')
.expect(403);
});
it('should fail if start is not present as query param', async () => {
await updatePermission('view-livechat-manager', ['admin']);
const response = await request
.get(api('livechat/analytics/departments/total-service-time'))
.set(credentials)
.query({ end: '2020-01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not present as query param', async () => {
const response = await request
.get(api('livechat/analytics/departments/total-service-time'))
.set(credentials)
.query({ start: '2020-01-01' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if start is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/departments/total-service-time'))
.set(credentials)
.query({ start: '2020-01-01x', end: date })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/departments/total-service-time'))
.set(credentials)
.query({ start: date, end: '2020-x01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should return an array of departments', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const { body } = await request
.get(api('livechat/analytics/departments/total-service-time'))
.set(credentials)
.query({ start: date, end: new Date().toISOString() })
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body.departments).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.departments[0]).to.have.a.property('chats').that.is.a('number');
expect(body.departments[0]).to.have.a.property('serviceTimeDuration').that.is.a('number');
expect(body.departments[0]).to.have.a.property('_id');
});
it('should accept pagination', async () => {
await pagination('livechat/analytics/departments/total-service-time', credentials, {
start: new Date().toISOString(),
end: new Date().toISOString(),
});
});
});
describe('livechat/analytics/departments/average-waiting-time', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-manager', []);
await request
.get(api('livechat/analytics/departments/average-waiting-time'))
.set(credentials)
.query({ start: '2020-01-01', end: '2020-01-02' })
.expect('Content-Type', 'application/json')
.expect(403);
});
it('should fail if start is not present as query param', async () => {
await updatePermission('view-livechat-manager', ['admin']);
const response = await request
.get(api('livechat/analytics/departments/average-waiting-time'))
.set(credentials)
.query({ end: '2020-01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not present as query param', async () => {
const response = await request
.get(api('livechat/analytics/departments/average-waiting-time'))
.set(credentials)
.query({ start: '2020-01-01' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if start is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/departments/average-waiting-time'))
.set(credentials)
.query({ start: '2020-01-01x', end: date })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/departments/average-waiting-time'))
.set(credentials)
.query({ start: date, end: '2020-x01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
describe('with data', () => {
let department: ILivechatDepartment;
let visitor: ILivechatVisitor;
before(async () => {
department = await createDepartment({}, [{ agentId: credentials['X-User-Id'] }]);
visitor = await createVisitor(department._id);
const room = await createLivechatRoom(visitor.token);
const inq = await fetchInquiry(room._id);
await takeInquiry(inq._id);
await sendAgentMessage(room._id);
await closeOmnichannelRoom(room._id);
});
after(async () => {
await deleteVisitor(visitor.token);
await deleteDepartment(department._id);
});
it('should return an array of departments', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const { body } = await request
.get(api('livechat/analytics/departments/average-waiting-time'))
.set(credentials)
.query({ start: date, end: new Date().toISOString() })
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body.departments).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.departments[0]).to.have.a.property('averageWaitingTimeInSeconds').that.is.a('number');
expect(body.departments[0]).to.have.a.property('_id');
});
});
it('should accept pagination', async () => {
await pagination('livechat/analytics/departments/average-waiting-time', credentials, {
start: new Date().toISOString(),
end: new Date().toISOString(),
});
});
});
describe('livechat/analytics/departments/total-transferred-chats', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-manager', []);
await request
.get(api('livechat/analytics/departments/total-transferred-chats'))
.set(credentials)
.query({ start: '2020-01-01', end: '2020-01-02' })
.expect('Content-Type', 'application/json')
.expect(403);
});
it('should fail if start is not present as query param', async () => {
await updatePermission('view-livechat-manager', ['admin']);
const response = await request
.get(api('livechat/analytics/departments/total-transferred-chats'))
.set(credentials)
.query({ end: '2020-01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not present as query param', async () => {
const response = await request
.get(api('livechat/analytics/departments/total-transferred-chats'))
.set(credentials)
.query({ start: '2020-01-01' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if start is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/departments/total-transferred-chats'))
.set(credentials)
.query({ start: '2020-01-01x', end: date })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/departments/total-transferred-chats'))
.set(credentials)
.query({ start: date, end: '2020-x01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should return an array of departments', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const { body } = await request
.get(api('livechat/analytics/departments/total-transferred-chats'))
.set(credentials)
.query({ start: date, end: new Date().toISOString() })
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body.departments).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.departments[0]).to.have.a.property('numberOfTransferredRooms').that.is.a('number');
expect(body.departments[0]).to.have.a.property('_id');
});
it('should accept pagination', async () => {
await pagination('livechat/analytics/departments/total-transferred-chats', credentials, {
start: new Date().toISOString(),
end: new Date().toISOString(),
});
});
});
describe('livechat/analytics/departments/total-abandoned-chats', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-manager', []);
await request
.get(api('livechat/analytics/departments/total-abandoned-chats'))
.set(credentials)
.query({ start: '2020-01-01', end: '2020-01-02' })
.expect('Content-Type', 'application/json')
.expect(403);
});
it('should fail if start is not present as query param', async () => {
await updatePermission('view-livechat-manager', ['admin']);
const response = await request
.get(api('livechat/analytics/departments/total-abandoned-chats'))
.set(credentials)
.query({ end: '2020-01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not present as query param', async () => {
const response = await request
.get(api('livechat/analytics/departments/total-abandoned-chats'))
.set(credentials)
.query({ start: '2020-01-01' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if start is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/departments/total-abandoned-chats'))
.set(credentials)
.query({ start: '2020-01-01x', end: date })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/departments/total-abandoned-chats'))
.set(credentials)
.query({ start: date, end: '2020-x01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should return an array of departments', async () => {
await updateSetting('Livechat_visitor_inactivity_timeout', 0);
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
const inq = await fetchInquiry(room._id);
await takeInquiry(inq._id);
await sendMessage(room._id, 'first message', visitor.token);
await sendAgentMessage(room._id);
await closeOmnichannelRoom(room._id);
const { body } = await request
.get(api('livechat/analytics/departments/total-abandoned-chats'))
.set(credentials)
.query({ start: date, end: new Date().toISOString() })
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body.departments).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.departments[0]).to.have.a.property('abandonedRooms').that.is.a('number');
expect(body.departments[0]).to.have.a.property('_id');
});
it('should accept pagination', async () => {
await pagination('livechat/analytics/departments/total-abandoned-chats', credentials, {
start: new Date().toISOString(),
end: new Date().toISOString(),
});
});
});
describe('livechat/analytics/departments/percentage-abandoned-chats', () => {
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-manager', []);
await request
.get(api('livechat/analytics/departments/percentage-abandoned-chats'))
.set(credentials)
.query({ start: '2020-01-01', end: '2020-01-02' })
.expect('Content-Type', 'application/json')
.expect(403);
});
it('should fail if start is not present as query param', async () => {
await updatePermission('view-livechat-manager', ['admin']);
const response = await request
.get(api('livechat/analytics/departments/percentage-abandoned-chats'))
.set(credentials)
.query({ end: '2020-01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not present as query param', async () => {
const response = await request
.get(api('livechat/analytics/departments/percentage-abandoned-chats'))
.set(credentials)
.query({ start: '2020-01-01' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if start is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/departments/percentage-abandoned-chats'))
.set(credentials)
.query({ start: '2020-01-01x', end: date })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should fail if end is not a valid date', async () => {
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const response = await request
.get(api('livechat/analytics/departments/percentage-abandoned-chats'))
.set(credentials)
.query({ start: date, end: '2020-x01-02' })
.expect(400);
expect(response.body).to.have.property('success', false);
});
it('should return an array of departments', async () => {
await updateSetting('Livechat_visitor_inactivity_timeout', 0);
const date = new Date(new Date().setDate(new Date().getDate() - 2)).toISOString();
const { body } = await request
.get(api('livechat/analytics/departments/percentage-abandoned-chats'))
.set(credentials)
.query({ start: date, end: new Date().toISOString() })
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
expect(body.departments).to.be.an('array').with.lengthOf.greaterThan(0);
expect(body.departments[0]).to.have.a.property('percentageOfAbandonedChats').that.is.a('number');
expect(body.departments[0]).to.have.a.property('_id');
});
it('should accept pagination', async () => {
await pagination('livechat/analytics/departments/percentage-abandoned-chats', credentials, {
start: new Date().toISOString(),
end: new Date().toISOString(),
});
});
});
});

View File

@@ -0,0 +1,374 @@
import type { Credentials } from '@rocket.chat/api-client';
import type { IOmnichannelRoom, IUser } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import {
createVisitor,
createLivechatRoom,
sendMessage,
sendAgentMessage,
placeRoomOnHold,
getLivechatRoomInfo,
startANewLivechatRoomAndTakeIt,
makeAgentAvailable,
createAgent,
closeOmnichannelRoom,
fetchMessages,
} from '../../../data/livechat/rooms';
import { sleep } from '../../../data/livechat/utils';
import { updateEESetting, updatePermission, updateSetting } from '../../../data/permissions.helper';
import { password } from '../../../data/user';
import { createUser, deleteUser, login } from '../../../data/users.helper';
import { IS_EE } from '../../../e2e/config/constants';
(IS_EE ? describe : describe.skip)('[EE] LIVECHAT - rooms', () => {
before((done) => getCredentials(done));
let agent2: { user: IUser; credentials: Credentials };
before(async () => {
await updateSetting('Livechat_enabled', true);
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
await createAgent();
await makeAgentAvailable();
});
before(async () => {
const user = await createUser();
const userCredentials = await login(user.username, password);
await createAgent(user.username);
await updateSetting('Livechat_allow_manual_on_hold', true);
agent2 = {
user,
credentials: userCredentials,
};
});
after(async () => {
await deleteUser(agent2.user);
await updateSetting('Livechat_allow_manual_on_hold', false);
await updateSetting('Livechat_allow_manual_on_hold_upon_agent_engagement_only', true);
});
describe('livechat/room.onHold', () => {
it('should fail if user doesnt have on-hold-livechat-room permission', async () => {
await updatePermission('on-hold-livechat-room', []);
const response = await request
.post(api('livechat/room.onHold'))
.set(credentials)
.send({
roomId: 'invalid-room-id',
})
.expect(403);
expect(response.body.success).to.be.false;
await updatePermission('on-hold-livechat-room', ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin']);
});
it('should fail if roomId is invalid', async () => {
const response = await request
.post(api('livechat/room.onHold'))
.set(credentials)
.send({
roomId: 'invalid-room-id',
})
.expect(400);
expect(response.body.success).to.be.false;
expect(response.body.error).to.be.equal('error-invalid-room');
});
it('should fail if room is an empty string', async () => {
const response = await request
.post(api('livechat/room.onHold'))
.set(credentials)
.send({
roomId: ' ',
})
.expect(400);
expect(response.body.success).to.be.false;
expect(response.body.error).to.be.equal('error-invalid-room');
});
it('should fail if room is not a livechat room', async () => {
const response = await request
.post(api('livechat/room.onHold'))
.set(credentials)
.send({
roomId: 'GENERAL',
})
.expect(400);
expect(response.body.success).to.be.false;
expect(response.body.error).to.be.equal('error-invalid-room');
});
it('should fail if visitor is awaiting response (visitor sent last message)', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await sendMessage(room._id, 'test', visitor.token);
const response = await request
.post(api('livechat/room.onHold'))
.set(credentials)
.send({
roomId: room._id,
})
.expect(400);
expect(response.body.success).to.be.false;
expect(response.body.error).to.be.equal('error-cannot-place-chat-on-hold');
});
it('should fail if room is closed', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await closeOmnichannelRoom(room._id);
const response = await request
.post(api('livechat/room.onHold'))
.set(credentials)
.send({
roomId: room._id,
})
.expect(400);
expect(response.body.success).to.be.false;
expect(response.body.error).to.be.equal('error-room-already-closed');
});
it('should fail if user is not serving the chat and doesnt have on-hold-others-livechat-room permission', async () => {
const { room } = await startANewLivechatRoomAndTakeIt();
await sendAgentMessage(room._id);
const response = await request
.post(api('livechat/room.onHold'))
.set(agent2.credentials)
.send({
roomId: room._id,
})
.expect(400);
expect(response.body.success).to.be.false;
expect(response.body.error).to.be.equal('Not_authorized');
});
it('should put room on hold', async () => {
const { room } = await startANewLivechatRoomAndTakeIt();
await sendAgentMessage(room._id);
const response = await request
.post(api('livechat/room.onHold'))
.set(credentials)
.send({
roomId: room._id,
})
.expect(200);
expect(response.body.success).to.be.true;
const updatedRoom = await getLivechatRoomInfo(room._id);
expect(updatedRoom.onHold).to.be.true;
});
it('Should put room on hold, even in the visitor sent the last message', async () => {
const { room, visitor } = await startANewLivechatRoomAndTakeIt();
await updateSetting('Livechat_allow_manual_on_hold_upon_agent_engagement_only', false);
await sendMessage(room._id, '-', visitor.token);
const response = await request
.post(api('livechat/room.onHold'))
.set(credentials)
.send({
roomId: room._id,
})
.expect(200);
expect(response.body.success).to.be.true;
const updatedRoom = await getLivechatRoomInfo(room._id);
expect(updatedRoom.onHold).to.be.true;
});
it('should not put room on hold when visitor sent the last message', async () => {
const { room, visitor } = await startANewLivechatRoomAndTakeIt();
await updateSetting('Livechat_allow_manual_on_hold_upon_agent_engagement_only', true);
await sendMessage(room._id, '-', visitor.token);
const response = await request
.post(api('livechat/room.onHold'))
.set(credentials)
.send({
roomId: room._id,
})
.expect(400);
expect(response.body.success).to.be.false;
expect(response.body.error).to.be.equal('error-cannot-place-chat-on-hold');
});
});
describe('livechat/room.resumeOnHold', () => {
it('should fail if user doesnt have view-l-room permission', async () => {
await updatePermission('view-l-room', []);
const response = await request
.post(api('livechat/room.resumeOnHold'))
.set(credentials)
.send({
roomId: 'invalid-room-id',
})
.expect(403);
expect(response.body.success).to.be.false;
});
it('should fail if roomId is invalid', async () => {
await updatePermission('view-l-room', ['admin', 'livechat-manager', 'livechat-agent']);
const response = await request
.post(api('livechat/room.resumeOnHold'))
.set(credentials)
.send({
roomId: 'invalid-room-id',
})
.expect(400);
expect(response.body.success).to.be.false;
expect(response.body.error).to.be.equal('error-invalid-room');
});
it('should fail if room is not a livechat room', async () => {
const response = await request
.post(api('livechat/room.resumeOnHold'))
.set(credentials)
.send({
roomId: 'GENERAL',
})
.expect(400);
expect(response.body.success).to.be.false;
expect(response.body.error).to.be.equal('error-invalid-room');
});
it('should fail if room is not on hold', async () => {
const { room } = await startANewLivechatRoomAndTakeIt();
const response = await request
.post(api('livechat/room.resumeOnHold'))
.set(credentials)
.send({
roomId: room._id,
})
.expect(400);
expect(response.body.success).to.be.false;
expect(response.body.error).to.be.equal('error-room-not-on-hold');
});
it('should resume room on hold', async () => {
const { room } = await startANewLivechatRoomAndTakeIt();
await sendAgentMessage(room._id);
await placeRoomOnHold(room._id);
const response = await request
.post(api('livechat/room.resumeOnHold'))
.set(credentials)
.send({
roomId: room._id,
})
.expect(200);
expect(response.body.success).to.be.true;
const updatedRoom = await getLivechatRoomInfo(room._id);
expect(updatedRoom).to.not.have.property('onHold');
});
it('should resume room on hold and send proper system message', async () => {
const { room, visitor } = await startANewLivechatRoomAndTakeIt();
await sendAgentMessage(room._id);
await placeRoomOnHold(room._id);
const response = await request
.post(api('livechat/room.resumeOnHold'))
.set(credentials)
.send({
roomId: room._id,
})
.expect(200);
expect(response.body.success).to.be.true;
const messages = await fetchMessages(room._id, visitor.token);
expect(messages).to.be.an('array');
expect(messages[0]).to.not.be.undefined;
expect(messages[0]).to.have.property('t', 'omnichannel_on_hold_chat_resumed');
expect(messages[0]).to.have.property('comment', 'The chat was manually resumed from On Hold by RocketChat Internal Admin Test');
});
it('should resume chat automatically if visitor sent a message', async () => {
const { room, visitor } = await startANewLivechatRoomAndTakeIt();
await sendAgentMessage(room._id);
await placeRoomOnHold(room._id);
await sendMessage(room._id, 'test', visitor.token);
// wait for the room to be resumed since that logic is within callbacks
await sleep(500);
const updatedRoom = await getLivechatRoomInfo(room._id);
expect(updatedRoom).to.not.have.property('onHold');
});
});
describe('visitor abandonment feature', () => {
let room: IOmnichannelRoom;
before(async () => {
await updateSetting('Livechat_abandoned_rooms_action', 'Livechat_close_chat');
await updateSetting('Livechat_visitor_inactivity_timeout', 60);
});
it('should set predictedVisitorAbandonmentAt when agent sends a message', async () => {
const { room: newRoom } = await startANewLivechatRoomAndTakeIt();
room = newRoom;
await sendAgentMessage(room._id);
const updatedRoom = await getLivechatRoomInfo(room._id);
const lastMessageTs = updatedRoom.responseBy?.lastMessageTs;
const firstResponseTs = updatedRoom.responseBy?.firstResponseTs;
const predictedVisitorAbandonmentAt = updatedRoom.omnichannel?.predictedVisitorAbandonmentAt;
expect(predictedVisitorAbandonmentAt).to.not.be.undefined;
expect(lastMessageTs).to.not.be.undefined;
expect(firstResponseTs).to.not.be.undefined;
// expect predictedVisitorAbandonmentAt to be 60 seconds after lastMessageTs
const lastMessageTsDate = new Date(lastMessageTs as Date);
const predictedVisitorAbandonmentAtDate = new Date(predictedVisitorAbandonmentAt as Date);
const firstResponseTsDate = new Date(firstResponseTs as Date);
expect(predictedVisitorAbandonmentAtDate.getTime()).to.be.equal(lastMessageTsDate.getTime() + 60000);
expect(firstResponseTsDate.getTime()).to.be.equal(lastMessageTsDate.getTime());
});
it('should not update predictedVisitorAbandonmentAt when agent sends yet another message', async () => {
await sendAgentMessage(room._id);
const updatedRoom = await getLivechatRoomInfo(room._id);
const lastMessageTs = updatedRoom.responseBy?.lastMessageTs;
const firstResponseTs = updatedRoom.responseBy?.firstResponseTs;
const predictedVisitorAbandonmentAt = updatedRoom.omnichannel?.predictedVisitorAbandonmentAt;
expect(predictedVisitorAbandonmentAt).to.not.be.undefined;
expect(lastMessageTs).to.not.be.undefined;
// expect predictedVisitorAbandonmentAt to be 60 seconds after first message
const lastMessageTsDate = new Date(lastMessageTs as Date);
const predictedVisitorAbandonmentAtDate = new Date(predictedVisitorAbandonmentAt as Date);
const firstResponseTsDate = new Date(firstResponseTs as Date);
expect(predictedVisitorAbandonmentAtDate.getTime()).to.be.equal(firstResponseTsDate.getTime() + 60000);
// lastMessageTs should be updated
expect(lastMessageTsDate.getTime()).to.not.be.equal(firstResponseTsDate.getTime());
expect(lastMessageTsDate.getTime()).to.be.greaterThan(firstResponseTsDate.getTime());
});
after(async () => {
await updateSetting('Livechat_abandoned_rooms_action', 'none');
await updateSetting('Livechat_visitor_inactivity_timeout', 3600);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,124 @@
import { faker } from '@faker-js/faker';
import type { ILivechatAgent, ILivechatVisitor, IOmnichannelRoom, IRoom } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { before, describe, it, after } from 'mocha';
import { api, getCredentials, request } from '../../../data/api-data';
import { sendSimpleMessage } from '../../../data/chat.helper';
import {
sendMessage,
startANewLivechatRoomAndTakeIt,
sendAgentMessage,
createAgent,
makeAgentAvailable,
uploadFile,
closeOmnichannelRoom,
} from '../../../data/livechat/rooms';
import { removeAgent } from '../../../data/livechat/users';
import { updateEESetting, updateSetting } from '../../../data/permissions.helper';
import { createRoom, deleteRoom } from '../../../data/rooms.helper';
describe('LIVECHAT - messages', () => {
let agent: ILivechatAgent;
before((done) => getCredentials(done));
before(async () => {
agent = await createAgent();
await makeAgentAvailable();
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
});
after(() => Promise.all([updateSetting('Livechat_Routing_Method', 'Auto_Selection'), removeAgent(agent._id)]));
describe('Quote message feature for visitors', () => {
it('it should verify if visitor can quote message', async () => {
const {
room: { _id: roomId },
visitor: { token },
} = await startANewLivechatRoomAndTakeIt();
await sendMessage(roomId, 'Hello from visitor', token);
const agentMsgSentence = faker.lorem.sentence();
const agentMsg = await sendAgentMessage(roomId, agentMsgSentence);
const siteUrl = process.env.SITE_URL || process.env.TEST_API_URL || 'http://localhost:3000';
const msgLink = `${siteUrl}/live/${roomId}?msg=${agentMsg._id}`;
const quotedMsgSentence = faker.lorem.sentence();
const wholeQuotedMsg = `[${quotedMsgSentence}](${msgLink})`;
const quotedMessage = await sendMessage(roomId, wholeQuotedMsg, token);
expect(quotedMessage).to.have.property('_id');
expect(quotedMessage).to.have.property('msg').that.is.equal(wholeQuotedMsg);
expect(quotedMessage).to.have.property('attachments');
expect(quotedMessage.attachments).to.be.an('array').that.has.lengthOf(1);
const quotedMessageAttachments = quotedMessage.attachments?.[0];
if (quotedMessageAttachments) {
expect(quotedMessageAttachments).to.have.property('message_link').that.is.equal(msgLink);
expect(quotedMessageAttachments).to.have.property('text').that.is.equal(agentMsgSentence);
}
});
it('should verify if visitor is receiving a message with a image attachment', async () => {
const {
room: { _id: roomId },
visitor: { token },
} = await startANewLivechatRoomAndTakeIt();
const imgMessage = await uploadFile(roomId, token);
expect(imgMessage).to.have.property('files').that.is.an('array');
expect(imgMessage.files?.[0]).to.have.keys('_id', 'name', 'type');
expect(imgMessage).to.have.property('file').that.deep.equal(imgMessage?.files?.[0]);
});
});
describe('Livechat Messages', async () => {
let room: IOmnichannelRoom;
let privateRoom: IRoom;
let visitor: ILivechatVisitor;
before(async () => {
await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
const data = await startANewLivechatRoomAndTakeIt();
visitor = data.visitor;
room = data.room;
const response = await createRoom({ type: 'p', name: `private-room-${Math.random()}` } as any);
privateRoom = response.body.group;
});
after(() => Promise.all([closeOmnichannelRoom(room._id), deleteRoom({ roomId: privateRoom._id, type: 'p' })]));
it('should not allow fetching arbitrary messages from another channel', async () => {
const response = await sendSimpleMessage({ roomId: privateRoom._id } as any);
const { message } = response.body;
await request
.get(api(`livechat/message/${message._id}`))
.query({ token: visitor.token, rid: room._id })
.send()
.expect(400)
.expect((res) => {
expect(res.body.error).to.be.equal('invalid-message');
});
});
it('should allow fetching messages using their _id and roomId', async () => {
const message = await sendMessage(room._id, 'Hello from visitor', visitor.token);
await request
.get(api(`livechat/message/${message._id}`))
.query({ token: visitor.token, rid: room._id })
.send()
.expect(200)
.expect((res) => {
expect(res.body.message._id).to.be.equal(message._id);
});
});
});
});

View File

@@ -0,0 +1,573 @@
import type { Credentials } from '@rocket.chat/api-client';
import type { IUser, IOmnichannelRoom } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';
import { api, request, credentials, getCredentials } from '../../../data/api-data';
import { createDepartment, addOrRemoveAgentFromDepartment } from '../../../data/livechat/department';
import { startANewLivechatRoomAndTakeIt, createAgent } from '../../../data/livechat/rooms';
import { createMonitor, createUnit } from '../../../data/livechat/units';
import { restorePermissionToRoles, updateEESetting, updatePermission } from '../../../data/permissions.helper';
import { password } from '../../../data/user';
import { createUser, deleteUser, login } from '../../../data/users.helper';
import { IS_EE } from '../../../e2e/config/constants';
(IS_EE ? describe : describe.skip)('LIVECHAT - reports', () => {
before((done) => getCredentials(done));
let agent2: { user: IUser; credentials: Credentials };
let agent3: { user: IUser; credentials: Credentials };
before(async () => {
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
const user = await createUser();
const userCredentials = await login(user.username, password);
if (!user.username) {
throw new Error('user not created');
}
await createMonitor(user.username);
agent2 = {
user,
credentials: userCredentials,
};
});
before(async () => {
const user = await createUser();
const userCredentials = await login(user.username, password);
await createAgent();
if (!user.username) {
throw new Error('user not created');
}
await createMonitor(user.username);
const dep1 = await createDepartment();
await addOrRemoveAgentFromDepartment(
dep1._id,
{ agentId: 'rocketchat.internal.admin.test', username: 'rocketchat.internal.admin.test', count: 0, order: 0 },
true,
);
const { room, visitor } = await startANewLivechatRoomAndTakeIt({ departmentId: dep1._id });
await request
.post(api('livechat/room.saveInfo'))
.set(credentials)
.send({
roomData: {
_id: room._id,
topic: 'new topic',
tags: ['tag1', 'tag2'],
},
guestData: {
_id: visitor._id,
},
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
});
await createUnit(user._id, user.username, [dep1._id]);
agent3 = {
user,
credentials: userCredentials,
};
});
after(async () => {
await deleteUser(agent2.user);
await deleteUser(agent3.user);
});
describe('livechat/analytics/dashboards/conversations-by-source', () => {
it('should return an error when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-reports', []);
await request
.get(api('livechat/analytics/dashboards/conversations-by-source'))
.set(credentials)
.query({ start: 'test', end: 'test' })
.expect(403);
});
it('should return an error when the start and end parameters are not provided', async () => {
await restorePermissionToRoles('view-livechat-reports');
await request.get(api('livechat/analytics/dashboards/conversations-by-source')).set(credentials).expect(400);
});
it('should return an error when the start parameter is not provided', async () => {
await request.get(api('livechat/analytics/dashboards/conversations-by-source')).set(credentials).query({ end: 'test' }).expect(400);
});
it('should return an error when the end parameter is not provided', async () => {
await request.get(api('livechat/analytics/dashboards/conversations-by-source')).set(credentials).query({ start: 'test' }).expect(400);
});
it('should return an error when the start parameter is not a valid date', async () => {
await request
.get(api('livechat/analytics/dashboards/conversations-by-source'))
.set(credentials)
.query({ start: 'test', end: 'test' })
.expect(400);
});
it('should fail if dates are more than 1 year apart', async () => {
const oneYearAgo = new Date(Date.now() - 380 * 24 * 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
await request
.get(api('livechat/analytics/dashboards/conversations-by-source'))
.set(credentials)
.query({ start: oneYearAgo, end: now })
.expect(400);
});
it('should return an error when start is after end', async () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
await request
.get(api('livechat/analytics/dashboards/conversations-by-source'))
.set(credentials)
.query({ start: now, end: oneHourAgo })
.expect(400);
});
it('should return an error when the end parameter is not a valid date', async () => {
await request
.get(api('livechat/analytics/dashboards/conversations-by-source'))
.set(credentials)
.query({ start: '2020-01-01', end: 'test' })
.expect(400);
});
it('should return the proper data when the parameters are valid', async () => {
// Note: this way all data will come as 0
const now = new Date().toISOString();
const { body } = await request
.get(api('livechat/analytics/dashboards/conversations-by-source'))
.set(credentials)
.query({ start: now, end: now })
.expect(200);
expect(body).to.have.property('data').and.to.be.an('array');
expect(body.data).to.have.lengthOf(0);
expect(body.total).to.be.equal(0);
expect(body.success).to.be.true;
});
it('should return only public rooms for a monitor with no units', async () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
const { body } = await request
.get(api('livechat/analytics/dashboards/conversations-by-source'))
.set(agent2.credentials)
.query({ start: oneHourAgo, end: now })
.expect(200);
expect(body).to.have.property('data').and.to.be.an('array');
expect(body.data.every((room: IOmnichannelRoom) => !room.departmentAncestors && !room.departmentId)).to.be.true;
});
it('should return only the data from the unit the monitor belongs to', async () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
const { body } = await request
.get(api('livechat/analytics/dashboards/conversations-by-source'))
.set(agent3.credentials)
.query({ start: oneHourAgo, end: now })
.expect(200);
expect(body).to.have.property('data').and.to.be.an('array');
expect(body.data).to.have.lengthOf.greaterThan(0);
expect(body.data.every((item: { value: number }) => item.value >= 0)).to.be.true;
expect(body.total).to.be.greaterThan(0);
expect(body.success).to.be.true;
});
it('should return valid data when login as a manager', async () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
const { body } = await request
.get(api('livechat/analytics/dashboards/conversations-by-source'))
.set(credentials)
.query({ start: oneHourAgo, end: now })
.expect(200);
expect(body).to.have.property('data').and.to.be.an('array');
expect(body.data).to.have.lengthOf.greaterThan(0);
expect(body.data.every((item: { value: number }) => item.value >= 0)).to.be.true;
expect(body.total).to.be.greaterThan(0);
});
});
describe('livechat/analytics/dashboards/conversations-by-status', () => {
it('should return an error when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-reports', []);
await request
.get(api('livechat/analytics/dashboards/conversations-by-status'))
.set(credentials)
.query({ start: 'test', end: 'test' })
.expect(403);
});
it('should return an error when the start and end parameters are not provided', async () => {
await restorePermissionToRoles('view-livechat-reports');
await request.get(api('livechat/analytics/dashboards/conversations-by-status')).set(credentials).expect(400);
});
it('should return an error when the start parameter is not provided', async () => {
await request.get(api('livechat/analytics/dashboards/conversations-by-status')).set(credentials).query({ end: 'test' }).expect(400);
});
it('should return an error when the end parameter is not provided', async () => {
await request.get(api('livechat/analytics/dashboards/conversations-by-status')).set(credentials).query({ start: 'test' }).expect(400);
});
it('should return an error when the start parameter is not a valid date', async () => {
await request
.get(api('livechat/analytics/dashboards/conversations-by-status'))
.set(credentials)
.query({ start: 'test', end: 'test' })
.expect(400);
});
it('should return an error when the end parameter is not a valid date', async () => {
await request
.get(api('livechat/analytics/dashboards/conversations-by-status'))
.set(credentials)
.query({ start: '2020-01-01', end: 'test' })
.expect(400);
});
it('should return an error if dates are more than 1 year apart', async () => {
const oneYearAgo = new Date(Date.now() - 380 * 24 * 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
await request
.get(api('livechat/analytics/dashboards/conversations-by-status'))
.set(credentials)
.query({ start: oneYearAgo, end: now })
.expect(400);
});
it('should return an error when start is after end', async () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
await request
.get(api('livechat/analytics/dashboards/conversations-by-status'))
.set(credentials)
.query({ start: now, end: oneHourAgo })
.expect(400);
});
it('should return the proper data when the parameters are valid', async () => {
// Note: this way all data will come as 0
const now = new Date().toISOString();
const { body } = await request
.get(api('livechat/analytics/dashboards/conversations-by-status'))
.set(credentials)
.query({ start: now, end: now })
.expect(200);
expect(body).to.have.property('data').and.to.be.an('array');
expect(body.data).to.have.lengthOf(0);
expect(body.success).to.be.true;
});
it('should return public rooms for a monitor with no units', async () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
const { body } = await request
.get(api('livechat/analytics/dashboards/conversations-by-status'))
.set(agent2.credentials)
.query({ start: oneHourAgo, end: now })
.expect(200);
expect(body).to.have.property('data').and.to.be.an('array');
expect(body.data.every((room: IOmnichannelRoom) => !room.departmentAncestors && !room.departmentId)).to.be.true;
});
it('should return the proper data when login as a manager', async () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
const { body } = await request
.get(api('livechat/analytics/dashboards/conversations-by-status'))
.set(credentials)
.query({ start: oneHourAgo, end: now })
.expect(200);
expect(body).to.have.property('data').and.to.be.an('array');
expect(body.data).to.have.lengthOf.greaterThan(0);
expect(body.data.every((item: { value: number }) => item.value >= 0)).to.be.true;
});
});
describe('livechat/analytics/dashboards/conversations-by-department', () => {
it('should return an error when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-reports', []);
await request
.get(api('livechat/analytics/dashboards/conversations-by-department'))
.set(credentials)
.query({ start: 'test', end: 'test' })
.expect(403);
});
it('should return an error when the start and end parameters are not provided', async () => {
await restorePermissionToRoles('view-livechat-reports');
await request.get(api('livechat/analytics/dashboards/conversations-by-department')).set(credentials).expect(400);
});
it('should return an error when the start parameter is not provided', async () => {
await request
.get(api('livechat/analytics/dashboards/conversations-by-department'))
.set(credentials)
.query({ end: 'test' })
.expect(400);
});
it('should return an error when the end parameter is not provided', async () => {
await request
.get(api('livechat/analytics/dashboards/conversations-by-department'))
.set(credentials)
.query({ start: 'test' })
.expect(400);
});
it('should return an error when the start parameter is not a valid date', async () => {
await request
.get(api('livechat/analytics/dashboards/conversations-by-department'))
.set(credentials)
.query({ start: 'test', end: 'test' })
.expect(400);
});
it('should return an error when the end parameter is not a valid date', async () => {
await request
.get(api('livechat/analytics/dashboards/conversations-by-department'))
.set(credentials)
.query({ start: '2020-01-01', end: 'test' })
.expect(400);
});
it('should return an error if dates are more than 1 year apart', async () => {
const oneYearAgo = new Date(Date.now() - 380 * 24 * 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
await request
.get(api('livechat/analytics/dashboards/conversations-by-department'))
.set(credentials)
.query({ start: oneYearAgo, end: now })
.expect(400);
});
it('should return an error when start is after end', async () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
await request
.get(api('livechat/analytics/dashboards/conversations-by-department'))
.set(credentials)
.query({ start: now, end: oneHourAgo })
.expect(400);
});
it('should return the proper data when the parameters are valid', async () => {
// Note: this way all data will come as 0
const now = new Date().toISOString();
const { body } = await request
.get(api('livechat/analytics/dashboards/conversations-by-department'))
.set(credentials)
.query({ start: now, end: now })
.expect(200);
expect(body).to.have.property('data').and.to.be.an('array');
expect(body.data).to.have.lengthOf(0);
expect(body.success).to.be.true;
});
it('should return public rooms for a monitor with no units', async () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
const { body } = await request
.get(api('livechat/analytics/dashboards/conversations-by-department'))
.set(agent2.credentials)
.query({ start: oneHourAgo, end: now })
.expect(200);
expect(body).to.have.property('data').and.to.be.an('array');
expect(body.data.every((room: IOmnichannelRoom) => !room.departmentAncestors && !room.departmentId)).to.be.true;
});
it('should return the proper data when login as a manager', async () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
const { body } = await request
.get(api('livechat/analytics/dashboards/conversations-by-department'))
.set(credentials)
.query({ start: oneHourAgo, end: now })
.expect(200);
expect(body).to.have.property('data').and.to.be.an('array');
expect(body.data).to.have.lengthOf.greaterThan(0);
expect(body.data.every((item: { value: number }) => item.value >= 0)).to.be.true;
});
});
describe('livechat/analytics/dashboards/conversations-by-tags', () => {
it('should return an error when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-reports', []);
await request
.get(api('livechat/analytics/dashboards/conversations-by-tags'))
.set(credentials)
.query({ start: 'test', end: 'test' })
.expect(403);
});
it('should return an error when the start and end parameters are not provided', async () => {
await restorePermissionToRoles('view-livechat-reports');
await request.get(api('livechat/analytics/dashboards/conversations-by-tags')).set(credentials).expect(400);
});
it('should return an error when the start parameter is not provided', async () => {
await request.get(api('livechat/analytics/dashboards/conversations-by-tags')).set(credentials).query({ end: 'test' }).expect(400);
});
it('should return an error when the end parameter is not provided', async () => {
await request.get(api('livechat/analytics/dashboards/conversations-by-tags')).set(credentials).query({ start: 'test' }).expect(400);
});
it('should return an error when the start parameter is not a valid date', async () => {
await request
.get(api('livechat/analytics/dashboards/conversations-by-tags'))
.set(credentials)
.query({ start: 'test', end: 'test' })
.expect(400);
});
it('should return an error when the end parameter is not a valid date', async () => {
await request
.get(api('livechat/analytics/dashboards/conversations-by-tags'))
.set(credentials)
.query({ start: '2020-01-01', end: 'test' })
.expect(400);
});
it('should return an error if dates are more than 1 year apart', async () => {
const oneYearAgo = new Date(Date.now() - 380 * 24 * 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
await request
.get(api('livechat/analytics/dashboards/conversations-by-tags'))
.set(credentials)
.query({ start: oneYearAgo, end: now })
.expect(400);
});
it('should return an error when start is after end', async () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
await request
.get(api('livechat/analytics/dashboards/conversations-by-tags'))
.set(credentials)
.query({ start: now, end: oneHourAgo })
.expect(400);
});
it('should return the proper data when the parameters are valid', async () => {
// Note: this way all data will come as 0
const now = new Date().toISOString();
const { body } = await request
.get(api('livechat/analytics/dashboards/conversations-by-tags'))
.set(credentials)
.query({ start: now, end: now })
.expect(200);
expect(body).to.have.property('data').and.to.be.an('array');
expect(body.data).to.have.lengthOf(0);
expect(body.success).to.be.true;
});
it('should return public rooms for a monitor with no units', async () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
const { body } = await request
.get(api('livechat/analytics/dashboards/conversations-by-tags'))
.set(agent2.credentials)
.query({ start: oneHourAgo, end: now })
.expect(200);
expect(body).to.have.property('data').and.to.be.an('array');
expect(body.data.every((room: IOmnichannelRoom) => !room.departmentAncestors && !room.departmentId)).to.be.true;
});
it('should return the proper data when login as a manager', async () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
const { body } = await request
.get(api('livechat/analytics/dashboards/conversations-by-tags'))
.set(credentials)
.query({ start: oneHourAgo, end: now })
.expect(200);
expect(body).to.have.property('data').and.to.be.an('array');
expect(body.data).to.have.lengthOf.greaterThan(0);
expect(body.data.every((item: { value: number }) => item.value >= 0)).to.be.true;
});
});
describe('livechat/analytics/dashboards/conversations-by-agent', () => {
it('should return an error when the user does not have the necessary permission', async () => {
await updatePermission('view-livechat-reports', []);
await request
.get(api('livechat/analytics/dashboards/conversations-by-agent'))
.set(credentials)
.query({ start: 'test', end: 'test' })
.expect(403);
});
it('should return an error when the start and end parameters are not provided', async () => {
await restorePermissionToRoles('view-livechat-reports');
await request.get(api('livechat/analytics/dashboards/conversations-by-agent')).set(credentials).expect(400);
});
it('should return an error when the start parameter is not provided', async () => {
await request.get(api('livechat/analytics/dashboards/conversations-by-agent')).set(credentials).query({ end: 'test' }).expect(400);
});
it('should return an error when the end parameter is not provided', async () => {
await request.get(api('livechat/analytics/dashboards/conversations-by-agent')).set(credentials).query({ start: 'test' }).expect(400);
});
it('should return an error when the start parameter is not a valid date', async () => {
await request
.get(api('livechat/analytics/dashboards/conversations-by-agent'))
.set(credentials)
.query({ start: 'test', end: 'test' })
.expect(400);
});
it('should return an error when the end parameter is not a valid date', async () => {
await request
.get(api('livechat/analytics/dashboards/conversations-by-agent'))
.set(credentials)
.query({ start: '2020-01-01', end: 'test' })
.expect(400);
});
it('should return an error if dates are more than 1 year apart', async () => {
const oneYearAgo = new Date(Date.now() - 380 * 24 * 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
await request
.get(api('livechat/analytics/dashboards/conversations-by-agent'))
.set(credentials)
.query({ start: oneYearAgo, end: now })
.expect(400);
});
it('should return an error when start is after end', async () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
await request
.get(api('livechat/analytics/dashboards/conversations-by-agent'))
.set(credentials)
.query({ start: now, end: oneHourAgo })
.expect(400);
});
it('should return the proper data when the parameters are valid', async () => {
// Note: this way all data will come as 0
const now = new Date().toISOString();
const { body } = await request
.get(api('livechat/analytics/dashboards/conversations-by-agent'))
.set(credentials)
.query({ start: now, end: now })
.expect(200);
expect(body).to.have.property('data').and.to.be.an('array');
expect(body.data).to.have.lengthOf(0);
});
it('should return public rooms for a monitor with no units', async () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
const { body } = await request
.get(api('livechat/analytics/dashboards/conversations-by-agent'))
.set(agent2.credentials)
.query({ start: oneHourAgo, end: now })
.expect(200);
expect(body).to.have.property('data').and.to.be.an('array');
expect(body.data.every((room: IOmnichannelRoom) => !room.departmentAncestors && !room.departmentId)).to.be.true;
});
it('should return the proper data when login as a manager', async () => {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
const now = new Date().toISOString();
const { body } = await request
.get(api('livechat/analytics/dashboards/conversations-by-agent'))
.set(credentials)
.query({ start: oneHourAgo, end: now })
.expect(200);
expect(body).to.have.property('data').and.to.be.an('array');
expect(body.data).to.have.lengthOf.greaterThan(0);
expect(body.data.every((item: { value: number }) => item.value >= 0)).to.be.true;
});
});
});

View File

@@ -0,0 +1,301 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type { Credentials } from '@rocket.chat/api-client';
import type { ILivechatDepartment, IUser } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { before, it, describe, after } from 'mocha';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { addOrRemoveAgentFromDepartment, createDepartment } from '../../../data/livechat/department';
import {
createAgent,
createLivechatRoom,
createManager,
createVisitor,
getLivechatRoomInfo,
makeAgentAvailable,
} from '../../../data/livechat/rooms';
import { createMonitor, createUnit } from '../../../data/livechat/units';
import { updateSetting, updatePermission, restorePermissionToRoles, removePermissionFromAllRoles } from '../../../data/permissions.helper';
import { password } from '../../../data/user';
import { createUser, deleteUser, login, setUserActiveStatus } from '../../../data/users.helper';
import { IS_EE } from '../../../e2e/config/constants';
type TestUser = { user: IUser; credentials: Credentials };
(IS_EE ? describe : describe.skip)('Omnichannel - Monitors', () => {
let manager: TestUser;
let monitor: TestUser;
let noUnitDepartment: ILivechatDepartment;
let unitDepartment: ILivechatDepartment;
before((done) => getCredentials(done));
before(async () => {
await updateSetting('Livechat_accept_chats_with_no_agents', true);
await setUserActiveStatus('rocketchat.internal.admin.test', true);
await createAgent();
await makeAgentAvailable();
});
before(async () => {
const user = await createUser();
const userCredentials = await login(user.username, password);
if (!user.username) {
throw new Error('user not created');
}
await createManager(user.username);
manager = {
user,
credentials: userCredentials,
};
});
before(async () => {
const user = await createUser();
const userCredentials = await login(user.username, password);
if (!user.username) {
throw new Error('user not created');
}
await createMonitor(user.username);
monitor = {
user,
credentials: userCredentials,
};
});
before(async () => {
noUnitDepartment = await createDepartment();
unitDepartment = await createDepartment();
await createUnit(monitor.user._id, monitor.user.username!, [unitDepartment._id]);
});
before(async () => {
await updatePermission('transfer-livechat-guest', ['admin', 'livechat-manager', 'livechat-agent', 'livechat-monitor']);
});
describe('Monitors', () => {
let user: IUser;
before(async () => {
user = await createUser();
});
after(async () => {
await deleteUser(user);
});
it('should properly create a new monitor', async () => {
const { body } = await request.post(api('livechat/monitors.create')).send({ username: user.username }).set(credentials).expect(200);
expect(body.success).to.be.true;
});
it('should not fail when trying to create a monitor that already exists', async () => {
const { body } = await request.post(api('livechat/monitors.create')).send({ username: user.username }).set(credentials).expect(200);
expect(body.success).to.be.true;
});
it('should fail when trying to create a monitor with an invalid username', async () => {
const { body } = await request
.post(api('livechat/monitors.create'))
.set(credentials)
.send({ username: 'invalid-username' })
.expect(400);
expect(body.success).to.be.false;
expect(body).to.have.property('error').to.be.equal('Invalid user [error-invalid-user]');
});
it('should fail when trying to create a monitor with an empty username', async () => {
const { body } = await request.post(api('livechat/monitors.create')).set(credentials).send({ username: '' }).expect(400);
expect(body.success).to.be.false;
expect(body).to.have.property('error').to.be.equal('Invalid user [error-invalid-user]');
});
it('should remove a monitor', async () => {
const { body } = await request.post(api('livechat/monitors.delete')).set(credentials).send({ username: user.username }).expect(200);
expect(body.success).to.be.true;
});
it('should fail when trying to remove a monitor that does not exist', async () => {
const { body } = await request
.post(api('livechat/monitors.delete'))
.set(credentials)
.send({ username: 'some-random-username' })
.expect(400);
expect(body.success).to.be.false;
expect(body).to.have.property('error').to.be.equal('Invalid user');
});
});
describe('[GET] livechat/monitors', () => {
it('should fail if manage-livechat-monitors permission is missing', async () => {
await removePermissionFromAllRoles('manage-livechat-monitors');
return request.get(api('livechat/monitors')).set(credentials).expect(403);
});
it('should return all monitors', async () => {
await restorePermissionToRoles('manage-livechat-monitors');
const user = await createUser();
await createMonitor(user.username);
const { body } = await request.get(api('livechat/monitors')).set(credentials).query({ text: user.username }).expect(200);
expect(body).to.have.property('monitors');
expect(body.monitors).to.have.lengthOf(1);
expect(body.monitors[0]).to.have.property('username', user.username);
// cleanup
await deleteUser(user);
});
});
describe('livechat/monitors/:username', () => {
it('should fail if manage-livechat-monitors permission is missing', async () => {
await removePermissionFromAllRoles('manage-livechat-monitors');
return request.get(api('livechat/monitors/123')).set(credentials).expect(403);
});
it('should return a monitor', async () => {
await restorePermissionToRoles('manage-livechat-monitors');
const user = await createUser();
await createMonitor(user.username);
const { body } = await request
.get(api(`livechat/monitors/${user.username}`))
.set(credentials)
.expect(200);
expect(body).to.have.property('username', user.username);
// cleanup
await deleteUser(user);
});
});
describe('Monitors & Rooms', () => {
it('should not return a room of a department that the monitor is not assigned to', async () => {
const visitor = await createVisitor(noUnitDepartment._id);
const room = await createLivechatRoom(visitor.token);
const { body } = await request
.get(api('livechat/rooms'))
.set(monitor.credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('rooms').that.is.an('array');
expect(body.rooms.find((r: any) => r._id === room._id)).to.not.exist;
});
it('should return a room of a department the monitor is assigned to', async () => {
const visitor = await createVisitor(unitDepartment._id);
const room = await createLivechatRoom(visitor.token);
const { body } = await request
.get(api('livechat/rooms'))
.set(monitor.credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('rooms').that.is.an('array');
expect(body.rooms.find((r: any) => r._id === room._id)).to.exist;
});
});
describe('Monitors & Departments', () => {
it('should not return a department that the monitor is not assigned to', async () => {
const { body } = await request
.get(api('livechat/department'))
.query({ onlyMyDepartments: true })
.set(monitor.credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('departments').that.is.an('array');
expect(body.departments.find((d: any) => d._id === noUnitDepartment._id)).to.not.exist;
});
it('should return a department that the monitor is assigned to', async () => {
const { body } = await request
.get(api('livechat/department'))
.query({ onlyMyDepartments: true })
.set(monitor.credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('departments').that.is.an('array');
expect(body.departments.length).to.be.equal(1);
expect(body.departments.find((d: any) => d._id === unitDepartment._id)).to.exist;
});
it('should return both created departments to a manager', async () => {
const { body } = await request
.get(api('livechat/department'))
.query({ onlyMyDepartments: true, sort: '{ "_updatedAt": 1 }' })
.set(manager.credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('departments').that.is.an('array');
expect(body.departments.find((d: any) => d._id === noUnitDepartment._id)).to.exist;
expect(body.departments.find((d: any) => d._id === unitDepartment._id)).to.exist;
});
// OnlyMyDepartments to return data from units + the ones you serve in
it.skip('should not return a department when monitor is only assigned as agent there', async () => {
await createAgent(monitor.user.username!);
await addOrRemoveAgentFromDepartment(
noUnitDepartment._id,
{ agentId: monitor.user._id, username: monitor.user.username!, count: 0, order: 0 },
true,
);
const { body } = await request
.get(api('livechat/department'))
.query({ onlyMyDepartments: true })
.set(monitor.credentials)
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('departments').that.is.an('array');
expect(body.departments.length).to.be.equal(1);
expect(body.departments.find((d: any) => d._id === noUnitDepartment._id)).to.not.exist;
});
});
describe('Monitors & Forward', () => {
it('should successfully forward a room to another agent', async () => {
const visitor = await createVisitor(unitDepartment._id);
const room = await createLivechatRoom(visitor.token);
const { body } = await request
.post(api('livechat/room.forward'))
.set(monitor.credentials)
.send({
roomId: room._id,
userId: 'rocketchat.internal.admin.test',
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
const room2 = await getLivechatRoomInfo(room._id);
expect(room2).to.have.property('servedBy').that.is.an('object');
expect(room2.servedBy).to.have.property('_id', 'rocketchat.internal.admin.test');
});
it('should successfully forward a room to a department', async () => {
const visitor = await createVisitor(noUnitDepartment._id);
const room = await createLivechatRoom(visitor.token);
const { body } = await request
.post(api('livechat/room.forward'))
.set(monitor.credentials)
.send({
roomId: room._id,
departmentId: unitDepartment._id,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
const room2 = await getLivechatRoomInfo(room._id);
expect(room2.departmentId).to.be.equal(unitDepartment._id);
});
});
});

View File

@@ -0,0 +1,134 @@
import type { ILivechatVisitor } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { before, afterEach, after, describe, it } from 'mocha';
import moment from 'moment';
import { api, getCredentials, request, credentials } from '../../../data/api-data';
import {
createVisitor,
createLivechatRoom,
createAgent,
makeAgentAvailable,
sendAgentMessage,
getLivechatRoomInfo,
fetchInquiry,
closeOmnichannelRoom,
deleteVisitor,
} from '../../../data/livechat/rooms';
describe('MAC', () => {
before((done) => getCredentials(done));
before(async () => {
await createAgent();
await makeAgentAvailable();
});
describe('MAC rooms', () => {
let visitor: ILivechatVisitor;
let multipleContactsVisitor: ILivechatVisitor;
afterEach(() => deleteVisitor(visitor.token));
after(() => deleteVisitor(multipleContactsVisitor.token));
it('Should create an innactive room and contact by default', async () => {
visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
expect(room).to.be.an('object');
expect(room.v.activity).to.be.undefined;
});
it('Should create an innactive contact by default', async () => {
visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
const res = await request.get(api(`omnichannel/contacts.get`)).set(credentials).query({ contactId: room.contactId });
expect(res.body.contact.channels[0].visitor.visitorId).to.be.equal(visitor._id);
expect(res.body.contact).not.to.have.property('activity');
});
it('should mark room as active when agent sends a message', async () => {
visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await sendAgentMessage(room._id);
const updatedRoom = await getLivechatRoomInfo(room._id);
expect(updatedRoom).to.have.nested.property('v.activity').and.to.be.an('array');
});
it('should mark contact as active when agent sends a message', async () => {
multipleContactsVisitor = await createVisitor();
const room = await createLivechatRoom(multipleContactsVisitor.token);
await sendAgentMessage(room._id);
const res = await request.get(api(`omnichannel/contacts.get`)).set(credentials).query({ contactId: room.contactId });
expect(res.body.contact.channels[0].visitor.visitorId).to.be.equal(multipleContactsVisitor._id);
expect(res.body.contact).to.have.property('activity').that.is.an('array').with.lengthOf(1);
expect(res.body.contact.activity[0]).to.equal(moment.utc().format('YYYY-MM'));
});
it('should mark multiple rooms as active when they come from same contact after an agent sends a message', async () => {
const room = await createLivechatRoom(multipleContactsVisitor.token);
await sendAgentMessage(room._id);
const updatedRoom = await getLivechatRoomInfo(room._id);
expect(updatedRoom).to.have.nested.property('v.activity').and.to.be.an('array');
await closeOmnichannelRoom(room._id);
});
it('should keep contact active when reusing it and an agent response is received', async () => {
const room = await createLivechatRoom(multipleContactsVisitor.token);
await sendAgentMessage(room._id);
const res = await request.get(api(`omnichannel/contacts.get`)).set(credentials).query({ contactId: room.contactId });
expect(res.body.contact.channels[0].visitor.visitorId).to.be.equal(multipleContactsVisitor._id);
expect(res.body.contact).to.have.property('activity').that.is.an('array').with.lengthOf(1);
expect(res.body.contact.activity[0]).to.equal(moment.utc().format('YYYY-MM'));
await closeOmnichannelRoom(room._id);
});
it('should mark room as active when it comes from same contact on same period, even without agent interaction', async () => {
const room = await createLivechatRoom(multipleContactsVisitor.token);
expect(room).to.have.nested.property('v.activity').and.to.be.an('array');
expect(room.v.activity?.includes(moment.utc().format('YYYY-MM'))).to.be.true;
await closeOmnichannelRoom(room._id);
});
it('should mark an inquiry as active when it comes from same contact on same period, even without agent interaction', async () => {
const room = await createLivechatRoom(multipleContactsVisitor.token);
const inquiry = await fetchInquiry(room._id);
expect(inquiry).to.have.nested.property('v.activity').and.to.be.an('array');
expect(inquiry.v.activity?.includes(moment.utc().format('YYYY-MM'))).to.be.true;
expect(room.v.activity?.includes(moment.utc().format('YYYY-MM'))).to.be.true;
await closeOmnichannelRoom(room._id);
});
it('contact should be marked as active for period', async () => {
visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await sendAgentMessage(room._id);
const res = await request.get(api(`omnichannel/contacts.get`)).set(credentials).query({ contactId: room.contactId });
expect(res.status).to.be.equal(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('contact').and.to.be.an('object');
expect(res.body.contact.channels).to.be.an('array').with.lengthOf(1);
expect(res.body.contact.channels[0].name).to.be.equal('api');
expect(res.body.contact.channels[0].visitor.visitorId).to.be.equal(visitor._id);
expect(res.body.contact).to.have.nested.property('activity').and.to.be.an('array').with.lengthOf(1);
expect(res.body.contact.activity[0]).to.equal(moment.utc().format('YYYY-MM'));
await closeOmnichannelRoom(room._id);
});
});
});

View File

@@ -0,0 +1,702 @@
import { faker } from '@faker-js/faker';
import type { Credentials } from '@rocket.chat/api-client';
import { UserStatus } from '@rocket.chat/core-typings';
import type { ILivechatDepartment, IUser, ILivechatAgent } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';
import { getCredentials, request, api, credentials } from '../../../data/api-data';
import {
createAgent,
makeAgentAvailable,
createDepartment,
createVisitor,
createLivechatRoom,
getLivechatRoomInfo,
makeAgentUnavailable,
switchLivechatStatus,
} from '../../../data/livechat/rooms';
import { getAgent } from '../../../data/livechat/users';
import { getSettingValueById, updateSetting } from '../../../data/permissions.helper';
import { password } from '../../../data/user';
import { createUser, deleteUser, login, setUserActiveStatus, setUserStatus } from '../../../data/users.helper';
import { IS_EE } from '../../../e2e/config/constants';
(IS_EE ? describe : describe.skip)('Omnichannel - Routing', () => {
before((done) => getCredentials(done));
after(async () => {
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
});
// Basically: if there's a bot in the department, it should be assigned to the conversation
// No matter what settings workspace has in, as long as the setting to assign new conversations to bots is enabled
describe('Bots - Manual selection', () => {
let botUser: { user: IUser; credentials: Credentials };
let testDepartment: ILivechatDepartment;
let testDepartment2: ILivechatDepartment;
before(async () => {
const bot = await createUser({ roles: ['bot', 'livechat-agent'] });
const credentials = await login(bot.username, password);
botUser = { user: bot, credentials };
});
before(async () => {
testDepartment = await createDepartment([{ agentId: botUser.user._id }]);
testDepartment2 = await createDepartment();
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
await updateSetting('Livechat_assign_new_conversation_to_bot', true);
await updateSetting('Livechat_accept_chats_with_no_agents', true);
});
after(async () => {
await deleteUser(botUser.user);
await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
await updateSetting('Livechat_assign_new_conversation_to_bot', false);
await updateSetting('Livechat_accept_chats_with_no_agents', false);
});
it('should assign conversation to bot', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy?._id).to.be.equal(botUser.user._id);
});
it('should not assign conversation to bot if department has no bots', async () => {
const visitor = await createVisitor(testDepartment2._id);
const room = await createLivechatRoom(visitor.token);
expect(room.servedBy).to.be.undefined;
});
describe('with setting disabled', () => {
before(async () => {
await updateSetting('Livechat_assign_new_conversation_to_bot', false);
});
after(async () => {
await updateSetting('Livechat_assign_new_conversation_to_bot', true);
});
it('should not assign conversation to bot', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
expect(room.servedBy).to.be.undefined;
});
});
});
describe('Bots - Auto selection', () => {
let botUser: { user: IUser; credentials: Credentials };
let otherUser: { user: IUser; credentials: Credentials };
let testDepartment: ILivechatDepartment;
let testDepartment2: ILivechatDepartment;
before(async () => {
const bot = await createUser({ roles: ['bot', 'livechat-agent'] });
const credentials = await login(bot.username, password);
const other = await createUser({ roles: ['livechat-agent'] });
const otherCredentials = await login(other.username, password);
await makeAgentAvailable(otherCredentials);
botUser = { user: bot, credentials };
otherUser = { user: other, credentials: otherCredentials };
});
before(async () => {
testDepartment = await createDepartment([{ agentId: botUser.user._id }, { agentId: otherUser.user._id }]);
testDepartment2 = await createDepartment();
await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
await updateSetting('Livechat_assign_new_conversation_to_bot', true);
await updateSetting('Livechat_accept_chats_with_no_agents', true);
});
after(async () => {
await deleteUser(botUser.user);
await updateSetting('Livechat_assign_new_conversation_to_bot', false);
await updateSetting('Livechat_accept_chats_with_no_agents', false);
});
it('should assign conversation to bot', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy?._id).to.be.equal(botUser.user._id);
});
it('should not assign conversation to bot if department has no bots', async () => {
const visitor = await createVisitor(testDepartment2._id);
const room = await createLivechatRoom(visitor.token);
expect(room.servedBy).to.be.undefined;
});
describe('with setting disabled', () => {
before(async () => {
await updateSetting('Livechat_assign_new_conversation_to_bot', false);
});
after(async () => {
await updateSetting('Livechat_assign_new_conversation_to_bot', true);
});
it('should not assign conversation to bot', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
expect(room.servedBy?._id).to.be.equal(otherUser.user._id);
});
});
});
describe('Bots - Auto selection & Waiting queue', () => {
let botUser: { user: IUser; credentials: Credentials };
let testDepartment: ILivechatDepartment;
let testDepartment2: ILivechatDepartment;
before(async () => {
const bot = await createUser({ roles: ['bot', 'livechat-agent'] });
const credentials = await login(bot.username, password);
botUser = { user: bot, credentials };
});
before(async () => {
testDepartment = await createDepartment([{ agentId: botUser.user._id }]);
testDepartment2 = await createDepartment();
await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
await updateSetting('Livechat_waiting_queue', true);
await updateSetting('Livechat_assign_new_conversation_to_bot', true);
await updateSetting('Livechat_accept_chats_with_no_agents', true);
});
after(async () => {
await deleteUser(botUser.user);
await updateSetting('Livechat_waiting_queue', false);
await updateSetting('Livechat_assign_new_conversation_to_bot', false);
await updateSetting('Livechat_accept_chats_with_no_agents', false);
});
it('should assign conversation to bot', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy?._id).to.be.equal(botUser.user._id);
});
it('should not assign conversation to bot if department has no bots', async () => {
const visitor = await createVisitor(testDepartment2._id);
const room = await createLivechatRoom(visitor.token);
expect(room.servedBy).to.be.undefined;
});
describe('with setting disabled', () => {
before(async () => {
await updateSetting('Livechat_assign_new_conversation_to_bot', false);
});
after(async () => {
await updateSetting('Livechat_assign_new_conversation_to_bot', true);
});
it('should not assign conversation to bot', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
expect(room.servedBy).to.be.undefined;
});
});
});
describe('Auto-Selection', () => {
before(async () => {
await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
});
let testUser: { user: IUser; credentials: Credentials };
let testUser2: { user: IUser; credentials: Credentials };
let testUser3: { user: IUser; credentials: Credentials };
let testDepartment: ILivechatDepartment;
let visitorEmail: string;
before(async () => {
const user = await createUser();
await createAgent(user.username);
const credentials2 = await login(user.username, password);
await makeAgentAvailable(credentials2);
testUser = {
user,
credentials: credentials2,
};
});
before(async () => {
const user = await createUser();
await createAgent(user.username);
const credentials2 = await login(user.username, password);
await makeAgentUnavailable(credentials2);
testUser2 = {
user,
credentials: credentials2,
};
});
before(async () => {
const user = await createUser();
await createAgent(user.username);
const credentials3 = await login(user.username, password);
await makeAgentAvailable(credentials3);
testUser3 = {
user,
credentials: credentials3,
};
});
before(async () => {
testDepartment = await createDepartment([{ agentId: testUser.user._id }, { agentId: testUser3.user._id }]);
const visitorName = faker.person.fullName();
visitorEmail = faker.internet.email().toLowerCase();
await request
.post(api('omnichannel/contacts'))
.set(credentials)
.send({
name: visitorName,
emails: [visitorEmail],
phones: [],
contactManager: testUser3.user._id,
});
});
after(async () =>
Promise.all([
deleteUser(testUser.user),
deleteUser(testUser2.user),
deleteUser(testUser3.user),
updateSetting('Livechat_enabled_when_agent_idle', true),
]),
);
it('should route a room to an available agent', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser.user._id);
expect(roomInfo.servedBy?._id).to.not.be.equal(testUser2.user._id);
});
it('should not route to a not-available agent', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.not.be.equal(testUser2.user._id);
});
it('should route to contact manager if it is online and Omnichannel_contact_manager_routing is enabled', async () => {
const visitor = await createVisitor(testDepartment._id, faker.person.fullName(), visitorEmail);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser3.user._id);
});
it('should fail to start a conversation if there is noone available and Livechat_accept_chats_with_no_agents is false', async () => {
await updateSetting('Livechat_accept_chats_with_no_agents', false);
await makeAgentUnavailable(testUser.credentials);
await makeAgentUnavailable(testUser3.credentials);
const visitor = await createVisitor(testDepartment._id);
const { body } = await request.get(api('livechat/room')).query({ token: visitor.token }).expect(400);
expect(body.error).to.be.equal('Sorry, no online agents [no-agent-online]');
});
it('should accept a conversation but not route to anyone when Livechat_accept_chats_with_no_agents is true', async () => {
await updateSetting('Livechat_accept_chats_with_no_agents', true);
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.undefined;
});
it('should not allow users to take more than Livechat_maximum_chats_per_agent chats', async () => {
await updateSetting('Livechat_maximum_chats_per_agent', 2);
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.undefined;
});
it('should ignore disabled users', async () => {
await updateSetting('Livechat_maximum_chats_per_agent', 0);
await setUserActiveStatus(testUser2.user._id, false);
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.undefined;
});
it('should route to another available agent if contact manager is unavailable and Omnichannel_contact_manager_routing is enabled', async () => {
await makeAgentAvailable(testUser.credentials);
const visitor = await createVisitor(testDepartment._id, faker.person.fullName(), visitorEmail);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser.user._id);
});
describe('with setting Omnichannel_contact_manager_routing disabled', () => {
let testDepartment2: ILivechatDepartment;
before(async () => {
await updateSetting('Omnichannel_contact_manager_routing', false);
// We should update the count of the user
});
after(async () => {
await updateSetting('Omnichannel_contact_manager_routing', true);
});
before(async () => {
// This will deprioritize the CM from normal routing. So the test is predictable
// IF the setting is off, the CM will be treated as a normal agent
// IF the setting is on, the CM will be treated as a CM and no matter the count it will get assigned
testDepartment2 = await createDepartment([{ agentId: testUser.user._id }, { agentId: testUser3.user._id, count: 10000 }]);
const visitorName = faker.person.fullName();
visitorEmail = faker.internet.email().toLowerCase();
await request
.post(api('omnichannel/contacts'))
.set(credentials)
.send({
name: visitorName,
emails: [visitorEmail],
phones: [],
contactManager: testUser3.user._id,
});
});
it('should not route to contact manager if it is online but setting is off', async () => {
const visitor = await createVisitor(testDepartment2._id, faker.person.fullName(), visitorEmail);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object').that.has.property('_id').that.is.not.equal(testUser3.user._id);
});
});
describe('Auto_Selection - Livechat_accept_chats_with_no_agents', async () => {
let initialSettingValue: any;
let testUserInitialState: ILivechatAgent;
let testUser3InitialState: ILivechatAgent;
before(async () => {
testUserInitialState = await getAgent(testUser.user._id);
testUser3InitialState = await getAgent(testUser3.user._id);
initialSettingValue = await getSettingValueById('Livechat_accept_chats_with_no_agents');
});
after(async () => {
await updateSetting('Livechat_accept_chats_with_no_agents', initialSettingValue);
await switchLivechatStatus(testUserInitialState.statusLivechat, testUser.credentials);
await switchLivechatStatus(testUser3InitialState.statusLivechat, testUser3.credentials);
await setUserStatus(testUser3.credentials, testUser3InitialState.status);
});
describe('Livechat_accept_chats_with_no_agents is false', () => {
before(async () => {
await updateSetting('Livechat_accept_chats_with_no_agents', false);
await switchLivechatStatus('not-available', testUser.credentials);
await switchLivechatStatus('available', testUser3.credentials);
await setUserStatus(testUser3.credentials, UserStatus.OFFLINE);
});
it('should fail to start a conversation if there is an available but offline agent and Livechat_accept_chats_with_no_agents is false', async () => {
const visitor = await createVisitor(testDepartment._id);
const { body } = await request.get(api('livechat/room')).query({ token: visitor.token }).expect(400);
expect(body.error).to.be.equal('Sorry, no online agents [no-agent-online]');
});
});
describe('Livechat_accept_chats_with_no_agents is true', () => {
before(async () => {
await updateSetting('Livechat_accept_chats_with_no_agents', true);
await switchLivechatStatus('available', testUser3.credentials);
await setUserStatus(testUser3.credentials, UserStatus.OFFLINE);
});
it('should accept a conversation and route to an offline agent', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser3.user._id);
});
});
});
});
describe('Load Balancing', () => {
before(async () => {
await updateSetting('Livechat_Routing_Method', 'Load_Balancing');
});
let testUser: { user: IUser; credentials: Credentials };
let testUser2: { user: IUser; credentials: Credentials };
let testDepartment: ILivechatDepartment;
before(async () => {
const user = await createUser();
await createAgent(user.username);
const credentials2 = await login(user.username, password);
await makeAgentAvailable(credentials2);
testUser = {
user,
credentials: credentials2,
};
});
before(async () => {
const user = await createUser();
await createAgent(user.username);
const credentials2 = await login(user.username, password);
await makeAgentUnavailable(credentials2);
testUser2 = {
user,
credentials: credentials2,
};
});
before(async () => {
testDepartment = await createDepartment([{ agentId: testUser.user._id }, { agentId: testUser2.user._id }]);
});
after(async () => {
await deleteUser(testUser.user);
await deleteUser(testUser2.user);
});
it('should route a room to an available agent', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser.user._id);
expect(roomInfo.servedBy?._id).to.not.be.equal(testUser2.user._id);
});
it('should not route to a not-available agent', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.not.be.equal(testUser2.user._id);
});
it('should route the chat to the less busy agent when both are available', async () => {
await makeAgentAvailable(testUser2.credentials);
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser2.user._id);
});
it('should route again to the less busy agent', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser2.user._id);
});
describe('Load_Balancing - Livechat_accept_chats_with_no_agents', async () => {
let initialSettingValue: any;
let testUserInitialState: ILivechatAgent;
let testUser2InitialState: ILivechatAgent;
before(async () => {
testUserInitialState = await getAgent(testUser.user._id);
testUser2InitialState = await getAgent(testUser2.user._id);
initialSettingValue = await getSettingValueById('Livechat_accept_chats_with_no_agents');
});
after(async () => {
await updateSetting('Livechat_accept_chats_with_no_agents', initialSettingValue);
await switchLivechatStatus(testUserInitialState.statusLivechat, testUser.credentials);
await switchLivechatStatus(testUser2InitialState.statusLivechat, testUser2.credentials);
await setUserStatus(testUser2.credentials, testUser2InitialState.status);
});
describe('Livechat_accept_chats_with_no_agents is false', () => {
before(async () => {
await updateSetting('Livechat_accept_chats_with_no_agents', false);
await switchLivechatStatus('not-available', testUser.credentials);
await switchLivechatStatus('available', testUser2.credentials);
await setUserStatus(testUser2.credentials, UserStatus.OFFLINE);
});
it('should fail to start a conversation if there is an available but offline agent and Livechat_accept_chats_with_no_agents is false', async () => {
const visitor = await createVisitor(testDepartment._id);
const { body } = await request.get(api('livechat/room')).query({ token: visitor.token }).expect(400);
expect(body.error).to.be.equal('Sorry, no online agents [no-agent-online]');
});
});
describe('Livechat_accept_chats_with_no_agents is true', () => {
before(async () => {
await updateSetting('Livechat_accept_chats_with_no_agents', true);
await switchLivechatStatus('available', testUser2.credentials);
await setUserStatus(testUser2.credentials, UserStatus.OFFLINE);
});
it('should accept a conversation and route to an offline agent', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser2.user._id);
});
});
});
});
describe('Load Rotation', () => {
before(async () => {
await updateSetting('Livechat_Routing_Method', 'Load_Rotation');
});
let testUser: { user: IUser; credentials: Credentials };
let testUser2: { user: IUser; credentials: Credentials };
let testDepartment: ILivechatDepartment;
before(async () => {
const user = await createUser();
await createAgent(user.username);
const credentials2 = await login(user.username, password);
await makeAgentAvailable(credentials2);
testUser = {
user,
credentials: credentials2,
};
});
before(async () => {
const user = await createUser();
await createAgent(user.username);
const credentials2 = await login(user.username, password);
await makeAgentUnavailable(credentials2);
testUser2 = {
user,
credentials: credentials2,
};
});
before(async () => {
testDepartment = await createDepartment([{ agentId: testUser.user._id }, { agentId: testUser2.user._id }]);
});
after(async () => {
await deleteUser(testUser.user);
await deleteUser(testUser2.user);
});
it('should route a room to an available agent', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser.user._id);
expect(roomInfo.servedBy?._id).to.not.be.equal(testUser2.user._id);
});
it('should not route chat to an unavailable agent', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.not.be.equal(testUser2.user._id);
});
it('should route the chat to the agent with the oldest routing time when both are available', async () => {
await makeAgentAvailable(testUser2.credentials);
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser2.user._id);
});
it('should route again to the agent with the oldest routing time', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser.user._id);
});
describe('Load_Rotation - Livechat_accept_chats_with_no_agents', async () => {
let initialSettingValue: any;
let testUserInitialState: ILivechatAgent;
let testUser2InitialState: ILivechatAgent;
before(async () => {
testUserInitialState = await getAgent(testUser.user._id);
testUser2InitialState = await getAgent(testUser2.user._id);
initialSettingValue = await getSettingValueById('Livechat_accept_chats_with_no_agents');
});
after(async () => {
await updateSetting('Livechat_accept_chats_with_no_agents', initialSettingValue);
await switchLivechatStatus(testUserInitialState.statusLivechat, testUser.credentials);
await switchLivechatStatus(testUser2InitialState.statusLivechat, testUser2.credentials);
await setUserStatus(testUser2.credentials, testUser2InitialState.status);
});
describe('Livechat_accept_chats_with_no_agents is false', () => {
before(async () => {
await updateSetting('Livechat_accept_chats_with_no_agents', false);
await switchLivechatStatus('not-available', testUser.credentials);
await switchLivechatStatus('available', testUser2.credentials);
await setUserStatus(testUser2.credentials, UserStatus.OFFLINE);
});
it('should fail to start a conversation if there is an available but offline agent and Livechat_accept_chats_with_no_agents is false', async () => {
const visitor = await createVisitor(testDepartment._id);
const { body } = await request.get(api('livechat/room')).query({ token: visitor.token }).expect(400);
expect(body.error).to.be.equal('Sorry, no online agents [no-agent-online]');
});
});
describe('Livechat_accept_chats_with_no_agents is true', () => {
before(async () => {
await updateSetting('Livechat_accept_chats_with_no_agents', true);
await switchLivechatStatus('available', testUser2.credentials);
await setUserStatus(testUser2.credentials, UserStatus.OFFLINE);
});
it('should accept a conversation and route to an offline agent', async () => {
const visitor = await createVisitor(testDepartment._id);
const room = await createLivechatRoom(visitor.token);
const roomInfo = await getLivechatRoomInfo(room._id);
expect(roomInfo.servedBy).to.be.an('object');
expect(roomInfo.servedBy?._id).to.be.equal(testUser2.user._id);
});
});
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,297 @@
import type { ILivechatCustomField } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';
import type { Response } from 'supertest';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { createCustomField, deleteCustomField } from '../../../data/livechat/custom-fields';
import {
getSettingValueById,
removePermissionFromAllRoles,
restorePermissionToRoles,
updateSetting,
} from '../../../data/permissions.helper';
describe('LIVECHAT - custom fields', () => {
let settingLivechatEnabled: boolean;
before((done) => getCredentials(done));
before(async () => {
settingLivechatEnabled = (await getSettingValueById('Livechat_enabled')) as boolean;
await updateSetting('Livechat_enabled', true);
});
after(async () => {
await updateSetting('Livechat_enabled', settingLivechatEnabled);
});
describe('livechat/custom-fields.save', () => {
let customFieldId: string;
after(async () => {
if (customFieldId) {
await deleteCustomField(customFieldId);
}
});
describe('Authentication/Authorization', () => {
it('should return an "unauthenticated error" when user is not logged in', async () => {
await request
.post(api('livechat/custom-fields.save'))
.send({
customFieldId: null,
customFieldData: {
field: 'test_field',
label: 'Test Field',
scope: 'visitor',
visibility: 'public',
},
})
.expect('Content-Type', 'application/json')
.expect(401);
});
it('should return an "unauthorized error" when the user does not have the necessary permission', async () => {
await removePermissionFromAllRoles('view-livechat-manager');
await request
.post(api('livechat/custom-fields.save'))
.set(credentials)
.send({
customFieldId: null,
customFieldData: {
field: 'test_field',
label: 'Test Field',
scope: 'visitor',
visibility: 'public',
},
})
.expect('Content-Type', 'application/json')
.expect(403);
await restorePermissionToRoles('view-livechat-manager');
});
});
describe('Create custom field', () => {
afterEach(async () => {
if (customFieldId) {
await deleteCustomField(customFieldId);
customFieldId = '';
}
});
it('should create a new custom field with minimal required fields', async () => {
const fieldName = `field_${Date.now()}`;
await request
.post(api('livechat/custom-fields.save'))
.set(credentials)
.send({
customFieldId: null,
customFieldData: {
field: fieldName,
label: 'Test Field',
scope: 'visitor',
visibility: 'public',
},
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('customField');
expect(res.body.customField).to.have.property('_id');
expect(res.body.customField).to.have.property('label', 'Test Field');
expect(res.body.customField).to.have.property('scope', 'visitor');
expect(res.body.customField).to.have.property('visibility', 'public');
customFieldId = res.body.customField._id;
});
});
it('should create a new custom field with scope "room"', async () => {
const fieldName = `room_field_${Date.now()}`;
await request
.post(api('livechat/custom-fields.save'))
.set(credentials)
.send({
customFieldId: null,
customFieldData: {
field: fieldName,
label: 'Room Test Field',
scope: 'room',
visibility: 'public',
},
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('customField');
expect(res.body.customField).to.have.property('scope', 'room');
customFieldId = res.body.customField._id;
});
});
it('should create a new custom field with all optional fields', async () => {
const fieldName = `full_field_${Date.now()}`;
await request
.post(api('livechat/custom-fields.save'))
.set(credentials)
.send({
customFieldId: null,
customFieldData: {
field: fieldName,
label: 'Full Test Field',
scope: 'visitor',
visibility: 'public',
type: 'input',
regexp: '^[A-Za-z]+$',
required: true,
defaultValue: 'default',
options: 'option1,option2,option3',
public: true,
searchable: true,
},
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('customField');
expect(res.body.customField).to.have.property('type', 'input');
expect(res.body.customField).to.have.property('regexp', '^[A-Za-z]+$');
expect(res.body.customField).to.have.property('required', true);
expect(res.body.customField).to.have.property('defaultValue', 'default');
expect(res.body.customField).to.have.property('options', 'option1,option2,option3');
expect(res.body.customField).to.have.property('public', true);
expect(res.body.customField).to.have.property('searchable', true);
customFieldId = res.body.customField._id;
});
});
it('should fail when trying to create a custom field with a field name that already exists', async () => {
const fieldName = `duplicate_field_${Date.now()}`;
// Create the first custom field
const { body } = await request
.post(api('livechat/custom-fields.save'))
.set(credentials)
.send({
customFieldId: null,
customFieldData: {
field: fieldName,
label: 'First Field',
scope: 'visitor',
visibility: 'public',
},
})
.expect(200);
customFieldId = body.customField._id;
// Try to create another with the same field name
await request
.post(api('livechat/custom-fields.save'))
.set(credentials)
.send({
customFieldId: null,
customFieldData: {
field: fieldName,
label: 'Second Field',
scope: 'visitor',
visibility: 'public',
},
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
});
});
});
describe('Update custom field', () => {
let existingField: ILivechatCustomField;
before(async () => {
const fieldName = `update_test_field_${Date.now()}`;
existingField = await createCustomField({
searchable: true,
field: fieldName,
label: 'Original Label',
defaultValue: 'original_default',
scope: 'visitor',
visibility: 'public',
regexp: '',
});
});
after(async () => {
if (existingField?._id) {
await deleteCustomField(existingField._id);
}
});
it('should fail when trying to update a non-existent custom field', async () => {
await request
.post(api('livechat/custom-fields.save'))
.set(credentials)
.send({
customFieldId: 'non-existent-id',
customFieldData: {
field: 'test_field',
label: 'Updated Label',
scope: 'visitor',
visibility: 'public',
},
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res: Response) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
});
});
it('should update an existing custom field with all optional fields', async () => {
await request
.post(api('livechat/custom-fields.save'))
.set(credentials)
.send({
customFieldId: existingField._id,
customFieldData: {
field: existingField._id,
label: 'Fully Updated Field',
scope: existingField.scope,
visibility: 'public',
type: 'select',
regexp: '^[0-9]+$',
required: true,
defaultValue: 'new_default',
options: 'opt1,opt2,opt3',
public: true,
searchable: false,
},
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body.customField).to.have.property('label', 'Fully Updated Field');
expect(res.body.customField).to.have.property('type', 'select');
expect(res.body.customField).to.have.property('regexp', '^[0-9]+$');
expect(res.body.customField).to.have.property('required', true);
expect(res.body.customField).to.have.property('defaultValue', 'new_default');
expect(res.body.customField).to.have.property('options', 'opt1,opt2,opt3');
expect(res.body.customField).to.have.property('public', true);
expect(res.body.customField).to.have.property('searchable', false);
});
});
});
});
});