From 7e2facc979488b059dd4ecf49fe53c48d3387141 Mon Sep 17 00:00:00 2001
From: Abhinav Kumar <abhinav@avitechlab.com>
Date: Tue, 20 Aug 2024 01:33:07 +0530
Subject: [PATCH] fix: customFields ignored in livechat room creation (#33047)

---
 .changeset/twelve-windows-train.md            |  5 ++
 .../server/hooks/beforeNewRoom.ts             |  9 ++--
 apps/meteor/lib/utils/isPlainObject.ts        |  3 ++
 .../livechat/hooks/beforeNewRoom.spec.ts      | 52 +++++++++++++++++++
 4 files changed, 66 insertions(+), 3 deletions(-)
 create mode 100644 .changeset/twelve-windows-train.md
 create mode 100644 apps/meteor/lib/utils/isPlainObject.ts
 create mode 100644 apps/meteor/tests/unit/server/livechat/hooks/beforeNewRoom.spec.ts

diff --git a/.changeset/twelve-windows-train.md b/.changeset/twelve-windows-train.md
new file mode 100644
index 00000000000..4c6ef548e65
--- /dev/null
+++ b/.changeset/twelve-windows-train.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixed: Custom fields in extraData now correctly added to extraRoomInfo by livechat.beforeRoom callback during livechat room creation.
diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/beforeNewRoom.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/beforeNewRoom.ts
index 35219fc6e03..4b0db6814bf 100644
--- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/beforeNewRoom.ts
+++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/beforeNewRoom.ts
@@ -2,6 +2,7 @@ import { OmnichannelServiceLevelAgreements } from '@rocket.chat/models';
 import { Meteor } from 'meteor/meteor';
 
 import { callbacks } from '../../../../../lib/callbacks';
+import { isPlainObject } from '../../../../../lib/utils/isPlainObject';
 
 callbacks.add(
 	'livechat.beforeRoom',
@@ -10,9 +11,11 @@ callbacks.add(
 			return roomInfo;
 		}
 
-		const { sla: searchTerm } = extraData;
+		const { sla: searchTerm, customFields } = extraData;
+		const roomInfoWithExtraData = { ...roomInfo, ...(isPlainObject(customFields) && { customFields }) };
+
 		if (!searchTerm) {
-			return roomInfo;
+			return roomInfoWithExtraData;
 		}
 
 		const sla = await OmnichannelServiceLevelAgreements.findOneByIdOrName(searchTerm);
@@ -23,7 +26,7 @@ callbacks.add(
 		}
 
 		const { _id: slaId } = sla;
-		return { ...roomInfo, slaId };
+		return { ...roomInfoWithExtraData, slaId };
 	},
 	callbacks.priority.MEDIUM,
 	'livechat-before-new-room',
diff --git a/apps/meteor/lib/utils/isPlainObject.ts b/apps/meteor/lib/utils/isPlainObject.ts
new file mode 100644
index 00000000000..a2bcf15cc59
--- /dev/null
+++ b/apps/meteor/lib/utils/isPlainObject.ts
@@ -0,0 +1,3 @@
+export function isPlainObject(value: unknown) {
+	return value !== null && typeof value === 'object' && !Array.isArray(value);
+}
diff --git a/apps/meteor/tests/unit/server/livechat/hooks/beforeNewRoom.spec.ts b/apps/meteor/tests/unit/server/livechat/hooks/beforeNewRoom.spec.ts
new file mode 100644
index 00000000000..9ba9ae73fe5
--- /dev/null
+++ b/apps/meteor/tests/unit/server/livechat/hooks/beforeNewRoom.spec.ts
@@ -0,0 +1,52 @@
+import { expect } from 'chai';
+import { describe, it, beforeEach } from 'mocha';
+import proxyquire from 'proxyquire';
+import sinon from 'sinon';
+
+import { callbacks } from '../../../../../lib/callbacks';
+
+const findStub = sinon.stub();
+
+proxyquire.noCallThru().load('../../../../../ee/app/livechat-enterprise/server/hooks/beforeNewRoom.ts', {
+	'meteor/meteor': {
+		Meteor: {
+			Error,
+		},
+	},
+	'@rocket.chat/models': {
+		OmnichannelServiceLevelAgreements: {
+			findOneByIdOrName: findStub,
+		},
+	},
+});
+
+describe('livechat.beforeRoom', () => {
+	beforeEach(() => findStub.withArgs('high').resolves({ _id: 'high' }).withArgs('invalid').resolves(null));
+
+	it('should return roomInfo with customFields when provided', async () => {
+		const roomInfo = { name: 'test' };
+		const extraData = { customFields: { test: 'test' } };
+		const result = await callbacks.run('livechat.beforeRoom', roomInfo, extraData);
+		expect(result).to.deep.equal({ ...roomInfo, customFields: extraData.customFields });
+	});
+
+	it('should throw an error when provided with an invalid sla', async () => {
+		const roomInfo = { name: 'test' };
+		const extraData = { customFields: { test: 'test' }, sla: 'invalid' };
+		await expect(callbacks.run('livechat.beforeRoom', roomInfo, extraData)).to.be.rejectedWith(Error, 'error-invalid-sla');
+	});
+
+	it('should not include field in roomInfo when extraData has field other than customFields, sla', async () => {
+		const roomInfo = { name: 'test' };
+		const extraData = { customFields: { test: 'test' }, sla: 'high' };
+		const result = await callbacks.run('livechat.beforeRoom', roomInfo, extraData);
+		expect(result).to.deep.equal({ ...roomInfo, customFields: extraData.customFields, slaId: 'high' });
+	});
+
+	it('should return roomInfo with no customFields when customFields is not an object', async () => {
+		const roomInfo = { name: 'test' };
+		const extraData = { customFields: 'not an object' };
+		const result = await callbacks.run('livechat.beforeRoom', roomInfo, extraData);
+		expect(result).to.deep.equal({ ...roomInfo });
+	});
+});
-- 
GitLab