@webex/contact-center 3.10.0 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/dist/cc.js +42 -31
  2. package/dist/cc.js.map +1 -1
  3. package/dist/config.js.map +1 -1
  4. package/dist/constants.js.map +1 -1
  5. package/dist/index.js +17 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/logger-proxy.js.map +1 -1
  8. package/dist/metrics/MetricsManager.js +2 -1
  9. package/dist/metrics/MetricsManager.js.map +1 -1
  10. package/dist/metrics/behavioral-events.js +12 -0
  11. package/dist/metrics/behavioral-events.js.map +1 -1
  12. package/dist/metrics/constants.js +4 -0
  13. package/dist/metrics/constants.js.map +1 -1
  14. package/dist/services/AddressBook.js +2 -3
  15. package/dist/services/AddressBook.js.map +1 -1
  16. package/dist/services/EntryPoint.js +2 -3
  17. package/dist/services/EntryPoint.js.map +1 -1
  18. package/dist/services/Queue.js +2 -3
  19. package/dist/services/Queue.js.map +1 -1
  20. package/dist/services/WebCallingService.js +1 -1
  21. package/dist/services/WebCallingService.js.map +1 -1
  22. package/dist/services/agent/index.js +1 -2
  23. package/dist/services/agent/index.js.map +1 -1
  24. package/dist/services/agent/types.js +10 -0
  25. package/dist/services/agent/types.js.map +1 -1
  26. package/dist/services/config/Util.js.map +1 -1
  27. package/dist/services/config/constants.js.map +1 -1
  28. package/dist/services/config/index.js +1 -1
  29. package/dist/services/config/index.js.map +1 -1
  30. package/dist/services/config/types.js +2 -2
  31. package/dist/services/config/types.js.map +1 -1
  32. package/dist/services/constants.js.map +1 -1
  33. package/dist/services/core/Err.js.map +1 -1
  34. package/dist/services/core/GlobalTypes.js.map +1 -1
  35. package/dist/services/core/Utils.js +92 -74
  36. package/dist/services/core/Utils.js.map +1 -1
  37. package/dist/services/core/WebexRequest.js +1 -2
  38. package/dist/services/core/WebexRequest.js.map +1 -1
  39. package/dist/services/core/aqm-reqs.js +2 -3
  40. package/dist/services/core/aqm-reqs.js.map +1 -1
  41. package/dist/services/core/constants.js +17 -1
  42. package/dist/services/core/constants.js.map +1 -1
  43. package/dist/services/core/types.js.map +1 -1
  44. package/dist/services/core/websocket/WebSocketManager.js +11 -3
  45. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  46. package/dist/services/core/websocket/connection-service.js +1 -1
  47. package/dist/services/core/websocket/connection-service.js.map +1 -1
  48. package/dist/services/core/websocket/keepalive.worker.js.map +1 -1
  49. package/dist/services/core/websocket/types.js.map +1 -1
  50. package/dist/services/index.js +1 -1
  51. package/dist/services/index.js.map +1 -1
  52. package/dist/services/task/AutoWrapup.js +1 -1
  53. package/dist/services/task/AutoWrapup.js.map +1 -1
  54. package/dist/services/task/TaskManager.js +177 -56
  55. package/dist/services/task/TaskManager.js.map +1 -1
  56. package/dist/services/task/TaskUtils.js +122 -5
  57. package/dist/services/task/TaskUtils.js.map +1 -1
  58. package/dist/services/task/constants.js +3 -1
  59. package/dist/services/task/constants.js.map +1 -1
  60. package/dist/services/task/contact.js +0 -2
  61. package/dist/services/task/contact.js.map +1 -1
  62. package/dist/services/task/dialer.js.map +1 -1
  63. package/dist/services/task/index.js +78 -51
  64. package/dist/services/task/index.js.map +1 -1
  65. package/dist/services/task/types.js +377 -4
  66. package/dist/services/task/types.js.map +1 -1
  67. package/dist/types.js +5 -0
  68. package/dist/types.js.map +1 -1
  69. package/dist/utils/PageCache.js +1 -1
  70. package/dist/utils/PageCache.js.map +1 -1
  71. package/dist/webex-config.js.map +1 -1
  72. package/dist/webex.js +2 -2
  73. package/dist/webex.js.map +1 -1
  74. package/package.json +9 -9
  75. package/src/cc.ts +50 -41
  76. package/src/index.ts +1 -0
  77. package/src/metrics/behavioral-events.ts +12 -0
  78. package/src/metrics/constants.ts +4 -0
  79. package/src/services/config/types.ts +2 -2
  80. package/src/services/core/Utils.ts +101 -85
  81. package/src/services/core/constants.ts +16 -0
  82. package/src/services/core/websocket/WebSocketManager.ts +10 -1
  83. package/src/services/task/TaskManager.ts +204 -36
  84. package/src/services/task/TaskUtils.ts +145 -5
  85. package/src/services/task/constants.ts +2 -0
  86. package/src/services/task/index.ts +88 -74
  87. package/src/services/task/types.ts +72 -15
  88. package/test/unit/spec/cc.ts +1 -0
  89. package/test/unit/spec/metrics/behavioral-events.ts +14 -0
  90. package/test/unit/spec/services/core/Utils.ts +262 -31
  91. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +20 -0
  92. package/test/unit/spec/services/task/TaskManager.ts +748 -5
  93. package/test/unit/spec/services/task/TaskUtils.ts +311 -9
  94. package/test/unit/spec/services/task/index.ts +440 -68
  95. package/umd/contact-center.min.js +2 -2
  96. package/umd/contact-center.min.js.map +1 -1
  97. package/dist/types/cc.d.ts +0 -756
  98. package/dist/types/config.d.ts +0 -66
  99. package/dist/types/constants.d.ts +0 -46
  100. package/dist/types/index.d.ts +0 -183
  101. package/dist/types/logger-proxy.d.ts +0 -71
  102. package/dist/types/metrics/MetricsManager.d.ts +0 -223
  103. package/dist/types/metrics/behavioral-events.d.ts +0 -29
  104. package/dist/types/metrics/constants.d.ts +0 -151
  105. package/dist/types/services/AddressBook.d.ts +0 -74
  106. package/dist/types/services/EntryPoint.d.ts +0 -67
  107. package/dist/types/services/Queue.d.ts +0 -76
  108. package/dist/types/services/WebCallingService.d.ts +0 -1
  109. package/dist/types/services/agent/index.d.ts +0 -46
  110. package/dist/types/services/agent/types.d.ts +0 -413
  111. package/dist/types/services/config/Util.d.ts +0 -19
  112. package/dist/types/services/config/constants.d.ts +0 -237
  113. package/dist/types/services/config/index.d.ts +0 -168
  114. package/dist/types/services/config/types.d.ts +0 -1134
  115. package/dist/types/services/constants.d.ts +0 -97
  116. package/dist/types/services/core/Err.d.ts +0 -119
  117. package/dist/types/services/core/GlobalTypes.d.ts +0 -58
  118. package/dist/types/services/core/Utils.d.ts +0 -75
  119. package/dist/types/services/core/WebexRequest.d.ts +0 -22
  120. package/dist/types/services/core/aqm-reqs.d.ts +0 -16
  121. package/dist/types/services/core/constants.d.ts +0 -85
  122. package/dist/types/services/core/types.d.ts +0 -47
  123. package/dist/types/services/core/websocket/WebSocketManager.d.ts +0 -34
  124. package/dist/types/services/core/websocket/connection-service.d.ts +0 -27
  125. package/dist/types/services/core/websocket/keepalive.worker.d.ts +0 -2
  126. package/dist/types/services/core/websocket/types.d.ts +0 -37
  127. package/dist/types/services/index.d.ts +0 -52
  128. package/dist/types/services/task/AutoWrapup.d.ts +0 -40
  129. package/dist/types/services/task/TaskManager.d.ts +0 -1
  130. package/dist/types/services/task/TaskUtils.d.ts +0 -28
  131. package/dist/types/services/task/constants.d.ts +0 -69
  132. package/dist/types/services/task/contact.d.ts +0 -69
  133. package/dist/types/services/task/dialer.d.ts +0 -28
  134. package/dist/types/services/task/index.d.ts +0 -632
  135. package/dist/types/services/task/types.d.ts +0 -1243
  136. package/dist/types/types.d.ts +0 -614
  137. package/dist/types/utils/PageCache.d.ts +0 -173
  138. package/dist/types/webex-config.d.ts +0 -53
  139. package/dist/types/webex.d.ts +0 -7
@@ -39,7 +39,8 @@ describe('Task', () => {
39
39
  let loggerInfoSpy;
40
40
  let loggerLogSpy;
41
41
  let loggerErrorSpy;
42
- let getDestinationAgentIdSpy;
42
+ let calculateDestAgentIdSpy;
43
+ let calculateDestTypeSpy;
43
44
 
44
45
  const taskId = '0ae913a4-c857-4705-8d49-76dd3dde75e4';
45
46
  const mockTrack = {} as MediaStreamTrack;
@@ -119,6 +120,32 @@ describe('Task', () => {
119
120
  interaction: {
120
121
  mediaType: 'telephony',
121
122
  mainInteractionId: taskId,
123
+ participants: {
124
+ '723a8ffb-a26e-496d-b14a-ff44fb83b64f': {
125
+ pType: 'Agent',
126
+ type: 'AGENT',
127
+ id: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
128
+ hasLeft: false,
129
+ hasJoined: true,
130
+ isWrapUp: false,
131
+ },
132
+ 'f520d6b5-28ad-4f2f-b83e-781bb64af617': {
133
+ pType: 'Agent',
134
+ type: 'AGENT',
135
+ id: 'f520d6b5-28ad-4f2f-b83e-781bb64af617',
136
+ hasLeft: false,
137
+ hasJoined: true,
138
+ isWrapUp: false,
139
+ },
140
+ 'ebeb893b-ba67-4f36-8418-95c7492b28c2': {
141
+ pType: 'Agent',
142
+ type: 'AGENT',
143
+ id: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
144
+ hasLeft: false,
145
+ hasJoined: true,
146
+ isWrapUp: false,
147
+ },
148
+ },
122
149
  media: {
123
150
  '58a45567-4e61-4f4b-a580-5bc86357bef0': {
124
151
  holdTimestamp: null,
@@ -145,13 +172,18 @@ describe('Task', () => {
145
172
  },
146
173
  };
147
174
 
148
- // Mock destination agent id resolution from participants
149
- getDestinationAgentIdSpy = jest
150
- .spyOn(Utils, 'getDestinationAgentId')
151
- .mockReturnValue(taskDataMock.destAgentId);
175
+ // Mock calculateDestAgentId to return the expected destination agent
176
+ calculateDestAgentIdSpy = jest.spyOn(Utils, 'calculateDestAgentId').mockReturnValue(taskDataMock.destAgentId);
177
+
178
+ // Mock calculateDestType to return 'agent' by default
179
+ calculateDestTypeSpy = jest.spyOn(Utils, 'calculateDestType').mockReturnValue('agent');
152
180
 
153
- // Create an instance of Task
154
- task = new Task(contactMock, webCallingService, taskDataMock);
181
+ // Create an instance of Task with wrapupData and agentId
182
+ task = new Task(contactMock, webCallingService, taskDataMock, {
183
+ wrapUpProps: { wrapUpReasonList: [] },
184
+ autoWrapEnabled: false,
185
+ autoWrapAfterSeconds: 0
186
+ }, taskDataMock.agentId);
155
187
 
156
188
  // Mock navigator.mediaDevices
157
189
  global.navigator.mediaDevices = {
@@ -625,6 +657,40 @@ describe('Task', () => {
625
657
  );
626
658
  });
627
659
 
660
+ it('should hold the task with custom mediaResourceId and return the expected response', async () => {
661
+ const customMediaResourceId = 'custom-media-resource-id-123';
662
+ const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
663
+ contactMock.hold.mockResolvedValue(expectedResponse);
664
+
665
+ const response = await task.hold(customMediaResourceId);
666
+
667
+ expect(contactMock.hold).toHaveBeenCalledWith({
668
+ interactionId: taskId,
669
+ data: {mediaResourceId: customMediaResourceId},
670
+ });
671
+ expect(response).toEqual(expectedResponse);
672
+ expect(loggerInfoSpy).toHaveBeenCalledWith(`Holding task`, {
673
+ module: TASK_FILE,
674
+ method: 'hold',
675
+ interactionId: task.data.interactionId,
676
+ });
677
+ expect(loggerLogSpy).toHaveBeenCalledWith(`Task placed on hold successfully`, {
678
+ module: TASK_FILE,
679
+ method: 'hold',
680
+ interactionId: task.data.interactionId,
681
+ });
682
+ expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
683
+ 1,
684
+ METRIC_EVENT_NAMES.TASK_HOLD_SUCCESS,
685
+ {
686
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(expectedResponse),
687
+ taskId: taskDataMock.interactionId,
688
+ mediaResourceId: customMediaResourceId,
689
+ },
690
+ ['operational', 'behavioral']
691
+ );
692
+ });
693
+
628
694
  it('should handle errors in hold method', async () => {
629
695
  const error = {details: (global as any).makeFailure('Hold Failed')};
630
696
  contactMock.hold.mockImplementation(() => {
@@ -654,6 +720,36 @@ describe('Task', () => {
654
720
  );
655
721
  });
656
722
 
723
+ it('should handle errors in hold method with custom mediaResourceId', async () => {
724
+ const customMediaResourceId = 'custom-media-resource-id-456';
725
+ const error = {details: (global as any).makeFailure('Hold Failed with custom mediaResourceId')};
726
+ contactMock.hold.mockImplementation(() => {
727
+ throw error;
728
+ });
729
+
730
+ await expect(task.hold(customMediaResourceId)).rejects.toThrow(error.details.data.reason);
731
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'hold', TASK_FILE);
732
+ const expectedTaskErrorFieldsHold = {
733
+ trackingId: error.details.trackingId,
734
+ errorMessage: error.details.data.reason,
735
+ errorType: '',
736
+ errorData: '',
737
+ reasonCode: 0,
738
+ };
739
+ expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
740
+ 1,
741
+ METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
742
+ {
743
+ taskId: taskDataMock.interactionId,
744
+ mediaResourceId: customMediaResourceId,
745
+ error: error.toString(),
746
+ ...expectedTaskErrorFieldsHold,
747
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
748
+ },
749
+ ['operational', 'behavioral']
750
+ );
751
+ });
752
+
657
753
  it('should resume the task and return the expected response', async () => {
658
754
  const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
659
755
  contactMock.unHold.mockResolvedValue(expectedResponse);
@@ -678,6 +774,29 @@ describe('Task', () => {
678
774
  );
679
775
  });
680
776
 
777
+ it('should resume the task with custom mediaResourceId and return the expected response', async () => {
778
+ const customMediaResourceId = 'custom-media-resource-id-789';
779
+ const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
780
+ contactMock.unHold.mockResolvedValue(expectedResponse);
781
+ const response = await task.resume(customMediaResourceId);
782
+ expect(contactMock.unHold).toHaveBeenCalledWith({
783
+ interactionId: taskId,
784
+ data: {mediaResourceId: customMediaResourceId},
785
+ });
786
+ expect(response).toEqual(expectedResponse);
787
+ expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
788
+ 1,
789
+ METRIC_EVENT_NAMES.TASK_RESUME_SUCCESS,
790
+ {
791
+ taskId: taskDataMock.interactionId,
792
+ mainInteractionId: taskDataMock.interaction.mainInteractionId,
793
+ mediaResourceId: customMediaResourceId,
794
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(expectedResponse),
795
+ },
796
+ ['operational', 'behavioral']
797
+ );
798
+ });
799
+
681
800
  it('should handle errors in resume method', async () => {
682
801
  const error = {details: (global as any).makeFailure('Resume Failed')};
683
802
  contactMock.unHold.mockImplementation(() => {
@@ -709,6 +828,36 @@ describe('Task', () => {
709
828
  );
710
829
  });
711
830
 
831
+ it('should handle errors in resume method with custom mediaResourceId', async () => {
832
+ const customMediaResourceId = 'custom-media-resource-id-999';
833
+ const error = {details: (global as any).makeFailure('Resume Failed with custom mediaResourceId')};
834
+ contactMock.unHold.mockImplementation(() => {
835
+ throw error;
836
+ });
837
+
838
+ await expect(task.resume(customMediaResourceId)).rejects.toThrow(error.details.data.reason);
839
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'resume', TASK_FILE);
840
+ const expectedTaskErrorFieldsResume = {
841
+ trackingId: error.details.trackingId,
842
+ errorMessage: error.details.data.reason,
843
+ errorType: '',
844
+ errorData: '',
845
+ reasonCode: 0,
846
+ };
847
+ expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
848
+ 1,
849
+ METRIC_EVENT_NAMES.TASK_RESUME_FAILED,
850
+ {
851
+ taskId: taskDataMock.interactionId,
852
+ mainInteractionId: taskDataMock.interaction.mainInteractionId,
853
+ mediaResourceId: customMediaResourceId,
854
+ ...expectedTaskErrorFieldsResume,
855
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
856
+ },
857
+ ['operational', 'behavioral']
858
+ );
859
+ });
860
+
712
861
  it('should initiate a consult call and return the expected response', async () => {
713
862
  const consultPayload = {
714
863
  to: '1234',
@@ -876,15 +1025,16 @@ describe('Task', () => {
876
1025
  );
877
1026
  });
878
1027
 
879
- it('should send DIALNUMBER when task destinationType is DN during consultTransfer', async () => {
1028
+ it('should send DIALNUMBER when calculateDestType returns dialNumber during consultTransfer', async () => {
880
1029
  const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
881
1030
  contactMock.consultTransfer.mockResolvedValue(expectedResponse);
882
1031
 
883
- // Ensure task data indicates DN scenario
884
- task.data.destinationType = 'DN' as unknown as string;
1032
+ // Mock calculateDestType to return dialNumber
1033
+ calculateDestTypeSpy.mockReturnValue(CONSULT_TRANSFER_DESTINATION_TYPE.DIALNUMBER);
885
1034
 
886
1035
  await task.consultTransfer();
887
1036
 
1037
+ expect(calculateDestTypeSpy).toHaveBeenCalledWith(taskDataMock.interaction, taskDataMock.agentId);
888
1038
  expect(contactMock.consultTransfer).toHaveBeenCalledWith({
889
1039
  interactionId: taskId,
890
1040
  data: {
@@ -894,15 +1044,16 @@ describe('Task', () => {
894
1044
  });
895
1045
  });
896
1046
 
897
- it('should send ENTRYPOINT when task destinationType is EPDN during consultTransfer', async () => {
1047
+ it('should send ENTRYPOINT when calculateDestType returns entryPoint during consultTransfer', async () => {
898
1048
  const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
899
1049
  contactMock.consultTransfer.mockResolvedValue(expectedResponse);
900
1050
 
901
- // Ensure task data indicates EP/EPDN scenario
902
- task.data.destinationType = 'EPDN' as unknown as string;
1051
+ // Mock calculateDestType to return entryPoint
1052
+ calculateDestTypeSpy.mockReturnValue(CONSULT_TRANSFER_DESTINATION_TYPE.ENTRYPOINT);
903
1053
 
904
1054
  await task.consultTransfer();
905
1055
 
1056
+ expect(calculateDestTypeSpy).toHaveBeenCalledWith(taskDataMock.interaction, taskDataMock.agentId);
906
1057
  expect(contactMock.consultTransfer).toHaveBeenCalledWith({
907
1058
  interactionId: taskId,
908
1059
  data: {
@@ -912,15 +1063,16 @@ describe('Task', () => {
912
1063
  });
913
1064
  });
914
1065
 
915
- it('should keep AGENT when task destinationType is neither DN nor EPDN/ENTRYPOINT', async () => {
1066
+ it('should use AGENT when calculateDestType returns agent during consultTransfer', async () => {
916
1067
  const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
917
1068
  contactMock.consultTransfer.mockResolvedValue(expectedResponse);
918
1069
 
919
- // Ensure task data indicates non-DN and non-EP/EPDN scenario
920
- task.data.destinationType = 'SOMETHING_ELSE' as unknown as string;
1070
+ // Mock calculateDestType to return agent (default behavior)
1071
+ calculateDestTypeSpy.mockReturnValue(CONSULT_TRANSFER_DESTINATION_TYPE.AGENT);
921
1072
 
922
1073
  await task.consultTransfer();
923
1074
 
1075
+ expect(calculateDestTypeSpy).toHaveBeenCalledWith(taskDataMock.interaction, taskDataMock.agentId);
924
1076
  expect(contactMock.consultTransfer).toHaveBeenCalledWith({
925
1077
  interactionId: taskId,
926
1078
  data: {
@@ -957,7 +1109,11 @@ describe('Task', () => {
957
1109
  const taskWithoutDestAgentId = new Task(contactMock, webCallingService, {
958
1110
  ...taskDataMock,
959
1111
  destAgentId: undefined,
960
- });
1112
+ }, {
1113
+ wrapUpProps: { wrapUpReasonList: [] },
1114
+ autoWrapEnabled: false,
1115
+ autoWrapAfterSeconds: 0
1116
+ }, taskDataMock.agentId);
961
1117
 
962
1118
  const queueConsultTransferPayload: ConsultTransferPayLoad = {
963
1119
  to: 'some-queue-id',
@@ -965,61 +1121,123 @@ describe('Task', () => {
965
1121
  };
966
1122
 
967
1123
  // For this negative case, ensure computed destination is empty
968
- getDestinationAgentIdSpy.mockReturnValueOnce('');
1124
+ calculateDestAgentIdSpy.mockReturnValueOnce('');
969
1125
 
970
1126
  await expect(
971
1127
  taskWithoutDestAgentId.consultTransfer(queueConsultTransferPayload)
972
- ).rejects.toThrow('Error while performing consultTransfer');
1128
+ ).rejects.toThrow('No agent has accepted this queue consult yet');
973
1129
  });
974
1130
 
975
- it('should handle errors in consult transfer', async () => {
976
- const consultPayload = {
977
- destination: '1234',
978
- destinationType: DESTINATION_TYPE.AGENT,
979
- };
980
- const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
981
- contactMock.consult.mockResolvedValue(expectedResponse);
1131
+ describe('consultTransfer', () => {
1132
+ it('should successfully perform consult transfer with agent destination', async () => {
1133
+ const expectedResponse: TaskResponse = {
1134
+ data: {interactionId: taskId},
1135
+ trackingId: 'test-tracking-id'
1136
+ } as AgentContact;
1137
+ contactMock.consultTransfer.mockResolvedValue(expectedResponse);
1138
+
1139
+ calculateDestTypeSpy.mockReturnValue(CONSULT_TRANSFER_DESTINATION_TYPE.AGENT);
982
1140
 
983
- const response = await task.consult(consultPayload);
1141
+ const result = await task.consultTransfer();
984
1142
 
985
- expect(contactMock.consult).toHaveBeenCalledWith({interactionId: taskId, data: consultPayload});
986
- expect(response).toEqual(expectedResponse);
1143
+ expect(calculateDestAgentIdSpy).toHaveBeenCalledWith(taskDataMock.interaction, taskDataMock.agentId);
1144
+ expect(calculateDestTypeSpy).toHaveBeenCalledWith(taskDataMock.interaction, taskDataMock.agentId);
1145
+ expect(contactMock.consultTransfer).toHaveBeenCalledWith({
1146
+ interactionId: taskId,
1147
+ data: {
1148
+ to: taskDataMock.destAgentId,
1149
+ destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
1150
+ },
1151
+ });
1152
+ expect(result).toEqual(expectedResponse);
1153
+ expect(loggerInfoSpy).toHaveBeenCalledWith(
1154
+ `Initiating consult transfer to ${taskDataMock.destAgentId}`,
1155
+ {
1156
+ module: TASK_FILE,
1157
+ method: 'consultTransfer',
1158
+ interactionId: taskId,
1159
+ }
1160
+ );
1161
+ expect(loggerLogSpy).toHaveBeenCalledWith(
1162
+ `Consult transfer completed successfully to ${taskDataMock.destAgentId}`,
1163
+ {
1164
+ module: TASK_FILE,
1165
+ method: 'consultTransfer',
1166
+ trackingId: expectedResponse.trackingId,
1167
+ interactionId: taskId,
1168
+ }
1169
+ );
1170
+ });
1171
+
1172
+ it('should track metrics on successful consult transfer', async () => {
1173
+ const expectedResponse: TaskResponse = {
1174
+ data: {interactionId: taskId},
1175
+ trackingId: 'test-tracking-id'
1176
+ } as AgentContact;
1177
+ contactMock.consultTransfer.mockResolvedValue(expectedResponse);
1178
+
1179
+ await task.consultTransfer();
1180
+
1181
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
1182
+ METRIC_EVENT_NAMES.TASK_TRANSFER_SUCCESS,
1183
+ {
1184
+ taskId: taskDataMock.interactionId,
1185
+ destination: taskDataMock.destAgentId,
1186
+ destinationType: 'agent',
1187
+ isConsultTransfer: true,
1188
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(expectedResponse),
1189
+ },
1190
+ ['operational', 'behavioral', 'business']
1191
+ );
1192
+ });
987
1193
 
988
- const error = {details: (global as any).makeFailure('Consult Transfer Failed')};
989
- contactMock.consultTransfer.mockImplementation(() => {
990
- throw error;
1194
+ it('should throw error when no destination agent is found', async () => {
1195
+ calculateDestAgentIdSpy.mockReturnValue('');
1196
+
1197
+ await expect(task.consultTransfer()).rejects.toThrow('No agent has accepted this queue consult yet');
1198
+
1199
+ expect(contactMock.consultTransfer).not.toHaveBeenCalled();
991
1200
  });
992
1201
 
993
- const consultTransferPayload: ConsultTransferPayLoad = {
994
- to: '1234',
995
- destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
996
- };
1202
+ it('should handle and rethrow contact method errors', async () => {
1203
+ const mockError = new Error('Consult Transfer Failed');
1204
+ contactMock.consultTransfer.mockRejectedValue(mockError);
1205
+ generateTaskErrorObjectSpy.mockReturnValue(mockError);
997
1206
 
998
- await expect(task.consultTransfer(consultTransferPayload)).rejects.toThrow(
999
- error.details.data.reason
1000
- );
1001
- expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'consultTransfer', TASK_FILE);
1002
- const expectedTaskErrorFieldsConsultTransfer = {
1003
- trackingId: error.details.trackingId,
1004
- errorMessage: error.details.data.reason,
1005
- errorType: '',
1006
- errorData: '',
1007
- reasonCode: 0,
1008
- };
1009
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1010
- 2,
1011
- METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
1012
- {
1013
- taskId: taskDataMock.interactionId,
1014
- destination: taskDataMock.destAgentId,
1015
- destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
1016
- isConsultTransfer: true,
1017
- error: error.toString(),
1018
- ...expectedTaskErrorFieldsConsultTransfer,
1019
- ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
1020
- },
1021
- ['operational', 'behavioral', 'business']
1022
- );
1207
+ await expect(task.consultTransfer()).rejects.toThrow('Consult Transfer Failed');
1208
+
1209
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(mockError, 'consultTransfer', TASK_FILE);
1210
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
1211
+ METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
1212
+ expect.objectContaining({
1213
+ taskId: taskDataMock.interactionId,
1214
+ destination: taskDataMock.destAgentId,
1215
+ destinationType: 'agent',
1216
+ isConsultTransfer: true,
1217
+ error: mockError.toString(),
1218
+ }),
1219
+ ['operational', 'behavioral', 'business']
1220
+ );
1221
+ });
1222
+
1223
+ it('should dynamically calculate destAgentId when not available', async () => {
1224
+ const consultedAgentId = 'dynamic-agent-123';
1225
+ calculateDestAgentIdSpy.mockReturnValue(consultedAgentId);
1226
+
1227
+ const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
1228
+ contactMock.consultTransfer.mockResolvedValue(expectedResponse);
1229
+
1230
+ await task.consultTransfer();
1231
+
1232
+ expect(calculateDestAgentIdSpy).toHaveBeenCalledWith(taskDataMock.interaction, taskDataMock.agentId);
1233
+ expect(contactMock.consultTransfer).toHaveBeenCalledWith({
1234
+ interactionId: taskId,
1235
+ data: {
1236
+ to: consultedAgentId,
1237
+ destinationType: 'agent',
1238
+ },
1239
+ });
1240
+ });
1023
1241
  });
1024
1242
 
1025
1243
  it('should do vteamTransfer if destinationType is queue and return the expected response', async () => {
@@ -1642,12 +1860,6 @@ describe('Task', () => {
1642
1860
  conferenceTransfer: jest.fn(),
1643
1861
  };
1644
1862
 
1645
- // Re-setup the getDestinationAgentId spy for conference methods
1646
- getDestinationAgentIdSpy = jest
1647
- .spyOn(Utils, 'getDestinationAgentId')
1648
- .mockReturnValue(taskDataMock.destAgentId);
1649
-
1650
-
1651
1863
  task = new Task(contactMock, webCallingService, taskDataMock, {
1652
1864
  wrapUpProps: { wrapUpReasonList: [] },
1653
1865
  autoWrapEnabled: false,
@@ -1671,7 +1883,7 @@ describe('Task', () => {
1671
1883
  interactionId: taskId,
1672
1884
  data: {
1673
1885
  agentId: taskDataMock.agentId, // From task data agent ID
1674
- to: taskDataMock.destAgentId, // From getDestinationAgentId() using task participants
1886
+ to: taskDataMock.destAgentId, // From calculateDestAgentId() using task participants
1675
1887
  destinationType: 'agent', // From consultation data
1676
1888
  },
1677
1889
  });
@@ -1713,6 +1925,166 @@ describe('Task', () => {
1713
1925
  interactionId: taskId,
1714
1926
  });
1715
1927
  });
1928
+
1929
+ it('should dynamically calculate destAgentId from participants when this.data.destAgentId is null', async () => {
1930
+ // Simulate scenario where destAgentId is not preserved (e.g., after hold/unhold)
1931
+ task.data.destAgentId = null;
1932
+
1933
+ const consultedAgentId = 'consulted-agent-123';
1934
+ calculateDestAgentIdSpy.mockReturnValue(consultedAgentId);
1935
+
1936
+ const mockResponse = {
1937
+ trackingId: 'test-tracking-dynamic',
1938
+ interactionId: taskId,
1939
+ };
1940
+ contactMock.consultConference.mockResolvedValue(mockResponse);
1941
+
1942
+ const result = await task.consultConference();
1943
+
1944
+ // Verify calculateDestAgentId was called to dynamically calculate the destination
1945
+ expect(calculateDestAgentIdSpy).toHaveBeenCalledWith(
1946
+ taskDataMock.interaction,
1947
+ taskDataMock.agentId
1948
+ );
1949
+
1950
+ // Verify the conference was called with the dynamically calculated destAgentId
1951
+ expect(contactMock.consultConference).toHaveBeenCalledWith({
1952
+ interactionId: taskId,
1953
+ data: {
1954
+ agentId: taskDataMock.agentId,
1955
+ to: consultedAgentId, // Dynamically calculated value
1956
+ destinationType: 'agent',
1957
+ },
1958
+ });
1959
+ expect(result).toEqual(mockResponse);
1960
+ });
1961
+
1962
+ it('should throw error when no destination agent is found (queue consult not accepted)', async () => {
1963
+ // Simulate queue consult scenario where no agent has accepted yet
1964
+ calculateDestAgentIdSpy.mockReturnValue(''); // No agent found
1965
+
1966
+ await expect(task.consultConference()).rejects.toThrow('No agent has accepted this queue consult yet');
1967
+
1968
+ // Verify the conference was NOT called
1969
+ expect(contactMock.consultConference).not.toHaveBeenCalled();
1970
+ });
1971
+
1972
+ it('should calculate destination type from participant type for regular agents', async () => {
1973
+ const destAgentId = 'consulted-agent-456';
1974
+
1975
+ calculateDestAgentIdSpy = jest.spyOn(Utils, 'calculateDestAgentId').mockReturnValue(destAgentId);
1976
+ calculateDestTypeSpy = jest.spyOn(Utils, 'calculateDestType').mockReturnValue('agent');
1977
+
1978
+ const mockResponse = {trackingId: 'test-tracking-id', interactionId: taskId};
1979
+ contactMock.consultConference.mockResolvedValue(mockResponse);
1980
+
1981
+ await task.consultConference();
1982
+
1983
+ expect(calculateDestTypeSpy).toHaveBeenCalledWith(
1984
+ task.data.interaction,
1985
+ taskDataMock.agentId
1986
+ );
1987
+
1988
+ expect(contactMock.consultConference).toHaveBeenCalledWith({
1989
+ interactionId: taskId,
1990
+ data: {
1991
+ agentId: taskDataMock.agentId,
1992
+ to: destAgentId,
1993
+ destinationType: 'agent',
1994
+ },
1995
+ });
1996
+ });
1997
+
1998
+ it('should use DN destination type for dial number participants', async () => {
1999
+ const destAgentId = 'dn-uuid-123';
2000
+
2001
+ calculateDestAgentIdSpy = jest.spyOn(Utils, 'calculateDestAgentId').mockReturnValue(destAgentId);
2002
+ calculateDestTypeSpy = jest.spyOn(Utils, 'calculateDestType').mockReturnValue('dialNumber');
2003
+
2004
+ const mockResponse = {trackingId: 'test-tracking-id-dn', interactionId: taskId};
2005
+ contactMock.consultConference.mockResolvedValue(mockResponse);
2006
+
2007
+ await task.consultConference();
2008
+
2009
+ expect(contactMock.consultConference).toHaveBeenCalledWith({
2010
+ interactionId: taskId,
2011
+ data: {
2012
+ agentId: taskDataMock.agentId,
2013
+ to: destAgentId,
2014
+ destinationType: 'dialNumber',
2015
+ },
2016
+ });
2017
+ });
2018
+
2019
+ it('should use EpDn destination type for entry point dial number participants', async () => {
2020
+ const destAgentId = 'epdn-uuid-456';
2021
+
2022
+ calculateDestAgentIdSpy = jest.spyOn(Utils, 'calculateDestAgentId').mockReturnValue(destAgentId);
2023
+ calculateDestTypeSpy = jest.spyOn(Utils, 'calculateDestType').mockReturnValue('entryPoint');
2024
+
2025
+ const mockResponse = {trackingId: 'test-tracking-id-epdn', interactionId: taskId};
2026
+ contactMock.consultConference.mockResolvedValue(mockResponse);
2027
+
2028
+ await task.consultConference();
2029
+
2030
+ expect(contactMock.consultConference).toHaveBeenCalledWith({
2031
+ interactionId: taskId,
2032
+ data: {
2033
+ agentId: taskDataMock.agentId,
2034
+ to: destAgentId,
2035
+ destinationType: 'entryPoint',
2036
+ },
2037
+ });
2038
+ });
2039
+
2040
+ it('should fall back to task.data.destinationType when calculateDestType returns empty', async () => {
2041
+ const destAgentId = 'consulted-agent-789';
2042
+
2043
+ calculateDestAgentIdSpy = jest.spyOn(Utils, 'calculateDestAgentId').mockReturnValue(destAgentId);
2044
+ calculateDestTypeSpy = jest.spyOn(Utils, 'calculateDestType').mockReturnValue(''); // No type found
2045
+
2046
+ task.data.destinationType = 'EPDN';
2047
+
2048
+ const mockResponse = {trackingId: 'test-tracking-id-fallback', interactionId: taskId};
2049
+ contactMock.consultConference.mockResolvedValue(mockResponse);
2050
+
2051
+ await task.consultConference();
2052
+
2053
+ expect(contactMock.consultConference).toHaveBeenCalledWith({
2054
+ interactionId: taskId,
2055
+ data: {
2056
+ agentId: taskDataMock.agentId,
2057
+ to: destAgentId,
2058
+ destinationType: 'EPDN', // Falls back to task.data.destinationType
2059
+ },
2060
+ });
2061
+ });
2062
+
2063
+ it('should handle CBT scenarios with correct destination type', async () => {
2064
+ const destAgentId = 'agent-cbt-uuid';
2065
+
2066
+ calculateDestAgentIdSpy = jest.spyOn(Utils, 'calculateDestAgentId').mockReturnValue(destAgentId);
2067
+ calculateDestTypeSpy = jest.spyOn(Utils, 'calculateDestType').mockReturnValue('dialNumber');
2068
+
2069
+ const mockResponse = {trackingId: 'test-tracking-id-cbt', interactionId: taskId};
2070
+ contactMock.consultConference.mockResolvedValue(mockResponse);
2071
+
2072
+ await task.consultConference();
2073
+
2074
+ expect(calculateDestTypeSpy).toHaveBeenCalledWith(
2075
+ task.data.interaction,
2076
+ taskDataMock.agentId
2077
+ );
2078
+
2079
+ expect(contactMock.consultConference).toHaveBeenCalledWith({
2080
+ interactionId: taskId,
2081
+ data: {
2082
+ agentId: taskDataMock.agentId,
2083
+ to: destAgentId,
2084
+ destinationType: 'dialNumber', // dialNumber for CBT scenarios
2085
+ },
2086
+ });
2087
+ });
1716
2088
  });
1717
2089
 
1718
2090
  describe('exitConference', () => {