@webex/contact-center 3.12.0-next.8 → 3.12.0-task-refactor.1

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 (200) hide show
  1. package/AGENTS.md +438 -0
  2. package/ai-docs/README.md +131 -0
  3. package/ai-docs/RULES.md +455 -0
  4. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  5. package/ai-docs/patterns/testing-patterns.md +480 -0
  6. package/ai-docs/patterns/typescript-patterns.md +365 -0
  7. package/ai-docs/templates/README.md +102 -0
  8. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  9. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  10. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  11. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  12. package/ai-docs/templates/new-method/00-master.md +80 -0
  13. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  14. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  15. package/ai-docs/templates/new-method/03-tests.md +201 -0
  16. package/ai-docs/templates/new-method/04-validation.md +141 -0
  17. package/ai-docs/templates/new-service/00-master.md +109 -0
  18. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  19. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  20. package/ai-docs/templates/new-service/03-integration.md +178 -0
  21. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  22. package/ai-docs/templates/new-service/05-validation.md +145 -0
  23. package/dist/cc.js +65 -123
  24. package/dist/cc.js.map +1 -1
  25. package/dist/constants.js +13 -2
  26. package/dist/constants.js.map +1 -1
  27. package/dist/index.js +13 -5
  28. package/dist/index.js.map +1 -1
  29. package/dist/metrics/behavioral-events.js +26 -13
  30. package/dist/metrics/behavioral-events.js.map +1 -1
  31. package/dist/metrics/constants.js +7 -6
  32. package/dist/metrics/constants.js.map +1 -1
  33. package/dist/services/ApiAiAssistant.js +0 -3
  34. package/dist/services/ApiAiAssistant.js.map +1 -1
  35. package/dist/services/config/Util.js +2 -3
  36. package/dist/services/config/Util.js.map +1 -1
  37. package/dist/services/config/types.js +16 -14
  38. package/dist/services/config/types.js.map +1 -1
  39. package/dist/services/constants.js +0 -1
  40. package/dist/services/constants.js.map +1 -1
  41. package/dist/services/core/Err.js.map +1 -1
  42. package/dist/services/core/Utils.js +79 -55
  43. package/dist/services/core/Utils.js.map +1 -1
  44. package/dist/services/core/aqm-reqs.js +17 -92
  45. package/dist/services/core/aqm-reqs.js.map +1 -1
  46. package/dist/services/core/websocket/WebSocketManager.js +5 -25
  47. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  48. package/dist/services/core/websocket/types.js.map +1 -1
  49. package/dist/services/index.js +1 -2
  50. package/dist/services/index.js.map +1 -1
  51. package/dist/services/task/Task.js +644 -0
  52. package/dist/services/task/Task.js.map +1 -0
  53. package/dist/services/task/TaskFactory.js +45 -0
  54. package/dist/services/task/TaskFactory.js.map +1 -0
  55. package/dist/services/task/TaskManager.js +556 -532
  56. package/dist/services/task/TaskManager.js.map +1 -1
  57. package/dist/services/task/TaskUtils.js +132 -28
  58. package/dist/services/task/TaskUtils.js.map +1 -1
  59. package/dist/services/task/constants.js +7 -6
  60. package/dist/services/task/constants.js.map +1 -1
  61. package/dist/services/task/dialer.js +0 -51
  62. package/dist/services/task/dialer.js.map +1 -1
  63. package/dist/services/task/digital/Digital.js +77 -0
  64. package/dist/services/task/digital/Digital.js.map +1 -0
  65. package/dist/services/task/state-machine/TaskStateMachine.js +634 -0
  66. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  67. package/dist/services/task/state-machine/actions.js +366 -0
  68. package/dist/services/task/state-machine/actions.js.map +1 -0
  69. package/dist/services/task/state-machine/constants.js +139 -0
  70. package/dist/services/task/state-machine/constants.js.map +1 -0
  71. package/dist/services/task/state-machine/guards.js +256 -0
  72. package/dist/services/task/state-machine/guards.js.map +1 -0
  73. package/dist/services/task/state-machine/index.js +53 -0
  74. package/dist/services/task/state-machine/index.js.map +1 -0
  75. package/dist/services/task/state-machine/types.js +54 -0
  76. package/dist/services/task/state-machine/types.js.map +1 -0
  77. package/dist/services/task/state-machine/uiControlsComputer.js +369 -0
  78. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  79. package/dist/services/task/taskDataNormalizer.js +99 -0
  80. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  81. package/dist/services/task/types.js +157 -18
  82. package/dist/services/task/types.js.map +1 -1
  83. package/dist/services/task/voice/Voice.js +1031 -0
  84. package/dist/services/task/voice/Voice.js.map +1 -0
  85. package/dist/services/task/voice/WebRTC.js +149 -0
  86. package/dist/services/task/voice/WebRTC.js.map +1 -0
  87. package/dist/types/cc.d.ts +4 -33
  88. package/dist/types/constants.d.ts +13 -2
  89. package/dist/types/index.d.ts +11 -5
  90. package/dist/types/metrics/constants.d.ts +5 -3
  91. package/dist/types/services/ApiAiAssistant.d.ts +1 -1
  92. package/dist/types/services/config/types.d.ts +97 -25
  93. package/dist/types/services/core/Err.d.ts +0 -2
  94. package/dist/types/services/core/Utils.d.ts +25 -23
  95. package/dist/types/services/core/aqm-reqs.d.ts +0 -49
  96. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -1
  97. package/dist/types/services/core/websocket/connection-service.d.ts +0 -1
  98. package/dist/types/services/core/websocket/types.d.ts +1 -1
  99. package/dist/types/services/index.d.ts +1 -1
  100. package/dist/types/services/task/Task.d.ts +146 -0
  101. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  102. package/dist/types/services/task/TaskUtils.d.ts +39 -8
  103. package/dist/types/services/task/constants.d.ts +5 -4
  104. package/dist/types/services/task/dialer.d.ts +0 -15
  105. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  106. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +906 -0
  107. package/dist/types/services/task/state-machine/actions.d.ts +8 -0
  108. package/dist/types/services/task/state-machine/constants.d.ts +91 -0
  109. package/dist/types/services/task/state-machine/guards.d.ts +78 -0
  110. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  111. package/dist/types/services/task/state-machine/types.d.ts +256 -0
  112. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  113. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  114. package/dist/types/services/task/types.d.ts +539 -88
  115. package/dist/types/services/task/voice/Voice.d.ts +183 -0
  116. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  117. package/dist/types/types.d.ts +68 -0
  118. package/dist/types/webex.d.ts +1 -0
  119. package/dist/types.js +70 -0
  120. package/dist/types.js.map +1 -1
  121. package/dist/webex.js +14 -2
  122. package/dist/webex.js.map +1 -1
  123. package/package.json +14 -11
  124. package/src/cc.ts +91 -177
  125. package/src/constants.ts +13 -2
  126. package/src/index.ts +14 -5
  127. package/src/metrics/ai-docs/AGENTS.md +348 -0
  128. package/src/metrics/ai-docs/ARCHITECTURE.md +336 -0
  129. package/src/metrics/behavioral-events.ts +28 -14
  130. package/src/metrics/constants.ts +7 -8
  131. package/src/services/ApiAiAssistant.ts +2 -4
  132. package/src/services/agent/ai-docs/AGENTS.md +238 -0
  133. package/src/services/agent/ai-docs/ARCHITECTURE.md +302 -0
  134. package/src/services/ai-docs/AGENTS.md +384 -0
  135. package/src/services/config/Util.ts +2 -3
  136. package/src/services/config/ai-docs/AGENTS.md +253 -0
  137. package/src/services/config/ai-docs/ARCHITECTURE.md +424 -0
  138. package/src/services/config/types.ts +108 -20
  139. package/src/services/constants.ts +0 -1
  140. package/src/services/core/Err.ts +0 -1
  141. package/src/services/core/Utils.ts +90 -67
  142. package/src/services/core/ai-docs/AGENTS.md +379 -0
  143. package/src/services/core/ai-docs/ARCHITECTURE.md +696 -0
  144. package/src/services/core/aqm-reqs.ts +22 -100
  145. package/src/services/core/websocket/WebSocketManager.ts +4 -23
  146. package/src/services/core/websocket/types.ts +1 -1
  147. package/src/services/index.ts +1 -2
  148. package/src/services/task/Task.ts +785 -0
  149. package/src/services/task/TaskFactory.ts +55 -0
  150. package/src/services/task/TaskManager.ts +567 -633
  151. package/src/services/task/TaskUtils.ts +175 -31
  152. package/src/services/task/ai-docs/AGENTS.md +448 -0
  153. package/src/services/task/ai-docs/ARCHITECTURE.md +573 -0
  154. package/src/services/task/constants.ts +5 -4
  155. package/src/services/task/dialer.ts +1 -56
  156. package/src/services/task/digital/Digital.ts +95 -0
  157. package/src/services/task/state-machine/TaskStateMachine.ts +793 -0
  158. package/src/services/task/state-machine/actions.ts +409 -0
  159. package/src/services/task/state-machine/ai-docs/AGENTS.md +495 -0
  160. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1135 -0
  161. package/src/services/task/state-machine/constants.ts +150 -0
  162. package/src/services/task/state-machine/guards.ts +295 -0
  163. package/src/services/task/state-machine/index.ts +28 -0
  164. package/src/services/task/state-machine/types.ts +228 -0
  165. package/src/services/task/state-machine/uiControlsComputer.ts +529 -0
  166. package/src/services/task/taskDataNormalizer.ts +137 -0
  167. package/src/services/task/types.ts +641 -95
  168. package/src/services/task/voice/Voice.ts +1255 -0
  169. package/src/services/task/voice/WebRTC.ts +187 -0
  170. package/src/types.ts +88 -5
  171. package/src/utils/AGENTS.md +276 -0
  172. package/src/webex.js +2 -0
  173. package/test/unit/spec/cc.ts +59 -142
  174. package/test/unit/spec/logger-proxy.ts +70 -0
  175. package/test/unit/spec/services/ApiAiAssistant.ts +17 -0
  176. package/test/unit/spec/services/config/index.ts +26 -55
  177. package/test/unit/spec/services/core/Utils.ts +103 -52
  178. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +48 -112
  179. package/test/unit/spec/services/core/websocket/connection-service.ts +5 -4
  180. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  181. package/test/unit/spec/services/task/Task.ts +416 -0
  182. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  183. package/test/unit/spec/services/task/TaskManager.ts +781 -1735
  184. package/test/unit/spec/services/task/TaskUtils.ts +125 -0
  185. package/test/unit/spec/services/task/dialer.ts +112 -198
  186. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  187. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +473 -0
  188. package/test/unit/spec/services/task/state-machine/guards.ts +288 -0
  189. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  190. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +147 -0
  191. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  192. package/test/unit/spec/services/task/voice/Voice.ts +587 -0
  193. package/test/unit/spec/services/task/voice/WebRTC.ts +242 -0
  194. package/umd/contact-center.min.js +2 -2
  195. package/umd/contact-center.min.js.map +1 -1
  196. package/dist/services/task/index.js +0 -1525
  197. package/dist/services/task/index.js.map +0 -1
  198. package/dist/types/services/task/index.d.ts +0 -650
  199. package/src/services/task/index.ts +0 -1801
  200. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -1,2184 +0,0 @@
1
- import 'jsdom-global/register';
2
- import {CALL_EVENT_KEYS, CallingClientConfig, LocalMicrophoneStream} from '@webex/calling';
3
- import {LoginOption, WebexSDK} from '../../../../../src/types';
4
- import {TASK_FILE} from '../../../../../src/constants';
5
- import Task from '../../../../../src/services/task';
6
- import * as Utils from '../../../../../src/services/core/Utils';
7
- import {CC_EVENTS} from '../../../../../src/services/config/types';
8
- import config from '../../../../../src/config';
9
- import WebCallingService from '../../../../../src/services/WebCallingService';
10
- import {
11
- TASK_EVENTS,
12
- TaskResponse,
13
- AgentContact,
14
- ConsultEndPayload,
15
- TaskData,
16
- DESTINATION_TYPE,
17
- CONSULT_TRANSFER_DESTINATION_TYPE,
18
- ConsultTransferPayLoad,
19
- TransferPayLoad,
20
- } from '../../../../../src/services/task/types';
21
- import WebexRequest from '../../../../../src/services/core/WebexRequest';
22
- import MetricsManager from '../../../../../src/metrics/MetricsManager';
23
- import {METRIC_EVENT_NAMES} from '../../../../../src/metrics/constants';
24
- import LoggerProxy from '../../../../../src/logger-proxy';
25
-
26
- jest.mock('@webex/calling');
27
- jest.mock('../../../../../src/logger-proxy');
28
-
29
- describe('Task', () => {
30
- let onSpy;
31
- let task;
32
- let contactMock;
33
- let mockMetricsManager;
34
- let taskDataMock;
35
- let webCallingService;
36
- let generateTaskErrorObjectSpy;
37
- let mockWebexRequest;
38
- let webex: WebexSDK;
39
- let loggerInfoSpy;
40
- let loggerLogSpy;
41
- let loggerErrorSpy;
42
- let calculateDestAgentIdSpy;
43
- let calculateDestTypeSpy;
44
-
45
- const taskId = '0ae913a4-c857-4705-8d49-76dd3dde75e4';
46
- const mockTrack = {} as MediaStreamTrack;
47
- const mockStream = {
48
- outputStream: {
49
- getAudioTracks: jest.fn().mockReturnValue([mockTrack]),
50
- },
51
- };
52
-
53
- beforeEach(() => {
54
- webex = {
55
- logger: {
56
- log: jest.fn(),
57
- error: jest.fn(),
58
- info: jest.fn(),
59
- },
60
- } as unknown as WebexSDK;
61
-
62
- loggerInfoSpy = jest.spyOn(LoggerProxy, 'info');
63
- loggerLogSpy = jest.spyOn(LoggerProxy, 'log');
64
- loggerErrorSpy = jest.spyOn(LoggerProxy, 'error');
65
-
66
- contactMock = {
67
- accept: jest.fn().mockResolvedValue({}),
68
- hold: jest.fn().mockResolvedValue({}),
69
- unHold: jest.fn().mockResolvedValue({}),
70
- consult: jest.fn().mockResolvedValue({}),
71
- consultEnd: jest.fn().mockResolvedValue({}),
72
- blindTransfer: jest.fn().mockResolvedValue({}),
73
- vteamTransfer: jest.fn().mockResolvedValue({}),
74
- consultTransfer: jest.fn().mockResolvedValue({}),
75
- end: jest.fn().mockResolvedValue({}),
76
- wrapup: jest.fn().mockResolvedValue({}),
77
- pauseRecording: jest.fn().mockResolvedValue({}),
78
- resumeRecording: jest.fn().mockResolvedValue({}),
79
- consultConference: jest.fn().mockResolvedValue({}),
80
- exitConference: jest.fn().mockResolvedValue({}),
81
- conferenceTransfer: jest.fn().mockResolvedValue({}),
82
- };
83
-
84
- mockMetricsManager = {
85
- trackEvent: jest.fn(),
86
- timeEvent: jest.fn(),
87
- };
88
-
89
- jest.spyOn(MetricsManager, 'getInstance').mockReturnValue(mockMetricsManager);
90
-
91
- webCallingService = new WebCallingService(
92
- webex,
93
- config.cc.callingClientConfig as CallingClientConfig
94
- );
95
-
96
- mockWebexRequest = {
97
- request: jest.fn(),
98
- uploadLogs: jest.fn(),
99
- };
100
-
101
- jest.spyOn(WebexRequest, 'getInstance').mockReturnValue(mockWebexRequest);
102
-
103
-
104
- webCallingService.loginOption = LoginOption.BROWSER;
105
- onSpy = jest.spyOn(webCallingService, 'on');
106
-
107
- // Mock task data
108
- taskDataMock = {
109
- type: CC_EVENTS.AGENT_CONTACT_RESERVED,
110
- agentId: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
111
- eventTime: 1733211616959,
112
- eventType: 'RoutingMessage',
113
- interactionId: taskId,
114
- orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a',
115
- trackingId: '575c0ec2-618c-42af-a61c-53aeb0a221ee',
116
- mediaResourceId: '0ae913a4-c857-4705-8d49-76dd3dde75e4',
117
- destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
118
- owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
119
- queueMgr: 'aqm',
120
- interaction: {
121
- mediaType: 'telephony',
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
- },
149
- media: {
150
- '58a45567-4e61-4f4b-a580-5bc86357bef0': {
151
- holdTimestamp: null,
152
- isHold: false,
153
- mType: 'consult',
154
- mediaMgr: 'callmm',
155
- mediaResourceId: '58a45567-4e61-4f4b-a580-5bc86357bef0',
156
- mediaType: 'telephony',
157
- participants: [
158
- 'f520d6b5-28ad-4f2f-b83e-781bb64af617',
159
- '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
160
- ],
161
- },
162
- [taskId]: {
163
- holdTimestamp: 1734667567279,
164
- isHold: true,
165
- mType: 'mainCall',
166
- mediaMgr: 'callmm',
167
- mediaResourceId: taskId,
168
- mediaType: 'telephony',
169
- participants: ['+14696762938', '723a8ffb-a26e-496d-b14a-ff44fb83b64f'],
170
- },
171
- },
172
- },
173
- };
174
-
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');
180
-
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);
187
-
188
- // Mock navigator.mediaDevices
189
- global.navigator.mediaDevices = {
190
- getUserMedia: jest.fn(() =>
191
- Promise.resolve({
192
- getAudioTracks: jest.fn().mockReturnValue([mockTrack]),
193
- })
194
- ),
195
- };
196
-
197
- // Mock MediaStream (if needed)
198
- global.MediaStream = jest.fn().mockImplementation((tracks) => {
199
- return mockStream;
200
- });
201
-
202
- generateTaskErrorObjectSpy = jest.spyOn(Utils, 'generateTaskErrorObject');
203
- generateTaskErrorObjectSpy.mockImplementation((error: any, methodName: string) => {
204
- const trackingId = error?.details?.trackingId;
205
- const msg = error?.details?.msg;
206
- const legacyReason = error?.details?.data?.reason;
207
- const errorMessage = msg?.errorMessage || legacyReason || `Error while performing ${methodName}`;
208
- const errorType = msg?.errorType || '';
209
- const errorData = msg?.errorData || '';
210
- const reasonCode = msg?.reasonCode || 0;
211
- const reason = legacyReason || (errorType ? `${errorType}: ${errorMessage}` : errorMessage);
212
- const err: any = new Error(reason);
213
- err.data = {
214
- trackingId,
215
- message: errorMessage,
216
- errorType,
217
- errorData,
218
- reasonCode,
219
- };
220
- return err;
221
- });
222
-
223
- (global as any).makeFailure = (reason: string, trackingId = '1234', orgId = 'org1') => ({
224
- type: 'failure_event',
225
- orgId,
226
- trackingId,
227
- data: {
228
- agentId: 'agent1',
229
- reason,
230
- reasonCode: 0,
231
- },
232
- });
233
- });
234
-
235
- afterEach(() => {
236
- jest.clearAllMocks();
237
- jest.restoreAllMocks();
238
- });
239
-
240
- it('test the on spy', async () => {
241
- const taskEmitSpy = jest.spyOn(task, 'emit');
242
- const remoteMediaCb = onSpy.mock.calls[0][1];
243
-
244
- expect(onSpy).toHaveBeenCalledWith(CALL_EVENT_KEYS.REMOTE_MEDIA, remoteMediaCb);
245
-
246
- remoteMediaCb(mockTrack);
247
-
248
- expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_MEDIA, mockTrack);
249
- });
250
-
251
- describe('updateTaskData cases', () => {
252
- it('updates the task data by overwrite', async () => {
253
- const newData = {
254
- type: CC_EVENTS.AGENT_CONTACT_ASSIGNED,
255
- agentId: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
256
- eventTime: 1733211616959,
257
- eventType: 'RoutingMessage',
258
- interactionId: taskId,
259
- orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a',
260
- trackingId: '575c0ec2-618c-42af-a61c-53aeb0a221ee',
261
- mediaResourceId: '0ae913a4-c857-4705-8d49-76dd3dde75e4',
262
- destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
263
- owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
264
- queueMgr: 'aqm',
265
- interaction: {
266
- mainInteractionId: taskId,
267
- media: {
268
- '58a45567-4e61-4f4b-a580-5bc86357bef0': {
269
- holdTimestamp: null,
270
- isHold: false,
271
- mType: 'consult',
272
- mediaMgr: 'callmm',
273
- mediaResourceId: '58a45567-4e61-4f4b-a580-5bc86357bef0',
274
- mediaType: 'telephony',
275
- participants: [
276
- 'f520d6b5-28ad-4f2f-b83e-781bb64af617',
277
- '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
278
- ],
279
- },
280
- [taskId]: {
281
- holdTimestamp: 1734667567279,
282
- isHold: true,
283
- mType: 'mainCall',
284
- mediaMgr: 'callmm',
285
- mediaResourceId: taskId,
286
- mediaType: 'telephony',
287
- participants: ['+14696762938', '723a8ffb-a26e-496d-b14a-ff44fb83b64f'],
288
- },
289
- },
290
- },
291
- };
292
-
293
- expect(task.data).toEqual(taskDataMock);
294
-
295
- const shouldOverwrite = true;
296
- task.updateTaskData(newData, shouldOverwrite);
297
-
298
- expect(task.data).toEqual(newData);
299
- });
300
-
301
- it('updates the task data by merging with key removal', async () => {
302
- const newData = {
303
- // Purposefully omit other keys to test remove and merge behavior
304
- isConsulting: true, // Add a new custom key to test persistence
305
- interaction: {
306
- // Purposefully omit other interaction keys to test removal
307
- media: {
308
- '58a45567-4e61-4f4b-a580-5bc86357bef0': {
309
- holdTimestamp: null,
310
- isHold: true,
311
- mType: 'consult',
312
- mediaMgr: 'callmm',
313
- mediaResourceId: '58a45567-4e61-4f4b-a580-5bc86357bef0',
314
- mediaType: 'telephony',
315
- participants: [
316
- 'f520d6b5-28ad-4f2f-b83e-781bb64af617',
317
- '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
318
- ],
319
- },
320
- [taskId]: {
321
- holdTimestamp: 1734667567279,
322
- isHold: false,
323
- mType: 'mainCall',
324
- mediaMgr: 'callmm',
325
- mediaResourceId: taskId,
326
- mediaType: 'telephony',
327
- participants: ['+14696762938', '723a8ffb-a26e-496d-b14a-ff44fb83b64f'],
328
- },
329
- },
330
- },
331
- };
332
-
333
- // The reconcileData method removes keys from oldData that are not in newData
334
- // This means only keys present in newData will remain in the final result
335
- const expectedData: TaskData = {
336
- isConsulting: true, // New key is added
337
- interaction: {
338
- // Only the media key from newData.interaction remains
339
- media: {
340
- '58a45567-4e61-4f4b-a580-5bc86357bef0': {
341
- holdTimestamp: null,
342
- isHold: true,
343
- mType: 'consult',
344
- mediaMgr: 'callmm',
345
- mediaResourceId: '58a45567-4e61-4f4b-a580-5bc86357bef0',
346
- mediaType: 'telephony',
347
- participants: [
348
- 'f520d6b5-28ad-4f2f-b83e-781bb64af617',
349
- '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
350
- ],
351
- },
352
- [taskId]: {
353
- holdTimestamp: 1734667567279,
354
- isHold: false,
355
- mType: 'mainCall',
356
- mediaMgr: 'callmm',
357
- mediaResourceId: taskId,
358
- mediaType: 'telephony',
359
- participants: ['+14696762938', '723a8ffb-a26e-496d-b14a-ff44fb83b64f'],
360
- },
361
- },
362
- },
363
- };
364
-
365
- expect(task.data).toEqual(taskDataMock);
366
- const shouldOverwrite = false;
367
- task.updateTaskData(newData, shouldOverwrite);
368
-
369
- expect(task.data).toEqual(expectedData);
370
- });
371
-
372
- it('updates the task data by merging and preserving existing keys', async () => {
373
- const newData = {
374
- ...taskDataMock, // Include all existing keys to test merge without removal
375
- isConsulting: true, // Add a new custom key
376
- interaction: {
377
- ...taskDataMock.interaction, // Include existing interaction data
378
- media: {
379
- ...taskDataMock.interaction.media, // Include existing media
380
- '58a45567-4e61-4f4b-a580-5bc86357bef0': {
381
- holdTimestamp: null,
382
- isHold: true,
383
- mType: 'consult',
384
- mediaMgr: 'callmm',
385
- mediaResourceId: '58a45567-4e61-4f4b-a580-5bc86357bef0',
386
- mediaType: 'telephony',
387
- participants: [
388
- 'f520d6b5-28ad-4f2f-b83e-781bb64af617',
389
- '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
390
- ],
391
- },
392
- },
393
- },
394
- };
395
-
396
- const expectedData: TaskData = {
397
- ...taskDataMock,
398
- isConsulting: true,
399
- interaction: {
400
- ...taskDataMock.interaction,
401
- media: {
402
- ...taskDataMock.interaction.media,
403
- '58a45567-4e61-4f4b-a580-5bc86357bef0': {
404
- holdTimestamp: null,
405
- isHold: true,
406
- mType: 'consult',
407
- mediaMgr: 'callmm',
408
- mediaResourceId: '58a45567-4e61-4f4b-a580-5bc86357bef0',
409
- mediaType: 'telephony',
410
- participants: [
411
- 'f520d6b5-28ad-4f2f-b83e-781bb64af617',
412
- '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
413
- ],
414
- },
415
- },
416
- },
417
- };
418
-
419
- expect(task.data).toEqual(taskDataMock);
420
- const shouldOverwrite = false;
421
- task.updateTaskData(newData, shouldOverwrite);
422
-
423
- expect(task.data).toEqual(expectedData);
424
- });
425
- });
426
-
427
- it('should accept a task and answer call when using BROWSER login option', async () => {
428
- const answerCallSpy = jest.spyOn(webCallingService, 'answerCall');
429
-
430
- await task.accept();
431
-
432
- expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({audio: true});
433
- expect(LocalMicrophoneStream).toHaveBeenCalledWith(mockStream);
434
- expect(answerCallSpy).toHaveBeenCalledWith(expect.any(LocalMicrophoneStream), taskId);
435
- expect(loggerInfoSpy).toHaveBeenCalledWith(`Accepting task`, {
436
- module: TASK_FILE,
437
- method: 'accept',
438
- interactionId: task.data.interactionId,
439
- });
440
- expect(loggerLogSpy).toHaveBeenCalledWith(
441
- `Task accepted successfully with webrtc calling`,
442
- {
443
- module: TASK_FILE,
444
- method: 'accept',
445
- interactionId: task.data.interactionId,
446
- }
447
- );
448
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
449
- 1,
450
- METRIC_EVENT_NAMES.TASK_ACCEPT_SUCCESS,
451
- {
452
- taskId: task.data.interactionId,
453
- ...MetricsManager.getCommonTrackingFieldForAQMResponse(task),
454
- eventType: 'AgentContactReserved',
455
- notifTrackingId: '575c0ec2-618c-42af-a61c-53aeb0a221ee',
456
- trackingId: undefined,
457
- },
458
- ['operational', 'behavioral', 'business']
459
- );
460
- });
461
-
462
- it('should accept a task when mediaType chat', async () => {
463
- task.data.interaction.mediaType = 'chat';
464
- const answerCallSpy = jest.spyOn(webCallingService, 'answerCall');
465
- const response = {};
466
- contactMock.accept.mockResolvedValue(response);
467
-
468
- await task.accept();
469
-
470
- expect(contactMock.accept).toHaveBeenCalledWith({
471
- interactionId: taskId,
472
- });
473
- expect(answerCallSpy).not.toHaveBeenCalled();
474
- expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
475
- METRIC_EVENT_NAMES.TASK_ACCEPT_SUCCESS,
476
- METRIC_EVENT_NAMES.TASK_ACCEPT_FAILED,
477
- ]);
478
- const expectedMetrics = {
479
- taskId: task.data.interactionId,
480
- agentId: task.data.agentId,
481
- eventType: task.data.type,
482
- notifTrackingId: task.data.trackingId,
483
- orgId: task.data.orgId,
484
- };
485
- expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
486
- METRIC_EVENT_NAMES.TASK_ACCEPT_SUCCESS,
487
- expectedMetrics,
488
- ['operational', 'behavioral', 'business']
489
- );
490
- expect(loggerInfoSpy).toHaveBeenCalledWith(`Accepting task`, {
491
- module: TASK_FILE,
492
- method: 'accept',
493
- interactionId: task.data.interactionId,
494
- });
495
- expect(loggerLogSpy).toHaveBeenCalledWith(`Task accepted successfully`, {
496
- module: TASK_FILE,
497
- method: 'accept',
498
- interactionId: task.data.interactionId,
499
- });
500
- });
501
-
502
- it('should accept a task when mediaType email', async () => {
503
- task.data.interaction.mediaType = 'email';
504
- const answerCallSpy = jest.spyOn(webCallingService, 'answerCall');
505
- const response = {};
506
- contactMock.accept.mockResolvedValue(response);
507
-
508
- await task.accept();
509
-
510
- expect(contactMock.accept).toHaveBeenCalledWith({
511
- interactionId: taskId,
512
- });
513
- expect(answerCallSpy).not.toHaveBeenCalled();
514
- expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
515
- METRIC_EVENT_NAMES.TASK_ACCEPT_SUCCESS,
516
- METRIC_EVENT_NAMES.TASK_ACCEPT_FAILED,
517
- ]);
518
- const expectedMetrics = {
519
- taskId: task.data.interactionId,
520
- agentId: task.data.agentId,
521
- eventType: task.data.type,
522
- notifTrackingId: task.data.trackingId,
523
- orgId: task.data.orgId,
524
- };
525
- expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
526
- METRIC_EVENT_NAMES.TASK_ACCEPT_SUCCESS,
527
- expectedMetrics,
528
- ['operational', 'behavioral', 'business']
529
- );
530
- expect(loggerInfoSpy).toHaveBeenCalledWith(`Accepting task`, {
531
- module: TASK_FILE,
532
- method: 'accept',
533
- interactionId: task.data.interactionId,
534
- });
535
- expect(loggerLogSpy).toHaveBeenCalledWith(`Task accepted successfully`, {
536
- module: TASK_FILE,
537
- method: 'accept',
538
- interactionId: task.data.interactionId,
539
- });
540
- });
541
-
542
- it('should handle errors in accept method', async () => {
543
- const error = {details: (global as any).makeFailure('Accept Failed')};
544
-
545
- jest.spyOn(webCallingService, 'answerCall').mockImplementation(() => {
546
- throw error;
547
- });
548
-
549
- await expect(task.accept()).rejects.toThrow(new Error(error.details.data.reason));
550
- expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'accept', TASK_FILE);
551
- const expectedTaskErrorFields = {
552
- trackingId: error.details.trackingId,
553
- errorMessage: error.details.data.reason,
554
- errorType: '',
555
- errorData: '',
556
- reasonCode: 0,
557
- };
558
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
559
- 1,
560
- METRIC_EVENT_NAMES.TASK_ACCEPT_FAILED,
561
- {
562
- taskId: taskDataMock.interactionId,
563
- error: error.toString(),
564
- ...expectedTaskErrorFields,
565
- ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
566
- },
567
- ['operational', 'behavioral', 'business']
568
- );
569
- });
570
-
571
- it('should decline call using webCallingService', async () => {
572
- const declineCallSpy = jest.spyOn(webCallingService, 'declineCall');
573
- const offSpy = jest.spyOn(webCallingService, 'off');
574
-
575
- await task.decline();
576
-
577
- expect(declineCallSpy).toHaveBeenCalledWith(taskId);
578
- expect(offSpy).toHaveBeenCalledWith(CALL_EVENT_KEYS.REMOTE_MEDIA, offSpy.mock.calls[0][1]);
579
- expect(loggerInfoSpy).toHaveBeenCalledWith(`Declining task`, {
580
- module: TASK_FILE,
581
- method: 'decline',
582
- interactionId: task.data.interactionId,
583
- });
584
- expect(loggerLogSpy).toHaveBeenCalledWith(`Task declined successfully`, {
585
- module: TASK_FILE,
586
- method: 'decline',
587
- interactionId: task.data.interactionId,
588
- });
589
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
590
- 1,
591
- METRIC_EVENT_NAMES.TASK_DECLINE_SUCCESS,
592
- {
593
- taskId: taskDataMock.interactionId,
594
- },
595
- ['operational', 'behavioral']
596
- );
597
- });
598
-
599
- it('should handle errors in decline method', async () => {
600
- const error = {details: (global as any).makeFailure('Decline Failed')};
601
-
602
- jest.spyOn(webCallingService, 'declineCall').mockImplementation(() => {
603
- throw error;
604
- });
605
- await expect(task.decline()).rejects.toThrow(new Error(error.details.data.reason));
606
- expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'decline', TASK_FILE);
607
- const expectedTaskErrorFieldsDecline = {
608
- trackingId: error.details.trackingId,
609
- errorMessage: error.details.data.reason,
610
- errorType: '',
611
- errorData: '',
612
- reasonCode: 0,
613
- };
614
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
615
- 1,
616
- METRIC_EVENT_NAMES.TASK_DECLINE_FAILED,
617
- {
618
- taskId: taskDataMock.interactionId,
619
- error: error.toString(),
620
- ...expectedTaskErrorFieldsDecline,
621
- ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
622
- },
623
- ['operational', 'behavioral']
624
- );
625
- });
626
-
627
- it('should hold the task and return the expected response', async () => {
628
- const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
629
- contactMock.hold.mockResolvedValue(expectedResponse);
630
-
631
- const response = await task.hold();
632
-
633
- expect(contactMock.hold).toHaveBeenCalledWith({
634
- interactionId: taskId,
635
- data: {mediaResourceId: taskDataMock.mediaResourceId},
636
- });
637
- expect(response).toEqual(expectedResponse);
638
- expect(loggerInfoSpy).toHaveBeenCalledWith(`Holding task`, {
639
- module: TASK_FILE,
640
- method: 'hold',
641
- interactionId: task.data.interactionId,
642
- });
643
- expect(loggerLogSpy).toHaveBeenCalledWith(`Task placed on hold successfully`, {
644
- module: TASK_FILE,
645
- method: 'hold',
646
- interactionId: task.data.interactionId,
647
- });
648
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
649
- 1,
650
- METRIC_EVENT_NAMES.TASK_HOLD_SUCCESS,
651
- {
652
- ...MetricsManager.getCommonTrackingFieldForAQMResponse(expectedResponse),
653
- taskId: taskDataMock.interactionId,
654
- mediaResourceId: taskDataMock.mediaResourceId,
655
- },
656
- ['operational', 'behavioral']
657
- );
658
- });
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
-
694
- it('should handle errors in hold method', async () => {
695
- const error = {details: (global as any).makeFailure('Hold Failed')};
696
- contactMock.hold.mockImplementation(() => {
697
- throw error;
698
- });
699
-
700
- await expect(task.hold()).rejects.toThrow(error.details.data.reason);
701
- expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'hold', TASK_FILE);
702
- const expectedTaskErrorFieldsHold = {
703
- trackingId: error.details.trackingId,
704
- errorMessage: error.details.data.reason,
705
- errorType: '',
706
- errorData: '',
707
- reasonCode: 0,
708
- };
709
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
710
- 1,
711
- METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
712
- {
713
- taskId: taskDataMock.interactionId,
714
- mediaResourceId: taskDataMock.mediaResourceId,
715
- error: error.toString(),
716
- ...expectedTaskErrorFieldsHold,
717
- ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
718
- },
719
- ['operational', 'behavioral']
720
- );
721
- });
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
-
753
- it('should resume the task and return the expected response', async () => {
754
- const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
755
- contactMock.unHold.mockResolvedValue(expectedResponse);
756
- const response = await task.resume();
757
- expect(contactMock.unHold).toHaveBeenCalledWith({
758
- interactionId: taskId,
759
- data: {mediaResourceId: taskDataMock.mediaResourceId},
760
- });
761
- expect(response).toEqual(expectedResponse);
762
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
763
- 1,
764
- METRIC_EVENT_NAMES.TASK_RESUME_SUCCESS,
765
- {
766
- taskId: taskDataMock.interactionId,
767
- mainInteractionId: taskDataMock.interaction.mainInteractionId,
768
- mediaResourceId:
769
- taskDataMock.interaction.media[taskDataMock.interaction.mainInteractionId]
770
- .mediaResourceId,
771
- ...MetricsManager.getCommonTrackingFieldForAQMResponse(expectedResponse),
772
- },
773
- ['operational', 'behavioral']
774
- );
775
- });
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
-
800
- it('should handle errors in resume method', async () => {
801
- const error = {details: (global as any).makeFailure('Resume Failed')};
802
- contactMock.unHold.mockImplementation(() => {
803
- throw error;
804
- });
805
-
806
- await expect(task.resume()).rejects.toThrow(error.details.data.reason);
807
- expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'resume', TASK_FILE);
808
- const expectedTaskErrorFieldsResume = {
809
- trackingId: error.details.trackingId,
810
- errorMessage: error.details.data.reason,
811
- errorType: '',
812
- errorData: '',
813
- reasonCode: 0,
814
- };
815
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
816
- 1,
817
- METRIC_EVENT_NAMES.TASK_RESUME_FAILED,
818
- {
819
- taskId: taskDataMock.interactionId,
820
- mainInteractionId: taskDataMock.interaction.mainInteractionId,
821
- mediaResourceId:
822
- taskDataMock.interaction.media[taskDataMock.interaction.mainInteractionId]
823
- .mediaResourceId,
824
- ...expectedTaskErrorFieldsResume,
825
- ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
826
- },
827
- ['operational', 'behavioral']
828
- );
829
- });
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
-
861
- it('should initiate a consult call and return the expected response', async () => {
862
- const consultPayload = {
863
- to: '1234',
864
- destinationType: DESTINATION_TYPE.AGENT,
865
- };
866
- const expectedResponse: TaskResponse = {data: {interactionId: taskId}, trackingId: '1234'} as AgentContact;
867
- contactMock.consult.mockResolvedValue(expectedResponse);
868
-
869
- const response = await task.consult(consultPayload);
870
-
871
- expect(contactMock.consult).toHaveBeenCalledWith({interactionId: taskId, data: consultPayload});
872
- expect(response).toEqual(expectedResponse);
873
- expect(loggerInfoSpy).toHaveBeenCalledWith(`Starting consult`, {
874
- module: TASK_FILE,
875
- method: 'consult',
876
- interactionId: task.data.interactionId,
877
- });
878
- expect(loggerLogSpy).toHaveBeenCalledWith(`Consult started successfully to ${consultPayload.to}`, {
879
- module: TASK_FILE,
880
- method: 'consult',
881
- interactionId: task.data.interactionId,
882
- trackingId: '1234',
883
- });
884
- expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
885
- METRIC_EVENT_NAMES.TASK_CONSULT_START_SUCCESS,
886
- {
887
- taskId: taskDataMock.interactionId,
888
- destination: consultPayload.to,
889
- destinationType: consultPayload.destinationType,
890
- ...MetricsManager.getCommonTrackingFieldForAQMResponse(expectedResponse),
891
- },
892
- ['operational', 'behavioral', 'business']
893
- );
894
- });
895
-
896
- it('should handle errors in consult method', async () => {
897
- const error = {details: (global as any).makeFailure('Consult Failed')};
898
- contactMock.consult.mockImplementation(() => {
899
- throw error;
900
- });
901
-
902
- const consultPayload = {
903
- to: '1234',
904
- destinationType: DESTINATION_TYPE.AGENT,
905
- };
906
-
907
- await expect(task.consult(consultPayload)).rejects.toThrow(error.details.data.reason);
908
- expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'consult', TASK_FILE);
909
- expect(loggerInfoSpy).toHaveBeenCalledWith(`Starting consult`, {
910
- module: TASK_FILE,
911
- method: 'consult',
912
- interactionId: task.data.interactionId,
913
- });
914
- const expectedTaskErrorFieldsConsult = {
915
- trackingId: error.details.trackingId,
916
- errorMessage: error.details.data.reason,
917
- errorType: '',
918
- errorData: '',
919
- reasonCode: 0,
920
- };
921
- expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
922
- METRIC_EVENT_NAMES.TASK_CONSULT_START_FAILED,
923
- {
924
- taskId: taskDataMock.interactionId,
925
- destination: consultPayload.to,
926
- destinationType: consultPayload.destinationType,
927
- error: error.toString(),
928
- ...expectedTaskErrorFieldsConsult,
929
- ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
930
- },
931
- ['operational', 'behavioral', 'business']
932
- );
933
- });
934
-
935
- it('should end the consult call and return the expected response', async () => {
936
- const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
937
- contactMock.consultEnd.mockResolvedValue(expectedResponse);
938
-
939
- const consultEndPayload: ConsultEndPayload = {
940
- isConsult: true,
941
- taskId: taskId,
942
- };
943
- const response = await task.endConsult(consultEndPayload);
944
-
945
- expect(contactMock.consultEnd).toHaveBeenCalledWith({
946
- interactionId: taskId,
947
- data: consultEndPayload,
948
- });
949
- expect(response).toEqual(expectedResponse);
950
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
951
- 1,
952
- METRIC_EVENT_NAMES.TASK_CONSULT_END_SUCCESS,
953
- {
954
- taskId: taskDataMock.interactionId,
955
- ...MetricsManager.getCommonTrackingFieldForAQMResponse(expectedResponse),
956
- },
957
- ['operational', 'behavioral', 'business']
958
- );
959
- });
960
-
961
- it('should handle errors in endConsult method', async () => {
962
- const error = {details: (global as any).makeFailure('End Consult Failed')};
963
- contactMock.consultEnd.mockImplementation(() => {
964
- throw error;
965
- });
966
-
967
- const consultEndPayload: ConsultEndPayload = {
968
- isConsult: true,
969
- taskId: taskId,
970
- };
971
-
972
- await expect(task.endConsult(consultEndPayload)).rejects.toThrow(error.details.data.reason);
973
- expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'endConsult', TASK_FILE);
974
- const expectedTaskErrorFieldsEndConsult = {
975
- trackingId: error.details.trackingId,
976
- errorMessage: error.details.data.reason,
977
- errorType: '',
978
- errorData: '',
979
- reasonCode: 0,
980
- };
981
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
982
- 1,
983
- METRIC_EVENT_NAMES.TASK_CONSULT_END_FAILED,
984
- {
985
- taskId: taskDataMock.interactionId,
986
- error: error.toString(),
987
- ...expectedTaskErrorFieldsEndConsult,
988
- ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
989
- },
990
- ['operational', 'behavioral', 'business']
991
- );
992
- });
993
-
994
- it('should do consult transfer the task to consulted agent and return the expected response', async () => {
995
- const consultPayload = {
996
- destination: '1234',
997
- destinationType: DESTINATION_TYPE.AGENT,
998
- };
999
- const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
1000
- contactMock.consult.mockResolvedValue(expectedResponse);
1001
-
1002
- const response = await task.consult(consultPayload);
1003
-
1004
- expect(contactMock.consult).toHaveBeenCalledWith({interactionId: taskId, data: consultPayload});
1005
- expect(response).toEqual(expectedResponse);
1006
-
1007
- const consultTransferResponse = await task.consultTransfer();
1008
- expect(contactMock.consultTransfer).toHaveBeenCalledWith({
1009
- interactionId: taskId,
1010
- data: {
1011
- to: taskDataMock.destAgentId,
1012
- destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
1013
- },
1014
- });
1015
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1016
- 2,
1017
- METRIC_EVENT_NAMES.TASK_TRANSFER_SUCCESS,
1018
- {
1019
- taskId: taskDataMock.interactionId,
1020
- destination: taskDataMock.destAgentId,
1021
- destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
1022
- isConsultTransfer: true,
1023
- },
1024
- ['operational', 'behavioral', 'business']
1025
- );
1026
- });
1027
-
1028
- it('should send DIALNUMBER when calculateDestType returns dialNumber during consultTransfer', async () => {
1029
- const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
1030
- contactMock.consultTransfer.mockResolvedValue(expectedResponse);
1031
-
1032
- // Mock calculateDestType to return dialNumber
1033
- calculateDestTypeSpy.mockReturnValue(CONSULT_TRANSFER_DESTINATION_TYPE.DIALNUMBER);
1034
-
1035
- await task.consultTransfer();
1036
-
1037
- expect(calculateDestTypeSpy).toHaveBeenCalledWith(taskDataMock.interaction, taskDataMock.agentId);
1038
- expect(contactMock.consultTransfer).toHaveBeenCalledWith({
1039
- interactionId: taskId,
1040
- data: {
1041
- to: taskDataMock.destAgentId,
1042
- destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.DIALNUMBER,
1043
- },
1044
- });
1045
- });
1046
-
1047
- it('should send ENTRYPOINT when calculateDestType returns entryPoint during consultTransfer', async () => {
1048
- const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
1049
- contactMock.consultTransfer.mockResolvedValue(expectedResponse);
1050
-
1051
- // Mock calculateDestType to return entryPoint
1052
- calculateDestTypeSpy.mockReturnValue(CONSULT_TRANSFER_DESTINATION_TYPE.ENTRYPOINT);
1053
-
1054
- await task.consultTransfer();
1055
-
1056
- expect(calculateDestTypeSpy).toHaveBeenCalledWith(taskDataMock.interaction, taskDataMock.agentId);
1057
- expect(contactMock.consultTransfer).toHaveBeenCalledWith({
1058
- interactionId: taskId,
1059
- data: {
1060
- to: taskDataMock.destAgentId,
1061
- destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.ENTRYPOINT,
1062
- },
1063
- });
1064
- });
1065
-
1066
- it('should use AGENT when calculateDestType returns agent during consultTransfer', async () => {
1067
- const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
1068
- contactMock.consultTransfer.mockResolvedValue(expectedResponse);
1069
-
1070
- // Mock calculateDestType to return agent (default behavior)
1071
- calculateDestTypeSpy.mockReturnValue(CONSULT_TRANSFER_DESTINATION_TYPE.AGENT);
1072
-
1073
- await task.consultTransfer();
1074
-
1075
- expect(calculateDestTypeSpy).toHaveBeenCalledWith(taskDataMock.interaction, taskDataMock.agentId);
1076
- expect(contactMock.consultTransfer).toHaveBeenCalledWith({
1077
- interactionId: taskId,
1078
- data: {
1079
- to: taskDataMock.destAgentId,
1080
- destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
1081
- },
1082
- });
1083
- });
1084
-
1085
- it('should do consult transfer to a queue by using the destAgentId from task data', async () => {
1086
- const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
1087
- contactMock.consultTransfer.mockResolvedValue(expectedResponse);
1088
-
1089
- const queueConsultTransferPayload: ConsultTransferPayLoad = {
1090
- to: 'some-queue-id',
1091
- destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.QUEUE,
1092
- };
1093
-
1094
- const expectedPayload = {
1095
- to: taskDataMock.destAgentId,
1096
- destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
1097
- };
1098
-
1099
- const response = await task.consultTransfer(queueConsultTransferPayload);
1100
-
1101
- expect(contactMock.consultTransfer).toHaveBeenCalledWith({
1102
- interactionId: taskId,
1103
- data: expectedPayload,
1104
- });
1105
- expect(response).toEqual(expectedResponse);
1106
- });
1107
-
1108
- it('should throw error when attempting to transfer to queue with no destAgentId', async () => {
1109
- const taskWithoutDestAgentId = new Task(contactMock, webCallingService, {
1110
- ...taskDataMock,
1111
- destAgentId: undefined,
1112
- }, {
1113
- wrapUpProps: { wrapUpReasonList: [] },
1114
- autoWrapEnabled: false,
1115
- autoWrapAfterSeconds: 0
1116
- }, taskDataMock.agentId);
1117
-
1118
- const queueConsultTransferPayload: ConsultTransferPayLoad = {
1119
- to: 'some-queue-id',
1120
- destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.QUEUE,
1121
- };
1122
-
1123
- // For this negative case, ensure computed destination is empty
1124
- calculateDestAgentIdSpy.mockReturnValueOnce('');
1125
-
1126
- await expect(
1127
- taskWithoutDestAgentId.consultTransfer(queueConsultTransferPayload)
1128
- ).rejects.toThrow('No agent has accepted this queue consult yet');
1129
- });
1130
-
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);
1140
-
1141
- const result = await task.consultTransfer();
1142
-
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
- });
1193
-
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();
1200
- });
1201
-
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);
1206
-
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
- });
1241
- });
1242
-
1243
- it('should do vteamTransfer if destinationType is queue and return the expected response', async () => {
1244
- const transferPayload: TransferPayLoad = {
1245
- to: '1234',
1246
- destinationType: DESTINATION_TYPE.QUEUE,
1247
- };
1248
-
1249
- const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
1250
- contactMock.vteamTransfer.mockResolvedValue(expectedResponse);
1251
-
1252
- const response = await task.transfer(transferPayload);
1253
-
1254
- expect(contactMock.vteamTransfer).toHaveBeenCalledWith({
1255
- interactionId: taskId,
1256
- data: transferPayload,
1257
- });
1258
- expect(response).toEqual(expectedResponse);
1259
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1260
- 1,
1261
- METRIC_EVENT_NAMES.TASK_TRANSFER_SUCCESS,
1262
- {
1263
- taskId: taskDataMock.interactionId,
1264
- destination: transferPayload.to,
1265
- destinationType: transferPayload.destinationType,
1266
- isConsultTransfer: false,
1267
- ...MetricsManager.getCommonTrackingFieldForAQMResponse(expectedResponse),
1268
- },
1269
- ['operational', 'behavioral', 'business']
1270
- );
1271
- });
1272
-
1273
- it('should do blindTransfer if destinationType is anything other than queue and return the expected response', async () => {
1274
- const transferPayload: TransferPayLoad = {
1275
- to: '1234',
1276
- destinationType: DESTINATION_TYPE.AGENT,
1277
- };
1278
-
1279
- const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
1280
- contactMock.blindTransfer.mockResolvedValue(expectedResponse);
1281
-
1282
- const response = await task.transfer(transferPayload);
1283
-
1284
- expect(contactMock.blindTransfer).toHaveBeenCalledWith({
1285
- interactionId: taskId,
1286
- data: transferPayload,
1287
- });
1288
- expect(response).toEqual(expectedResponse);
1289
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1290
- 1,
1291
- METRIC_EVENT_NAMES.TASK_TRANSFER_SUCCESS,
1292
- {
1293
- taskId: taskDataMock.interactionId,
1294
- destination: transferPayload.to,
1295
- destinationType: transferPayload.destinationType,
1296
- isConsultTransfer: false,
1297
- ...MetricsManager.getCommonTrackingFieldForAQMResponse(expectedResponse),
1298
- },
1299
- ['operational', 'behavioral', 'business']
1300
- );
1301
- });
1302
-
1303
- it('should handle errors in transfer method', async () => {
1304
- const error = {details: (global as any).makeFailure('Consult Transfer Failed')};
1305
- contactMock.blindTransfer.mockImplementation(() => {
1306
- throw error;
1307
- });
1308
-
1309
- const blindTransferPayload: TransferPayLoad = {
1310
- to: '1234',
1311
- destinationType: DESTINATION_TYPE.AGENT,
1312
- };
1313
-
1314
- await expect(task.transfer(blindTransferPayload)).rejects.toThrow(error.details.data.reason);
1315
- expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'transfer', TASK_FILE);
1316
- const expectedTaskErrorFieldsTransfer = {
1317
- trackingId: error.details.trackingId,
1318
- errorMessage: error.details.data.reason,
1319
- errorType: '',
1320
- errorData: '',
1321
- reasonCode: 0,
1322
- };
1323
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1324
- 1,
1325
- METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
1326
- {
1327
- taskId: taskDataMock.interactionId,
1328
- destination: blindTransferPayload.to,
1329
- destinationType: blindTransferPayload.destinationType,
1330
- isConsultTransfer: false,
1331
- error: error.toString(),
1332
- ...expectedTaskErrorFieldsTransfer,
1333
- ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
1334
- },
1335
- ['operational', 'behavioral', 'business']
1336
- );
1337
- });
1338
-
1339
- it('should end the task and return the expected response', async () => {
1340
- const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
1341
- contactMock.end.mockResolvedValue(expectedResponse);
1342
-
1343
- const response = await task.end();
1344
-
1345
- expect(contactMock.end).toHaveBeenCalledWith({interactionId: taskId});
1346
- expect(response).toEqual(expectedResponse);
1347
- expect(loggerInfoSpy).toHaveBeenCalledWith(`Ending task`, {
1348
- module: TASK_FILE,
1349
- method: 'end',
1350
- interactionId: expectedResponse.data.interactionId,
1351
- });
1352
- expect(loggerLogSpy).toHaveBeenCalledWith(`Task ended successfully`, {
1353
- module: TASK_FILE,
1354
- method: 'end',
1355
- interactionId: expectedResponse.data.interactionId,
1356
- });
1357
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1358
- 1,
1359
- METRIC_EVENT_NAMES.TASK_END_SUCCESS,
1360
- {
1361
- taskId: taskDataMock.interactionId,
1362
- ...MetricsManager.getCommonTrackingFieldForAQMResponse(expectedResponse),
1363
- },
1364
- ['operational', 'behavioral', 'business']
1365
- );
1366
- });
1367
-
1368
- it('should handle errors in end method', async () => {
1369
- const error = {details: (global as any).makeFailure('End Failed')};
1370
- contactMock.end.mockImplementation(() => {
1371
- throw error;
1372
- });
1373
-
1374
- await expect(task.end()).rejects.toThrow(error.details.data.reason);
1375
- expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'end', TASK_FILE);
1376
- const expectedTaskErrorFieldsEnd = {
1377
- trackingId: error.details.trackingId,
1378
- errorMessage: error.details.data.reason,
1379
- errorType: '',
1380
- errorData: '',
1381
- reasonCode: 0,
1382
- };
1383
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1384
- 1,
1385
- METRIC_EVENT_NAMES.TASK_END_FAILED,
1386
- {
1387
- taskId: taskDataMock.interactionId,
1388
- ...expectedTaskErrorFieldsEnd,
1389
- ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
1390
- },
1391
- ['operational', 'behavioral', 'business']
1392
- );
1393
- });
1394
-
1395
- it('should wrap up the task and return the expected response', async () => {
1396
- const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
1397
- const wrapupPayload = {
1398
- wrapUpReason: 'Customer request',
1399
- auxCodeId: 'auxCodeId123',
1400
- };
1401
- contactMock.wrapup.mockResolvedValue(expectedResponse);
1402
-
1403
- const response = await task.wrapup(wrapupPayload);
1404
-
1405
- expect(contactMock.wrapup).toHaveBeenCalledWith({interactionId: taskId, data: wrapupPayload});
1406
- expect(response).toEqual(expectedResponse);
1407
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1408
- 1,
1409
- METRIC_EVENT_NAMES.TASK_WRAPUP_SUCCESS,
1410
- {
1411
- taskId: taskDataMock.interactionId,
1412
- wrapUpCode: wrapupPayload.auxCodeId,
1413
- wrapUpReason: wrapupPayload.wrapUpReason,
1414
- ...MetricsManager.getCommonTrackingFieldForAQMResponse(expectedResponse),
1415
- },
1416
- ['operational', 'behavioral', 'business']
1417
- );
1418
- });
1419
-
1420
- it('should handle errors in wrapup method', async () => {
1421
- const error = {details: (global as any).makeFailure('Wrapup Failed')};
1422
- contactMock.wrapup.mockImplementation(() => {
1423
- throw error;
1424
- });
1425
-
1426
- const wrapupPayload = {
1427
- wrapUpReason: 'Customer request',
1428
- auxCodeId: 'auxCodeId123',
1429
- };
1430
-
1431
- await expect(task.wrapup(wrapupPayload)).rejects.toThrow(error.details.data.reason);
1432
- expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'wrapup', TASK_FILE);
1433
- const expectedTaskErrorFieldsWrapup = {
1434
- trackingId: error.details.trackingId,
1435
- errorMessage: error.details.data.reason,
1436
- errorType: '',
1437
- errorData: '',
1438
- reasonCode: 0,
1439
- };
1440
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1441
- 1,
1442
- METRIC_EVENT_NAMES.TASK_WRAPUP_FAILED,
1443
- {
1444
- taskId: taskDataMock.interactionId,
1445
- wrapUpCode: wrapupPayload.auxCodeId,
1446
- wrapUpReason: wrapupPayload.wrapUpReason,
1447
- ...expectedTaskErrorFieldsWrapup,
1448
- ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
1449
- },
1450
- ['operational', 'behavioral', 'business']
1451
- );
1452
- });
1453
-
1454
- it('should throw an error if auxCodeId is missing in wrapup method', async () => {
1455
- const wrapupPayload = {
1456
- wrapUpReason: 'Customer request',
1457
- auxCodeId: '',
1458
- };
1459
- await expect(task.wrapup(wrapupPayload)).rejects.toThrow();
1460
- });
1461
-
1462
- it('should throw an error if wrapUpReason is missing in wrapup method', async () => {
1463
- const wrapupPayload = {
1464
- wrapUpReason: '',
1465
- auxCodeId: 'auxCodeId123',
1466
- };
1467
- await expect(task.wrapup(wrapupPayload)).rejects.toThrow();
1468
- });
1469
-
1470
- it('should throw an error if this.data is missing when wrapup is invoked', async () => {
1471
- const wrapupPayload = {
1472
- wrapUpReason: 'Customer request',
1473
- auxCodeId: 'auxCodeId123',
1474
- };
1475
-
1476
- task.data = undefined;
1477
- await expect(task.wrapup(wrapupPayload)).rejects.toThrow();
1478
- });
1479
-
1480
- it('should pause the recording of the task', async () => {
1481
- await task.pauseRecording();
1482
-
1483
- expect(contactMock.pauseRecording).toHaveBeenCalledWith({interactionId: taskId});
1484
- expect(loggerInfoSpy).toHaveBeenCalledWith(`Pausing recording`, {
1485
- module: TASK_FILE,
1486
- method: 'pauseRecording',
1487
- interactionId: task.data.interactionId,
1488
- });
1489
- expect(loggerLogSpy).toHaveBeenCalledWith(`Recording paused successfully`, {
1490
- module: TASK_FILE,
1491
- method: 'pauseRecording',
1492
- interactionId: task.data.interactionId,
1493
- });
1494
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1495
- 1,
1496
- METRIC_EVENT_NAMES.TASK_PAUSE_RECORDING_SUCCESS,
1497
- {
1498
- taskId: taskDataMock.interactionId,
1499
- },
1500
- ['operational', 'behavioral', 'business']
1501
- );
1502
- });
1503
-
1504
- it('should handle errors in pauseRecording method', async () => {
1505
- const error = {details: (global as any).makeFailure('Pause Recording Failed')};
1506
- contactMock.pauseRecording.mockImplementation(() => {
1507
- throw error;
1508
- });
1509
-
1510
- await expect(task.pauseRecording()).rejects.toThrow(error.details.data.reason);
1511
- expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'pauseRecording', TASK_FILE);
1512
- const expectedTaskErrorFieldsPause = {
1513
- trackingId: error.details.trackingId,
1514
- errorMessage: error.details.data.reason,
1515
- errorType: '',
1516
- errorData: '',
1517
- reasonCode: 0,
1518
- };
1519
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1520
- 1,
1521
- METRIC_EVENT_NAMES.TASK_PAUSE_RECORDING_FAILED,
1522
- {
1523
- taskId: taskDataMock.interactionId,
1524
- error: error.toString(),
1525
- ...expectedTaskErrorFieldsPause,
1526
- ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
1527
- },
1528
- ['operational', 'behavioral', 'business']
1529
- );
1530
- });
1531
-
1532
- it('should resume the recording of the task', async () => {
1533
- const resumePayload = {
1534
- autoResumed: true,
1535
- interactionId: taskId,
1536
- };
1537
-
1538
- await task.resumeRecording(resumePayload);
1539
-
1540
- expect(contactMock.resumeRecording).toHaveBeenCalledWith({
1541
- interactionId: resumePayload.interactionId,
1542
- data: resumePayload,
1543
- });
1544
- expect(loggerInfoSpy).toHaveBeenCalledWith(`Resuming recording`, {
1545
- module: TASK_FILE,
1546
- method: 'resumeRecording',
1547
- interactionId: task.data.interactionId,
1548
- });
1549
- expect(loggerLogSpy).toHaveBeenCalledWith(`Recording resumed successfully`, {
1550
- module: TASK_FILE,
1551
- method: 'resumeRecording',
1552
- interactionId: task.data.interactionId,
1553
- });
1554
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1555
- 1,
1556
- METRIC_EVENT_NAMES.TASK_RESUME_RECORDING_SUCCESS,
1557
- {
1558
- taskId: taskDataMock.interactionId,
1559
- },
1560
- ['operational', 'behavioral', 'business']
1561
- );
1562
- });
1563
-
1564
- it('should resume the recording of the task if the payload is empty', async () => {
1565
- const resumePayload = {
1566
- autoResumed: false,
1567
- };
1568
-
1569
- await task.resumeRecording();
1570
-
1571
- expect(contactMock.resumeRecording).toHaveBeenCalledWith({
1572
- interactionId: taskId,
1573
- data: resumePayload,
1574
- });
1575
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1576
- 1,
1577
- METRIC_EVENT_NAMES.TASK_RESUME_RECORDING_SUCCESS,
1578
- {
1579
- taskId: taskDataMock.interactionId,
1580
- },
1581
- ['operational', 'behavioral', 'business']
1582
- );
1583
- });
1584
-
1585
- it('should handle errors in resumeRecording method', async () => {
1586
- const error = {details: (global as any).makeFailure('Resume Recording Failed')};
1587
- contactMock.resumeRecording.mockImplementation(() => {
1588
- throw error;
1589
- });
1590
-
1591
- const resumePayload = {
1592
- autoResumed: true,
1593
- };
1594
-
1595
- await expect(task.resumeRecording(resumePayload)).rejects.toThrow(error.details.data.reason);
1596
- expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'resumeRecording', TASK_FILE);
1597
- const expectedTaskErrorFieldsResumeRec = {
1598
- trackingId: error.details.trackingId,
1599
- errorMessage: error.details.data.reason,
1600
- errorType: '',
1601
- errorData: '',
1602
- reasonCode: 0,
1603
- };
1604
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1605
- 1,
1606
- METRIC_EVENT_NAMES.TASK_RESUME_RECORDING_FAILED,
1607
- {
1608
- taskId: taskDataMock.interactionId,
1609
- error: error.toString(),
1610
- ...expectedTaskErrorFieldsResumeRec,
1611
- ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
1612
- },
1613
- ['operational', 'behavioral', 'business']
1614
- );
1615
- });
1616
-
1617
- it('should mute call for Desktop login mode', async () => {
1618
- task.localAudioStream = mockStream;
1619
- const muteCallSpy = jest.spyOn(webCallingService, 'muteUnmuteCall');
1620
-
1621
- await task.toggleMute();
1622
-
1623
- expect(muteCallSpy).toHaveBeenCalledWith(mockStream);
1624
- expect(loggerInfoSpy).toHaveBeenCalledWith(`Toggling mute state`, {
1625
- module: TASK_FILE,
1626
- method: 'toggleMute',
1627
- interactionId: task.data.interactionId,
1628
- });
1629
- expect(loggerLogSpy).toHaveBeenCalledWith(`Mute state toggled successfully isCallMuted: ${webCallingService.isCallMuted()}`, {
1630
- module: TASK_FILE,
1631
- method: 'toggleMute',
1632
- interactionId: task.data.interactionId,
1633
- });
1634
- });
1635
-
1636
- it('should handle errors in mute method', async () => {
1637
- const error = {
1638
- details: {
1639
- trackingId: '1234',
1640
- data: {
1641
- reason: 'Mute Failed',
1642
- },
1643
- },
1644
- };
1645
-
1646
- jest.spyOn(webCallingService, 'muteUnmuteCall').mockImplementation(() => {
1647
- throw error;
1648
- });
1649
- await expect(task.toggleMute()).rejects.toThrow(new Error(error.details.data.reason));
1650
- expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'toggleMute', TASK_FILE);
1651
- expect(loggerInfoSpy).toHaveBeenCalledWith(`Toggling mute state`, {
1652
- module: TASK_FILE,
1653
- method: 'toggleMute',
1654
- interactionId: task.data.interactionId,
1655
- });
1656
- });
1657
-
1658
- describe('AutoWrapup initialization tests', () => {
1659
- beforeEach(() => {
1660
- jest.useFakeTimers();
1661
- });
1662
-
1663
- afterEach(() => {
1664
- jest.restoreAllMocks();
1665
- jest.useRealTimers();
1666
- });
1667
-
1668
- it('should not initialize AutoWrapup if wrapUpRequired is false', () => {
1669
- const wrapupProps = {
1670
- wrapUpProps: {
1671
- autoWrapup: true,
1672
- autoWrapupInterval: 5000,
1673
- wrapUpReasonList: [{ isDefault: true, name: 'Default Reason', id: '123', isSystem: false }]
1674
- }
1675
- };
1676
-
1677
- const taskData = { ...taskDataMock, wrapUpRequired: false };
1678
- const taskInstance = new Task(contactMock, webCallingService, taskData, wrapupProps);
1679
-
1680
- expect(taskInstance.autoWrapup).toBeUndefined();
1681
- });
1682
-
1683
- it('should not initialize AutoWrapup if autoWrapup is set to false', () => {
1684
- const wrapupProps = {
1685
- wrapUpProps: {
1686
- autoWrapup: false,
1687
- autoWrapupInterval: 5000,
1688
- wrapUpReasonList: [{ isDefault: true, name: 'Default Reason', id: '123', isSystem: false }]
1689
- }
1690
- };
1691
-
1692
- const taskData = { ...taskDataMock, wrapUpRequired: true };
1693
- const taskInstance = new Task(contactMock, webCallingService, taskData, wrapupProps);
1694
-
1695
- expect(taskInstance.autoWrapup).toBeUndefined();
1696
- expect(loggerInfoSpy).toHaveBeenCalledWith('Auto wrap-up is not required for this task', {
1697
- module: TASK_FILE,
1698
- method: 'setupAutoWrapupTimer',
1699
- interactionId: taskData.interactionId,
1700
- });
1701
- });
1702
-
1703
- it('should initialize AutoWrapup with custom interval when specified', () => {
1704
- const customInterval = 15000;
1705
- const wrapupProps = {
1706
- wrapUpProps: {
1707
- autoWrapup: true,
1708
- autoWrapupInterval: customInterval,
1709
- wrapUpReasonList: [{ isDefault: true, name: 'Default Reason', id: '123', isSystem: false }]
1710
- }
1711
- };
1712
-
1713
- const taskData = { ...taskDataMock, wrapUpRequired: true };
1714
- const taskInstance = new Task(contactMock, webCallingService, taskData, wrapupProps);
1715
-
1716
- expect(taskInstance.autoWrapup).toBeDefined();
1717
- });
1718
-
1719
- it('should cancel AutoWrapup timer when wrapup is called', async () => {
1720
- const wrapupProps = {
1721
- wrapUpProps: {
1722
- autoWrapup: true,
1723
- autoWrapupInterval: 5000,
1724
- wrapUpReasonList: [{ isDefault: true, name: 'Default Reason', id: '123', isSystem: false }]
1725
- }
1726
- };
1727
-
1728
- const taskData = { ...taskDataMock, wrapUpRequired: true };
1729
- const taskInstance = new Task(contactMock, webCallingService, taskData, wrapupProps);
1730
-
1731
- // Mock the autoWrapup object and its clear method
1732
- const clearSpy = jest.spyOn(taskInstance.autoWrapup, 'clear');
1733
-
1734
- // Call wrapup method which should cancel the timer
1735
- await taskInstance.wrapup({ wrapUpReason: 'Test Reason', auxCodeId: '123' });
1736
-
1737
- // Verify that clear was called
1738
- expect(clearSpy).toHaveBeenCalled();
1739
- expect(loggerInfoSpy).toHaveBeenCalledWith('Auto wrap-up timer cancelled', {
1740
- module: TASK_FILE,
1741
- method: 'cancelAutoWrapupTimer',
1742
- interactionId: taskData.interactionId,
1743
- });
1744
- });
1745
-
1746
- it('should directly call cancelAutoWrapUpTimer successfully', () => {
1747
- const wrapupProps = {
1748
- wrapUpProps: {
1749
- autoWrapup: true,
1750
- autoWrapupInterval: 5000,
1751
- wrapUpReasonList: [{ isDefault: true, name: 'Default Reason', id: '123', isSystem: false }]
1752
- }
1753
- };
1754
-
1755
- const taskData = { ...taskDataMock, wrapUpRequired: true };
1756
- const taskInstance = new Task(contactMock, webCallingService, taskData, wrapupProps);
1757
-
1758
- const clearSpy = jest.spyOn(taskInstance.autoWrapup, 'clear');
1759
- taskInstance.cancelAutoWrapupTimer();
1760
-
1761
- expect(clearSpy).toHaveBeenCalled();
1762
- expect(loggerInfoSpy).toHaveBeenCalledWith('Auto wrap-up timer cancelled', {
1763
- module: TASK_FILE,
1764
- method: 'cancelAutoWrapupTimer',
1765
- interactionId: taskData.interactionId,
1766
- });
1767
- });
1768
-
1769
- it('should use default interval when autoWrapupInterval is not specified', () => {
1770
- const wrapupProps = {
1771
- wrapUpProps: {
1772
- autoWrapup: true,
1773
- wrapUpReasonList: [{ isDefault: true, name: 'Default Reason', id: '123', isSystem: false }]
1774
- }
1775
- };
1776
-
1777
- const taskData = { ...taskDataMock, wrapUpRequired: true };
1778
- const taskInstance = new Task(contactMock, webCallingService, taskData, wrapupProps);
1779
-
1780
- expect(taskInstance.autoWrapup).toBeDefined();
1781
- });
1782
-
1783
- it('should setup autoWrapup with a callback that executes wrapup', () => {
1784
- // Create a task with AutoWrapup enabled and a default wrapup reason
1785
- const defaultWrapUpReason = { isDefault: true, name: 'Default Reason', id: '123', isSystem: false };
1786
- const wrapupProps = {
1787
- wrapUpProps: {
1788
- autoWrapup: true,
1789
- autoWrapupInterval: 5000,
1790
- wrapUpReasonList: [defaultWrapUpReason]
1791
- }
1792
- };
1793
-
1794
- const taskData = { ...taskDataMock, wrapUpRequired: true };
1795
-
1796
- let capturedCallback;
1797
- jest.spyOn(global, 'setTimeout').mockImplementation((callback, timeout) => {
1798
- capturedCallback = callback;
1799
- return {} as any;
1800
- });
1801
-
1802
- // Create our task instance
1803
- const taskInstance = new Task(contactMock, webCallingService, taskData, wrapupProps);
1804
-
1805
- // Mock the wrapup method to verify it gets called with correct parameters
1806
- const wrapupMock = jest.fn().mockResolvedValue({});
1807
- taskInstance.wrapup = wrapupMock;
1808
-
1809
- // Verify autoWrapup was initialized
1810
- expect(taskInstance.autoWrapup).toBeDefined();
1811
-
1812
- if (capturedCallback) {
1813
- capturedCallback();
1814
- }
1815
-
1816
- // Verify wrapup was called with correct parameters
1817
- expect(wrapupMock).toHaveBeenCalledWith({
1818
- wrapUpReason: defaultWrapUpReason.name,
1819
- auxCodeId: defaultWrapUpReason.id
1820
- });
1821
- });
1822
-
1823
- it('should handle case when no default wrapup reason is found', () => {
1824
- // Create a task with AutoWrapup enabled but NO default wrapup reason
1825
- const wrapupProps = {
1826
- wrapUpProps: {
1827
- autoWrapup: true,
1828
- autoWrapupInterval: 5000,
1829
- wrapUpReasonList: [
1830
- { isDefault: false, name: 'Reason 1', id: '123', isSystem: false },
1831
- { isDefault: false, name: 'Reason 2', id: '456', isSystem: false }
1832
- ]
1833
- }
1834
- };
1835
-
1836
- const taskData = { ...taskDataMock, wrapUpRequired: true };
1837
-
1838
- // Create our task instance
1839
- const taskInstance = new Task(contactMock, webCallingService, taskData, wrapupProps);
1840
-
1841
- // Mock the wrapup method to verify if it gets called
1842
- const wrapupSpy = jest.fn().mockResolvedValue({});
1843
- taskInstance.wrapup = wrapupSpy;
1844
-
1845
- jest.runOnlyPendingTimers();
1846
-
1847
- // Verify wrapup was called with the first reason (since no default exists)
1848
- expect(wrapupSpy).toHaveBeenCalledWith({
1849
- wrapUpReason: wrapupProps.wrapUpProps.wrapUpReasonList[0].name,
1850
- auxCodeId: wrapupProps.wrapUpProps.wrapUpReasonList[0].id
1851
- });
1852
- });
1853
- });
1854
-
1855
- describe('Conference methods', () => {
1856
- beforeEach(() => {
1857
- contactMock = {
1858
- consultConference: jest.fn(),
1859
- exitConference: jest.fn(),
1860
- conferenceTransfer: jest.fn(),
1861
- };
1862
-
1863
- task = new Task(contactMock, webCallingService, taskDataMock, {
1864
- wrapUpProps: { wrapUpReasonList: [] },
1865
- autoWrapEnabled: false,
1866
- autoWrapAfterSeconds: 0
1867
- }, taskDataMock.agentId);
1868
- });
1869
-
1870
- describe('consultConference', () => {
1871
-
1872
- it('should successfully start conference and emit event', async () => {
1873
- const mockResponse = {
1874
- trackingId: 'test-tracking-id',
1875
- interactionId: taskId,
1876
- };
1877
- contactMock.consultConference.mockResolvedValue(mockResponse);
1878
-
1879
-
1880
- const result = await task.consultConference();
1881
-
1882
- expect(contactMock.consultConference).toHaveBeenCalledWith({
1883
- interactionId: taskId,
1884
- data: {
1885
- agentId: taskDataMock.agentId, // From task data agent ID
1886
- to: taskDataMock.destAgentId, // From calculateDestAgentId() using task participants
1887
- destinationType: 'agent', // From consultation data
1888
- },
1889
- });
1890
- expect(result).toEqual(mockResponse);
1891
- expect(LoggerProxy.info).toHaveBeenCalledWith(`Initiating consult conference to ${taskDataMock.destAgentId}`, {
1892
- module: TASK_FILE,
1893
- method: 'consultConference',
1894
- interactionId: taskId,
1895
- });
1896
- expect(LoggerProxy.log).toHaveBeenCalledWith('Consult conference started successfully', {
1897
- module: TASK_FILE,
1898
- method: 'consultConference',
1899
- interactionId: taskId,
1900
- });
1901
- });
1902
-
1903
- it('should handle basic validation scenarios', async () => {
1904
- // Agent Desktop logic validates data structure but not participant availability
1905
- // This test confirms the method works with the Agent Desktop data flow
1906
- const mockResponse = {
1907
- trackingId: 'test-tracking-validation',
1908
- interactionId: taskId,
1909
- };
1910
- contactMock.consultConference.mockResolvedValue(mockResponse);
1911
-
1912
- const result = await task.consultConference();
1913
- expect(result).toEqual(mockResponse);
1914
- });
1915
-
1916
- it('should handle and rethrow contact method errors', async () => {
1917
- const mockError = new Error('Conference start failed');
1918
- contactMock.consultConference.mockRejectedValue(mockError);
1919
- generateTaskErrorObjectSpy.mockReturnValue(mockError);
1920
-
1921
- await expect(task.consultConference()).rejects.toThrow('Conference start failed');
1922
- expect(LoggerProxy.error).toHaveBeenCalledWith('Failed to start consult conference', {
1923
- module: TASK_FILE,
1924
- method: 'consultConference',
1925
- interactionId: taskId,
1926
- });
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
- });
2088
- });
2089
-
2090
- describe('exitConference', () => {
2091
- it('should successfully end conference and emit event', async () => {
2092
- const mockResponse = {
2093
- trackingId: 'test-tracking-id-end',
2094
- interactionId: taskId,
2095
- };
2096
- contactMock.exitConference.mockResolvedValue(mockResponse);
2097
-
2098
- const result = await task.exitConference();
2099
-
2100
- expect(contactMock.exitConference).toHaveBeenCalledWith({
2101
- interactionId: taskId,
2102
- });
2103
- expect(result).toEqual(mockResponse);
2104
- expect(LoggerProxy.info).toHaveBeenCalledWith('Exiting consult conference', {
2105
- module: TASK_FILE,
2106
- method: 'exitConference',
2107
- interactionId: taskId,
2108
- });
2109
- expect(LoggerProxy.log).toHaveBeenCalledWith('Consult conference exited successfully', {
2110
- module: TASK_FILE,
2111
- method: 'exitConference',
2112
- interactionId: taskId,
2113
- });
2114
- });
2115
-
2116
- it('should throw error for invalid interaction ID', async () => {
2117
- task.data.interactionId = '';
2118
-
2119
- await expect(task.exitConference()).rejects.toThrow('Error while performing exitConference');
2120
- expect(contactMock.exitConference).not.toHaveBeenCalled();
2121
- });
2122
-
2123
- it('should handle and rethrow contact method errors', async () => {
2124
- const mockError = new Error('Conference end failed');
2125
- contactMock.exitConference.mockRejectedValue(mockError);
2126
- generateTaskErrorObjectSpy.mockReturnValue(mockError);
2127
-
2128
- await expect(task.exitConference()).rejects.toThrow('Conference end failed');
2129
- expect(LoggerProxy.error).toHaveBeenCalledWith('Failed to exit consult conference', {
2130
- module: TASK_FILE,
2131
- method: 'exitConference',
2132
- interactionId: taskId,
2133
- });
2134
- });
2135
- });
2136
-
2137
- describe('transferConference', () => {
2138
- it('should successfully transfer conference', async () => {
2139
- const mockResponse = {
2140
- trackingId: 'test-tracking-id-transfer',
2141
- interactionId: taskId,
2142
- };
2143
- contactMock.conferenceTransfer.mockResolvedValue(mockResponse);
2144
-
2145
- const result = await task.transferConference();
2146
-
2147
- expect(contactMock.conferenceTransfer).toHaveBeenCalledWith({
2148
- interactionId: taskId,
2149
- });
2150
- expect(result).toEqual(mockResponse);
2151
- expect(LoggerProxy.info).toHaveBeenCalledWith('Transferring conference', {
2152
- module: TASK_FILE,
2153
- method: 'transferConference',
2154
- interactionId: taskId,
2155
- });
2156
- expect(LoggerProxy.log).toHaveBeenCalledWith('Conference transferred successfully', {
2157
- module: TASK_FILE,
2158
- method: 'transferConference',
2159
- interactionId: taskId,
2160
- });
2161
- });
2162
-
2163
- it('should throw error for invalid interaction ID', async () => {
2164
- task.data.interactionId = '';
2165
-
2166
- await expect(task.transferConference()).rejects.toThrow('Error while performing transferConference');
2167
- expect(contactMock.conferenceTransfer).not.toHaveBeenCalled();
2168
- });
2169
-
2170
- it('should handle and rethrow contact method errors', async () => {
2171
- const mockError = new Error('Conference transfer failed');
2172
- contactMock.conferenceTransfer.mockRejectedValue(mockError);
2173
- generateTaskErrorObjectSpy.mockReturnValue(mockError);
2174
-
2175
- await expect(task.transferConference()).rejects.toThrow('Conference transfer failed');
2176
- expect(LoggerProxy.error).toHaveBeenCalledWith('Failed to transfer conference', {
2177
- module: TASK_FILE,
2178
- method: 'transferConference',
2179
- interactionId: taskId,
2180
- });
2181
- });
2182
- });
2183
- });
2184
- });