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
1129 lines
48 KiB
TypeScript
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');
|
|
});
|
|
});
|
|
});
|