Files
upstream-rocketchat/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts
upstream-mirror 30810eecd7
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
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

1129 lines
48 KiB
TypeScript

import type { Credentials } from '@rocket.chat/api-client';
import type { ILivechatAgent, ILivechatBusinessHour, ILivechatDepartment } from '@rocket.chat/core-typings';
import { LivechatBusinessHourBehaviors, LivechatBusinessHourTypes, ILivechatAgentStatus } 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 {
getDefaultBusinessHour,
removeAllCustomBusinessHours,
saveBusinessHour,
openOrCloseBusinessHour,
createCustomBusinessHour,
getCustomBusinessHourById,
getWorkHours,
} from '../../../data/livechat/businessHours';
import {
addOrRemoveAgentFromDepartment,
archiveDepartment,
createDepartmentWithAnOnlineAgent,
createDepartmentWithAgent,
disableDepartment,
getDepartmentById,
deleteDepartment,
} from '../../../data/livechat/department';
import { createAgent, createManager, makeAgentAvailable } from '../../../data/livechat/rooms';
import { createAnOnlineAgent, removeAgent } from '../../../data/livechat/users';
import { removePermissionFromAllRoles, restorePermissionToRoles, updateSetting, updateEESetting } from '../../../data/permissions.helper';
import { password } from '../../../data/user';
import type { TestUser } from '../../../data/users.helper';
import { setUserActiveStatus, createUser, deleteUser, getMe, getUserByUsername, login } from '../../../data/users.helper';
import { IS_EE } from '../../../e2e/config/constants';
import { retry } from '../helpers/retry';
describe('LIVECHAT - business hours', () => {
before((done) => getCredentials(done));
before(async () => {
await updateSetting('Livechat_enabled', true);
await updateSetting('Livechat_enable_business_hours', true);
await updateSetting('Omnichannel_enable_department_removal', true);
await createAgent();
});
after(async () => {
await updateSetting('Omnichannel_enable_department_removal', false);
});
let defaultBhId: any;
describe('[CE] livechat/business-hour', () => {
after(async () => {
const { _updatedAt, ts, ...cleanedBusinessHour } = defaultBhId;
await saveBusinessHour({
...cleanedBusinessHour,
timezone: 'America/Sao_Paulo',
timezoneName: 'America/Sao_Paulo',
workHours: getWorkHours(true),
});
});
it('should fail when user doesnt have view-livechat-business-hours permission', async () => {
await removePermissionFromAllRoles('view-livechat-business-hours');
const response = await request.get(api('livechat/business-hour')).set(credentials).expect(403);
expect(response.body.success).to.be.false;
await restorePermissionToRoles('view-livechat-business-hours');
});
it('should fail when business hour type is not a valid BH type', async () => {
const response = await request.get(api('livechat/business-hour')).set(credentials).query({ type: 'invalid' }).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.businessHour).to.be.null;
});
it('should return a business hour of type default', async () => {
const response = await request.get(api('livechat/business-hour')).set(credentials).query({ type: 'default' }).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.businessHour).to.be.an('object');
expect(response.body.businessHour._id).to.be.a('string');
expect(response.body.businessHour.workHours).to.be.an('array');
expect(response.body.businessHour.workHours[0]).to.be.an('object');
expect(response.body.businessHour.workHours[0].day)
.to.be.an('string')
.that.is.oneOf(['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']);
expect(response.body.businessHour.workHours[0].start).to.be.an('object');
expect(response.body.businessHour.workHours[0].finish).to.be.an('object');
expect(response.body.businessHour.workHours[0].open).to.be.a('boolean');
expect(response.body.businessHour.timezone).to.be.an('object').that.has.property('name').that.is.an('string');
defaultBhId = response.body.businessHour;
});
it('should not allow a user to be available if BH are closed', async () => {
const { _updatedAt, ts, ...cleanedBusinessHour } = defaultBhId;
await saveBusinessHour({
...cleanedBusinessHour,
timezone: 'America/Sao_Paulo',
timezoneName: 'America/Sao_Paulo',
workHours: [
{
day: 'Monday',
open: true,
start: '00:00',
finish: '00:01',
},
],
});
const { body } = await makeAgentAvailable(credentials);
expect(body).to.have.property('success', false);
expect(body.error).to.be.equal('error-business-hours-are-closed');
});
it('should allow a user to be available if BH are open', async () => {
const { _updatedAt, ts, ...cleanedBusinessHour } = defaultBhId;
await saveBusinessHour({
...cleanedBusinessHour,
timezone: 'America/Sao_Paulo',
timezoneName: 'America/Sao_Paulo',
workHours: getWorkHours(true),
});
const { body } = await makeAgentAvailable(credentials);
expect(body).to.have.property('success', true);
});
it('should save a default business hour with proper timezone settings', async () => {
const { _updatedAt, ts, ...cleanedBusinessHour } = defaultBhId;
await saveBusinessHour({
...cleanedBusinessHour,
timezone: 'Asia/Kolkata',
workHours: getWorkHours(true),
timezoneName: 'Asia/Kolkata',
});
const { body } = await request
.get(api('livechat/business-hour'))
.set(credentials)
.query({ type: LivechatBusinessHourTypes.DEFAULT })
.expect(200);
expect(body.success).to.be.true;
expect(body.businessHour).to.be.an('object');
expect(body.businessHour.timezone).to.be.an('object').that.has.property('name').that.is.equal('Asia/Kolkata');
expect(body.businessHour.workHours).to.be.an('array').with.lengthOf(7);
const { workHours } = body.businessHour;
expect(workHours[0].day).to.be.equal('Sunday');
expect(workHours[0].start.utc.dayOfWeek).to.be.equal('Saturday');
expect(workHours[0].finish.utc.dayOfWeek).to.be.equal('Sunday');
});
it('should allow agents to be available', async () => {
const { body } = await makeAgentAvailable(credentials);
expect(body).to.have.property('success', true);
});
});
(IS_EE ? describe : describe.skip)('[EE] livechat/business-hour', () => {
it('should fail if user doesnt have view-livechat-business-hours permission', async () => {
await removePermissionFromAllRoles('view-livechat-business-hours');
const response = await request.get(api('livechat/business-hours')).set(credentials).expect(403);
expect(response.body.success).to.be.false;
await restorePermissionToRoles('view-livechat-business-hours');
});
it('should return a list of business hours', async () => {
const response = await request.get(api('livechat/business-hours')).set(credentials).expect(200);
expect(response.body.success).to.be.true;
expect(response.body.businessHours).to.be.an('array').with.lengthOf.greaterThan(0);
expect(response.body.businessHours[0]).to.be.an('object');
expect(response.body.businessHours[0]._id).to.be.a('string');
expect(response.body.businessHours[0].workHours).to.be.an('array');
expect(response.body.businessHours[0].workHours[0]).to.be.an('object');
expect(response.body.businessHours[0].workHours[0].day)
.to.be.an('string')
.that.is.oneOf(['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']);
expect(response.body.businessHours[0].workHours[0].start).to.be.an('object');
expect(response.body.businessHours[0].workHours[0].finish).to.be.an('object');
expect(response.body.businessHours[0].workHours[0].open).to.be.a('boolean');
expect(response.body.businessHours[0].timezone).to.be.an('object').that.has.property('name').that.is.an('string');
expect(response.body.businessHours[0].active).to.be.a('boolean');
});
it('should return a just created custom business hour', async () => {
const name = `business-hour-${Date.now()}`;
await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE);
await saveBusinessHour({
name,
active: true,
type: LivechatBusinessHourTypes.CUSTOM,
workHours: [{ day: 'Monday', start: '08:00', finish: '18:00', open: true }],
daysOpen: ['Monday'],
daysTime: [{ day: 'Monday', start: { time: '08:00' }, finish: { time: '18:00' }, open: true }],
departmentsToApplyBusinessHour: '',
timezone: 'America/Sao_Paulo',
timezoneName: 'America/Sao_Paulo',
});
const { body } = await request.get(api('livechat/business-hours')).set(credentials).query({ name }).expect(200);
expect(body.success).to.be.true;
expect(body.businessHours).to.be.an('array').with.lengthOf(1);
expect(body.businessHours[0]).to.be.an('object');
expect(body.businessHours[0]._id).to.be.a('string');
expect(body.businessHours[0]).to.have.property('name', name);
expect(body.businessHours[0]).to.have.property('active', true);
expect(body.businessHours[0]).to.have.property('type', LivechatBusinessHourTypes.CUSTOM);
expect(body.businessHours[0]).to.have.property('workHours').that.is.an('array').with.lengthOf(1);
expect(body.businessHours[0].workHours[0]).to.be.an('object').with.property('day', 'Monday');
expect(body.businessHours[0].workHours[0]).to.have.property('start').that.is.an('object');
expect(body.businessHours[0].workHours[0]).to.have.property('finish').that.is.an('object');
expect(body.businessHours[0]).to.have.property('timezone').that.is.an('object').with.property('name', 'America/Sao_Paulo');
});
it('should fail if start and finish time are the same', async () => {
const name = `business-hour-${Date.now()}`;
await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE);
const result = await saveBusinessHour({
name,
active: true,
type: LivechatBusinessHourTypes.CUSTOM,
workHours: [{ day: 'Monday', open: true, start: '08:00', finish: '08:00' }],
daysOpen: ['Monday'],
daysTime: [{ day: 'Monday', start: { time: '08:00' }, finish: { time: '08:00' }, open: true }],
timezone: 'America/Sao_Paulo',
departmentsToApplyBusinessHour: '',
timezoneName: 'America/Sao_Paulo',
});
expect(result).to.have.property('error');
});
it('should fail if finish is before start time', async () => {
const name = `business-hour-${Date.now()}`;
await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE);
const result = await saveBusinessHour({
name,
active: true,
type: LivechatBusinessHourTypes.CUSTOM,
workHours: [{ day: 'Monday', open: true, start: '10:00', finish: '08:00' }],
daysOpen: ['Monday'],
daysTime: [{ day: 'Monday', start: { time: '10:00' }, finish: { time: '08:00' }, open: true }],
timezone: 'America/Sao_Paulo',
departmentsToApplyBusinessHour: '',
timezoneName: 'America/Sao_Paulo',
});
expect(result).to.have.property('error');
});
it('should fail if data is invalid', async () => {
const name = `business-hour-${Date.now()}`;
await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE);
const result = await saveBusinessHour({
name,
active: true,
type: LivechatBusinessHourTypes.CUSTOM,
workHours: [{ day: 'Monday', open: true, start: '20000', finish: 'xxxxx' }],
daysOpen: ['Monday'],
daysTime: [{ day: 'Monday', start: { time: '20000' }, finish: { time: 'xxxxx' }, open: true }],
timezone: 'America/Sao_Paulo',
departmentsToApplyBusinessHour: '',
timezoneName: 'America/Sao_Paulo',
});
expect(result).to.have.property('error');
});
});
(IS_EE ? describe : describe.skip)('[EE][BH] On Business Hour created', () => {
let defaultBusinessHour: ILivechatBusinessHour;
before(async () => {
await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE);
// wait for the callbacks to be registered
await sleep(1000);
// cleanup any existing business hours
await removeAllCustomBusinessHours();
// get default business hour
defaultBusinessHour = await getDefaultBusinessHour();
// close default business hour
await openOrCloseBusinessHour(defaultBusinessHour, true);
});
it('should create a custom business hour which is closed by default', async () => {
// create custom business hour and link it to a department
const { department, agent } = await createDepartmentWithAnOnlineAgent();
await createCustomBusinessHour([department._id], false);
const latestAgent = await getMe<ILivechatAgent>(agent.credentials);
expect(latestAgent).to.be.an('object');
expect(latestAgent.openBusinessHours).to.be.an('array').of.length(0);
expect(latestAgent.statusLivechat).to.be.equal(ILivechatAgentStatus.NOT_AVAILABLE);
});
it('should create a custom business hour which is closed by default, but a bot agent shouldnt be affected', async () => {
const bot = await createUser({ roles: ['bot', 'livechat-agent'] });
const creds = await login(bot.username, password);
await makeAgentAvailable(creds);
const { department } = await createDepartmentWithAgent({ user: bot, credentials: creds });
await createCustomBusinessHour([department._id], false);
const latestAgent: ILivechatAgent = await getMe(creds);
expect(latestAgent).to.be.an('object');
expect(latestAgent.openBusinessHours).to.be.an('array').of.length(0);
expect(latestAgent.statusLivechat).to.be.equal(ILivechatAgentStatus.AVAILABLE);
});
});
// Scenario: A BH is linked to departments and gets re-saved with a smaller (or empty) department list
// Expected result: the removed departments lose the link (and their agents lose the BH),
// while the remaining departments stay linked
(IS_EE ? describe : describe.skip)('[EE][BH] On Business Hour departments removed via save', () => {
before(async () => {
await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE);
// wait for the callbacks to be registered
await sleep(1000);
});
const createdDepartments: Awaited<ReturnType<typeof createDepartmentWithAnOnlineAgent>>[] = [];
let keptDepartmentWithAgent: Awaited<ReturnType<typeof createDepartmentWithAnOnlineAgent>>;
let removedDepartmentWithAgent: Awaited<ReturnType<typeof createDepartmentWithAnOnlineAgent>>;
beforeEach(async () => {
await removeAllCustomBusinessHours();
await openOrCloseBusinessHour(await getDefaultBusinessHour(), false);
keptDepartmentWithAgent = await createDepartmentWithAnOnlineAgent();
removedDepartmentWithAgent = await createDepartmentWithAnOnlineAgent();
createdDepartments.push(keptDepartmentWithAgent, removedDepartmentWithAgent);
});
after(async () => {
await Promise.all(createdDepartments.map(({ department }) => deleteDepartment(department._id)));
await Promise.all(createdDepartments.map(({ agent }) => deleteUser(agent.user)));
});
const resaveWithDepartments = async (businessHour: ILivechatBusinessHour, departmentsToApplyBusinessHour: string) => {
const { _updatedAt, ts, departments, ...cleanedBusinessHour } = businessHour;
await saveBusinessHour({
...cleanedBusinessHour,
timezoneName: businessHour.timezone.name,
timezone: businessHour.timezone.name,
workHours: getWorkHours(true),
daysOpen: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
departmentsToApplyBusinessHour,
} as any);
};
it('should unlink only the removed departments when re-saving with a smaller department list', async () => {
const { department: keptDepartment, agent: keptAgent } = keptDepartmentWithAgent;
const { department: removedDepartment, agent: removedAgent } = removedDepartmentWithAgent;
const customBusinessHour = await createCustomBusinessHour([keptDepartment._id, removedDepartment._id]);
await openOrCloseBusinessHour(customBusinessHour, true);
await resaveWithDepartments(customBusinessHour, keptDepartment._id);
const latestKeptDepartment = await getDepartmentById(keptDepartment._id);
expect(latestKeptDepartment.businessHourId).to.be.equal(customBusinessHour._id);
const latestRemovedDepartment = await getDepartmentById(removedDepartment._id);
expect(latestRemovedDepartment.businessHourId).to.be.undefined;
const latestKeptAgent = await getMe<ILivechatAgent>(keptAgent.credentials);
expect(latestKeptAgent.openBusinessHours).to.be.an('array').that.includes(customBusinessHour._id);
const latestRemovedAgent = await getMe<ILivechatAgent>(removedAgent.credentials);
expect(latestRemovedAgent.openBusinessHours).to.be.an('array').that.does.not.include(customBusinessHour._id);
});
it('should unlink all departments when re-saving with an empty department list', async () => {
const { department } = keptDepartmentWithAgent;
const customBusinessHour = await createCustomBusinessHour([department._id]);
await openOrCloseBusinessHour(customBusinessHour, true);
await resaveWithDepartments(customBusinessHour, '');
const latestDepartment = await getDepartmentById(department._id);
expect(latestDepartment.businessHourId).to.be.undefined;
});
});
// Scenario: Assume we have a BH linked to a department, and we archive the department
// Expected result:
// 1) If BH is open and only linked to that department, it should be closed
// 2) If BH is open and linked to other departments, it should remain open
// 3) Agents within the archived department should be assigned to default BH
// 3.1) We'll also need to handle the case where if an agent is assigned to "dep1"
// and "dep2" and both these depts are connected to same BH, then in this case after
// archiving "dep1", we'd still need to BH within this user's cache since he's part of
// "dep2" which is linked to BH
(IS_EE ? describe : describe.skip)('[EE][BH] On Department archived', () => {
let defaultBusinessHour: ILivechatBusinessHour;
let customBusinessHour: ILivechatBusinessHour;
let deptLinkedToCustomBH: ILivechatDepartment;
let agentLinkedToDept: Awaited<ReturnType<typeof createDepartmentWithAnOnlineAgent>>['agent'];
before(async () => {
await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE);
// wait for the callbacks to be registered
await sleep(1000);
});
beforeEach(async () => {
// cleanup any existing business hours
await removeAllCustomBusinessHours();
// get default business hour
defaultBusinessHour = await getDefaultBusinessHour();
// close default business hour
await openOrCloseBusinessHour(defaultBusinessHour, false);
// create custom business hour and link it to a department
const { department, agent } = await createDepartmentWithAnOnlineAgent();
customBusinessHour = await createCustomBusinessHour([department._id]);
agentLinkedToDept = agent;
deptLinkedToCustomBH = department;
// open custom business hour
await openOrCloseBusinessHour(customBusinessHour, true);
});
it('upon archiving a department, if BH is open and only linked to that department, it should be closed', async () => {
// archive department
await archiveDepartment(deptLinkedToCustomBH._id);
// verify if department is archived and BH link is removed
const department = await getDepartmentById(deptLinkedToCustomBH._id);
expect(department).to.be.an('object');
expect(department).to.have.property('archived', true);
expect(department.businessHourId).to.be.undefined;
// verify if BH is closed
const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id);
expect(latestCustomBH).to.be.an('object');
expect(latestCustomBH).to.have.property('active', false);
expect(latestCustomBH.departments).to.be.an('array').that.is.empty;
});
it('upon archiving a department, if BH is open and linked to other departments, it should remain open', async () => {
// create another department and link it to the same BH
const { department, agent } = await createDepartmentWithAnOnlineAgent();
await removeAllCustomBusinessHours();
customBusinessHour = await createCustomBusinessHour([deptLinkedToCustomBH._id, department._id]);
// archive department
await archiveDepartment(deptLinkedToCustomBH._id);
// verify if department is archived and BH link is removed
const archivedDepartment = await getDepartmentById(deptLinkedToCustomBH._id);
expect(archivedDepartment).to.be.an('object');
expect(archivedDepartment).to.have.property('archived', true);
expect(archivedDepartment.businessHourId).to.be.undefined;
// verify if other department is not archived and BH link is not removed
const otherDepartment = await getDepartmentById(department._id);
expect(otherDepartment).to.be.an('object');
expect(otherDepartment.businessHourId).to.be.equal(customBusinessHour._id);
// verify if BH is still open
const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id);
expect(latestCustomBH).to.be.an('object');
expect(latestCustomBH).to.have.property('active', true);
expect(latestCustomBH.departments).to.be.an('array').of.length(1);
expect(latestCustomBH?.departments?.[0]._id).to.be.equal(department._id);
// cleanup
await deleteDepartment(department._id);
await deleteUser(agent.user);
});
it('upon archiving a department, agents within the archived department should be assigned to default BH', async () => {
await openOrCloseBusinessHour(defaultBusinessHour, true);
// archive department
await archiveDepartment(deptLinkedToCustomBH._id);
const latestAgent: ILivechatAgent = await getMe(agentLinkedToDept.credentials);
expect(latestAgent).to.be.an('object');
expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1);
expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(defaultBusinessHour._id);
});
it('upon archiving a department, overlapping agents should still have BH within their cache', async () => {
// create another department and link it to the same BH
const { department, agent } = await createDepartmentWithAnOnlineAgent();
await removeAllCustomBusinessHours();
customBusinessHour = await createCustomBusinessHour([deptLinkedToCustomBH._id, department._id]);
// create overlapping agent by adding previous agent to newly created department
await addOrRemoveAgentFromDepartment(
department._id,
{
agentId: agentLinkedToDept.user._id,
username: agentLinkedToDept.user.username || '',
},
true,
);
// archive department
await archiveDepartment(deptLinkedToCustomBH._id);
// verify if department is archived and BH link is removed
const archivedDepartment = await getDepartmentById(deptLinkedToCustomBH._id);
expect(archivedDepartment).to.be.an('object');
expect(archivedDepartment).to.have.property('archived', true);
expect(archivedDepartment.businessHourId).to.be.undefined;
// verify if BH is still open
const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id);
expect(latestCustomBH).to.be.an('object');
expect(latestCustomBH).to.have.property('active', true);
expect(latestCustomBH.departments).to.be.an('array').of.length(1);
expect(latestCustomBH?.departments?.[0]?._id).to.be.equal(department._id);
// verify if overlapping agent still has BH within his cache
const latestAgent: ILivechatAgent = await getMe(agentLinkedToDept.credentials);
expect(latestAgent).to.be.an('object');
expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1);
expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(customBusinessHour._id);
// verify if other agent still has BH within his cache
const otherAgent: ILivechatAgent = await getMe(agent.credentials);
expect(otherAgent).to.be.an('object');
expect(otherAgent.openBusinessHours).to.be.an('array').of.length(1);
expect(otherAgent?.openBusinessHours?.[0]).to.be.equal(customBusinessHour._id);
// cleanup
await deleteDepartment(department._id);
await deleteUser(agent.user);
});
afterEach(async () => {
await deleteDepartment(deptLinkedToCustomBH._id);
await deleteUser(agentLinkedToDept.user);
});
});
(IS_EE ? describe : describe.skip)('[EE][BH] On Department disabled', () => {
let defaultBusinessHour: ILivechatBusinessHour;
let customBusinessHour: ILivechatBusinessHour;
let deptLinkedToCustomBH: ILivechatDepartment;
let agentLinkedToDept: Awaited<ReturnType<typeof createDepartmentWithAnOnlineAgent>>['agent'];
before(async () => {
await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE);
// wait for the callbacks to be registered
await sleep(1000);
});
beforeEach(async () => {
// cleanup any existing business hours
await removeAllCustomBusinessHours();
// get default business hour
defaultBusinessHour = await getDefaultBusinessHour();
// close default business hour
await openOrCloseBusinessHour(defaultBusinessHour, false);
// create custom business hour and link it to a department
const { department, agent } = await createDepartmentWithAnOnlineAgent();
customBusinessHour = await createCustomBusinessHour([department._id]);
agentLinkedToDept = agent;
deptLinkedToCustomBH = department;
// open custom business hour
await openOrCloseBusinessHour(customBusinessHour, true);
});
it('upon disabling a department, if BH is open and only linked to that department, it should be closed', async () => {
// disable department
await disableDepartment(deptLinkedToCustomBH);
// verify if BH link is removed
const department = await getDepartmentById(deptLinkedToCustomBH._id);
expect(department).to.be.an('object');
expect(department.businessHourId).to.be.undefined;
// verify if BH is closed
const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id);
expect(latestCustomBH).to.be.an('object');
expect(latestCustomBH.active).to.be.false;
expect(latestCustomBH.departments).to.be.an('array').that.is.empty;
});
it('upon disabling a department, if BH is open and linked to other departments, it should remain open', async () => {
// create another department and link it to the same BH
const { department, agent } = await createDepartmentWithAnOnlineAgent();
await removeAllCustomBusinessHours();
customBusinessHour = await createCustomBusinessHour([deptLinkedToCustomBH._id, department._id]);
// disable department
await disableDepartment(deptLinkedToCustomBH);
// verify if BH link is removed
const disabledDepartment = await getDepartmentById(deptLinkedToCustomBH._id);
expect(disabledDepartment).to.be.an('object');
expect(disabledDepartment.businessHourId).to.be.undefined;
// verify if other department BH link is not removed
const otherDepartment = await getDepartmentById(department._id);
expect(otherDepartment).to.be.an('object');
expect(otherDepartment.businessHourId).to.be.equal(customBusinessHour._id);
// verify if BH is still open
const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id);
expect(latestCustomBH).to.be.an('object');
expect(latestCustomBH).to.have.property('active', true);
expect(latestCustomBH.departments).to.be.an('array').of.length(1);
expect(latestCustomBH?.departments?.[0]._id).to.be.equal(department._id);
// cleanup
await deleteDepartment(department._id);
await deleteUser(agent.user);
});
it('upon disabling a department, agents within the disabled department should be assigned to default BH', async () => {
await openOrCloseBusinessHour(defaultBusinessHour, true);
// disable department
await disableDepartment(deptLinkedToCustomBH);
const latestAgent: ILivechatAgent = await getMe(agentLinkedToDept.credentials);
expect(latestAgent).to.be.an('object');
expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1);
expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(defaultBusinessHour._id);
});
it('upon disabling a department, overlapping agents should still have BH within their cache', async () => {
// create another department and link it to the same BH
const { department, agent } = await createDepartmentWithAnOnlineAgent();
await removeAllCustomBusinessHours();
customBusinessHour = await createCustomBusinessHour([deptLinkedToCustomBH._id, department._id]);
// create overlapping agent by adding previous agent to newly created department
await addOrRemoveAgentFromDepartment(
department._id,
{
agentId: agentLinkedToDept.user._id,
username: agentLinkedToDept.user.username || '',
},
true,
);
// disable department
await disableDepartment(deptLinkedToCustomBH);
// verify if BH link is removed
const disabledDepartment = await getDepartmentById(deptLinkedToCustomBH._id);
expect(disabledDepartment).to.be.an('object');
expect(disabledDepartment.businessHourId).to.be.undefined;
// verify if BH is still open
const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id);
expect(latestCustomBH).to.be.an('object');
expect(latestCustomBH).to.have.property('active', true);
expect(latestCustomBH.departments).to.be.an('array').of.length(1);
expect(latestCustomBH?.departments?.[0]?._id).to.be.equal(department._id);
// verify if overlapping agent still has BH within his cache
const latestAgent: ILivechatAgent = await getMe(agentLinkedToDept.credentials);
expect(latestAgent).to.be.an('object');
expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1);
expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(customBusinessHour._id);
// verify if other agent still has BH within his cache
const otherAgent: ILivechatAgent = await getMe(agent.credentials);
expect(otherAgent).to.be.an('object');
expect(otherAgent.openBusinessHours).to.be.an('array').of.length(1);
expect(otherAgent?.openBusinessHours?.[0]).to.be.equal(customBusinessHour._id);
// cleanup
await deleteDepartment(department._id);
await deleteUser(agent.user);
});
afterEach(async () => {
await deleteDepartment(deptLinkedToCustomBH._id);
await deleteUser(agentLinkedToDept.user);
});
});
(IS_EE ? describe : describe.skip)('[EE][BH] On Department removed', () => {
let defaultBusinessHour: ILivechatBusinessHour;
let customBusinessHour: ILivechatBusinessHour;
let deptLinkedToCustomBH: ILivechatDepartment;
let agentLinkedToDept: Awaited<ReturnType<typeof createDepartmentWithAnOnlineAgent>>['agent'];
before(async () => {
await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE);
// wait for the callbacks to be registered
await sleep(1000);
});
beforeEach(async () => {
// cleanup any existing business hours
await removeAllCustomBusinessHours();
// get default business hour
defaultBusinessHour = await getDefaultBusinessHour();
// close default business hour
await openOrCloseBusinessHour(defaultBusinessHour, false);
// create custom business hour and link it to a department
const { department, agent } = await createDepartmentWithAnOnlineAgent();
customBusinessHour = await createCustomBusinessHour([department._id]);
agentLinkedToDept = agent;
deptLinkedToCustomBH = department;
// open custom business hour
await openOrCloseBusinessHour(customBusinessHour, true);
});
it('upon deleting a department, if BH is open and only linked to that department, it should be closed', async () => {
await deleteDepartment(deptLinkedToCustomBH._id);
// verify if BH is closed
const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id);
expect(latestCustomBH).to.be.an('object');
expect(latestCustomBH.active).to.be.false;
expect(latestCustomBH.departments).to.be.an('array').that.is.empty;
});
it('upon deleting a department, if BH is open and linked to other departments, it should remain open', async () => {
// create another department and link it to the same BH
const { department, agent } = await createDepartmentWithAnOnlineAgent();
await removeAllCustomBusinessHours();
customBusinessHour = await createCustomBusinessHour([deptLinkedToCustomBH._id, department._id]);
await deleteDepartment(deptLinkedToCustomBH._id);
// verify if other department BH link is not removed
const otherDepartment = await getDepartmentById(department._id);
expect(otherDepartment).to.be.an('object');
expect(otherDepartment.businessHourId).to.be.equal(customBusinessHour._id);
// verify if BH is still open
const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id);
expect(latestCustomBH).to.be.an('object');
expect(latestCustomBH).to.have.property('active', true);
expect(latestCustomBH.departments).to.be.an('array').of.length(1);
expect(latestCustomBH?.departments?.[0]._id).to.be.equal(department._id);
// cleanup
await deleteDepartment(department._id);
await deleteUser(agent.user);
});
it('upon deleting a department, agents within the disabled department should be assigned to default BH', async () => {
await openOrCloseBusinessHour(defaultBusinessHour, true);
await deleteDepartment(deptLinkedToCustomBH._id);
const latestAgent: ILivechatAgent = await getMe(agentLinkedToDept.credentials);
expect(latestAgent).to.be.an('object');
expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1);
expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(defaultBusinessHour._id);
});
it('upon deleting a department, overlapping agents should still have BH within their cache', async () => {
// create another department and link it to the same BH
const { department, agent } = await createDepartmentWithAnOnlineAgent();
await removeAllCustomBusinessHours();
customBusinessHour = await createCustomBusinessHour([deptLinkedToCustomBH._id, department._id]);
// create overlapping agent by adding previous agent to newly created department
await addOrRemoveAgentFromDepartment(
department._id,
{
agentId: agentLinkedToDept.user._id,
username: agentLinkedToDept.user.username || '',
},
true,
);
await deleteDepartment(deptLinkedToCustomBH._id);
// verify if BH is still open
const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id);
expect(latestCustomBH).to.be.an('object');
expect(latestCustomBH).to.have.property('active', true);
expect(latestCustomBH.departments).to.be.an('array').of.length(1);
expect(latestCustomBH?.departments?.[0]?._id).to.be.equal(department._id);
// verify if overlapping agent still has BH within his cache
const latestAgent: ILivechatAgent = await getMe(agentLinkedToDept.credentials);
expect(latestAgent).to.be.an('object');
expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1);
expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(customBusinessHour._id);
// verify if other agent still has BH within his cache
const otherAgent: ILivechatAgent = await getMe(agent.credentials);
expect(otherAgent).to.be.an('object');
expect(otherAgent.openBusinessHours).to.be.an('array').of.length(1);
expect(otherAgent?.openBusinessHours?.[0]).to.be.equal(customBusinessHour._id);
// cleanup
await deleteDepartment(department._id);
await deleteUser(agent.user);
});
afterEach(async () => {
await deleteUser(agentLinkedToDept.user);
});
});
(IS_EE ? describe : describe.skip)('[EE][BH] On agent removed from department', () => {
let defaultBusinessHour: ILivechatBusinessHour;
let customBusinessHour: ILivechatBusinessHour;
let deptLinkedToCustomBH: ILivechatDepartment;
let agentLinkedToDept: Awaited<ReturnType<typeof createDepartmentWithAnOnlineAgent>>['agent'];
// the business hour recomputation runs on a fire-and-forget callback, so poll until it lands
const waitForAgentBusinessHours = async (agentCredentials: Credentials, expected: string[]): Promise<string[] | undefined> => {
const matches = (agent: ILivechatAgent) =>
JSON.stringify([...(agent.openBusinessHours || [])].sort()) === JSON.stringify([...expected].sort());
let latestAgent: ILivechatAgent = await getMe(agentCredentials);
for (let attempts = 0; attempts < 10 && !matches(latestAgent); attempts++) {
await sleep(500);
latestAgent = await getMe(agentCredentials);
}
return latestAgent.openBusinessHours;
};
before(async () => {
await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE);
// wait for the callbacks to be registered
await sleep(1000);
});
beforeEach(async () => {
await removeAllCustomBusinessHours();
defaultBusinessHour = await getDefaultBusinessHour();
await openOrCloseBusinessHour(defaultBusinessHour, true);
const { department, agent } = await createDepartmentWithAnOnlineAgent();
deptLinkedToCustomBH = department;
agentLinkedToDept = agent;
customBusinessHour = await createCustomBusinessHour([department._id]);
await openOrCloseBusinessHour(customBusinessHour, true);
});
it('upon removing an agent from its only department, the agent should fall back to the default business hour', async () => {
const openBusinessHoursBefore = await waitForAgentBusinessHours(agentLinkedToDept.credentials, [customBusinessHour._id]);
expect(openBusinessHoursBefore).to.be.an('array').of.length(1);
expect(openBusinessHoursBefore?.[0]).to.be.equal(customBusinessHour._id);
await addOrRemoveAgentFromDepartment(
deptLinkedToCustomBH._id,
{
agentId: agentLinkedToDept.user._id,
username: agentLinkedToDept.user.username || '',
},
false,
);
const openBusinessHours = await waitForAgentBusinessHours(agentLinkedToDept.credentials, [defaultBusinessHour._id]);
expect(openBusinessHours).to.be.an('array').of.length(1);
expect(openBusinessHours?.[0]).to.be.equal(defaultBusinessHour._id);
});
it('upon removing an agent from one department, the agent should keep the business hour shared with its other department', async () => {
// link a second department (with its own agent) to the same business hour and make the first agent overlap
const { department, agent } = await createDepartmentWithAnOnlineAgent();
await removeAllCustomBusinessHours();
customBusinessHour = await createCustomBusinessHour([deptLinkedToCustomBH._id, department._id]);
await openOrCloseBusinessHour(customBusinessHour, true);
await addOrRemoveAgentFromDepartment(
department._id,
{
agentId: agentLinkedToDept.user._id,
username: agentLinkedToDept.user.username || '',
},
true,
);
// control agent: belongs only to the first department and is removed in the same request as the
// overlapping agent, so both are processed by the same recomputation callback. Observing the control
// agent fall back to the default business hour proves that callback ran — only then is asserting the
// overlapping agent's unchanged state meaningful (it equals the pre-removal state, so it can't
// distinguish "correctly kept" from "callback never ran" on its own)
const controlAgent = await createAnOnlineAgent();
await addOrRemoveAgentFromDepartment(
deptLinkedToCustomBH._id,
{
agentId: controlAgent.user._id,
username: controlAgent.user.username || '',
},
true,
);
const overlappingBefore = await waitForAgentBusinessHours(agentLinkedToDept.credentials, [customBusinessHour._id]);
expect(overlappingBefore).to.be.an('array').of.length(1);
const controlBefore = await waitForAgentBusinessHours(controlAgent.credentials, [customBusinessHour._id]);
expect(controlBefore).to.be.an('array').of.length(1);
await request
.post(api(`livechat/department/${deptLinkedToCustomBH._id}/agents`))
.set(credentials)
.send({
upsert: [],
remove: [
{ agentId: agentLinkedToDept.user._id, username: agentLinkedToDept.user.username || '' },
{ agentId: controlAgent.user._id, username: controlAgent.user.username || '' },
],
})
.expect(200);
const controlOpenBusinessHours = await waitForAgentBusinessHours(controlAgent.credentials, [defaultBusinessHour._id]);
expect(controlOpenBusinessHours).to.be.an('array').of.length(1);
expect(controlOpenBusinessHours?.[0]).to.be.equal(defaultBusinessHour._id);
const latestAgent: ILivechatAgent = await getMe(agentLinkedToDept.credentials);
expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1);
expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(customBusinessHour._id);
await deleteDepartment(department._id);
await deleteUser(agent.user);
await deleteUser(controlAgent.user);
});
afterEach(async () => {
await deleteDepartment(deptLinkedToCustomBH._id);
await deleteUser(agentLinkedToDept.user);
});
after(async () => {
await removeAllCustomBusinessHours();
});
});
describe('[CE][BH] On Agent created/removed', () => {
let defaultBH: ILivechatBusinessHour;
let agent: ILivechatAgent;
let agentCredentials: Credentials;
before(async () => {
await updateSetting('Livechat_enable_business_hours', true);
await updateEESetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.SINGLE);
// wait for callbacks to run
await sleep(2000);
defaultBH = await getDefaultBusinessHour();
await openOrCloseBusinessHour(defaultBH, true);
agent = await createUser();
agentCredentials = await login(agent.username, password);
});
after(async () => {
await deleteUser(agent);
});
it('should create a new agent and verify if it is assigned to the default business hour which is open', async () => {
agent = await createAgent(agent.username);
// the BH assignment runs on a fire-and-forget callback, so the create-agent request returns before it lands
await retry(
'agent BH assignment is async, so openBusinessHours may still be unset on the first fetch',
async () => {
const latestAgent: ILivechatAgent = await getMe(agentCredentials);
expect(latestAgent).to.be.an('object');
expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1);
expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(defaultBH._id);
},
{ retries: 20, delayMs: 250 },
);
});
it('should create a new agent and verify if it is assigned to the default business hour which is closed', async () => {
await openOrCloseBusinessHour(defaultBH, false);
// the BH close recalculates agent statuses asynchronously; wait for it to propagate (existing agent loses the
// BH assignment) before creating the new agent, otherwise it is created while the BH still counts as open
await retry(
'BH close propagation is async, so the existing agent may still be assigned on the first fetch',
async () => {
const current: ILivechatAgent = await getMe(agentCredentials);
expect(current.openBusinessHours ?? []).to.have.lengthOf(0);
},
{ retries: 20, delayMs: 250 },
);
const newUser: ILivechatAgent = await createUser();
const newUserCredentials = await login(newUser.username, password);
await createAgent(newUser.username);
// createAgent sets the agent as available first and the async callback moves it to not-available, so the
// first fetch can still show the intermediate available state
await retry(
'agent BH assignment is async, so statusLivechat may still be available on the first fetch',
async () => {
const latestAgent: ILivechatAgent = await getMe(newUserCredentials);
expect(latestAgent).to.be.an('object');
expect(latestAgent.openBusinessHours).to.be.undefined;
expect(latestAgent.statusLivechat).to.be.equal(ILivechatAgentStatus.NOT_AVAILABLE);
},
{ retries: 20, delayMs: 250 },
);
// cleanup
await deleteUser(newUser);
});
it('should verify if agent is assigned to BH when it is opened', async () => {
// first verify if agent is not assigned to any BH
let latestAgent: ILivechatAgent = await getMe(agentCredentials);
expect(latestAgent).to.be.an('object');
expect(latestAgent.openBusinessHours).to.be.an('array').of.length(0);
expect(latestAgent.statusLivechat).to.be.equal(ILivechatAgentStatus.NOT_AVAILABLE);
// now open BH
await openOrCloseBusinessHour(defaultBH, true);
// verify if agent is assigned to BH
latestAgent = await getMe(agentCredentials);
expect(latestAgent).to.be.an('object');
expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1);
expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(defaultBH._id);
// verify if agent is able to make themselves available
await makeAgentAvailable(agentCredentials as any);
});
it('should verify if BH related props are cleared when an agent is deleted', async () => {
await removeAgent(agent._id);
const latestAgent: ILivechatAgent = await getMe(agentCredentials);
expect(latestAgent).to.be.an('object');
expect(latestAgent.openBusinessHours).to.be.undefined;
expect(latestAgent.statusLivechat).to.be.undefined;
});
describe('Special Case - Agent created, BH already enabled', () => {
let newAgent: ILivechatAgent;
let newAgentCredentials: Credentials;
before(async () => {
newAgent = await createUser({ roles: ['user', 'livechat-agent'] });
newAgentCredentials = await login(newAgent.username, password);
});
after(async () => {
await deleteUser(newAgent);
});
it('should verify a newly created agent to be assigned to the default business hour', async () => {
const latestAgent: ILivechatAgent = await getMe(newAgentCredentials);
expect(latestAgent).to.be.an('object');
expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1);
expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(defaultBH._id);
});
});
after(async () => {
await deleteUser(agent);
});
});
describe('[CE][BH] On Agent deactivated/activated', () => {
let defaultBH: ILivechatBusinessHour;
let agent: TestUser<ILivechatAgent>;
before(async () => {
await updateSetting('Livechat_enable_business_hours', true);
await updateEESetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.SINGLE);
// wait for callbacks to run
await sleep(2000);
defaultBH = await getDefaultBusinessHour();
await openOrCloseBusinessHour(defaultBH, true);
agent = await createUser();
await createAgent(agent.username);
});
after(async () => {
await deleteUser(agent);
await updateSetting('Livechat_enable_business_hours', false);
});
it('should verify if agent becomes unavailable to take chats when user is deactivated', async () => {
await makeAgentAvailable(await login(agent.username, password));
await setUserActiveStatus(agent._id, false);
const latestAgent = await getUserByUsername<ILivechatAgent>(agent.username);
expect(latestAgent).to.be.an('object');
expect(latestAgent.statusLivechat).to.be.equal(ILivechatAgentStatus.NOT_AVAILABLE);
});
it('should verify if agent becomes available to take chats when user is activated, if business hour is active', async () => {
await openOrCloseBusinessHour(defaultBH, true);
await setUserActiveStatus(agent._id, true);
const latestAgent = await getUserByUsername<ILivechatAgent>(agent.username);
expect(latestAgent).to.be.an('object');
expect(latestAgent.statusLivechat).to.be.equal(ILivechatAgentStatus.AVAILABLE);
});
it('should verify if agent becomes unavailable to take chats when user is activated, if business hour is inactive', async () => {
await openOrCloseBusinessHour(defaultBH, false);
await setUserActiveStatus(agent._id, false);
await setUserActiveStatus(agent._id, true);
const latestAgent = await getUserByUsername<ILivechatAgent>(agent.username);
expect(latestAgent).to.be.an('object');
expect(latestAgent.statusLivechat).to.be.equal(ILivechatAgentStatus.NOT_AVAILABLE);
});
it('should verify if managers are not able to make deactivated agents available', async () => {
await createManager();
await setUserActiveStatus(agent._id, false);
const response = await request
.post(api('livechat/agent.status'))
.set(credentials)
.send({
status: 'available',
agentId: agent._id,
})
.expect(400);
expect(response.body).to.have.property('success', false);
expect(response.body).to.have.property('error', 'error-user-deactivated');
});
});
});