@webex/contact-center 3.9.0 → 3.10.0-multi-llms.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.
- package/dist/cc.js +207 -47
- package/dist/cc.js.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/logger-proxy.js +24 -1
- package/dist/logger-proxy.js.map +1 -1
- package/dist/metrics/MetricsManager.js +1 -1
- package/dist/metrics/MetricsManager.js.map +1 -1
- package/dist/metrics/behavioral-events.js +89 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +32 -2
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/AddressBook.js +271 -0
- package/dist/services/AddressBook.js.map +1 -0
- package/dist/services/EntryPoint.js +227 -0
- package/dist/services/EntryPoint.js.map +1 -0
- package/dist/services/Queue.js +261 -0
- package/dist/services/Queue.js.map +1 -0
- package/dist/services/config/constants.js +36 -2
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/index.js +29 -21
- package/dist/services/config/index.js.map +1 -1
- package/dist/services/config/types.js +33 -1
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/core/GlobalTypes.js.map +1 -1
- package/dist/services/core/Utils.js +181 -2
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/core/aqm-reqs.js +0 -4
- package/dist/services/core/aqm-reqs.js.map +1 -1
- package/dist/services/core/constants.js +17 -1
- package/dist/services/core/constants.js.map +1 -1
- package/dist/services/core/websocket/WebSocketManager.js +0 -4
- package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
- package/dist/services/task/TaskManager.js +151 -7
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +104 -0
- package/dist/services/task/TaskUtils.js.map +1 -0
- package/dist/services/task/constants.js +26 -1
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/contact.js +86 -0
- package/dist/services/task/contact.js.map +1 -1
- package/dist/services/task/index.js +428 -91
- package/dist/services/task/index.js.map +1 -1
- package/dist/services/task/types.js +12 -0
- package/dist/services/task/types.js.map +1 -1
- package/dist/types/cc.d.ts +121 -35
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/index.d.ts +4 -3
- package/dist/types/metrics/constants.d.ts +25 -1
- package/dist/types/services/AddressBook.d.ts +74 -0
- package/dist/types/services/EntryPoint.d.ts +67 -0
- package/dist/types/services/Queue.d.ts +76 -0
- package/dist/types/services/config/constants.d.ts +35 -1
- package/dist/types/services/config/index.d.ts +6 -9
- package/dist/types/services/config/types.d.ts +79 -58
- package/dist/types/services/core/GlobalTypes.d.ts +25 -0
- package/dist/types/services/core/Utils.d.ts +55 -1
- package/dist/types/services/core/constants.d.ts +14 -0
- package/dist/types/services/task/TaskUtils.d.ts +42 -0
- package/dist/types/services/task/constants.d.ts +23 -0
- package/dist/types/services/task/contact.d.ts +10 -0
- package/dist/types/services/task/index.d.ts +85 -4
- package/dist/types/services/task/types.d.ts +245 -21
- package/dist/types/types.d.ts +162 -0
- package/dist/types/utils/PageCache.d.ts +173 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/PageCache.js +192 -0
- package/dist/utils/PageCache.js.map +1 -0
- package/dist/webex.js +1 -1
- package/package.json +10 -9
- package/src/cc.ts +232 -52
- package/src/constants.ts +1 -0
- package/src/index.ts +17 -2
- package/src/logger-proxy.ts +24 -1
- package/src/metrics/MetricsManager.ts +1 -1
- package/src/metrics/behavioral-events.ts +94 -0
- package/src/metrics/constants.ts +37 -1
- package/src/services/AddressBook.ts +291 -0
- package/src/services/EntryPoint.ts +241 -0
- package/src/services/Queue.ts +277 -0
- package/src/services/config/constants.ts +42 -2
- package/src/services/config/index.ts +30 -30
- package/src/services/config/types.ts +59 -58
- package/src/services/core/GlobalTypes.ts +27 -0
- package/src/services/core/Utils.ts +215 -1
- package/src/services/core/aqm-reqs.ts +0 -5
- package/src/services/core/constants.ts +16 -0
- package/src/services/core/websocket/WebSocketManager.ts +0 -4
- package/src/services/task/TaskManager.ts +182 -9
- package/src/services/task/TaskUtils.ts +113 -0
- package/src/services/task/constants.ts +25 -0
- package/src/services/task/contact.ts +80 -0
- package/src/services/task/index.ts +497 -71
- package/src/services/task/types.ts +264 -20
- package/src/types.ts +180 -0
- package/src/utils/PageCache.ts +252 -0
- package/test/unit/spec/cc.ts +282 -85
- package/test/unit/spec/metrics/MetricsManager.ts +0 -1
- package/test/unit/spec/metrics/behavioral-events.ts +42 -0
- package/test/unit/spec/services/AddressBook.ts +332 -0
- package/test/unit/spec/services/EntryPoint.ts +259 -0
- package/test/unit/spec/services/Queue.ts +323 -0
- package/test/unit/spec/services/config/index.ts +279 -65
- package/test/unit/spec/services/core/Utils.ts +282 -1
- package/test/unit/spec/services/core/aqm-reqs.ts +1 -3
- package/test/unit/spec/services/core/websocket/WebSocketManager.ts +0 -4
- package/test/unit/spec/services/task/TaskManager.ts +760 -2
- package/test/unit/spec/services/task/TaskUtils.ts +131 -0
- package/test/unit/spec/services/task/contact.ts +31 -1
- package/test/unit/spec/services/task/index.ts +873 -163
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -33,12 +33,14 @@ describe('Task', () => {
|
|
|
33
33
|
let mockMetricsManager;
|
|
34
34
|
let taskDataMock;
|
|
35
35
|
let webCallingService;
|
|
36
|
-
let
|
|
36
|
+
let generateTaskErrorObjectSpy;
|
|
37
37
|
let mockWebexRequest;
|
|
38
38
|
let webex: WebexSDK;
|
|
39
39
|
let loggerInfoSpy;
|
|
40
40
|
let loggerLogSpy;
|
|
41
41
|
let loggerErrorSpy;
|
|
42
|
+
let calculateDestAgentIdSpy;
|
|
43
|
+
let calculateDestTypeSpy;
|
|
42
44
|
|
|
43
45
|
const taskId = '0ae913a4-c857-4705-8d49-76dd3dde75e4';
|
|
44
46
|
const mockTrack = {} as MediaStreamTrack;
|
|
@@ -74,6 +76,9 @@ describe('Task', () => {
|
|
|
74
76
|
wrapup: jest.fn().mockResolvedValue({}),
|
|
75
77
|
pauseRecording: jest.fn().mockResolvedValue({}),
|
|
76
78
|
resumeRecording: jest.fn().mockResolvedValue({}),
|
|
79
|
+
consultConference: jest.fn().mockResolvedValue({}),
|
|
80
|
+
exitConference: jest.fn().mockResolvedValue({}),
|
|
81
|
+
conferenceTransfer: jest.fn().mockResolvedValue({}),
|
|
77
82
|
};
|
|
78
83
|
|
|
79
84
|
mockMetricsManager = {
|
|
@@ -115,6 +120,32 @@ describe('Task', () => {
|
|
|
115
120
|
interaction: {
|
|
116
121
|
mediaType: 'telephony',
|
|
117
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
|
+
},
|
|
118
149
|
media: {
|
|
119
150
|
'58a45567-4e61-4f4b-a580-5bc86357bef0': {
|
|
120
151
|
holdTimestamp: null,
|
|
@@ -141,8 +172,18 @@ describe('Task', () => {
|
|
|
141
172
|
},
|
|
142
173
|
};
|
|
143
174
|
|
|
144
|
-
//
|
|
145
|
-
|
|
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);
|
|
146
187
|
|
|
147
188
|
// Mock navigator.mediaDevices
|
|
148
189
|
global.navigator.mediaDevices = {
|
|
@@ -158,11 +199,42 @@ describe('Task', () => {
|
|
|
158
199
|
return mockStream;
|
|
159
200
|
});
|
|
160
201
|
|
|
161
|
-
|
|
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
|
+
});
|
|
162
233
|
});
|
|
163
234
|
|
|
164
235
|
afterEach(() => {
|
|
165
236
|
jest.clearAllMocks();
|
|
237
|
+
jest.restoreAllMocks();
|
|
166
238
|
});
|
|
167
239
|
|
|
168
240
|
it('test the on spy', async () => {
|
|
@@ -177,7 +249,7 @@ describe('Task', () => {
|
|
|
177
249
|
});
|
|
178
250
|
|
|
179
251
|
describe('updateTaskData cases', () => {
|
|
180
|
-
it('
|
|
252
|
+
it('updates the task data by overwrite', async () => {
|
|
181
253
|
const newData = {
|
|
182
254
|
type: CC_EVENTS.AGENT_CONTACT_ASSIGNED,
|
|
183
255
|
agentId: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
@@ -226,12 +298,12 @@ describe('Task', () => {
|
|
|
226
298
|
expect(task.data).toEqual(newData);
|
|
227
299
|
});
|
|
228
300
|
|
|
229
|
-
it('
|
|
301
|
+
it('updates the task data by merging with key removal', async () => {
|
|
230
302
|
const newData = {
|
|
231
|
-
//
|
|
303
|
+
// Purposefully omit other keys to test remove and merge behavior
|
|
232
304
|
isConsulting: true, // Add a new custom key to test persistence
|
|
233
305
|
interaction: {
|
|
234
|
-
//
|
|
306
|
+
// Purposefully omit other interaction keys to test removal
|
|
235
307
|
media: {
|
|
236
308
|
'58a45567-4e61-4f4b-a580-5bc86357bef0': {
|
|
237
309
|
holdTimestamp: null,
|
|
@@ -258,11 +330,12 @@ describe('Task', () => {
|
|
|
258
330
|
},
|
|
259
331
|
};
|
|
260
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
|
|
261
335
|
const expectedData: TaskData = {
|
|
262
|
-
|
|
263
|
-
isConsulting: true,
|
|
336
|
+
isConsulting: true, // New key is added
|
|
264
337
|
interaction: {
|
|
265
|
-
|
|
338
|
+
// Only the media key from newData.interaction remains
|
|
266
339
|
media: {
|
|
267
340
|
'58a45567-4e61-4f4b-a580-5bc86357bef0': {
|
|
268
341
|
holdTimestamp: null,
|
|
@@ -295,6 +368,60 @@ describe('Task', () => {
|
|
|
295
368
|
|
|
296
369
|
expect(task.data).toEqual(expectedData);
|
|
297
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
|
+
});
|
|
298
425
|
});
|
|
299
426
|
|
|
300
427
|
it('should accept a task and answer call when using BROWSER login option', async () => {
|
|
@@ -413,27 +540,28 @@ describe('Task', () => {
|
|
|
413
540
|
});
|
|
414
541
|
|
|
415
542
|
it('should handle errors in accept method', async () => {
|
|
416
|
-
const error = {
|
|
417
|
-
details: {
|
|
418
|
-
trackingId: '1234',
|
|
419
|
-
data: {
|
|
420
|
-
reason: 'Accept Failed',
|
|
421
|
-
},
|
|
422
|
-
},
|
|
423
|
-
};
|
|
543
|
+
const error = {details: (global as any).makeFailure('Accept Failed')};
|
|
424
544
|
|
|
425
545
|
jest.spyOn(webCallingService, 'answerCall').mockImplementation(() => {
|
|
426
546
|
throw error;
|
|
427
547
|
});
|
|
428
548
|
|
|
429
549
|
await expect(task.accept()).rejects.toThrow(new Error(error.details.data.reason));
|
|
430
|
-
expect(
|
|
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
|
+
};
|
|
431
558
|
expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
|
|
432
559
|
1,
|
|
433
560
|
METRIC_EVENT_NAMES.TASK_ACCEPT_FAILED,
|
|
434
561
|
{
|
|
435
562
|
taskId: taskDataMock.interactionId,
|
|
436
563
|
error: error.toString(),
|
|
564
|
+
...expectedTaskErrorFields,
|
|
437
565
|
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
|
|
438
566
|
},
|
|
439
567
|
['operational', 'behavioral', 'business']
|
|
@@ -469,26 +597,27 @@ describe('Task', () => {
|
|
|
469
597
|
});
|
|
470
598
|
|
|
471
599
|
it('should handle errors in decline method', async () => {
|
|
472
|
-
const error = {
|
|
473
|
-
details: {
|
|
474
|
-
trackingId: '1234',
|
|
475
|
-
data: {
|
|
476
|
-
reason: 'Decline Failed',
|
|
477
|
-
},
|
|
478
|
-
},
|
|
479
|
-
};
|
|
600
|
+
const error = {details: (global as any).makeFailure('Decline Failed')};
|
|
480
601
|
|
|
481
602
|
jest.spyOn(webCallingService, 'declineCall').mockImplementation(() => {
|
|
482
603
|
throw error;
|
|
483
604
|
});
|
|
484
605
|
await expect(task.decline()).rejects.toThrow(new Error(error.details.data.reason));
|
|
485
|
-
expect(
|
|
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
|
+
};
|
|
486
614
|
expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
|
|
487
615
|
1,
|
|
488
616
|
METRIC_EVENT_NAMES.TASK_DECLINE_FAILED,
|
|
489
617
|
{
|
|
490
618
|
taskId: taskDataMock.interactionId,
|
|
491
619
|
error: error.toString(),
|
|
620
|
+
...expectedTaskErrorFieldsDecline,
|
|
492
621
|
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
|
|
493
622
|
},
|
|
494
623
|
['operational', 'behavioral']
|
|
@@ -528,21 +657,55 @@ describe('Task', () => {
|
|
|
528
657
|
);
|
|
529
658
|
});
|
|
530
659
|
|
|
531
|
-
it('should
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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,
|
|
538
689
|
},
|
|
539
|
-
|
|
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')};
|
|
540
696
|
contactMock.hold.mockImplementation(() => {
|
|
541
697
|
throw error;
|
|
542
698
|
});
|
|
543
699
|
|
|
544
700
|
await expect(task.hold()).rejects.toThrow(error.details.data.reason);
|
|
545
|
-
expect(
|
|
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
|
+
};
|
|
546
709
|
expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
|
|
547
710
|
1,
|
|
548
711
|
METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
|
|
@@ -550,6 +713,37 @@ describe('Task', () => {
|
|
|
550
713
|
taskId: taskDataMock.interactionId,
|
|
551
714
|
mediaResourceId: taskDataMock.mediaResourceId,
|
|
552
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,
|
|
553
747
|
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
|
|
554
748
|
},
|
|
555
749
|
['operational', 'behavioral']
|
|
@@ -580,21 +774,44 @@ describe('Task', () => {
|
|
|
580
774
|
);
|
|
581
775
|
});
|
|
582
776
|
|
|
583
|
-
it('should
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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),
|
|
590
795
|
},
|
|
591
|
-
|
|
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')};
|
|
592
802
|
contactMock.unHold.mockImplementation(() => {
|
|
593
803
|
throw error;
|
|
594
804
|
});
|
|
595
805
|
|
|
596
806
|
await expect(task.resume()).rejects.toThrow(error.details.data.reason);
|
|
597
|
-
expect(
|
|
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
|
+
};
|
|
598
815
|
expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
|
|
599
816
|
1,
|
|
600
817
|
METRIC_EVENT_NAMES.TASK_RESUME_FAILED,
|
|
@@ -604,6 +821,37 @@ describe('Task', () => {
|
|
|
604
821
|
mediaResourceId:
|
|
605
822
|
taskDataMock.interaction.media[taskDataMock.interaction.mainInteractionId]
|
|
606
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,
|
|
607
855
|
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
|
|
608
856
|
},
|
|
609
857
|
['operational', 'behavioral']
|
|
@@ -630,8 +878,8 @@ describe('Task', () => {
|
|
|
630
878
|
expect(loggerLogSpy).toHaveBeenCalledWith(`Consult started successfully to ${consultPayload.to}`, {
|
|
631
879
|
module: TASK_FILE,
|
|
632
880
|
method: 'consult',
|
|
633
|
-
trackingId: expectedResponse.trackingId,
|
|
634
881
|
interactionId: task.data.interactionId,
|
|
882
|
+
trackingId: '1234',
|
|
635
883
|
});
|
|
636
884
|
expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
|
|
637
885
|
METRIC_EVENT_NAMES.TASK_CONSULT_START_SUCCESS,
|
|
@@ -646,14 +894,7 @@ describe('Task', () => {
|
|
|
646
894
|
});
|
|
647
895
|
|
|
648
896
|
it('should handle errors in consult method', async () => {
|
|
649
|
-
const error = {
|
|
650
|
-
details: {
|
|
651
|
-
trackingId: '1234',
|
|
652
|
-
data: {
|
|
653
|
-
reason: 'Consult Failed',
|
|
654
|
-
},
|
|
655
|
-
},
|
|
656
|
-
};
|
|
897
|
+
const error = {details: (global as any).makeFailure('Consult Failed')};
|
|
657
898
|
contactMock.consult.mockImplementation(() => {
|
|
658
899
|
throw error;
|
|
659
900
|
});
|
|
@@ -664,12 +905,19 @@ describe('Task', () => {
|
|
|
664
905
|
};
|
|
665
906
|
|
|
666
907
|
await expect(task.consult(consultPayload)).rejects.toThrow(error.details.data.reason);
|
|
667
|
-
expect(
|
|
908
|
+
expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'consult', TASK_FILE);
|
|
668
909
|
expect(loggerInfoSpy).toHaveBeenCalledWith(`Starting consult`, {
|
|
669
910
|
module: TASK_FILE,
|
|
670
911
|
method: 'consult',
|
|
671
912
|
interactionId: task.data.interactionId,
|
|
672
913
|
});
|
|
914
|
+
const expectedTaskErrorFieldsConsult = {
|
|
915
|
+
trackingId: error.details.trackingId,
|
|
916
|
+
errorMessage: error.details.data.reason,
|
|
917
|
+
errorType: '',
|
|
918
|
+
errorData: '',
|
|
919
|
+
reasonCode: 0,
|
|
920
|
+
};
|
|
673
921
|
expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
|
|
674
922
|
METRIC_EVENT_NAMES.TASK_CONSULT_START_FAILED,
|
|
675
923
|
{
|
|
@@ -677,6 +925,7 @@ describe('Task', () => {
|
|
|
677
925
|
destination: consultPayload.to,
|
|
678
926
|
destinationType: consultPayload.destinationType,
|
|
679
927
|
error: error.toString(),
|
|
928
|
+
...expectedTaskErrorFieldsConsult,
|
|
680
929
|
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
|
|
681
930
|
},
|
|
682
931
|
['operational', 'behavioral', 'business']
|
|
@@ -710,14 +959,7 @@ describe('Task', () => {
|
|
|
710
959
|
});
|
|
711
960
|
|
|
712
961
|
it('should handle errors in endConsult method', async () => {
|
|
713
|
-
const error = {
|
|
714
|
-
details: {
|
|
715
|
-
trackingId: '1234',
|
|
716
|
-
data: {
|
|
717
|
-
reason: 'End Consult Failed',
|
|
718
|
-
},
|
|
719
|
-
},
|
|
720
|
-
};
|
|
962
|
+
const error = {details: (global as any).makeFailure('End Consult Failed')};
|
|
721
963
|
contactMock.consultEnd.mockImplementation(() => {
|
|
722
964
|
throw error;
|
|
723
965
|
});
|
|
@@ -728,13 +970,21 @@ describe('Task', () => {
|
|
|
728
970
|
};
|
|
729
971
|
|
|
730
972
|
await expect(task.endConsult(consultEndPayload)).rejects.toThrow(error.details.data.reason);
|
|
731
|
-
expect(
|
|
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
|
+
};
|
|
732
981
|
expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
|
|
733
982
|
1,
|
|
734
983
|
METRIC_EVENT_NAMES.TASK_CONSULT_END_FAILED,
|
|
735
984
|
{
|
|
736
985
|
taskId: taskDataMock.interactionId,
|
|
737
986
|
error: error.toString(),
|
|
987
|
+
...expectedTaskErrorFieldsEndConsult,
|
|
738
988
|
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
|
|
739
989
|
},
|
|
740
990
|
['operational', 'behavioral', 'business']
|
|
@@ -754,29 +1004,84 @@ describe('Task', () => {
|
|
|
754
1004
|
expect(contactMock.consult).toHaveBeenCalledWith({interactionId: taskId, data: consultPayload});
|
|
755
1005
|
expect(response).toEqual(expectedResponse);
|
|
756
1006
|
|
|
757
|
-
const
|
|
758
|
-
to: '1234',
|
|
759
|
-
destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
|
|
760
|
-
};
|
|
761
|
-
|
|
762
|
-
const consultTransferResponse = await task.consultTransfer(consultTransferPayload);
|
|
1007
|
+
const consultTransferResponse = await task.consultTransfer();
|
|
763
1008
|
expect(contactMock.consultTransfer).toHaveBeenCalledWith({
|
|
764
1009
|
interactionId: taskId,
|
|
765
|
-
data:
|
|
1010
|
+
data: {
|
|
1011
|
+
to: taskDataMock.destAgentId,
|
|
1012
|
+
destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
|
|
1013
|
+
},
|
|
766
1014
|
});
|
|
767
1015
|
expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
|
|
768
1016
|
2,
|
|
769
1017
|
METRIC_EVENT_NAMES.TASK_TRANSFER_SUCCESS,
|
|
770
1018
|
{
|
|
771
1019
|
taskId: taskDataMock.interactionId,
|
|
772
|
-
destination:
|
|
773
|
-
destinationType:
|
|
1020
|
+
destination: taskDataMock.destAgentId,
|
|
1021
|
+
destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
|
|
774
1022
|
isConsultTransfer: true,
|
|
775
1023
|
},
|
|
776
1024
|
['operational', 'behavioral', 'business']
|
|
777
1025
|
);
|
|
778
1026
|
});
|
|
779
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
|
+
|
|
780
1085
|
it('should do consult transfer to a queue by using the destAgentId from task data', async () => {
|
|
781
1086
|
const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
|
|
782
1087
|
contactMock.consultTransfer.mockResolvedValue(expectedResponse);
|
|
@@ -804,65 +1109,135 @@ describe('Task', () => {
|
|
|
804
1109
|
const taskWithoutDestAgentId = new Task(contactMock, webCallingService, {
|
|
805
1110
|
...taskDataMock,
|
|
806
1111
|
destAgentId: undefined,
|
|
807
|
-
}
|
|
1112
|
+
}, {
|
|
1113
|
+
wrapUpProps: { wrapUpReasonList: [] },
|
|
1114
|
+
autoWrapEnabled: false,
|
|
1115
|
+
autoWrapAfterSeconds: 0
|
|
1116
|
+
}, taskDataMock.agentId);
|
|
808
1117
|
|
|
809
1118
|
const queueConsultTransferPayload: ConsultTransferPayLoad = {
|
|
810
1119
|
to: 'some-queue-id',
|
|
811
1120
|
destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.QUEUE,
|
|
812
1121
|
};
|
|
813
1122
|
|
|
1123
|
+
// For this negative case, ensure computed destination is empty
|
|
1124
|
+
calculateDestAgentIdSpy.mockReturnValueOnce('');
|
|
1125
|
+
|
|
814
1126
|
await expect(
|
|
815
1127
|
taskWithoutDestAgentId.consultTransfer(queueConsultTransferPayload)
|
|
816
|
-
).rejects.toThrow('
|
|
1128
|
+
).rejects.toThrow('No agent has accepted this queue consult yet');
|
|
817
1129
|
});
|
|
818
1130
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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);
|
|
828
1140
|
|
|
829
|
-
|
|
830
|
-
expect(response).toEqual(expectedResponse);
|
|
1141
|
+
const result = await task.consultTransfer();
|
|
831
1142
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1143
|
+
expect(calculateDestAgentIdSpy).toHaveBeenCalledWith(taskDataMock.interaction, taskDataMock.agentId);
|
|
1144
|
+
expect(calculateDestTypeSpy).toHaveBeenCalledWith(taskDataMock.interaction, taskDataMock.agentId);
|
|
1145
|
+
expect(contactMock.consultTransfer).toHaveBeenCalledWith({
|
|
1146
|
+
interactionId: taskId,
|
|
835
1147
|
data: {
|
|
836
|
-
|
|
1148
|
+
to: taskDataMock.destAgentId,
|
|
1149
|
+
destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
|
|
837
1150
|
},
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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
|
+
);
|
|
842
1170
|
});
|
|
843
1171
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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
|
+
});
|
|
848
1193
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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
|
+
});
|
|
866
1241
|
});
|
|
867
1242
|
|
|
868
1243
|
it('should do vteamTransfer if destinationType is queue and return the expected response', async () => {
|
|
@@ -926,14 +1301,7 @@ describe('Task', () => {
|
|
|
926
1301
|
});
|
|
927
1302
|
|
|
928
1303
|
it('should handle errors in transfer method', async () => {
|
|
929
|
-
const error = {
|
|
930
|
-
details: {
|
|
931
|
-
trackingId: '1234',
|
|
932
|
-
data: {
|
|
933
|
-
reason: 'Consult Transfer Failed',
|
|
934
|
-
},
|
|
935
|
-
},
|
|
936
|
-
};
|
|
1304
|
+
const error = {details: (global as any).makeFailure('Consult Transfer Failed')};
|
|
937
1305
|
contactMock.blindTransfer.mockImplementation(() => {
|
|
938
1306
|
throw error;
|
|
939
1307
|
});
|
|
@@ -944,7 +1312,14 @@ describe('Task', () => {
|
|
|
944
1312
|
};
|
|
945
1313
|
|
|
946
1314
|
await expect(task.transfer(blindTransferPayload)).rejects.toThrow(error.details.data.reason);
|
|
947
|
-
expect(
|
|
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
|
+
};
|
|
948
1323
|
expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
|
|
949
1324
|
1,
|
|
950
1325
|
METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
|
|
@@ -954,6 +1329,7 @@ describe('Task', () => {
|
|
|
954
1329
|
destinationType: blindTransferPayload.destinationType,
|
|
955
1330
|
isConsultTransfer: false,
|
|
956
1331
|
error: error.toString(),
|
|
1332
|
+
...expectedTaskErrorFieldsTransfer,
|
|
957
1333
|
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
|
|
958
1334
|
},
|
|
959
1335
|
['operational', 'behavioral', 'business']
|
|
@@ -990,25 +1366,26 @@ describe('Task', () => {
|
|
|
990
1366
|
});
|
|
991
1367
|
|
|
992
1368
|
it('should handle errors in end method', async () => {
|
|
993
|
-
const error = {
|
|
994
|
-
details: {
|
|
995
|
-
trackingId: '1234',
|
|
996
|
-
data: {
|
|
997
|
-
reason: 'End Failed',
|
|
998
|
-
},
|
|
999
|
-
},
|
|
1000
|
-
};
|
|
1369
|
+
const error = {details: (global as any).makeFailure('End Failed')};
|
|
1001
1370
|
contactMock.end.mockImplementation(() => {
|
|
1002
1371
|
throw error;
|
|
1003
1372
|
});
|
|
1004
1373
|
|
|
1005
1374
|
await expect(task.end()).rejects.toThrow(error.details.data.reason);
|
|
1006
|
-
expect(
|
|
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
|
+
};
|
|
1007
1383
|
expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
|
|
1008
1384
|
1,
|
|
1009
1385
|
METRIC_EVENT_NAMES.TASK_END_FAILED,
|
|
1010
1386
|
{
|
|
1011
1387
|
taskId: taskDataMock.interactionId,
|
|
1388
|
+
...expectedTaskErrorFieldsEnd,
|
|
1012
1389
|
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
|
|
1013
1390
|
},
|
|
1014
1391
|
['operational', 'behavioral', 'business']
|
|
@@ -1041,14 +1418,7 @@ describe('Task', () => {
|
|
|
1041
1418
|
});
|
|
1042
1419
|
|
|
1043
1420
|
it('should handle errors in wrapup method', async () => {
|
|
1044
|
-
const error = {
|
|
1045
|
-
details: {
|
|
1046
|
-
trackingId: '1234',
|
|
1047
|
-
data: {
|
|
1048
|
-
reason: 'Wrapup Failed',
|
|
1049
|
-
},
|
|
1050
|
-
},
|
|
1051
|
-
};
|
|
1421
|
+
const error = {details: (global as any).makeFailure('Wrapup Failed')};
|
|
1052
1422
|
contactMock.wrapup.mockImplementation(() => {
|
|
1053
1423
|
throw error;
|
|
1054
1424
|
});
|
|
@@ -1059,7 +1429,14 @@ describe('Task', () => {
|
|
|
1059
1429
|
};
|
|
1060
1430
|
|
|
1061
1431
|
await expect(task.wrapup(wrapupPayload)).rejects.toThrow(error.details.data.reason);
|
|
1062
|
-
expect(
|
|
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
|
+
};
|
|
1063
1440
|
expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
|
|
1064
1441
|
1,
|
|
1065
1442
|
METRIC_EVENT_NAMES.TASK_WRAPUP_FAILED,
|
|
@@ -1067,6 +1444,7 @@ describe('Task', () => {
|
|
|
1067
1444
|
taskId: taskDataMock.interactionId,
|
|
1068
1445
|
wrapUpCode: wrapupPayload.auxCodeId,
|
|
1069
1446
|
wrapUpReason: wrapupPayload.wrapUpReason,
|
|
1447
|
+
...expectedTaskErrorFieldsWrapup,
|
|
1070
1448
|
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
|
|
1071
1449
|
},
|
|
1072
1450
|
['operational', 'behavioral', 'business']
|
|
@@ -1124,26 +1502,27 @@ describe('Task', () => {
|
|
|
1124
1502
|
});
|
|
1125
1503
|
|
|
1126
1504
|
it('should handle errors in pauseRecording method', async () => {
|
|
1127
|
-
const error = {
|
|
1128
|
-
details: {
|
|
1129
|
-
trackingId: '1234',
|
|
1130
|
-
data: {
|
|
1131
|
-
reason: 'Pause Recording Failed',
|
|
1132
|
-
},
|
|
1133
|
-
},
|
|
1134
|
-
};
|
|
1505
|
+
const error = {details: (global as any).makeFailure('Pause Recording Failed')};
|
|
1135
1506
|
contactMock.pauseRecording.mockImplementation(() => {
|
|
1136
1507
|
throw error;
|
|
1137
1508
|
});
|
|
1138
1509
|
|
|
1139
1510
|
await expect(task.pauseRecording()).rejects.toThrow(error.details.data.reason);
|
|
1140
|
-
expect(
|
|
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
|
+
};
|
|
1141
1519
|
expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
|
|
1142
1520
|
1,
|
|
1143
1521
|
METRIC_EVENT_NAMES.TASK_PAUSE_RECORDING_FAILED,
|
|
1144
1522
|
{
|
|
1145
1523
|
taskId: taskDataMock.interactionId,
|
|
1146
1524
|
error: error.toString(),
|
|
1525
|
+
...expectedTaskErrorFieldsPause,
|
|
1147
1526
|
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
|
|
1148
1527
|
},
|
|
1149
1528
|
['operational', 'behavioral', 'business']
|
|
@@ -1204,14 +1583,7 @@ describe('Task', () => {
|
|
|
1204
1583
|
});
|
|
1205
1584
|
|
|
1206
1585
|
it('should handle errors in resumeRecording method', async () => {
|
|
1207
|
-
const error = {
|
|
1208
|
-
details: {
|
|
1209
|
-
trackingId: '1234',
|
|
1210
|
-
data: {
|
|
1211
|
-
reason: 'Resume Recording Failed',
|
|
1212
|
-
},
|
|
1213
|
-
},
|
|
1214
|
-
};
|
|
1586
|
+
const error = {details: (global as any).makeFailure('Resume Recording Failed')};
|
|
1215
1587
|
contactMock.resumeRecording.mockImplementation(() => {
|
|
1216
1588
|
throw error;
|
|
1217
1589
|
});
|
|
@@ -1221,13 +1593,21 @@ describe('Task', () => {
|
|
|
1221
1593
|
};
|
|
1222
1594
|
|
|
1223
1595
|
await expect(task.resumeRecording(resumePayload)).rejects.toThrow(error.details.data.reason);
|
|
1224
|
-
expect(
|
|
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
|
+
};
|
|
1225
1604
|
expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
|
|
1226
1605
|
1,
|
|
1227
1606
|
METRIC_EVENT_NAMES.TASK_RESUME_RECORDING_FAILED,
|
|
1228
1607
|
{
|
|
1229
1608
|
taskId: taskDataMock.interactionId,
|
|
1230
1609
|
error: error.toString(),
|
|
1610
|
+
...expectedTaskErrorFieldsResumeRec,
|
|
1231
1611
|
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
|
|
1232
1612
|
},
|
|
1233
1613
|
['operational', 'behavioral', 'business']
|
|
@@ -1267,7 +1647,7 @@ describe('Task', () => {
|
|
|
1267
1647
|
throw error;
|
|
1268
1648
|
});
|
|
1269
1649
|
await expect(task.toggleMute()).rejects.toThrow(new Error(error.details.data.reason));
|
|
1270
|
-
expect(
|
|
1650
|
+
expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'toggleMute', TASK_FILE);
|
|
1271
1651
|
expect(loggerInfoSpy).toHaveBeenCalledWith(`Toggling mute state`, {
|
|
1272
1652
|
module: TASK_FILE,
|
|
1273
1653
|
method: 'toggleMute',
|
|
@@ -1471,4 +1851,334 @@ describe('Task', () => {
|
|
|
1471
1851
|
});
|
|
1472
1852
|
});
|
|
1473
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
|
+
});
|
|
1474
2184
|
});
|