diff --git a/apps/meteor/app/api/server/api.d.ts b/apps/meteor/app/api/server/api.d.ts index 594f956edaea..0f6db1f2f439 100644 --- a/apps/meteor/app/api/server/api.d.ts +++ b/apps/meteor/app/api/server/api.d.ts @@ -68,7 +68,7 @@ type Options = ( twoFactorOptions?: ITwoFactorOptions; } ) & { - validateParams?: ValidateFunction; + validateParams?: ValidateFunction | { [key in Method]?: ValidateFunction }; authOrAnonRequired?: true; }; @@ -93,6 +93,8 @@ type ActionThis } + ? T + : TOptions extends { validateParams: { GET: ValidateFunction } } ? T : Partial> : Record; @@ -101,6 +103,10 @@ type ActionThis : TOptions extends { validateParams: ValidateFunction } ? T + : TOptions extends { validateParams: infer V } + ? V extends { [key in TMethod]: ValidateFunction } + ? T + : Partial> : Partial>; readonly request: Request; diff --git a/apps/meteor/app/api/server/api.js b/apps/meteor/app/api/server/api.js index 17b31a3b53c6..9619530116f8 100644 --- a/apps/meteor/app/api/server/api.js +++ b/apps/meteor/app/api/server/api.js @@ -425,9 +425,16 @@ export class APIClass extends Restivus { try { api.enforceRateLimit(objectForRateLimitMatch, this.request, this.response, this.userId); - if (_options.validateParams && !_options.validateParams(this.request.method === 'GET' ? this.queryParams : this.bodyParams)) { - throw new Meteor.Error('invalid-params', _options.validateParams.errors?.map((error) => error.message).join('\n ')); + if (_options.validateParams) { + const requestMethod = this.request.method; + const validatorFunc = + typeof _options.validateParams === 'function' ? _options.validateParams : _options.validateParams[requestMethod]; + + if (validatorFunc && !validatorFunc(requestMethod === 'GET' ? this.queryParams : this.bodyParams)) { + throw new Meteor.Error('invalid-params', validatorFunc.errors?.map((error) => error.message).join('\n ')); + } } + if (shouldVerifyPermissions && (!this.userId || !hasAllPermission(this.userId, _options.permissionsRequired))) { throw new Meteor.Error('error-unauthorized', 'User does not have the permissions required for this action', { permissions: _options.permissionsRequired, diff --git a/apps/meteor/app/livechat/imports/server/rest/departments.ts b/apps/meteor/app/livechat/imports/server/rest/departments.ts index ce0d9e83143e..d6e9481d0e0f 100644 --- a/apps/meteor/app/livechat/imports/server/rest/departments.ts +++ b/apps/meteor/app/livechat/imports/server/rest/departments.ts @@ -1,4 +1,4 @@ -import { isLivechatDepartmentProps } from '@rocket.chat/rest-typings'; +import { isGETLivechatDepartmentProps, isPOSTLivechatDepartmentProps } from '@rocket.chat/rest-typings'; import { Match, check } from 'meteor/check'; import { API } from '../../../../api/server'; @@ -15,7 +15,7 @@ import { API.v1.addRoute( 'livechat/department', - { authRequired: true, validateParams: isLivechatDepartmentProps }, + { authRequired: true, validateParams: { GET: isGETLivechatDepartmentProps, POST: isPOSTLivechatDepartmentProps } }, { async get() { if (!hasAtLeastOnePermission(this.userId, ['view-livechat-departments', 'view-l-room'])) { @@ -27,50 +27,44 @@ API.v1.addRoute( const { text, enabled, onlyMyDepartments, excludeDepartmentId } = this.queryParams; - const { departments, total } = Promise.await( - findDepartments({ - userId: this.userId, - text, - enabled: enabled === 'true', - onlyMyDepartments: onlyMyDepartments === 'true', - excludeDepartmentId, - pagination: { - offset, - count, - // IMO, sort type shouldn't be record, but a generic of the model we're trying to sort - // or the form { [k: keyof T]: number | string } - sort: sort as any, - }, - }), - ); + const { departments, total } = await findDepartments({ + userId: this.userId, + text, + enabled: enabled === 'true', + onlyMyDepartments: onlyMyDepartments === 'true', + excludeDepartmentId, + pagination: { + offset, + count, + // IMO, sort type shouldn't be record, but a generic of the model we're trying to sort + // or the form { [k: keyof T]: number | string } + sort: sort as any, + }, + }); return API.v1.success({ departments, count: departments.length, offset, total }); }, - post() { + async post() { if (!hasPermission(this.userId, 'manage-livechat-departments')) { return API.v1.unauthorized(); } - try { - check(this.bodyParams, { - department: Object, - agents: Match.Maybe(Array), - }); + check(this.bodyParams, { + department: Object, + agents: Match.Maybe(Array), + }); - const agents = this.bodyParams.agents ? { upsert: this.bodyParams.agents } : {}; - const department = Livechat.saveDepartment(null, this.bodyParams.department, agents); + const agents = this.bodyParams.agents ? { upsert: this.bodyParams.agents } : {}; + const department = Livechat.saveDepartment(null, this.bodyParams.department, agents); - if (department) { - return API.v1.success({ - department, - agents: LivechatDepartmentAgents.find({ departmentId: department._id }).fetch(), - }); - } - - return API.v1.failure(); - } catch (e) { - return API.v1.failure(e); + if (department) { + return API.v1.success({ + department, + agents: LivechatDepartmentAgents.find({ departmentId: department._id }).fetch(), + }); } + + return API.v1.failure(); }, }, ); @@ -170,20 +164,22 @@ API.v1.addRoute( 'livechat/department.autocomplete', { authRequired: true }, { - get() { + async get() { + if (!hasAtLeastOnePermission(this.userId, ['view-livechat-departments', 'view-l-room'])) { + return API.v1.unauthorized(); + } + const { selector, onlyMyDepartments } = this.queryParams; if (!selector) { return API.v1.failure("The 'selector' param is required"); } return API.v1.success( - Promise.await( - findDepartmentsToAutocomplete({ - uid: this.userId, - selector: JSON.parse(selector), - onlyMyDepartments: onlyMyDepartments === 'true', - }), - ), + await findDepartmentsToAutocomplete({ + uid: this.userId, + selector: JSON.parse(selector), + onlyMyDepartments: onlyMyDepartments === 'true', + }), ); }, }, @@ -239,7 +235,11 @@ API.v1.addRoute( 'livechat/department.listByIds', { authRequired: true }, { - get() { + async get() { + if (!hasAtLeastOnePermission(this.userId, ['view-livechat-departments', 'view-l-room'])) { + return API.v1.unauthorized(); + } + const { ids } = this.queryParams; const { fields } = this.parseJsonQuery(); if (!ids) { @@ -250,13 +250,10 @@ API.v1.addRoute( } return API.v1.success( - Promise.await( - findDepartmentsBetweenIds({ - uid: this.userId, - ids, - fields, - }), - ), + await findDepartmentsBetweenIds({ + ids, + fields, + }), ); }, }, diff --git a/apps/meteor/app/livechat/server/api/lib/departments.ts b/apps/meteor/app/livechat/server/api/lib/departments.ts index 423e4f4318dc..f482bd11bcfa 100644 --- a/apps/meteor/app/livechat/server/api/lib/departments.ts +++ b/apps/meteor/app/livechat/server/api/lib/departments.ts @@ -110,9 +110,6 @@ export async function findDepartmentsToAutocomplete({ selector, onlyMyDepartments = false, }: FindDepartmentToAutocompleteParams): Promise<{ items: ILivechatDepartmentRecord[] }> { - if (!(await hasPermissionAsync(uid, 'view-livechat-departments')) && !(await hasPermissionAsync(uid, 'view-l-room'))) { - return { items: [] }; - } const { exceptions = [] } = selector; let { conditions = {} } = selector; @@ -160,18 +157,12 @@ export async function findDepartmentAgents({ } export async function findDepartmentsBetweenIds({ - uid, ids, fields, }: { - uid: string; ids: string[]; fields: Record; }): Promise<{ departments: ILivechatDepartmentRecord[] }> { - if (!(await hasPermissionAsync(uid, 'view-livechat-departments')) && !(await hasPermissionAsync(uid, 'view-l-room'))) { - throw new Error('error-not-authorized'); - } - const departments = await LivechatDepartment.findInIds(ids, fields).toArray(); return { departments }; } diff --git a/apps/meteor/app/livechat/server/lib/Helper.js b/apps/meteor/app/livechat/server/lib/Helper.js index 3a51f2343d1e..810fa421a5c0 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.js +++ b/apps/meteor/app/livechat/server/lib/Helper.js @@ -600,14 +600,15 @@ export const updateDepartmentAgents = (departmentId, agents, departmentEnabled) } upsert.forEach((agent) => { - if (!Users.findOneById(agent.agentId, { fields: { _id: 1 } })) { + const agentFromDb = Users.findOneById(agent.agentId, { fields: { _id: 1, username: 1 } }); + if (!agentFromDb) { return; } LivechatDepartmentAgents.saveAgent({ agentId: agent.agentId, departmentId, - username: agent.username, + username: agentFromDb.username, count: agent.count ? parseInt(agent.count) : 0, order: agent.order ? parseInt(agent.order) : 0, departmentEnabled, diff --git a/apps/meteor/app/livechat/server/lib/Livechat.js b/apps/meteor/app/livechat/server/lib/Livechat.js index 218576effe4d..79c0e5c86bbd 100644 --- a/apps/meteor/app/livechat/server/lib/Livechat.js +++ b/apps/meteor/app/livechat/server/lib/Livechat.js @@ -1079,6 +1079,10 @@ export const Livechat = { ); } + if (fallbackForwardDepartment && !LivechatDepartment.findOneById(fallbackForwardDepartment)) { + throw new Meteor.Error('error-fallback-department-not-found', 'Fallback department not found', { method: 'livechat:saveDepartment' }); + } + const departmentDB = LivechatDepartment.createOrUpdateDepartment(_id, departmentData); if (departmentDB && departmentAgents) { updateDepartmentAgents(departmentDB._id, departmentAgents, departmentDB.enabled); diff --git a/apps/meteor/tests/data/livechat/rooms.js b/apps/meteor/tests/data/livechat/rooms.js index f915ee70bfa0..b95a087c21f5 100644 --- a/apps/meteor/tests/data/livechat/rooms.js +++ b/apps/meteor/tests/data/livechat/rooms.js @@ -39,6 +39,21 @@ export const createVisitor = () => }); }); +export const createDepartment = () => { + return new Promise((resolve, reject) => { + request + .post(api('livechat/department')) + .set(credentials) + .send({ department: { name: `Department ${Date.now()}`, enabled: true, showOnOfflineForm: true, showOnRegistration: true, email: 'a@b.com' } }) + .end((err, res) => { + if (err) { + return reject(err); + } + resolve(res.body.department); + }); + }); +} + export const createAgent = () => new Promise((resolve, reject) => { request diff --git a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts index 5ef8b603990f..bfd688572713 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts @@ -1,3 +1,5 @@ +/* eslint-env mocha */ + import { expect } from 'chai'; import { IOmnichannelRoom, IVisitor } from '@rocket.chat/core-typings'; import { Response } from 'supertest'; diff --git a/apps/meteor/tests/end-to-end/api/livechat/agents.js b/apps/meteor/tests/end-to-end/api/livechat/01-agents.js similarity index 100% rename from apps/meteor/tests/end-to-end/api/livechat/agents.js rename to apps/meteor/tests/end-to-end/api/livechat/01-agents.js diff --git a/apps/meteor/tests/end-to-end/api/livechat/appearance.js b/apps/meteor/tests/end-to-end/api/livechat/02-appearance.js similarity index 100% rename from apps/meteor/tests/end-to-end/api/livechat/appearance.js rename to apps/meteor/tests/end-to-end/api/livechat/02-appearance.js diff --git a/apps/meteor/tests/end-to-end/api/livechat/custom-fields.js b/apps/meteor/tests/end-to-end/api/livechat/03-custom-fields.js similarity index 100% rename from apps/meteor/tests/end-to-end/api/livechat/custom-fields.js rename to apps/meteor/tests/end-to-end/api/livechat/03-custom-fields.js diff --git a/apps/meteor/tests/end-to-end/api/livechat/dashboards.js b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.js similarity index 100% rename from apps/meteor/tests/end-to-end/api/livechat/dashboards.js rename to apps/meteor/tests/end-to-end/api/livechat/04-dashboards.js diff --git a/apps/meteor/tests/end-to-end/api/livechat/inquiries.js b/apps/meteor/tests/end-to-end/api/livechat/05-inquiries.js similarity index 100% rename from apps/meteor/tests/end-to-end/api/livechat/inquiries.js rename to apps/meteor/tests/end-to-end/api/livechat/05-inquiries.js diff --git a/apps/meteor/tests/end-to-end/api/livechat/integrations.js b/apps/meteor/tests/end-to-end/api/livechat/06-integrations.js similarity index 100% rename from apps/meteor/tests/end-to-end/api/livechat/integrations.js rename to apps/meteor/tests/end-to-end/api/livechat/06-integrations.js diff --git a/apps/meteor/tests/end-to-end/api/livechat/queue.js b/apps/meteor/tests/end-to-end/api/livechat/07-queue.js similarity index 100% rename from apps/meteor/tests/end-to-end/api/livechat/queue.js rename to apps/meteor/tests/end-to-end/api/livechat/07-queue.js diff --git a/apps/meteor/tests/end-to-end/api/livechat/triggers.js b/apps/meteor/tests/end-to-end/api/livechat/08-triggers.js similarity index 100% rename from apps/meteor/tests/end-to-end/api/livechat/triggers.js rename to apps/meteor/tests/end-to-end/api/livechat/08-triggers.js diff --git a/apps/meteor/tests/end-to-end/api/livechat/visitors.ts b/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts similarity index 99% rename from apps/meteor/tests/end-to-end/api/livechat/visitors.ts rename to apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts index 3d90e6890bb3..e6f9888687e9 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/visitors.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts @@ -1,3 +1,5 @@ +/* eslint-env mocha */ + import { expect } from 'chai'; import type { ILivechatVisitor, IOmnichannelRoom } from '@rocket.chat/core-typings'; import { Response } from 'supertest'; diff --git a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts new file mode 100644 index 000000000000..e35ecf5319ed --- /dev/null +++ b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts @@ -0,0 +1,341 @@ +/* eslint-env mocha */ +import { expect } from 'chai'; +import type { ILivechatDepartment } from '@rocket.chat/core-typings'; +import { Response } from 'supertest'; + +import { getCredentials, api, request, credentials } from '../../../data/api-data.js'; +import { updatePermission, updateSetting } from '../../../data/permissions.helper'; +import { makeAgentAvailable, createAgent, createDepartment } from '../../../data/livechat/rooms.js'; + +describe('LIVECHAT - Departments', function () { + before((done) => getCredentials(done)); + + before((done) => { + updateSetting('Livechat_enabled', true).then(() => + updatePermission('view-livechat-manager', ['admin']) + .then(() => createAgent()) + .then(() => makeAgentAvailable().then(() => done())), + ); + }); + + describe('GET livechat/department', () => { + it('should return unauthorized error when the user does not have the necessary permission', (done) => { + updatePermission('view-livechat-departments', []) + .then(() => updatePermission('view-l-room', [])) + .then(() => { + request.get(api('livechat/department')).set(credentials).expect('Content-Type', 'application/json').expect(403).end(done); + }); + }); + + it('should return a list of departments', (done) => { + updatePermission('view-livechat-departments', ['admin']).then(() => { + request + .get(api('livechat/department')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('departments'); + expect(res.body.departments).to.be.an('array'); + expect(res.body.departments).to.have.length.of.at.least(0); + }) + .end(done); + }); + }); + }); + + describe('POST livechat/departments', () => { + it('should return unauthorized error when the user does not have the necessary permission', (done) => { + updatePermission('manage-livechat-departments', []).then(() => { + request + .post(api('livechat/department')) + .set(credentials) + .send({ department: { name: 'Test', enabled: true, showOnOfflineForm: true, showOnRegistration: true, email: 'bla@bla' } }) + .expect('Content-Type', 'application/json') + .expect(403) + .end(done); + }); + }).timeout(5000); + + it('should return an error when no keys are provided', (done) => { + updatePermission('manage-livechat-departments', ['admin']).then(() => { + request + .post(api('livechat/department')) + .set(credentials) + .send({ department: {} }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + expect(res.body).to.have.property('errorType', 'invalid-params'); + }) + .end(done); + }); + }).timeout(5000); + + it('should create a new department', (done) => { + updatePermission('manage-livechat-departments', ['admin']).then(() => { + request + .post(api('livechat/department')) + .set(credentials) + .send({ department: { name: 'Test', enabled: true, showOnOfflineForm: true, showOnRegistration: true, email: 'bla@bla' } }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('department'); + expect(res.body.department).to.have.property('_id'); + expect(res.body.department).to.have.property('name', 'Test'); + expect(res.body.department).to.have.property('enabled', true); + expect(res.body.department).to.have.property('showOnOfflineForm', true); + expect(res.body.department).to.have.property('showOnRegistration', true); + }) + .end(done); + }); + }); + }); + + describe('GET livechat/department/:_id', () => { + it('should return unauthorized error when the user does not have the necessary permission', (done) => { + updatePermission('view-livechat-departments', []).then(() => { + request + .get(api('livechat/department/testetetetstetete')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(403) + .end(done); + }); + }).timeout(5000); + + it('should return an error when the department does not exist', (done) => { + updatePermission('view-livechat-departments', ['admin']).then(() => { + request + .get(api('livechat/department/testesteteste')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('department'); + expect(res.body.department).to.be.null; + }) + .end(done); + }); + }).timeout(5000); + + it('should return the department', (done) => { + let dep: ILivechatDepartment; + updatePermission('view-livechat-departments', ['admin']) + .then(() => createDepartment()) + .then((department: ILivechatDepartment) => { + dep = department; + }) + .then(() => { + request + .get(api(`livechat/department/${dep._id}`)) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('department'); + expect(res.body.department).to.have.property('_id'); + expect(res.body.department).to.have.property('name', dep.name); + expect(res.body.department).to.have.property('enabled', dep.enabled); + expect(res.body.department).to.have.property('showOnOfflineForm', dep.showOnOfflineForm); + expect(res.body.department).to.have.property('showOnRegistration', dep.showOnRegistration); + expect(res.body.department).to.have.property('email', dep.email); + }) + .end(done); + }); + }); + }); + + describe('GET livechat/department.autocomplete', () => { + it('should return an error when the user does not have the necessary permission', (done) => { + updatePermission('view-livechat-departments', []) + .then(() => updatePermission('view-l-room', [])) + .then(() => { + request + .get(api('livechat/department.autocomplete')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(403) + .end(done); + }); + }); + it('should return an error when the query is not provided', (done) => { + updatePermission('view-livechat-departments', ['admin']) + .then(() => updatePermission('view-l-room', ['admin'])) + .then(() => { + request + .get(api('livechat/department.autocomplete')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + }) + .end(done); + }); + }); + + it('should return an error when the query is empty', (done) => { + updatePermission('view-livechat-departments', ['admin']) + .then(() => updatePermission('view-l-room', ['admin'])) + .then(() => { + request + .get(api('livechat/department.autocomplete')) + .set(credentials) + .query({ selector: '' }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + }) + .end(done); + }); + }); + + it('should return an error when the query is not a string', (done) => { + updatePermission('view-livechat-departments', ['admin']) + .then(() => updatePermission('view-l-room', ['admin'])) + .then(() => { + request + .get(api('livechat/department.autocomplete')) + .set(credentials) + .query({ selector: { name: 'test' } }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + }) + .end(done); + }); + }); + + it('should return an error when selector is not valid JSON', (done) => { + updatePermission('view-livechat-departments', ['admin']) + .then(() => updatePermission('view-l-room', ['admin'])) + .then(() => { + request + .get(api('livechat/department.autocomplete')) + .set(credentials) + .query({ selector: '{name: "test"' }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + }) + .end(done); + }); + }); + + it('should return a list of departments that match selector.term', (done) => { + updatePermission('view-livechat-departments', ['admin']) + .then(() => updatePermission('view-l-room', ['admin'])) + .then(() => { + request + .get(api('livechat/department.autocomplete')) + .set(credentials) + .query({ selector: '{"term":"test"}' }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('items'); + expect(res.body.items).to.be.an('array'); + expect(res.body.items).to.have.length.of.at.least(1); + expect(res.body.items[0]).to.have.property('_id'); + expect(res.body.items[0]).to.have.property('name'); + }) + .end(done); + }); + }); + + it('should return a list of departments excluding the ids on selector.exceptions', (done) => { + let dep: ILivechatDepartment; + + updatePermission('view-livechat-departments', ['admin']) + .then(() => updatePermission('view-l-room', ['admin'])) + .then(() => createDepartment()) + .then((department: ILivechatDepartment) => { + dep = department; + }) + .then(() => { + request + .get(api('livechat/department.autocomplete')) + .set(credentials) + .query({ selector: `{"exceptions":["${dep._id}"]}` }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('items'); + expect(res.body.items).to.be.an('array'); + expect(res.body.items).to.have.length.of.at.least(1); + expect(res.body.items[0]).to.have.property('_id'); + expect(res.body.items[0]).to.have.property('name'); + expect(res.body.items.every((department: ILivechatDepartment) => department._id !== dep._id)).to.be.true; + }) + .end(done); + }); + }); + }); + + describe('GET livechat/departments.listByIds', () => { + it('should throw an error if the user doesnt have the permission to view the departments', (done) => { + updatePermission('view-livechat-departments', []) + .then(() => updatePermission('view-l-room', [])) + .then(() => { + request + .get(api('livechat/department.listByIds')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(403) + .end(done); + }); + }); + + it('should return an error when the query is not present', (done) => { + updatePermission('view-livechat-departments', ['admin']) + .then(() => updatePermission('view-l-room', ['admin'])) + .then(() => { + request + .get(api('livechat/department.listByIds')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + }) + .end(done); + }); + }); + + it('should return an error when the query is not an array', (done) => { + updatePermission('view-livechat-departments', ['admin']) + .then(() => updatePermission('view-l-room', ['admin'])) + .then(() => { + request + .get(api('livechat/department.listByIds')) + .set(credentials) + .query({ ids: 'test' }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + }) + .end(done); + }); + }); + }); +}); diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index 674acb0b4f35..eb09cadfc813 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -381,7 +381,95 @@ const LivechatDepartmentSchema = { additionalProperties: false, }; -export const isLivechatDepartmentProps = ajv.compile(LivechatDepartmentSchema); +export const isGETLivechatDepartmentProps = ajv.compile(LivechatDepartmentSchema); + +type POSTLivechatDepartmentProps = { + department: { + enabled: boolean; + name: string; + email: string; + description?: string; + showOnRegistration: boolean; + showOnOfflineForm: boolean; + requestTagsBeforeClosingChat?: boolean; + chatClosingTags?: string[]; + fallbackForwardDepartment?: string; + }; + agents: { agentId: string; count?: number; order?: number }[]; +}; + +const POSTLivechatDepartmentSchema = { + type: 'object', + properties: { + department: { + type: 'object', + properties: { + enabled: { + type: 'boolean', + }, + name: { + type: 'string', + }, + description: { + type: 'string', + nullable: true, + }, + showOnRegistration: { + type: 'boolean', + }, + showOnOfflineForm: { + type: 'boolean', + }, + requestTagsBeforeClosingChat: { + type: 'boolean', + nullable: true, + }, + chatClosingTags: { + type: 'array', + items: { + type: 'string', + }, + nullable: true, + }, + fallbackForwardDepartment: { + type: 'string', + nullable: true, + }, + email: { + type: 'string', + }, + }, + required: ['name', 'email', 'enabled', 'showOnRegistration', 'showOnOfflineForm'], + additionalProperties: true, + }, + agents: { + type: 'array', + items: { + type: 'object', + properties: { + agentId: { + type: 'string', + }, + count: { + type: 'number', + nullable: true, + }, + order: { + type: 'number', + nullable: true, + }, + }, + required: ['agentId'], + additionalProperties: false, + }, + nullable: true, + }, + }, + required: ['department'], + additionalProperties: false, +}; + +export const isPOSTLivechatDepartmentProps = ajv.compile(POSTLivechatDepartmentSchema); type LivechatDepartmentsAvailableByUnitIdProps = PaginatedRequest<{ text: string; onlyMyDepartments?: 'true' | 'false' }>;