@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.
Files changed (115) hide show
  1. package/dist/cc.js +207 -47
  2. package/dist/cc.js.map +1 -1
  3. package/dist/constants.js +1 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/index.js +9 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/logger-proxy.js +24 -1
  8. package/dist/logger-proxy.js.map +1 -1
  9. package/dist/metrics/MetricsManager.js +1 -1
  10. package/dist/metrics/MetricsManager.js.map +1 -1
  11. package/dist/metrics/behavioral-events.js +89 -0
  12. package/dist/metrics/behavioral-events.js.map +1 -1
  13. package/dist/metrics/constants.js +32 -2
  14. package/dist/metrics/constants.js.map +1 -1
  15. package/dist/services/AddressBook.js +271 -0
  16. package/dist/services/AddressBook.js.map +1 -0
  17. package/dist/services/EntryPoint.js +227 -0
  18. package/dist/services/EntryPoint.js.map +1 -0
  19. package/dist/services/Queue.js +261 -0
  20. package/dist/services/Queue.js.map +1 -0
  21. package/dist/services/config/constants.js +36 -2
  22. package/dist/services/config/constants.js.map +1 -1
  23. package/dist/services/config/index.js +29 -21
  24. package/dist/services/config/index.js.map +1 -1
  25. package/dist/services/config/types.js +33 -1
  26. package/dist/services/config/types.js.map +1 -1
  27. package/dist/services/core/GlobalTypes.js.map +1 -1
  28. package/dist/services/core/Utils.js +181 -2
  29. package/dist/services/core/Utils.js.map +1 -1
  30. package/dist/services/core/aqm-reqs.js +0 -4
  31. package/dist/services/core/aqm-reqs.js.map +1 -1
  32. package/dist/services/core/constants.js +17 -1
  33. package/dist/services/core/constants.js.map +1 -1
  34. package/dist/services/core/websocket/WebSocketManager.js +0 -4
  35. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  36. package/dist/services/task/TaskManager.js +151 -7
  37. package/dist/services/task/TaskManager.js.map +1 -1
  38. package/dist/services/task/TaskUtils.js +104 -0
  39. package/dist/services/task/TaskUtils.js.map +1 -0
  40. package/dist/services/task/constants.js +26 -1
  41. package/dist/services/task/constants.js.map +1 -1
  42. package/dist/services/task/contact.js +86 -0
  43. package/dist/services/task/contact.js.map +1 -1
  44. package/dist/services/task/index.js +428 -91
  45. package/dist/services/task/index.js.map +1 -1
  46. package/dist/services/task/types.js +12 -0
  47. package/dist/services/task/types.js.map +1 -1
  48. package/dist/types/cc.d.ts +121 -35
  49. package/dist/types/constants.d.ts +1 -0
  50. package/dist/types/index.d.ts +4 -3
  51. package/dist/types/metrics/constants.d.ts +25 -1
  52. package/dist/types/services/AddressBook.d.ts +74 -0
  53. package/dist/types/services/EntryPoint.d.ts +67 -0
  54. package/dist/types/services/Queue.d.ts +76 -0
  55. package/dist/types/services/config/constants.d.ts +35 -1
  56. package/dist/types/services/config/index.d.ts +6 -9
  57. package/dist/types/services/config/types.d.ts +79 -58
  58. package/dist/types/services/core/GlobalTypes.d.ts +25 -0
  59. package/dist/types/services/core/Utils.d.ts +55 -1
  60. package/dist/types/services/core/constants.d.ts +14 -0
  61. package/dist/types/services/task/TaskUtils.d.ts +42 -0
  62. package/dist/types/services/task/constants.d.ts +23 -0
  63. package/dist/types/services/task/contact.d.ts +10 -0
  64. package/dist/types/services/task/index.d.ts +85 -4
  65. package/dist/types/services/task/types.d.ts +245 -21
  66. package/dist/types/types.d.ts +162 -0
  67. package/dist/types/utils/PageCache.d.ts +173 -0
  68. package/dist/types.js +17 -0
  69. package/dist/types.js.map +1 -1
  70. package/dist/utils/PageCache.js +192 -0
  71. package/dist/utils/PageCache.js.map +1 -0
  72. package/dist/webex.js +1 -1
  73. package/package.json +10 -9
  74. package/src/cc.ts +232 -52
  75. package/src/constants.ts +1 -0
  76. package/src/index.ts +17 -2
  77. package/src/logger-proxy.ts +24 -1
  78. package/src/metrics/MetricsManager.ts +1 -1
  79. package/src/metrics/behavioral-events.ts +94 -0
  80. package/src/metrics/constants.ts +37 -1
  81. package/src/services/AddressBook.ts +291 -0
  82. package/src/services/EntryPoint.ts +241 -0
  83. package/src/services/Queue.ts +277 -0
  84. package/src/services/config/constants.ts +42 -2
  85. package/src/services/config/index.ts +30 -30
  86. package/src/services/config/types.ts +59 -58
  87. package/src/services/core/GlobalTypes.ts +27 -0
  88. package/src/services/core/Utils.ts +215 -1
  89. package/src/services/core/aqm-reqs.ts +0 -5
  90. package/src/services/core/constants.ts +16 -0
  91. package/src/services/core/websocket/WebSocketManager.ts +0 -4
  92. package/src/services/task/TaskManager.ts +182 -9
  93. package/src/services/task/TaskUtils.ts +113 -0
  94. package/src/services/task/constants.ts +25 -0
  95. package/src/services/task/contact.ts +80 -0
  96. package/src/services/task/index.ts +497 -71
  97. package/src/services/task/types.ts +264 -20
  98. package/src/types.ts +180 -0
  99. package/src/utils/PageCache.ts +252 -0
  100. package/test/unit/spec/cc.ts +282 -85
  101. package/test/unit/spec/metrics/MetricsManager.ts +0 -1
  102. package/test/unit/spec/metrics/behavioral-events.ts +42 -0
  103. package/test/unit/spec/services/AddressBook.ts +332 -0
  104. package/test/unit/spec/services/EntryPoint.ts +259 -0
  105. package/test/unit/spec/services/Queue.ts +323 -0
  106. package/test/unit/spec/services/config/index.ts +279 -65
  107. package/test/unit/spec/services/core/Utils.ts +282 -1
  108. package/test/unit/spec/services/core/aqm-reqs.ts +1 -3
  109. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +0 -4
  110. package/test/unit/spec/services/task/TaskManager.ts +760 -2
  111. package/test/unit/spec/services/task/TaskUtils.ts +131 -0
  112. package/test/unit/spec/services/task/contact.ts +31 -1
  113. package/test/unit/spec/services/task/index.ts +873 -163
  114. package/umd/contact-center.min.js +2 -2
  115. 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 getErrorDetailsSpy;
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
- // Create an instance of Task
145
- task = new Task(contactMock, webCallingService, taskDataMock);
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
- getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
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('test updating the task data by overwrite', async () => {
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('test updating the task data by merging', async () => {
301
+ it('updates the task data by merging with key removal', async () => {
230
302
  const newData = {
231
- // ...taskDataMock, // Purposefully omit this to test scenario when other keys isn't present
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
- // ...taskDataMock.interaction, // Purposefully omit this to test scenario when a nested key isn't present
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
- ...taskDataMock,
263
- isConsulting: true,
336
+ isConsulting: true, // New key is added
264
337
  interaction: {
265
- ...taskDataMock.interaction,
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(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'accept', TASK_FILE);
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(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'decline', TASK_FILE);
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 handle errors in hold method', async () => {
532
- const error = {
533
- details: {
534
- trackingId: '1234',
535
- data: {
536
- reason: 'Hold Failed',
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(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'hold', TASK_FILE);
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 handle errors in resume method', async () => {
584
- const error = {
585
- details: {
586
- trackingId: '1234',
587
- data: {
588
- reason: 'Resume Failed',
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(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'resume', TASK_FILE);
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(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'consult', TASK_FILE);
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(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'endConsult', TASK_FILE);
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 consultTransferPayload: ConsultTransferPayLoad = {
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: consultTransferPayload,
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: consultTransferPayload.to,
773
- destinationType: consultTransferPayload.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('Error while performing consultTransfer');
1128
+ ).rejects.toThrow('No agent has accepted this queue consult yet');
817
1129
  });
818
1130
 
819
- it('should handle errors in consult transfer', async () => {
820
- const consultPayload = {
821
- destination: '1234',
822
- destinationType: DESTINATION_TYPE.AGENT,
823
- };
824
- const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
825
- contactMock.consult.mockResolvedValue(expectedResponse);
826
-
827
- const response = await task.consult(consultPayload);
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
- expect(contactMock.consult).toHaveBeenCalledWith({interactionId: taskId, data: consultPayload});
830
- expect(response).toEqual(expectedResponse);
1141
+ const result = await task.consultTransfer();
831
1142
 
832
- const error = {
833
- details: {
834
- trackingId: '1234',
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
- reason: 'Consult Transfer Failed',
1148
+ to: taskDataMock.destAgentId,
1149
+ destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
837
1150
  },
838
- },
839
- };
840
- contactMock.consultTransfer.mockImplementation(() => {
841
- throw error;
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
- const consultTransferPayload: ConsultTransferPayLoad = {
845
- to: '1234',
846
- destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
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
- await expect(task.consultTransfer(consultTransferPayload)).rejects.toThrow(
850
- error.details.data.reason
851
- );
852
- expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'consultTransfer', TASK_FILE);
853
- expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
854
- 2,
855
- METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
856
- {
857
- taskId: taskDataMock.interactionId,
858
- destination: consultTransferPayload.to,
859
- destinationType: consultTransferPayload.destinationType,
860
- isConsultTransfer: true,
861
- error: error.toString(),
862
- ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
863
- },
864
- ['operational', 'behavioral', 'business']
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(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'transfer', TASK_FILE);
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(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'end', TASK_FILE);
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(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'wrapup', TASK_FILE);
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(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'pauseRecording', TASK_FILE);
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(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'resumeRecording', TASK_FILE);
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(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'toggleMute', TASK_FILE);
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
  });