snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
209
apps/meteor/tests/data/livechat/businessHours.ts
Normal file
209
apps/meteor/tests/data/livechat/businessHours.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
import type { ILivechatBusinessHour } from '@rocket.chat/core-typings';
|
||||
import { LivechatBusinessHourTypes } from '@rocket.chat/core-typings';
|
||||
import type { POSTLivechatBusinessHoursSaveParams } from '@rocket.chat/rest-typings';
|
||||
import moment from 'moment';
|
||||
|
||||
import { api, credentials, request } from '../api-data';
|
||||
import { updateEESetting, updateSetting } from '../permissions.helper';
|
||||
|
||||
type ISaveBhApiWorkHour = Omit<ILivechatBusinessHour, '_id' | 'ts' | 'timezone'> & {
|
||||
workHours: { day: string; start: string; finish: string; open: boolean }[];
|
||||
} & { departmentsToApplyBusinessHour?: string } & { timezoneName: string };
|
||||
|
||||
export const saveBusinessHour = async (businessHour: POSTLivechatBusinessHoursSaveParams) => {
|
||||
const { body } = await request.post(api('livechat/business-hours.save')).set(credentials).send(businessHour);
|
||||
|
||||
return body;
|
||||
};
|
||||
|
||||
export const createCustomBusinessHour = async (departments: string[], open = true): Promise<ILivechatBusinessHour> => {
|
||||
const name = `business-hour-${Date.now()}`;
|
||||
const businessHour: POSTLivechatBusinessHoursSaveParams = {
|
||||
name,
|
||||
active: true,
|
||||
type: LivechatBusinessHourTypes.CUSTOM,
|
||||
workHours: getWorkHours(open),
|
||||
timezoneName: 'Asia/Calcutta',
|
||||
timezone: 'Asia/Calcutta',
|
||||
departmentsToApplyBusinessHour: '',
|
||||
daysOpen: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
|
||||
daysTime: [
|
||||
{ day: 'Monday', start: { time: '08:00' }, finish: { time: '18:00' }, open },
|
||||
{ day: 'Tuesday', start: { time: '08:00' }, finish: { time: '18:00' }, open },
|
||||
{ day: 'Wednesday', start: { time: '08:00' }, finish: { time: '18:00' }, open },
|
||||
{ day: 'Thursday', start: { time: '08:00' }, finish: { time: '18:00' }, open },
|
||||
{ day: 'Friday', start: { time: '08:00' }, finish: { time: '18:00' }, open },
|
||||
],
|
||||
};
|
||||
|
||||
if (departments.length) {
|
||||
businessHour.departmentsToApplyBusinessHour = departments.join(',');
|
||||
}
|
||||
|
||||
await saveBusinessHour(businessHour);
|
||||
|
||||
const existingBusinessHours: ILivechatBusinessHour[] = await getAllCustomBusinessHours();
|
||||
const createdBusinessHour = existingBusinessHours.find((bh) => bh.name === name);
|
||||
if (!createdBusinessHour) {
|
||||
throw new Error('Could not create business hour');
|
||||
}
|
||||
|
||||
return createdBusinessHour;
|
||||
};
|
||||
|
||||
export const makeDefaultBusinessHourActiveAndClosed = async () => {
|
||||
// enable settings
|
||||
await updateSetting('Livechat_enable_business_hours', true);
|
||||
await updateEESetting('Livechat_business_hour_type', 'Single');
|
||||
|
||||
// create business hours
|
||||
const {
|
||||
body: { businessHour },
|
||||
} = await request.get(api('livechat/business-hour')).query({ type: 'default' }).set(credentials).send();
|
||||
|
||||
const { workHours } = businessHour;
|
||||
|
||||
// Remove properties not accepted by the endpoint schema
|
||||
const { _updatedAt, ts, ...cleanedBusinessHour } = businessHour;
|
||||
|
||||
const enabledBusinessHour = {
|
||||
...cleanedBusinessHour,
|
||||
timezoneName: 'America/Sao_Paulo',
|
||||
timezone: 'America/Sao_Paulo',
|
||||
departmentsToApplyBusinessHour: '',
|
||||
daysOpen: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
|
||||
daysTime: workHours.map((workHour: { open: boolean; start: { time: string }; finish: { time: string }; day: string }) => {
|
||||
return {
|
||||
open: true,
|
||||
start: { time: '00:00' },
|
||||
finish: { time: '00:01' },
|
||||
day: workHour.day,
|
||||
};
|
||||
}),
|
||||
workHours: workHours.map((workHour: { open: boolean; start: string; finish: string; day: string; code?: number }) => {
|
||||
workHour.open = true;
|
||||
workHour.start = '00:00';
|
||||
workHour.finish = '00:01'; // if a job runs between 00:00 and 00:01, then this test will fail :P
|
||||
delete workHour.code;
|
||||
return workHour;
|
||||
}),
|
||||
};
|
||||
|
||||
return request.post(api('livechat/business-hours.save')).set(credentials).send(enabledBusinessHour).expect(200);
|
||||
};
|
||||
|
||||
export const disableDefaultBusinessHour = async () => {
|
||||
// disable settings
|
||||
await updateSetting('Livechat_enable_business_hours', false);
|
||||
await updateEESetting('Livechat_business_hour_type', 'Single');
|
||||
|
||||
// create business hours
|
||||
const {
|
||||
body: { businessHour },
|
||||
} = await request.get(api('livechat/business-hour')).query({ type: 'default' }).set(credentials).send();
|
||||
|
||||
const { workHours } = businessHour;
|
||||
|
||||
// Remove properties not accepted by the endpoint schema
|
||||
const { _updatedAt, ts, ...cleanedBusinessHour } = businessHour;
|
||||
const disabledBusinessHour = {
|
||||
...cleanedBusinessHour,
|
||||
timezoneName: 'America/Sao_Paulo',
|
||||
timezone: 'America/Sao_Paulo',
|
||||
departmentsToApplyBusinessHour: '',
|
||||
daysOpen: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
|
||||
daysTime: workHours.map((workHour: { open: boolean; start: { time: string }; finish: { time: string }; day: string }) => {
|
||||
return {
|
||||
open: false,
|
||||
start: { time: '00:00' },
|
||||
finish: { time: '23:59' },
|
||||
day: workHour.day,
|
||||
};
|
||||
}),
|
||||
workHours: workHours.map((workHour: { open: boolean; start: string; finish: string; day: string }) => {
|
||||
workHour.open = false;
|
||||
workHour.start = '00:00';
|
||||
workHour.finish = '23:59';
|
||||
return workHour;
|
||||
}),
|
||||
};
|
||||
|
||||
return request.post(api('livechat/business-hours.save')).set(credentials).send(disabledBusinessHour).expect(200);
|
||||
};
|
||||
|
||||
const removeCustomBusinessHour = async (businessHourId: string) => {
|
||||
return request
|
||||
.post(api('livechat/business-hours.remove'))
|
||||
.set(credentials)
|
||||
.send({ _id: businessHourId, type: LivechatBusinessHourTypes.CUSTOM })
|
||||
.expect(200);
|
||||
};
|
||||
|
||||
const getAllCustomBusinessHours = async (): Promise<ILivechatBusinessHour[]> => {
|
||||
const response = await request.get(api('livechat/business-hours')).set(credentials).expect(200);
|
||||
return (response.body.businessHours || []).filter(
|
||||
(businessHour: ILivechatBusinessHour) => businessHour.type === LivechatBusinessHourTypes.CUSTOM,
|
||||
);
|
||||
};
|
||||
|
||||
export const removeAllCustomBusinessHours = async () => {
|
||||
const existingBusinessHours: ILivechatBusinessHour[] = await getAllCustomBusinessHours();
|
||||
|
||||
const promises = existingBusinessHours.map((businessHour) => removeCustomBusinessHour(businessHour._id));
|
||||
await Promise.all(promises);
|
||||
};
|
||||
|
||||
export const getDefaultBusinessHour = async (): Promise<ILivechatBusinessHour> => {
|
||||
const response = await request
|
||||
.get(api('livechat/business-hour'))
|
||||
.set(credentials)
|
||||
.query({ type: LivechatBusinessHourTypes.DEFAULT })
|
||||
.expect(200);
|
||||
return response.body.businessHour;
|
||||
};
|
||||
|
||||
export const getCustomBusinessHourById = async (businessHourId: string): Promise<ILivechatBusinessHour> => {
|
||||
const response = await request
|
||||
.get(api('livechat/business-hour'))
|
||||
.set(credentials)
|
||||
.query({ type: LivechatBusinessHourTypes.CUSTOM, _id: businessHourId })
|
||||
.expect(200);
|
||||
return response.body.businessHour;
|
||||
};
|
||||
|
||||
// TODO: Refactor logic so object passed is of the correct type for POST /livechat/business-hours.save. See: CORE-1552
|
||||
export const openOrCloseBusinessHour = async (businessHour: ILivechatBusinessHour, open: boolean) => {
|
||||
const { _updatedAt, ts, ...cleanedBusinessHour } = businessHour;
|
||||
const timezoneName = businessHour.timezone.name;
|
||||
|
||||
const enabledBusinessHour = {
|
||||
...cleanedBusinessHour,
|
||||
timezoneName,
|
||||
timezone: timezoneName,
|
||||
workHours: getWorkHours().map((workHour) => {
|
||||
return {
|
||||
...workHour,
|
||||
open,
|
||||
};
|
||||
}),
|
||||
departmentsToApplyBusinessHour: businessHour.departments?.map((department) => department._id).join(',') || '',
|
||||
};
|
||||
|
||||
await saveBusinessHour(enabledBusinessHour as any);
|
||||
};
|
||||
|
||||
export const getWorkHours = (open = true): ISaveBhApiWorkHour['workHours'] => {
|
||||
const workHours: ISaveBhApiWorkHour['workHours'] = [];
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
workHours.push({
|
||||
day: moment().day(i).format('dddd'),
|
||||
start: '00:00',
|
||||
finish: '23:59',
|
||||
|
||||
open,
|
||||
});
|
||||
}
|
||||
|
||||
return workHours;
|
||||
};
|
||||
25
apps/meteor/tests/data/livechat/canned-responses.ts
Normal file
25
apps/meteor/tests/data/livechat/canned-responses.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type { IOmnichannelCannedResponse } from '@rocket.chat/core-typings';
|
||||
|
||||
import { api, credentials, request } from '../api-data';
|
||||
import type { DummyResponse } from './utils';
|
||||
|
||||
export const createCannedResponse = (): Promise<Partial<IOmnichannelCannedResponse>> =>
|
||||
new Promise((resolve, reject) => {
|
||||
const response = {
|
||||
shortcut: `${faker.lorem.word()}-${Date.now()}`,
|
||||
scope: 'user',
|
||||
tags: [faker.string.uuid()],
|
||||
text: faker.lorem.sentence(),
|
||||
};
|
||||
return request
|
||||
.post(api(`canned-responses`))
|
||||
.set(credentials)
|
||||
.send(response)
|
||||
.end((_err: Error, _res: DummyResponse<boolean>) => {
|
||||
if (_err) {
|
||||
return reject(_err);
|
||||
}
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
49
apps/meteor/tests/data/livechat/custom-fields.ts
Normal file
49
apps/meteor/tests/data/livechat/custom-fields.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { ILivechatCustomField } from '@rocket.chat/core-typings';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { credentials, request, api } from '../api-data';
|
||||
|
||||
type ExtendedCustomField = Omit<ILivechatCustomField, '_id' | '_updatedAt'> & { field: string };
|
||||
|
||||
export const createCustomField = (customField: ExtendedCustomField): Promise<ILivechatCustomField> =>
|
||||
new Promise((resolve, reject) => {
|
||||
void request
|
||||
.get(api(`livechat/custom-fields/${customField.label}`))
|
||||
.set(credentials)
|
||||
.send()
|
||||
.end((err: Error, res: Response) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
if (res.body.customField !== null && res.body.customField !== undefined) {
|
||||
resolve(res.body.customField);
|
||||
} else {
|
||||
void request
|
||||
.post(api('livechat/custom-fields.save'))
|
||||
.set(credentials)
|
||||
.send({ customFieldId: null, customFieldData: customField })
|
||||
.end((err: Error, res: Response): void => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(res.body.customField);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export const deleteCustomField = (customFieldID: string) =>
|
||||
new Promise((resolve, reject) => {
|
||||
void request
|
||||
.post(api('livechat/custom-fields.delete'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
customFieldId: customFieldID,
|
||||
})
|
||||
.end((err: Error, res: Response): void => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(res.body);
|
||||
});
|
||||
});
|
||||
225
apps/meteor/tests/data/livechat/department.ts
Normal file
225
apps/meteor/tests/data/livechat/department.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { ILivechatDepartment, IUser, LivechatDepartmentDTO } from '@rocket.chat/core-typings';
|
||||
import { Random } from '@rocket.chat/random';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { api, credentials, request } from '../api-data';
|
||||
import { makeAgentAvailable } from './rooms';
|
||||
import { createAgentAndReLogin, createAnAwayAgent, createAnOfflineAgent } from './users';
|
||||
import type { WithRequiredProperty } from './utils';
|
||||
|
||||
const NewDepartmentData = ((): Partial<ILivechatDepartment> => ({
|
||||
enabled: true,
|
||||
name: `new department ${Random.id()}`,
|
||||
description: 'created from api',
|
||||
showOnRegistration: true,
|
||||
email: faker.internet.email(),
|
||||
showOnOfflineForm: true,
|
||||
}))();
|
||||
|
||||
export const createDepartment = async (
|
||||
departmentData?: Partial<ILivechatDepartment>,
|
||||
agents?: { agentId: string; count?: string; order?: string }[],
|
||||
): Promise<ILivechatDepartment> => {
|
||||
const department = { ...NewDepartmentData, ...departmentData };
|
||||
const response = await request
|
||||
.post(api('livechat/department'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
department,
|
||||
...(agents && { agents }),
|
||||
})
|
||||
.expect(200);
|
||||
return response.body.department;
|
||||
};
|
||||
|
||||
const updateDepartment = async (departmentId: string, departmentData: Partial<LivechatDepartmentDTO>): Promise<ILivechatDepartment> => {
|
||||
const response = await request
|
||||
.put(api(`livechat/department/${departmentId}`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
department: departmentData,
|
||||
})
|
||||
.expect(200);
|
||||
return response.body.department;
|
||||
};
|
||||
|
||||
export type OnlineAgent = {
|
||||
user: WithRequiredProperty<IUser, 'username'>;
|
||||
credentials: Credentials;
|
||||
};
|
||||
|
||||
export const createDepartmentWithAnOnlineAgent = async (): Promise<{ department: ILivechatDepartment; agent: OnlineAgent }> => {
|
||||
const { user, credentials: agentCredentials } = await createAgentAndReLogin();
|
||||
|
||||
const department = await createDepartment();
|
||||
|
||||
await addOrRemoveAgentFromDepartment(department._id, { agentId: user._id, username: user.username }, true);
|
||||
|
||||
await makeAgentAvailable(agentCredentials);
|
||||
|
||||
return {
|
||||
department,
|
||||
agent: {
|
||||
credentials: agentCredentials,
|
||||
user,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const createDepartmentWith2OnlineAgents = async (): Promise<{
|
||||
department: ILivechatDepartment;
|
||||
agent1: OnlineAgent;
|
||||
agent2: OnlineAgent;
|
||||
}> => {
|
||||
const [agent1, agent2] = await Promise.all([createAgentAndReLogin(), createAgentAndReLogin()]);
|
||||
|
||||
const department = await createDepartment(
|
||||
{
|
||||
enabled: true,
|
||||
name: Random.id(),
|
||||
showOnRegistration: true,
|
||||
email: `${Random.id()}@example.com`,
|
||||
showOnOfflineForm: true,
|
||||
},
|
||||
[{ agentId: agent1.user._id }, { agentId: agent2.user._id }],
|
||||
);
|
||||
|
||||
await Promise.all([makeAgentAvailable(agent1.credentials), makeAgentAvailable(agent2.credentials)]);
|
||||
|
||||
return {
|
||||
department,
|
||||
agent1,
|
||||
agent2,
|
||||
};
|
||||
};
|
||||
|
||||
export const createDepartmentWithAgent = async (agent: OnlineAgent): Promise<{ department: ILivechatDepartment; agent: OnlineAgent }> => {
|
||||
const { user, credentials } = agent;
|
||||
const department = await createDepartment();
|
||||
|
||||
await addOrRemoveAgentFromDepartment(department._id, { agentId: user._id, username: user.username }, true);
|
||||
|
||||
return {
|
||||
department,
|
||||
agent: {
|
||||
credentials,
|
||||
user,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const addOrRemoveAgentFromDepartment = async (
|
||||
departmentId: string,
|
||||
agent: { agentId: string; username: string; count?: number; order?: number },
|
||||
add: boolean,
|
||||
) => {
|
||||
const response = await request
|
||||
.post(api(`livechat/department/${departmentId}/agents`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
...(add ? { upsert: [agent], remove: [] } : { remove: [agent], upsert: [] }),
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`Failed to add or remove agent from department. Status code: ${response.status}\n${response.body}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const createDepartmentWithAnOfflineAgent = async ({
|
||||
allowReceiveForwardOffline = false,
|
||||
fallbackForwardDepartment,
|
||||
departmentsAllowedToForward,
|
||||
}: {
|
||||
allowReceiveForwardOffline?: boolean;
|
||||
fallbackForwardDepartment?: string;
|
||||
departmentsAllowedToForward?: string[];
|
||||
}): Promise<{
|
||||
department: ILivechatDepartment;
|
||||
agent: {
|
||||
credentials: Credentials;
|
||||
user: WithRequiredProperty<IUser, 'username'>;
|
||||
};
|
||||
}> => {
|
||||
const { user, credentials } = await createAnOfflineAgent();
|
||||
|
||||
const department = (await createDepartment({
|
||||
allowReceiveForwardOffline,
|
||||
fallbackForwardDepartment,
|
||||
departmentsAllowedToForward,
|
||||
})) as ILivechatDepartment;
|
||||
|
||||
await addOrRemoveAgentFromDepartment(department._id, { agentId: user._id, username: user.username }, true);
|
||||
|
||||
return {
|
||||
department,
|
||||
agent: {
|
||||
credentials,
|
||||
user,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const createDepartmentWithAnAwayAgent = async ({
|
||||
allowReceiveForwardOffline = false,
|
||||
fallbackForwardDepartment,
|
||||
departmentsAllowedToForward,
|
||||
}: {
|
||||
allowReceiveForwardOffline?: boolean;
|
||||
fallbackForwardDepartment?: string;
|
||||
departmentsAllowedToForward?: string[];
|
||||
}): Promise<{
|
||||
department: ILivechatDepartment;
|
||||
agent: {
|
||||
credentials: Credentials;
|
||||
user: WithRequiredProperty<IUser, 'username'>;
|
||||
};
|
||||
}> => {
|
||||
const { user, credentials } = await createAnAwayAgent();
|
||||
|
||||
const department = (await createDepartment({
|
||||
allowReceiveForwardOffline,
|
||||
fallbackForwardDepartment,
|
||||
departmentsAllowedToForward,
|
||||
})) as ILivechatDepartment;
|
||||
|
||||
await addOrRemoveAgentFromDepartment(department._id, { agentId: user._id, username: user.username }, true);
|
||||
|
||||
return {
|
||||
department,
|
||||
agent: {
|
||||
credentials,
|
||||
user,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const archiveDepartment = async (departmentId: string): Promise<void> => {
|
||||
await request
|
||||
.post(api(`livechat/department/${departmentId}/archive`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
};
|
||||
|
||||
export const disableDepartment = async (department: Omit<ILivechatDepartment, '_updatedAt'>): Promise<void> => {
|
||||
department.enabled = false;
|
||||
delete department._updatedAt;
|
||||
const updatedDepartment = await updateDepartment(department._id, department);
|
||||
expect(updatedDepartment.enabled).to.be.false;
|
||||
};
|
||||
|
||||
export const deleteDepartment = async (departmentId: string): Promise<void> => {
|
||||
await request
|
||||
.delete(api(`livechat/department/${departmentId}`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
};
|
||||
|
||||
export const getDepartmentById = async (departmentId: string): Promise<ILivechatDepartment> => {
|
||||
const response = await request
|
||||
.get(api(`livechat/department/${departmentId}`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
return response.body.department;
|
||||
};
|
||||
31
apps/meteor/tests/data/livechat/inboxes.ts
Normal file
31
apps/meteor/tests/data/livechat/inboxes.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { getCredentials, api, request, credentials } from '../api-data';
|
||||
|
||||
export const createEmailInbox = async (): Promise<{ _id: string }> => {
|
||||
await new Promise((resolve) => getCredentials(resolve));
|
||||
const { body } = await request
|
||||
.post(api('email-inbox'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name: 'test',
|
||||
active: false,
|
||||
email: `test${new Date().getTime()}@test.com`,
|
||||
description: 'test',
|
||||
senderInfo: 'test',
|
||||
smtp: {
|
||||
server: 'smtp.example.com',
|
||||
port: 587,
|
||||
username: 'xxxx',
|
||||
password: 'xxxx',
|
||||
secure: true,
|
||||
},
|
||||
imap: {
|
||||
server: 'imap.example.com',
|
||||
port: 993,
|
||||
username: 'xxxx',
|
||||
password: 'xxxx',
|
||||
secure: true,
|
||||
maxRetries: 10,
|
||||
},
|
||||
});
|
||||
return body;
|
||||
};
|
||||
31
apps/meteor/tests/data/livechat/inquiries.ts
Normal file
31
apps/meteor/tests/data/livechat/inquiries.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type { ILivechatInquiryRecord } from '@rocket.chat/core-typings';
|
||||
import type { PaginatedResult } from '@rocket.chat/rest-typings';
|
||||
|
||||
import { api, request } from '../api-data';
|
||||
|
||||
export const fetchAllInquiries = async (credentials: Credentials, department?: string): Promise<ILivechatInquiryRecord[]> => {
|
||||
const inquiries: ILivechatInquiryRecord[] = [];
|
||||
|
||||
let hasMore = true;
|
||||
let offset = 0;
|
||||
|
||||
while (hasMore) {
|
||||
const { body } = (await request
|
||||
.get(api('livechat/inquiries.queuedForUser'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
...(department && { department }),
|
||||
offset,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200)) as { body: PaginatedResult<{ inquiries: Array<ILivechatInquiryRecord> }> };
|
||||
|
||||
inquiries.push(...body.inquiries);
|
||||
|
||||
hasMore = body.total > inquiries.length;
|
||||
offset += body.count;
|
||||
}
|
||||
|
||||
return inquiries;
|
||||
};
|
||||
71
apps/meteor/tests/data/livechat/priorities.ts
Normal file
71
apps/meteor/tests/data/livechat/priorities.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import type { ILivechatPriority, IOmnichannelServiceLevelAgreements } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { generateRandomSLAData } from '../../e2e/utils/omnichannel/sla';
|
||||
import { api, credentials, request } from '../api-data';
|
||||
import type { DummyResponse } from './utils';
|
||||
|
||||
export const createSLA = (): Promise<Omit<IOmnichannelServiceLevelAgreements, '_updated'>> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
void request
|
||||
.post(api('livechat/sla'))
|
||||
.set(credentials)
|
||||
.send(generateRandomSLAData())
|
||||
.end((err: Error, res: DummyResponse<{ sla: Omit<IOmnichannelServiceLevelAgreements, '_updated'> }, 'unwrapped'>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(res.body.sla);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteSLA = (id: string): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
void request
|
||||
.delete(api(`livechat/sla/${id}`))
|
||||
.set(credentials)
|
||||
.send()
|
||||
.end((err: Error, _res: DummyResponse<void, 'not-wrapped'>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const bulkCreateSLA = (amount: number): Promise<Omit<IOmnichannelServiceLevelAgreements, '_updated'>[]> => {
|
||||
const promises = [];
|
||||
for (let i = 0; i < amount; i++) {
|
||||
promises.push(createSLA());
|
||||
}
|
||||
return Promise.all(promises);
|
||||
};
|
||||
|
||||
export const deleteAllSLA = async (): Promise<void> => {
|
||||
const response = await request.get(api('livechat/sla')).set(credentials).expect('Content-Type', 'application/json').expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.sla).to.be.an('array');
|
||||
const {
|
||||
body: { sla },
|
||||
} = response as { body: { sla: IOmnichannelServiceLevelAgreements[] } };
|
||||
const promises = sla.map((slaObj) =>
|
||||
request
|
||||
.delete(api(`livechat/sla/${slaObj._id}`))
|
||||
.set(credentials)
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200),
|
||||
);
|
||||
await Promise.all(promises);
|
||||
};
|
||||
|
||||
export const getRandomPriority = async (): Promise<ILivechatPriority> => {
|
||||
const response = await request.get(api('livechat/priorities')).set(credentials).expect('Content-Type', 'application/json').expect(200);
|
||||
expect(response.body).to.have.property('success', true);
|
||||
expect(response.body.priorities).to.be.an('array');
|
||||
const {
|
||||
body: { priorities },
|
||||
} = response as { body: { priorities: ILivechatPriority[] } };
|
||||
return priorities[Math.floor(Math.random() * priorities.length)];
|
||||
};
|
||||
494
apps/meteor/tests/data/livechat/rooms.ts
Normal file
494
apps/meteor/tests/data/livechat/rooms.ts
Normal file
@@ -0,0 +1,494 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import type {
|
||||
ILivechatInquiryRecord,
|
||||
ILivechatAgent,
|
||||
ILivechatDepartment,
|
||||
ILivechatVisitor,
|
||||
IMessage,
|
||||
IOmnichannelRoom,
|
||||
} from '@rocket.chat/core-typings';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { api, credentials, methodCall, request } from '../api-data';
|
||||
import { imgURL } from '../interactions';
|
||||
import { getSettingValueById, restorePermissionToRoles, updateSetting } from '../permissions.helper';
|
||||
import { adminUsername } from '../user';
|
||||
import { getRandomVisitorToken } from './users';
|
||||
import type { DummyResponse } from './utils';
|
||||
import { sleep } from './utils';
|
||||
|
||||
export const createLivechatRoom = async (visitorToken: string, extraRoomParams?: Record<string, string>): Promise<IOmnichannelRoom> => {
|
||||
const urlParams = new URLSearchParams();
|
||||
urlParams.append('token', visitorToken);
|
||||
if (extraRoomParams) {
|
||||
for (const [key, value] of Object.entries(extraRoomParams)) {
|
||||
urlParams.append(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await request.get(api('livechat/room')).query(urlParams.toString()).set(credentials).expect(200);
|
||||
|
||||
return response.body.room;
|
||||
};
|
||||
|
||||
export const createLivechatRoomWidget = async (
|
||||
visitorToken: string,
|
||||
extraRoomParams?: Record<string, string>,
|
||||
): Promise<IOmnichannelRoom> => {
|
||||
const urlParams = new URLSearchParams();
|
||||
urlParams.append('token', visitorToken);
|
||||
if (extraRoomParams) {
|
||||
for (const [key, value] of Object.entries(extraRoomParams)) {
|
||||
urlParams.append(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await request
|
||||
.get(api('livechat/room'))
|
||||
.set('Cookie', [`rc_room_type=l`, `rc_is_widget=t`])
|
||||
.query(urlParams.toString())
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
|
||||
return response.body.room;
|
||||
};
|
||||
|
||||
export const createVisitorWithCustomData = async ({
|
||||
department,
|
||||
visitorName,
|
||||
customPhone,
|
||||
customFields,
|
||||
customToken,
|
||||
customEmail,
|
||||
ignoreEmail = false,
|
||||
ignorePhone = false,
|
||||
}: {
|
||||
department?: string;
|
||||
customPhone?: string;
|
||||
visitorName?: string;
|
||||
customEmail?: string;
|
||||
customFields?: { key: string; value: string; overwrite: boolean }[];
|
||||
customToken?: string;
|
||||
ignoreEmail?: boolean;
|
||||
ignorePhone?: boolean;
|
||||
}): Promise<ILivechatVisitor> => {
|
||||
const token = customToken || getRandomVisitorToken();
|
||||
const email = customEmail || `${token}@${token}.com`;
|
||||
const phone = customPhone || `${Math.floor(Math.random() * 10000000000)}`;
|
||||
|
||||
try {
|
||||
const res = await request.get(api(`livechat/visitor/${token}`));
|
||||
if (res?.body?.visitor) {
|
||||
return res.body.visitor as ILivechatVisitor;
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors from GET; we will create the visitor below.
|
||||
}
|
||||
|
||||
const res = await request
|
||||
.post(api('livechat/visitor'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
visitor: {
|
||||
name: visitorName || `Visitor ${Date.now()}`,
|
||||
token,
|
||||
customFields: customFields || [{ key: 'address', value: 'Rocket.Chat street', overwrite: true }],
|
||||
...(department ? { department } : {}),
|
||||
...(!ignoreEmail ? { email } : {}),
|
||||
...(!ignorePhone ? { phone } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
return res.body.visitor as ILivechatVisitor;
|
||||
};
|
||||
|
||||
export const createVisitor = (
|
||||
department?: string,
|
||||
visitorName?: string,
|
||||
customEmail?: string,
|
||||
customPhone?: string,
|
||||
): Promise<ILivechatVisitor> =>
|
||||
new Promise((resolve, reject) => {
|
||||
const token = getRandomVisitorToken();
|
||||
const email = customEmail || `${token}@${token}.com`;
|
||||
const phone = customPhone || `${Math.floor(Math.random() * 10000000000)}`;
|
||||
void request.get(api(`livechat/visitor/${token}`)).end((err: Error, res: DummyResponse<ILivechatVisitor>) => {
|
||||
if (!err && res && res.body && res.body.visitor) {
|
||||
return resolve(res.body.visitor);
|
||||
}
|
||||
void request
|
||||
.post(api('livechat/visitor'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
visitor: {
|
||||
name: visitorName || `Visitor ${Date.now()}`,
|
||||
email,
|
||||
token,
|
||||
phone,
|
||||
customFields: [{ key: 'address', value: 'Rocket.Chat street', overwrite: true }],
|
||||
...(department ? { department } : {}),
|
||||
},
|
||||
})
|
||||
.end((err: Error, res: DummyResponse<ILivechatVisitor>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(res.body.visitor);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export const deleteVisitor = async (token: string): Promise<void> => {
|
||||
await request.delete(api(`livechat/visitor/${token}`));
|
||||
};
|
||||
|
||||
export const takeInquiry = async (inquiryId: string, agentCredentials?: Credentials): Promise<void> => {
|
||||
const userId = agentCredentials ? agentCredentials['X-User-Id'] : credentials['X-User-Id'];
|
||||
|
||||
await request
|
||||
.post(api('livechat/inquiries.take'))
|
||||
.set(agentCredentials || credentials)
|
||||
.send({ userId, inquiryId })
|
||||
.expect(200);
|
||||
};
|
||||
|
||||
export const fetchInquiry = (roomId: string): Promise<ILivechatInquiryRecord> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
void request
|
||||
.get(api('livechat/inquiries.getOne'))
|
||||
.query({ roomId })
|
||||
.set(credentials)
|
||||
.end((err: Error, res: DummyResponse<ILivechatInquiryRecord>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(res.body.inquiry);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const createDepartment = (
|
||||
agents?: { agentId: string; count?: number }[],
|
||||
name?: string,
|
||||
enabled = true,
|
||||
opts: Record<string, any> = {},
|
||||
departmentUnit?: { _id?: string },
|
||||
userCredentials: Credentials = credentials,
|
||||
): Promise<ILivechatDepartment> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
void request
|
||||
.post(api('livechat/department'))
|
||||
.set(userCredentials)
|
||||
.send({
|
||||
department: {
|
||||
name: name || `Department ${Date.now()}`,
|
||||
enabled,
|
||||
showOnOfflineForm: true,
|
||||
showOnRegistration: true,
|
||||
email: 'a@b.com',
|
||||
...opts,
|
||||
},
|
||||
agents,
|
||||
departmentUnit,
|
||||
})
|
||||
.end((err: Error, res: DummyResponse<ILivechatDepartment>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(res.body.department);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const updateDepartment = ({
|
||||
departmentId,
|
||||
userCredentials,
|
||||
agents,
|
||||
name,
|
||||
enabled = true,
|
||||
opts = {},
|
||||
departmentUnit,
|
||||
}: {
|
||||
departmentId: string;
|
||||
userCredentials: Credentials;
|
||||
agents?: { agentId: string }[];
|
||||
name?: string;
|
||||
enabled?: boolean;
|
||||
opts?: Record<string, any>;
|
||||
departmentUnit?: { _id?: string };
|
||||
}): Promise<ILivechatDepartment> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
void request
|
||||
.put(api(`livechat/department/${departmentId}`))
|
||||
.set(userCredentials)
|
||||
.send({
|
||||
department: {
|
||||
name: name || `Department ${Date.now()}`,
|
||||
enabled,
|
||||
showOnOfflineForm: true,
|
||||
showOnRegistration: true,
|
||||
email: 'a@b.com',
|
||||
...opts,
|
||||
},
|
||||
agents,
|
||||
departmentUnit,
|
||||
})
|
||||
.end((err: Error, res: DummyResponse<ILivechatDepartment>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(res.body.department);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteAgent = async (agentId: string = credentials['X-User-Id']): Promise<void> => {
|
||||
await request
|
||||
.delete(api(`livechat/users/agent/${agentId}`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
};
|
||||
|
||||
export const createAgent = (overrideUsername?: string): Promise<ILivechatAgent> =>
|
||||
new Promise((resolve, reject) => {
|
||||
void request
|
||||
.post(api('livechat/users/agent'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
username: overrideUsername || adminUsername,
|
||||
})
|
||||
.end((err: Error, res: DummyResponse<ILivechatAgent>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(res.body.user);
|
||||
});
|
||||
});
|
||||
|
||||
export const createManager = (overrideUsername?: string): Promise<ILivechatAgent> =>
|
||||
new Promise((resolve, reject) => {
|
||||
void request
|
||||
.post(api('livechat/users/manager'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
username: overrideUsername || adminUsername,
|
||||
})
|
||||
.end((err: Error, res: DummyResponse<ILivechatAgent>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(res.body.user);
|
||||
});
|
||||
});
|
||||
|
||||
export const switchLivechatStatus = async (status: 'available' | 'not-available', overrideCredentials?: Credentials): Promise<void> => {
|
||||
await request
|
||||
.post(api('livechat/agent.status'))
|
||||
.set(overrideCredentials || credentials)
|
||||
.send({ status })
|
||||
.expect(200);
|
||||
};
|
||||
|
||||
export const makeAgentAvailable = async (overrideCredentials?: Credentials): Promise<Response> => {
|
||||
await restorePermissionToRoles('view-l-room');
|
||||
await request
|
||||
.post(api('users.setStatus'))
|
||||
.set(overrideCredentials || credentials)
|
||||
.send({
|
||||
message: '',
|
||||
status: 'online',
|
||||
});
|
||||
|
||||
return request
|
||||
.post(api('livechat/agent.status'))
|
||||
.set(overrideCredentials || credentials)
|
||||
.send({
|
||||
status: 'available',
|
||||
});
|
||||
};
|
||||
|
||||
export const makeAgentUnavailable = async (overrideCredentials?: Credentials): Promise<void> => {
|
||||
await request
|
||||
.post(api('users.setStatus'))
|
||||
.set(overrideCredentials || credentials)
|
||||
.send({ message: '', status: 'offline' })
|
||||
.expect(200);
|
||||
await request
|
||||
.post(api('livechat/agent.status'))
|
||||
.set(overrideCredentials || credentials)
|
||||
.send({
|
||||
status: 'not-available',
|
||||
})
|
||||
.expect(200);
|
||||
};
|
||||
|
||||
export const getLivechatRoomInfo = (roomId: string): Promise<IOmnichannelRoom> => {
|
||||
return new Promise((resolve /* , reject*/) => {
|
||||
void request
|
||||
.get(api('channels.info'))
|
||||
.set(credentials)
|
||||
.query({
|
||||
roomId,
|
||||
})
|
||||
.end((_err: Error, res: DummyResponse<IOmnichannelRoom>) => {
|
||||
resolve(res.body.channel);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Sends message as visitor
|
||||
*/
|
||||
export const sendMessage = (roomId: string, message: string, visitorToken: string): Promise<IMessage> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
void request
|
||||
.post(api('livechat/message'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
rid: roomId,
|
||||
msg: message,
|
||||
token: visitorToken,
|
||||
})
|
||||
.end((err: Error, res: DummyResponse<IMessage>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(res.body.message);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const uploadFile = (roomId: string, visitorToken: string): Promise<IMessage> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
void request
|
||||
.post(api(`livechat/upload/${roomId}`))
|
||||
.set({ 'x-visitor-token': visitorToken, ...credentials })
|
||||
.attach('file', imgURL)
|
||||
.end((err: Error, res: DummyResponse<IMessage>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(res.body as unknown as IMessage);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Sends a message using sendMessage method from agent
|
||||
export const sendAgentMessage = (roomId: string, msg?: string, userCredentials: Credentials = credentials): Promise<IMessage> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
void request
|
||||
.post(methodCall('sendMessage'))
|
||||
.set(userCredentials)
|
||||
.send({
|
||||
message: JSON.stringify({
|
||||
method: 'sendMessage',
|
||||
params: [{ rid: roomId, msg: msg || faker.lorem.sentence() }],
|
||||
id: 'id',
|
||||
msg: 'method',
|
||||
}),
|
||||
})
|
||||
.end((err: Error, res: any) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(JSON.parse(res.body.message).result);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchMessages = (roomId: string, visitorToken: string): Promise<IMessage[]> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
void request
|
||||
.get(api(`livechat/messages.history/${roomId}`))
|
||||
.set(credentials)
|
||||
.query({
|
||||
token: visitorToken,
|
||||
})
|
||||
.end((err: Error, res: DummyResponse<IMessage[]>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
if (!res.body.success) {
|
||||
reject(res.body);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(res.body.messages);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const closeOmnichannelRoom = async (roomId: string, tags?: string[]): Promise<void> => {
|
||||
await request
|
||||
.post(api('livechat/room.closeByUser'))
|
||||
.set(credentials)
|
||||
.send({ rid: roomId, ...(tags && { tags }), comment: faker.lorem.sentence() })
|
||||
.expect(200);
|
||||
};
|
||||
|
||||
export const bulkCreateLivechatRooms = async (
|
||||
amount: number,
|
||||
department?: string,
|
||||
resolveRoomExtraParams?: (index: number) => Record<string, string> | undefined,
|
||||
): Promise<IOmnichannelRoom[]> => {
|
||||
const rooms: IOmnichannelRoom[] = [];
|
||||
|
||||
for (let i = 0; i < amount; i++) {
|
||||
const visitor = await createVisitor(department);
|
||||
const extraRoomParams = resolveRoomExtraParams ? resolveRoomExtraParams(i) : {};
|
||||
|
||||
const room = await createLivechatRoom(visitor.token, extraRoomParams);
|
||||
|
||||
rooms.push(room);
|
||||
}
|
||||
|
||||
return rooms;
|
||||
};
|
||||
|
||||
export const startANewLivechatRoomAndTakeIt = async ({
|
||||
departmentId,
|
||||
agent,
|
||||
}: {
|
||||
departmentId?: string;
|
||||
agent?: Credentials;
|
||||
} = {}): Promise<{ room: IOmnichannelRoom; visitor: ILivechatVisitor }> => {
|
||||
const currentRoutingMethod = await getSettingValueById('Livechat_Routing_Method');
|
||||
const routingMethodChanged = false;
|
||||
if (currentRoutingMethod !== 'Manual_Selection') {
|
||||
await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
|
||||
|
||||
// wait for routing algorithm to stop
|
||||
await sleep(1000);
|
||||
}
|
||||
|
||||
const visitor = await createVisitor(departmentId);
|
||||
const room = await createLivechatRoom(visitor.token);
|
||||
const { _id: roomId } = room;
|
||||
const inq = await fetchInquiry(roomId);
|
||||
await takeInquiry(inq._id, agent);
|
||||
await sendMessage(roomId, 'test message', visitor.token);
|
||||
|
||||
if (routingMethodChanged) {
|
||||
await updateSetting('Livechat_Routing_Method', currentRoutingMethod);
|
||||
|
||||
// wait for routing algorithm to start
|
||||
await sleep(1000);
|
||||
}
|
||||
|
||||
return { room, visitor };
|
||||
};
|
||||
|
||||
export const placeRoomOnHold = async (roomId: string): Promise<void> => {
|
||||
await request.post(api('livechat/room.onHold')).set(credentials).send({ roomId }).expect(200);
|
||||
};
|
||||
|
||||
export const moveBackToQueue = async (roomId: string, overrideCredentials?: Credentials): Promise<void> => {
|
||||
await request
|
||||
.post(api('livechat/inquiries.returnAsInquiry'))
|
||||
.set(overrideCredentials || credentials)
|
||||
.send({ roomId })
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
};
|
||||
BIN
apps/meteor/tests/data/livechat/sample.png
Normal file
BIN
apps/meteor/tests/data/livechat/sample.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
50
apps/meteor/tests/data/livechat/tags.ts
Normal file
50
apps/meteor/tests/data/livechat/tags.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type { ILivechatTag, FindTagsResult } from '@rocket.chat/core-typings';
|
||||
|
||||
import { credentials, request, api } from '../api-data';
|
||||
import type { DummyResponse } from './utils';
|
||||
|
||||
export const listTags = async (): Promise<FindTagsResult> => {
|
||||
const { body } = await request.get(api('livechat/tags')).set(credentials).query({ viewAll: 'true' });
|
||||
|
||||
return body;
|
||||
};
|
||||
|
||||
export const saveTags = async (departments: string[] = []): Promise<ILivechatTag> => {
|
||||
const { body } = await request
|
||||
.post(api('livechat/tags.save'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
tagData: {
|
||||
name: faker.string.uuid(),
|
||||
description: faker.lorem.sentence(),
|
||||
},
|
||||
...(departments.length > 0 && { tagDepartments: departments }),
|
||||
});
|
||||
|
||||
return body;
|
||||
};
|
||||
|
||||
export const removeAllTags = async (): Promise<boolean> => {
|
||||
const tagsList = await listTags();
|
||||
await Promise.all(tagsList.tags.map((tag) => removeTag(tag._id)));
|
||||
|
||||
const response = await request.get(api('livechat/tags')).set(credentials).expect('Content-Type', 'application/json').expect(200);
|
||||
|
||||
return response.body.tags.length === 0;
|
||||
};
|
||||
|
||||
export const removeTag = (id: string): Promise<boolean> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
void request
|
||||
.post(api('livechat/tags.delete'))
|
||||
.set(credentials)
|
||||
.send({ id })
|
||||
.end((err: Error, res: DummyResponse<string, 'wrapped'>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(JSON.parse(res.body.success).result);
|
||||
});
|
||||
});
|
||||
};
|
||||
55
apps/meteor/tests/data/livechat/triggers.ts
Normal file
55
apps/meteor/tests/data/livechat/triggers.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type { ILivechatTrigger } from '@rocket.chat/core-typings';
|
||||
|
||||
import { api, credentials, request } from '../api-data';
|
||||
import type { DummyResponse } from './utils';
|
||||
|
||||
export const createTrigger = (name: string): Promise<boolean> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
void request
|
||||
.post(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
name,
|
||||
description: faker.lorem.sentence(),
|
||||
enabled: true,
|
||||
runOnce: faker.datatype.boolean(),
|
||||
actions: [
|
||||
{
|
||||
name: 'send-message',
|
||||
params: {
|
||||
msg: faker.lorem.sentence(),
|
||||
name: faker.person.firstName(),
|
||||
sender: faker.helpers.arrayElement(['queue', 'custom']),
|
||||
},
|
||||
},
|
||||
],
|
||||
conditions: [
|
||||
{
|
||||
name: faker.helpers.arrayElement(['time-on-site', 'page-url', 'chat-opened-by-visitor', 'after-guest-registration']),
|
||||
value: faker.string.alpha(),
|
||||
},
|
||||
],
|
||||
})
|
||||
.end((err: Error, _res: DummyResponse<boolean, 'unwrapped'>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchTriggers = (): Promise<ILivechatTrigger[]> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
void request
|
||||
.get(api('livechat/triggers'))
|
||||
.set(credentials)
|
||||
.end((err: Error, res: DummyResponse<ILivechatTrigger[], 'wrapped'>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(res.body.triggers);
|
||||
});
|
||||
});
|
||||
};
|
||||
85
apps/meteor/tests/data/livechat/units.ts
Normal file
85
apps/meteor/tests/data/livechat/units.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type { IOmnichannelBusinessUnit } from '@rocket.chat/core-typings';
|
||||
|
||||
import { methodCall, credentials, request, api } from '../api-data';
|
||||
import type { DummyResponse } from './utils';
|
||||
|
||||
export const createMonitor = async (username: string): Promise<{ _id: string; username: string; role: string[] }> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
void request
|
||||
.post(api('livechat/monitors.create'))
|
||||
.set(credentials)
|
||||
.send({ username })
|
||||
.end((err: Error, res: DummyResponse<{ _id: string; username: string; role: string[] }, 'not-wrapped'>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(res.body);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const createUnit = async (
|
||||
monitorId: string,
|
||||
username: string,
|
||||
departmentIds: string[],
|
||||
name?: string,
|
||||
extraMonitor: { monitorId: string; username: string }[] = [],
|
||||
): Promise<IOmnichannelBusinessUnit> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
void request
|
||||
.post(api('livechat/units'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
unitData: {
|
||||
name: name || `${faker.person.firstName()} ${faker.string.uuid()}`,
|
||||
visibility: faker.helpers.arrayElement(['public', 'private']),
|
||||
},
|
||||
unitMonitors: [{ monitorId, username }, ...extraMonitor],
|
||||
unitDepartments: departmentIds.map((departmentId) => ({ departmentId })),
|
||||
})
|
||||
.end((err: Error, res: DummyResponse<IOmnichannelBusinessUnit, 'not-wrapped'>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(res.body);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteUnit = async (unit: IOmnichannelBusinessUnit): Promise<IOmnichannelBusinessUnit> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
void request
|
||||
.post(methodCall(`livechat:removeUnit`))
|
||||
.set(credentials)
|
||||
.send({
|
||||
message: JSON.stringify({
|
||||
method: 'livechat:removeUnit',
|
||||
params: [unit._id],
|
||||
id: '101',
|
||||
msg: 'method',
|
||||
}),
|
||||
})
|
||||
.end((err: Error, res: DummyResponse<string, 'wrapped'>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(JSON.parse(res.body.message).result);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const getUnit = (unitId: string): Promise<IOmnichannelBusinessUnit> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
void request
|
||||
.get(api(`livechat/units/${unitId}`))
|
||||
.set(credentials)
|
||||
.end((err: Error, res: DummyResponse<IOmnichannelBusinessUnit, 'not-wrapped'>) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(res.body);
|
||||
});
|
||||
});
|
||||
};
|
||||
128
apps/meteor/tests/data/livechat/users.ts
Normal file
128
apps/meteor/tests/data/livechat/users.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import type { Credentials } from '@rocket.chat/api-client';
|
||||
import { UserStatus, type ILivechatAgent, type IUser } from '@rocket.chat/core-typings';
|
||||
import { Random } from '@rocket.chat/random';
|
||||
|
||||
import { api, credentials, request } from '../api-data';
|
||||
import { password } from '../user';
|
||||
import { createUser, login, setUserAway, setUserStatus } from '../users.helper';
|
||||
import { createAgent, makeAgentAvailable, makeAgentUnavailable } from './rooms';
|
||||
|
||||
export const createBotAgent = async (): Promise<{
|
||||
credentials: Credentials;
|
||||
user: IUser;
|
||||
}> => {
|
||||
const agent = await createUser({
|
||||
roles: ['bot'],
|
||||
});
|
||||
const createdUserCredentials = await login(agent.username, password);
|
||||
await createAgent(agent.username);
|
||||
await makeAgentAvailable(createdUserCredentials);
|
||||
|
||||
return {
|
||||
credentials: createdUserCredentials,
|
||||
user: agent,
|
||||
};
|
||||
};
|
||||
|
||||
export const getRandomVisitorToken = (): string => faker.string.alphanumeric(17);
|
||||
|
||||
export const getAgent = async (userId: string): Promise<ILivechatAgent> => {
|
||||
const { body } = await request
|
||||
.get(api(`livechat/users/agent/${userId}`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
return body.user;
|
||||
};
|
||||
|
||||
export const removeAgent = async (userId: string): Promise<void> => {
|
||||
await request
|
||||
.delete(api(`livechat/users/agent/${userId}`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
};
|
||||
|
||||
export const createAgentAndReLogin = async (): Promise<{
|
||||
credentials: Credentials;
|
||||
user: IUser & { username: string };
|
||||
}> => {
|
||||
const username = `user.test.${Random.id()}`;
|
||||
const email = `${username}@rocket.chat`;
|
||||
const { body } = await request.post(api('users.create')).set(credentials).send({ email, name: username, username, password });
|
||||
const agent = body.user;
|
||||
const createdUserCredentials = await login(agent.username, password);
|
||||
await createAgent(agent.username);
|
||||
|
||||
return {
|
||||
credentials: createdUserCredentials,
|
||||
user: agent,
|
||||
};
|
||||
};
|
||||
|
||||
export const createAnOnlineAgent = async (): Promise<{
|
||||
credentials: Credentials;
|
||||
user: IUser & { username: string };
|
||||
}> => {
|
||||
const { credentials: createdUserCredentials, user: agent } = await createAgentAndReLogin();
|
||||
await makeAgentAvailable(createdUserCredentials);
|
||||
|
||||
return {
|
||||
credentials: createdUserCredentials,
|
||||
user: agent,
|
||||
};
|
||||
};
|
||||
|
||||
export const createAnOfflineAgent = async (): Promise<{
|
||||
credentials: Credentials;
|
||||
user: IUser & { username: string };
|
||||
}> => {
|
||||
const username = `user.test.${Date.now()}.offline`;
|
||||
const email = `${username}.offline@rocket.chat`;
|
||||
const { body } = await request.post(api('users.create')).set(credentials).send({ email, name: username, username, password });
|
||||
const agent = body.user;
|
||||
const createdUserCredentials = await login(agent.username, password);
|
||||
await createAgent(agent.username);
|
||||
await makeAgentUnavailable(createdUserCredentials);
|
||||
|
||||
return {
|
||||
credentials: createdUserCredentials,
|
||||
user: agent,
|
||||
};
|
||||
};
|
||||
|
||||
export const createAnAwayAgent = async (): Promise<{
|
||||
credentials: Credentials;
|
||||
user: IUser & { username: string };
|
||||
}> => {
|
||||
const username = `user.test.${Date.now()}.away`;
|
||||
const email = `${username}.offline@rocket.chat`;
|
||||
const { body } = await request.post(api('users.create')).set(credentials).send({ email, name: username, username, password });
|
||||
const agent = body.user;
|
||||
const createdUserCredentials = await login(agent.username, password);
|
||||
await createAgent(agent.username);
|
||||
await makeAgentAvailable(createdUserCredentials);
|
||||
await setUserStatus(createdUserCredentials, UserStatus.AWAY);
|
||||
await setUserAway(createdUserCredentials);
|
||||
|
||||
return {
|
||||
credentials: createdUserCredentials,
|
||||
user: agent,
|
||||
};
|
||||
};
|
||||
|
||||
export const updateLivechatSettingsForUser = async (
|
||||
agentId: string,
|
||||
livechatSettings: Record<string, any>,
|
||||
agentDepartments: string[] = [],
|
||||
): Promise<void> => {
|
||||
await request
|
||||
.post(api('livechat/agents.saveInfo'))
|
||||
.set(credentials)
|
||||
.send({
|
||||
agentId,
|
||||
agentData: livechatSettings,
|
||||
agentDepartments,
|
||||
})
|
||||
.expect('Content-Type', 'application/json')
|
||||
.expect(200);
|
||||
};
|
||||
17
apps/meteor/tests/data/livechat/utils.ts
Normal file
17
apps/meteor/tests/data/livechat/utils.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export type DummyResponse<T, E = 'wrapped'> = E extends 'wrapped' ? { body: { [k: string]: T } } : { body: T };
|
||||
|
||||
export type WithRequiredProperty<Type, Key extends keyof Type> = Type & {
|
||||
[Property in Key]-?: Type[Property];
|
||||
};
|
||||
|
||||
export const sleep = (ms: number) => {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
export const parseMethodResponse = (response: any) => {
|
||||
if (response.message) {
|
||||
return JSON.parse(response.message);
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
13
apps/meteor/tests/data/livechat/visitor.ts
Normal file
13
apps/meteor/tests/data/livechat/visitor.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { ILivechatVisitor } from '@rocket.chat/core-typings';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { api, credentials, request } from '../api-data';
|
||||
|
||||
export const getLivechatVisitorByToken = async (token: string): Promise<ILivechatVisitor> => {
|
||||
const response = await request
|
||||
.get(api(`livechat/visitor/${token}`))
|
||||
.set(credentials)
|
||||
.expect(200);
|
||||
expect(response.body).to.have.property('visitor');
|
||||
return response.body.visitor;
|
||||
};
|
||||
Reference in New Issue
Block a user