diff --git a/.changeset/slow-jobs-think.md b/.changeset/slow-jobs-think.md new file mode 100644 index 0000000000000..24d3bdba07aef --- /dev/null +++ b/.changeset/slow-jobs-think.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +fix: CRM integration mismatch on callback type vs code validation. Previously, callback was attempting to register a `LivechatStarted` event, however, our internal code was expecting a `LivechatStart` event, causing the hook to receive incomplete data diff --git a/apps/meteor/app/livechat/server/hooks/sendToCRM.ts b/apps/meteor/app/livechat/server/hooks/sendToCRM.ts index a39c1076169c8..5c3a2c0b54ab2 100644 --- a/apps/meteor/app/livechat/server/hooks/sendToCRM.ts +++ b/apps/meteor/app/livechat/server/hooks/sendToCRM.ts @@ -35,18 +35,28 @@ type OmnichannelRoomWithExtraFields = IOmnichannelRoom & { oldDepartmentId?: IOmnichannelRoom['departmentId']; }; +type CRMActions = + | 'LivechatSessionStart' + | 'LivechatSessionQueued' + | 'LivechatSession' + | 'LivechatSessionTaken' + | 'LivechatSessionForwarded' + | 'LivechatEdit' + | 'Message' + | 'LeadCapture'; + const msgNavType = 'livechat_navigation_history'; const msgClosingType = 'livechat-close'; -const isOmnichannelNavigationMessage = (message: IMessage): message is IOmnichannelSystemMessage => { +export const isOmnichannelNavigationMessage = (message: IMessage): message is IOmnichannelSystemMessage => { return message.t === msgNavType; }; -const isOmnichannelClosingMessage = (message: IMessage): message is IOmnichannelSystemMessage => { +export const isOmnichannelClosingMessage = (message: IMessage): message is IOmnichannelSystemMessage => { return message.t === msgClosingType; }; -const sendMessageType = (msgType: string): boolean => { +export const sendMessageType = (msgType: string): boolean => { switch (msgType) { case msgClosingType: return true; @@ -60,10 +70,10 @@ const sendMessageType = (msgType: string): boolean => { } }; -const getAdditionalFieldsByType = (type: string, room: OmnichannelRoomWithExtraFields): AdditionalFields => { +export const getAdditionalFieldsByType = (type: CRMActions, room: OmnichannelRoomWithExtraFields): AdditionalFields => { const { departmentId, servedBy, closedAt, closedBy, closer, oldServedBy, oldDepartmentId } = room; switch (type) { - case 'LivechatSessionStarted': + case 'LivechatSessionStart': case 'LivechatSessionQueued': return { departmentId }; case 'LivechatSession': @@ -78,7 +88,7 @@ const getAdditionalFieldsByType = (type: string, room: OmnichannelRoomWithExtraF }; async function sendToCRM( - type: string, + type: CRMActions, room: OmnichannelRoomWithExtraFields, includeMessages: boolean | IOmnichannelSystemMessage[] = true, ): Promise { diff --git a/apps/meteor/tests/unit/app/livechat/server/hooks/sendToCRM.tests.ts b/apps/meteor/tests/unit/app/livechat/server/hooks/sendToCRM.tests.ts new file mode 100644 index 0000000000000..074c7047ccafa --- /dev/null +++ b/apps/meteor/tests/unit/app/livechat/server/hooks/sendToCRM.tests.ts @@ -0,0 +1,123 @@ +import { expect } from 'chai'; +import p from 'proxyquire'; + +const resultObj = { + result: true, +}; + +const { sendMessageType, isOmnichannelNavigationMessage, isOmnichannelClosingMessage, getAdditionalFieldsByType } = p + .noCallThru() + .load('../../../../../../app/livechat/server/hooks/sendToCRM', { + '../../../settings/server': { + settings: { + get() { + return resultObj.result; + }, + }, + }, + '../../../utils/server/functions/normalizeMessageFileUpload': { + normalizeMessageFileUpload: (data: any) => data, + }, + '../lib/LivechatTyped': { + Livechat: {}, + }, + }); + +describe('[OC] Send TO CRM', () => { + describe('isOmnichannelNavigationMessage', () => { + it('should return true if the message is a navigation message', () => { + const message = { t: 'livechat_navigation_history' } as any; + expect(isOmnichannelNavigationMessage(message)).to.be.true; + }); + + it('should return false if the message is not a navigation message', () => { + const message = { t: 'livechat-close' } as any; + expect(isOmnichannelNavigationMessage(message)).to.be.false; + }); + }); + + describe('isOmnichannelClosingMessage', () => { + it('should return true if the message is a closing message', () => { + const message = { t: 'livechat-close' } as any; + expect(isOmnichannelClosingMessage(message)).to.be.true; + }); + + it('should return false if the message is not a closing message', () => { + const message = { t: 'livechat_navigation_history' } as any; + expect(isOmnichannelClosingMessage(message)).to.be.false; + }); + }); + + describe('sendMessageType', () => { + it('should return true if the message type is a closing message', () => { + expect(sendMessageType('livechat-close')).to.be.true; + }); + + it('should return true if the message type is a navigation message and the settings are enabled', () => { + expect(sendMessageType('livechat_navigation_history')).to.be.true; + }); + + it('should return false if the message type is a navigation message and the settings are disabled', () => { + resultObj.result = false; + expect(sendMessageType('livechat_navigation_history')).to.be.false; + }); + + it('should return false if the message type is not a closing or navigation message', () => { + expect(sendMessageType('message')).to.be.false; + }); + }); + + describe('getAdditionalFieldsByType', () => { + it('should return the correct fields for the LivechatSessionStart type', () => { + const room = { departmentId: 'departmentId' } as any; + expect(getAdditionalFieldsByType('LivechatSessionStart', room)).to.deep.equal({ departmentId: 'departmentId' }); + }); + + it('should return the correct fields for the LivechatSessionQueued type', () => { + const room = { departmentId: 'departmentId' } as any; + expect(getAdditionalFieldsByType('LivechatSessionQueued', room)).to.deep.equal({ departmentId: 'departmentId' }); + }); + + it('should return the correct fields for the LivechatSession type', () => { + const room = { + departmentId: 'departmentId', + servedBy: 'servedBy', + closedAt: 'closedAt', + closedBy: 'closedBy', + closer: 'closer', + } as any; + expect(getAdditionalFieldsByType('LivechatSession', room)).to.deep.equal({ + departmentId: 'departmentId', + servedBy: 'servedBy', + closedAt: 'closedAt', + closedBy: 'closedBy', + closer: 'closer', + }); + }); + + it('should return the correct fields for the LivechatSessionTaken type', () => { + const room = { departmentId: 'departmentId', servedBy: 'servedBy' } as any; + expect(getAdditionalFieldsByType('LivechatSessionTaken', room)).to.deep.equal({ departmentId: 'departmentId', servedBy: 'servedBy' }); + }); + + it('should return the correct fields for the LivechatSessionForwarded type', () => { + const room = { + departmentId: 'departmentId', + servedBy: 'servedBy', + oldDepartmentId: 'oldDepartmentId', + oldServedBy: 'oldServedBy', + } as any; + expect(getAdditionalFieldsByType('LivechatSessionForwarded', room)).to.deep.equal({ + departmentId: 'departmentId', + servedBy: 'servedBy', + oldDepartmentId: 'oldDepartmentId', + oldServedBy: 'oldServedBy', + }); + }); + + it('should return an empty object for an unknown type', () => { + const room = {} as any; + expect(getAdditionalFieldsByType('unknownType' as any, room)).to.deep.equal({}); + }); + }); +});