From f58d61304ea7bee2402a67b3ea1c8af31b69a19f Mon Sep 17 00:00:00 2001 From: dodaa08 Date: Tue, 3 Mar 2026 23:35:48 +0530 Subject: [PATCH 1/6] Chore: Add min: 0 validation for Omnichannel agent count and order --- .../departments/DepartmentAgentsTable/AgentRow.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/meteor/client/views/omnichannel/departments/DepartmentAgentsTable/AgentRow.tsx b/apps/meteor/client/views/omnichannel/departments/DepartmentAgentsTable/AgentRow.tsx index e732a84b0e80d..d93b9800e3f52 100644 --- a/apps/meteor/client/views/omnichannel/departments/DepartmentAgentsTable/AgentRow.tsx +++ b/apps/meteor/client/views/omnichannel/departments/DepartmentAgentsTable/AgentRow.tsx @@ -24,10 +24,10 @@ const AgentRow = ({ index, agent, register, onRemove }: AgentRowProps) => { - + - + @@ -35,4 +35,5 @@ const AgentRow = ({ index, agent, register, onRemove }: AgentRowProps) => { ); }; + export default memo(AgentRow); From b510c7668ce2d515b3ae89fce065100815fa5117 Mon Sep 17 00:00:00 2001 From: dodaa08 Date: Wed, 4 Mar 2026 00:38:00 +0530 Subject: [PATCH 2/6] Added a min 0 in rest-typings and added some test around it --- .../end-to-end/api/livechat/10-departments.ts | 35 +++++++++++++++++++ packages/rest-typings/src/v1/omnichannel.ts | 26 ++++++++------ 2 files changed, 51 insertions(+), 10 deletions(-) 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 index 4e65439fb762c..c1e1378cccf35 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts @@ -915,6 +915,41 @@ import { IS_EE } from '../../../e2e/config/constants'; .expect(200); expect(res.body).to.have.property('success', true); }); + it('should fail if count is negative', async () => { + await updatePermission('manage-livechat-departments', ['admin']); + await updatePermission('add-livechat-department-agents', ['admin', 'livechat-manager']); + const [dep, agent] = await Promise.all([createDepartment(), createAgent()]); + const res = await request + .post(api(`livechat/department/${dep._id}/agents`)) + .set(credentials) + .send({ upsert: [{ agentId: agent._id, username: agent.username, count: -1, order: 0 }], remove: [] }) + .expect(400); + expect(res.body).to.have.property('success', false); + expect(res.body.error).to.include('must be >= 0'); + }); + it('should fail if order is negative', async () => { + await updatePermission('manage-livechat-departments', ['admin']); + await updatePermission('add-livechat-department-agents', ['admin', 'livechat-manager']); + const [dep, agent] = await Promise.all([createDepartment(), createAgent()]); + const res = await request + .post(api(`livechat/department/${dep._id}/agents`)) + .set(credentials) + .send({ upsert: [{ agentId: agent._id, username: agent.username, count: 0, order: -1 }], remove: [] }) + .expect(400); + expect(res.body).to.have.property('success', false); + expect(res.body.error).to.include('must be >= 0'); + }); + + it('should successfully add an agent when count and order are 0', async () => { + const [dep, agent] = await Promise.all([createDepartment(), createAgent()]); + const res = await request + .post(api(`livechat/department/${dep._id}/agents`)) + .set(credentials) + .send({ upsert: [{ agentId: agent._id, username: agent.username, count: 0, order: 0 }], remove: [] }) + .expect(200); + expect(res.body).to.have.property('success', true); + }); + it('should successfully remove an agent from a department', async () => { const [dep, agent] = await Promise.all([createDepartment(), createAgent()]); const res = await request diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index d42354fd0e332..86d66447a929d 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -186,9 +186,11 @@ const LivechatDepartmentDepartmentIdAgentsPOSTSchema = { name: { type: 'string' }, count: { type: 'number', + minimum: 0, }, order: { type: 'number', + minimum: 0, }, }, required: ['agentId', 'username'], @@ -211,9 +213,11 @@ const LivechatDepartmentDepartmentIdAgentsPOSTSchema = { }, count: { type: 'number', + minimum: 0, }, order: { type: 'number', + minimum: 0, }, departmentEnabled: { type: 'boolean' }, departmentId: { type: 'string' }, @@ -826,10 +830,12 @@ const POSTLivechatDepartmentSchema = { }, count: { type: 'number', + minimum: 0, nullable: true, }, order: { type: 'number', + minimum: 0, nullable: true, }, }, @@ -2848,14 +2854,14 @@ type POSTLivechatRoomCloseByUserParams = { generateTranscriptPdf?: boolean; forceClose?: boolean; transcriptEmail?: - | { - // Note: if sendToVisitor is false, then any previously requested transcripts (like via livechat:requestTranscript) will be also cancelled - sendToVisitor: false; - } - | { - sendToVisitor: true; - requestData: Pick, 'email' | 'subject'>; - }; + | { + // Note: if sendToVisitor is false, then any previously requested transcripts (like via livechat:requestTranscript) will be also cancelled + sendToVisitor: false; + } + | { + sendToVisitor: true; + requestData: Pick, 'email' | 'subject'>; + }; }; const POSTLivechatRoomCloseByUserParamsSchema = { @@ -4369,8 +4375,8 @@ export const isLivechatTriggerWebhookCallParams = ajv.compile Date: Wed, 4 Mar 2026 00:51:34 +0530 Subject: [PATCH 3/6] Add clean ups --- apps/meteor/tests/end-to-end/api/livechat/10-departments.ts | 6 ++++++ 1 file changed, 6 insertions(+) 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 index c1e1378cccf35..624f7a1831d21 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts @@ -941,13 +941,19 @@ import { IS_EE } from '../../../e2e/config/constants'; }); it('should successfully add an agent when count and order are 0', async () => { + await updatePermission('manage-livechat-departments', ['admin']); + await updatePermission('add-livechat-department-agents', ['admin', 'livechat-manager']); + const [dep, agent] = await Promise.all([createDepartment(), createAgent()]); const res = await request .post(api(`livechat/department/${dep._id}/agents`)) .set(credentials) .send({ upsert: [{ agentId: agent._id, username: agent.username, count: 0, order: 0 }], remove: [] }) .expect(200); + expect(res.body).to.have.property('success', true); + + await deleteDepartment(dep._id); }); it('should successfully remove an agent from a department', async () => { From a1d42ef51dfdc95b50669727ab4a681d25049491 Mon Sep 17 00:00:00 2001 From: dodaa08 Date: Wed, 4 Mar 2026 00:54:36 +0530 Subject: [PATCH 4/6] prettier fi --- packages/rest-typings/src/v1/omnichannel.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index 86d66447a929d..49e77c8d02c30 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -2854,14 +2854,14 @@ type POSTLivechatRoomCloseByUserParams = { generateTranscriptPdf?: boolean; forceClose?: boolean; transcriptEmail?: - | { - // Note: if sendToVisitor is false, then any previously requested transcripts (like via livechat:requestTranscript) will be also cancelled - sendToVisitor: false; - } - | { - sendToVisitor: true; - requestData: Pick, 'email' | 'subject'>; - }; + | { + // Note: if sendToVisitor is false, then any previously requested transcripts (like via livechat:requestTranscript) will be also cancelled + sendToVisitor: false; + } + | { + sendToVisitor: true; + requestData: Pick, 'email' | 'subject'>; + }; }; const POSTLivechatRoomCloseByUserParamsSchema = { @@ -4375,8 +4375,8 @@ export const isLivechatTriggerWebhookCallParams = ajv.compile Date: Wed, 4 Mar 2026 00:58:46 +0530 Subject: [PATCH 5/6] Clean ups --- apps/meteor/tests/end-to-end/api/livechat/10-departments.ts | 2 ++ 1 file changed, 2 insertions(+) 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 index 624f7a1831d21..825dc89b44ddd 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts @@ -926,6 +926,7 @@ import { IS_EE } from '../../../e2e/config/constants'; .expect(400); expect(res.body).to.have.property('success', false); expect(res.body.error).to.include('must be >= 0'); + await deleteDepartment(dep._id); }); it('should fail if order is negative', async () => { await updatePermission('manage-livechat-departments', ['admin']); @@ -938,6 +939,7 @@ import { IS_EE } from '../../../e2e/config/constants'; .expect(400); expect(res.body).to.have.property('success', false); expect(res.body.error).to.include('must be >= 0'); + await deleteDepartment(dep._id); }); it('should successfully add an agent when count and order are 0', async () => { From cba231d4e9eb2ef0aac5bccccc51648d1d3c41cd Mon Sep 17 00:00:00 2001 From: dodaa08 Date: Wed, 4 Mar 2026 10:17:17 +0530 Subject: [PATCH 6/6] Fixed moved to a nested describe with before/after hooks --- .../end-to-end/api/livechat/10-departments.ts | 86 ++++++++++--------- 1 file changed, 44 insertions(+), 42 deletions(-) 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 index 825dc89b44ddd..34ee3ce32b4de 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts @@ -915,48 +915,6 @@ import { IS_EE } from '../../../e2e/config/constants'; .expect(200); expect(res.body).to.have.property('success', true); }); - it('should fail if count is negative', async () => { - await updatePermission('manage-livechat-departments', ['admin']); - await updatePermission('add-livechat-department-agents', ['admin', 'livechat-manager']); - const [dep, agent] = await Promise.all([createDepartment(), createAgent()]); - const res = await request - .post(api(`livechat/department/${dep._id}/agents`)) - .set(credentials) - .send({ upsert: [{ agentId: agent._id, username: agent.username, count: -1, order: 0 }], remove: [] }) - .expect(400); - expect(res.body).to.have.property('success', false); - expect(res.body.error).to.include('must be >= 0'); - await deleteDepartment(dep._id); - }); - it('should fail if order is negative', async () => { - await updatePermission('manage-livechat-departments', ['admin']); - await updatePermission('add-livechat-department-agents', ['admin', 'livechat-manager']); - const [dep, agent] = await Promise.all([createDepartment(), createAgent()]); - const res = await request - .post(api(`livechat/department/${dep._id}/agents`)) - .set(credentials) - .send({ upsert: [{ agentId: agent._id, username: agent.username, count: 0, order: -1 }], remove: [] }) - .expect(400); - expect(res.body).to.have.property('success', false); - expect(res.body.error).to.include('must be >= 0'); - await deleteDepartment(dep._id); - }); - - it('should successfully add an agent when count and order are 0', async () => { - await updatePermission('manage-livechat-departments', ['admin']); - await updatePermission('add-livechat-department-agents', ['admin', 'livechat-manager']); - - const [dep, agent] = await Promise.all([createDepartment(), createAgent()]); - const res = await request - .post(api(`livechat/department/${dep._id}/agents`)) - .set(credentials) - .send({ upsert: [{ agentId: agent._id, username: agent.username, count: 0, order: 0 }], remove: [] }) - .expect(200); - - expect(res.body).to.have.property('success', true); - - await deleteDepartment(dep._id); - }); it('should successfully remove an agent from a department', async () => { const [dep, agent] = await Promise.all([createDepartment(), createAgent()]); @@ -983,6 +941,50 @@ import { IS_EE } from '../../../e2e/config/constants'; expect(res.body).to.have.property('success', true); await deleteDepartment(dep._id); }); + + describe('count and order validation', () => { + let dep: ILivechatDepartment; + let agent: IUser; + + before(async () => { + await updatePermission('manage-livechat-departments', ['admin']); + await updatePermission('add-livechat-department-agents', ['admin', 'livechat-manager']); + [dep, agent] = await Promise.all([createDepartment(), createAgent()]); + }); + + after(async () => { + await deleteDepartment(dep._id); + }); + + it('should fail if count is negative', async () => { + const res = await request + .post(api(`livechat/department/${dep._id}/agents`)) + .set(credentials) + .send({ upsert: [{ agentId: agent._id, username: agent.username, count: -1, order: 0 }], remove: [] }) + .expect(400); + expect(res.body).to.have.property('success', false); + expect(res.body.error).to.include('must be >= 0'); + }); + + it('should fail if order is negative', async () => { + const res = await request + .post(api(`livechat/department/${dep._id}/agents`)) + .set(credentials) + .send({ upsert: [{ agentId: agent._id, username: agent.username, count: 0, order: -1 }], remove: [] }) + .expect(400); + expect(res.body).to.have.property('success', false); + expect(res.body.error).to.include('must be >= 0'); + }); + + it('should successfully add an agent when count and order are 0', async () => { + const res = await request + .post(api(`livechat/department/${dep._id}/agents`)) + .set(credentials) + .send({ upsert: [{ agentId: agent._id, username: agent.username, count: 0, order: 0 }], remove: [] }) + .expect(200); + expect(res.body).to.have.property('success', true); + }); + }); }); describe('Department archivation', () => {