@webex/contact-center 3.8.1 → 3.9.0-multipleLLM.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 (102) hide show
  1. package/dist/cc.js +106 -63
  2. package/dist/cc.js.map +1 -1
  3. package/dist/index.js +13 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/logger-proxy.js +24 -1
  6. package/dist/logger-proxy.js.map +1 -1
  7. package/dist/metrics/MetricsManager.js +1 -1
  8. package/dist/metrics/MetricsManager.js.map +1 -1
  9. package/dist/metrics/behavioral-events.js +88 -0
  10. package/dist/metrics/behavioral-events.js.map +1 -1
  11. package/dist/metrics/constants.js +26 -1
  12. package/dist/metrics/constants.js.map +1 -1
  13. package/dist/services/AddressBook.js +271 -0
  14. package/dist/services/AddressBook.js.map +1 -0
  15. package/dist/services/EntryPoint.js +227 -0
  16. package/dist/services/EntryPoint.js.map +1 -0
  17. package/dist/services/Queue.js +261 -0
  18. package/dist/services/Queue.js.map +1 -0
  19. package/dist/services/config/constants.js +24 -2
  20. package/dist/services/config/constants.js.map +1 -1
  21. package/dist/services/config/index.js +1 -43
  22. package/dist/services/config/index.js.map +1 -1
  23. package/dist/services/config/types.js +22 -5
  24. package/dist/services/config/types.js.map +1 -1
  25. package/dist/services/core/GlobalTypes.js.map +1 -1
  26. package/dist/services/core/Utils.js +162 -2
  27. package/dist/services/core/Utils.js.map +1 -1
  28. package/dist/services/core/aqm-reqs.js +0 -4
  29. package/dist/services/core/aqm-reqs.js.map +1 -1
  30. package/dist/services/core/websocket/WebSocketManager.js +0 -4
  31. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  32. package/dist/services/task/TaskManager.js +74 -2
  33. package/dist/services/task/TaskManager.js.map +1 -1
  34. package/dist/services/task/constants.js +7 -1
  35. package/dist/services/task/constants.js.map +1 -1
  36. package/dist/services/task/contact.js +86 -0
  37. package/dist/services/task/contact.js.map +1 -1
  38. package/dist/services/task/index.js +384 -72
  39. package/dist/services/task/index.js.map +1 -1
  40. package/dist/services/task/types.js +14 -0
  41. package/dist/services/task/types.js.map +1 -1
  42. package/dist/types/cc.d.ts +77 -43
  43. package/dist/types/index.d.ts +8 -3
  44. package/dist/types/metrics/constants.d.ts +20 -0
  45. package/dist/types/services/AddressBook.d.ts +74 -0
  46. package/dist/types/services/EntryPoint.d.ts +67 -0
  47. package/dist/types/services/Queue.d.ts +76 -0
  48. package/dist/types/services/config/constants.d.ts +23 -1
  49. package/dist/types/services/config/index.d.ts +1 -14
  50. package/dist/types/services/config/types.d.ts +44 -64
  51. package/dist/types/services/core/GlobalTypes.d.ts +25 -0
  52. package/dist/types/services/core/Utils.d.ts +40 -1
  53. package/dist/types/services/task/constants.d.ts +6 -0
  54. package/dist/types/services/task/contact.d.ts +10 -0
  55. package/dist/types/services/task/index.d.ts +44 -2
  56. package/dist/types/services/task/types.d.ts +123 -1
  57. package/dist/types/types.d.ts +162 -0
  58. package/dist/types/utils/PageCache.d.ts +173 -0
  59. package/dist/types.js +17 -0
  60. package/dist/types.js.map +1 -1
  61. package/dist/utils/PageCache.js +192 -0
  62. package/dist/utils/PageCache.js.map +1 -0
  63. package/dist/webex.js +1 -1
  64. package/package.json +10 -10
  65. package/src/cc.ts +122 -81
  66. package/src/index.ts +19 -3
  67. package/src/logger-proxy.ts +24 -1
  68. package/src/metrics/MetricsManager.ts +1 -1
  69. package/src/metrics/behavioral-events.ts +92 -0
  70. package/src/metrics/constants.ts +30 -0
  71. package/src/services/AddressBook.ts +291 -0
  72. package/src/services/EntryPoint.ts +241 -0
  73. package/src/services/Queue.ts +277 -0
  74. package/src/services/config/constants.ts +26 -2
  75. package/src/services/config/index.ts +1 -55
  76. package/src/services/config/types.ts +22 -65
  77. package/src/services/core/GlobalTypes.ts +27 -0
  78. package/src/services/core/Utils.ts +199 -1
  79. package/src/services/core/aqm-reqs.ts +0 -5
  80. package/src/services/core/websocket/WebSocketManager.ts +0 -4
  81. package/src/services/task/TaskManager.ts +79 -3
  82. package/src/services/task/constants.ts +6 -0
  83. package/src/services/task/contact.ts +80 -0
  84. package/src/services/task/index.ts +457 -57
  85. package/src/services/task/types.ts +133 -0
  86. package/src/types.ts +180 -0
  87. package/src/utils/PageCache.ts +252 -0
  88. package/test/unit/spec/cc.ts +31 -82
  89. package/test/unit/spec/metrics/MetricsManager.ts +0 -1
  90. package/test/unit/spec/metrics/behavioral-events.ts +56 -0
  91. package/test/unit/spec/services/AddressBook.ts +332 -0
  92. package/test/unit/spec/services/EntryPoint.ts +259 -0
  93. package/test/unit/spec/services/Queue.ts +323 -0
  94. package/test/unit/spec/services/config/index.ts +0 -71
  95. package/test/unit/spec/services/core/Utils.ts +50 -0
  96. package/test/unit/spec/services/core/aqm-reqs.ts +1 -3
  97. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +0 -4
  98. package/test/unit/spec/services/task/TaskManager.ts +145 -1
  99. package/test/unit/spec/services/task/contact.ts +31 -1
  100. package/test/unit/spec/services/task/index.ts +410 -123
  101. package/umd/contact-center.min.js +2 -2
  102. package/umd/contact-center.min.js.map +1 -1
@@ -33,12 +33,13 @@ 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 getDestinationAgentIdSpy;
42
43
 
43
44
  const taskId = '0ae913a4-c857-4705-8d49-76dd3dde75e4';
44
45
  const mockTrack = {} as MediaStreamTrack;
@@ -74,6 +75,9 @@ describe('Task', () => {
74
75
  wrapup: jest.fn().mockResolvedValue({}),
75
76
  pauseRecording: jest.fn().mockResolvedValue({}),
76
77
  resumeRecording: jest.fn().mockResolvedValue({}),
78
+ consultConference: jest.fn().mockResolvedValue({}),
79
+ exitConference: jest.fn().mockResolvedValue({}),
80
+ conferenceTransfer: jest.fn().mockResolvedValue({}),
77
81
  };
78
82
 
79
83
  mockMetricsManager = {
@@ -141,6 +145,11 @@ describe('Task', () => {
141
145
  },
142
146
  };
143
147
 
148
+ // Mock destination agent id resolution from participants
149
+ getDestinationAgentIdSpy = jest
150
+ .spyOn(Utils, 'getDestinationAgentId')
151
+ .mockReturnValue(taskDataMock.destAgentId);
152
+
144
153
  // Create an instance of Task
145
154
  task = new Task(contactMock, webCallingService, taskDataMock);
146
155
 
@@ -158,11 +167,42 @@ describe('Task', () => {
158
167
  return mockStream;
159
168
  });
160
169
 
161
- getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
170
+ generateTaskErrorObjectSpy = jest.spyOn(Utils, 'generateTaskErrorObject');
171
+ generateTaskErrorObjectSpy.mockImplementation((error: any, methodName: string) => {
172
+ const trackingId = error?.details?.trackingId;
173
+ const msg = error?.details?.msg;
174
+ const legacyReason = error?.details?.data?.reason;
175
+ const errorMessage = msg?.errorMessage || legacyReason || `Error while performing ${methodName}`;
176
+ const errorType = msg?.errorType || '';
177
+ const errorData = msg?.errorData || '';
178
+ const reasonCode = msg?.reasonCode || 0;
179
+ const reason = legacyReason || (errorType ? `${errorType}: ${errorMessage}` : errorMessage);
180
+ const err: any = new Error(reason);
181
+ err.data = {
182
+ trackingId,
183
+ message: errorMessage,
184
+ errorType,
185
+ errorData,
186
+ reasonCode,
187
+ };
188
+ return err;
189
+ });
190
+
191
+ (global as any).makeFailure = (reason: string, trackingId = '1234', orgId = 'org1') => ({
192
+ type: 'failure_event',
193
+ orgId,
194
+ trackingId,
195
+ data: {
196
+ agentId: 'agent1',
197
+ reason,
198
+ reasonCode: 0,
199
+ },
200
+ });
162
201
  });
163
202
 
164
203
  afterEach(() => {
165
204
  jest.clearAllMocks();
205
+ jest.restoreAllMocks();
166
206
  });
167
207
 
168
208
  it('test the on spy', async () => {
@@ -413,27 +453,28 @@ describe('Task', () => {
413
453
  });
414
454
 
415
455
  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
- };
456
+ const error = {details: (global as any).makeFailure('Accept Failed')};
424
457
 
425
458
  jest.spyOn(webCallingService, 'answerCall').mockImplementation(() => {
426
459
  throw error;
427
460
  });
428
461
 
429
462
  await expect(task.accept()).rejects.toThrow(new Error(error.details.data.reason));
430
- expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'accept', TASK_FILE);
463
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'accept', TASK_FILE);
464
+ const expectedTaskErrorFields = {
465
+ trackingId: error.details.trackingId,
466
+ errorMessage: error.details.data.reason,
467
+ errorType: '',
468
+ errorData: '',
469
+ reasonCode: 0,
470
+ };
431
471
  expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
432
472
  1,
433
473
  METRIC_EVENT_NAMES.TASK_ACCEPT_FAILED,
434
474
  {
435
475
  taskId: taskDataMock.interactionId,
436
476
  error: error.toString(),
477
+ ...expectedTaskErrorFields,
437
478
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
438
479
  },
439
480
  ['operational', 'behavioral', 'business']
@@ -469,26 +510,27 @@ describe('Task', () => {
469
510
  });
470
511
 
471
512
  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
- };
513
+ const error = {details: (global as any).makeFailure('Decline Failed')};
480
514
 
481
515
  jest.spyOn(webCallingService, 'declineCall').mockImplementation(() => {
482
516
  throw error;
483
517
  });
484
518
  await expect(task.decline()).rejects.toThrow(new Error(error.details.data.reason));
485
- expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'decline', TASK_FILE);
519
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'decline', TASK_FILE);
520
+ const expectedTaskErrorFieldsDecline = {
521
+ trackingId: error.details.trackingId,
522
+ errorMessage: error.details.data.reason,
523
+ errorType: '',
524
+ errorData: '',
525
+ reasonCode: 0,
526
+ };
486
527
  expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
487
528
  1,
488
529
  METRIC_EVENT_NAMES.TASK_DECLINE_FAILED,
489
530
  {
490
531
  taskId: taskDataMock.interactionId,
491
532
  error: error.toString(),
533
+ ...expectedTaskErrorFieldsDecline,
492
534
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
493
535
  },
494
536
  ['operational', 'behavioral']
@@ -529,20 +571,20 @@ describe('Task', () => {
529
571
  });
530
572
 
531
573
  it('should handle errors in hold method', async () => {
532
- const error = {
533
- details: {
534
- trackingId: '1234',
535
- data: {
536
- reason: 'Hold Failed',
537
- },
538
- },
539
- };
574
+ const error = {details: (global as any).makeFailure('Hold Failed')};
540
575
  contactMock.hold.mockImplementation(() => {
541
576
  throw error;
542
577
  });
543
578
 
544
579
  await expect(task.hold()).rejects.toThrow(error.details.data.reason);
545
- expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'hold', TASK_FILE);
580
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'hold', TASK_FILE);
581
+ const expectedTaskErrorFieldsHold = {
582
+ trackingId: error.details.trackingId,
583
+ errorMessage: error.details.data.reason,
584
+ errorType: '',
585
+ errorData: '',
586
+ reasonCode: 0,
587
+ };
546
588
  expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
547
589
  1,
548
590
  METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
@@ -550,6 +592,7 @@ describe('Task', () => {
550
592
  taskId: taskDataMock.interactionId,
551
593
  mediaResourceId: taskDataMock.mediaResourceId,
552
594
  error: error.toString(),
595
+ ...expectedTaskErrorFieldsHold,
553
596
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
554
597
  },
555
598
  ['operational', 'behavioral']
@@ -581,20 +624,20 @@ describe('Task', () => {
581
624
  });
582
625
 
583
626
  it('should handle errors in resume method', async () => {
584
- const error = {
585
- details: {
586
- trackingId: '1234',
587
- data: {
588
- reason: 'Resume Failed',
589
- },
590
- },
591
- };
627
+ const error = {details: (global as any).makeFailure('Resume Failed')};
592
628
  contactMock.unHold.mockImplementation(() => {
593
629
  throw error;
594
630
  });
595
631
 
596
632
  await expect(task.resume()).rejects.toThrow(error.details.data.reason);
597
- expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'resume', TASK_FILE);
633
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'resume', TASK_FILE);
634
+ const expectedTaskErrorFieldsResume = {
635
+ trackingId: error.details.trackingId,
636
+ errorMessage: error.details.data.reason,
637
+ errorType: '',
638
+ errorData: '',
639
+ reasonCode: 0,
640
+ };
598
641
  expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
599
642
  1,
600
643
  METRIC_EVENT_NAMES.TASK_RESUME_FAILED,
@@ -604,6 +647,7 @@ describe('Task', () => {
604
647
  mediaResourceId:
605
648
  taskDataMock.interaction.media[taskDataMock.interaction.mainInteractionId]
606
649
  .mediaResourceId,
650
+ ...expectedTaskErrorFieldsResume,
607
651
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
608
652
  },
609
653
  ['operational', 'behavioral']
@@ -630,8 +674,8 @@ describe('Task', () => {
630
674
  expect(loggerLogSpy).toHaveBeenCalledWith(`Consult started successfully to ${consultPayload.to}`, {
631
675
  module: TASK_FILE,
632
676
  method: 'consult',
633
- trackingId: expectedResponse.trackingId,
634
677
  interactionId: task.data.interactionId,
678
+ trackingId: '1234',
635
679
  });
636
680
  expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
637
681
  METRIC_EVENT_NAMES.TASK_CONSULT_START_SUCCESS,
@@ -646,14 +690,7 @@ describe('Task', () => {
646
690
  });
647
691
 
648
692
  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
- };
693
+ const error = {details: (global as any).makeFailure('Consult Failed')};
657
694
  contactMock.consult.mockImplementation(() => {
658
695
  throw error;
659
696
  });
@@ -664,12 +701,19 @@ describe('Task', () => {
664
701
  };
665
702
 
666
703
  await expect(task.consult(consultPayload)).rejects.toThrow(error.details.data.reason);
667
- expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'consult', TASK_FILE);
704
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'consult', TASK_FILE);
668
705
  expect(loggerInfoSpy).toHaveBeenCalledWith(`Starting consult`, {
669
706
  module: TASK_FILE,
670
707
  method: 'consult',
671
708
  interactionId: task.data.interactionId,
672
709
  });
710
+ const expectedTaskErrorFieldsConsult = {
711
+ trackingId: error.details.trackingId,
712
+ errorMessage: error.details.data.reason,
713
+ errorType: '',
714
+ errorData: '',
715
+ reasonCode: 0,
716
+ };
673
717
  expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
674
718
  METRIC_EVENT_NAMES.TASK_CONSULT_START_FAILED,
675
719
  {
@@ -677,6 +721,7 @@ describe('Task', () => {
677
721
  destination: consultPayload.to,
678
722
  destinationType: consultPayload.destinationType,
679
723
  error: error.toString(),
724
+ ...expectedTaskErrorFieldsConsult,
680
725
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
681
726
  },
682
727
  ['operational', 'behavioral', 'business']
@@ -710,14 +755,7 @@ describe('Task', () => {
710
755
  });
711
756
 
712
757
  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
- };
758
+ const error = {details: (global as any).makeFailure('End Consult Failed')};
721
759
  contactMock.consultEnd.mockImplementation(() => {
722
760
  throw error;
723
761
  });
@@ -728,13 +766,21 @@ describe('Task', () => {
728
766
  };
729
767
 
730
768
  await expect(task.endConsult(consultEndPayload)).rejects.toThrow(error.details.data.reason);
731
- expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'endConsult', TASK_FILE);
769
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'endConsult', TASK_FILE);
770
+ const expectedTaskErrorFieldsEndConsult = {
771
+ trackingId: error.details.trackingId,
772
+ errorMessage: error.details.data.reason,
773
+ errorType: '',
774
+ errorData: '',
775
+ reasonCode: 0,
776
+ };
732
777
  expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
733
778
  1,
734
779
  METRIC_EVENT_NAMES.TASK_CONSULT_END_FAILED,
735
780
  {
736
781
  taskId: taskDataMock.interactionId,
737
782
  error: error.toString(),
783
+ ...expectedTaskErrorFieldsEndConsult,
738
784
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
739
785
  },
740
786
  ['operational', 'behavioral', 'business']
@@ -754,29 +800,81 @@ describe('Task', () => {
754
800
  expect(contactMock.consult).toHaveBeenCalledWith({interactionId: taskId, data: consultPayload});
755
801
  expect(response).toEqual(expectedResponse);
756
802
 
757
- const consultTransferPayload: ConsultTransferPayLoad = {
758
- to: '1234',
759
- destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
760
- };
761
-
762
- const consultTransferResponse = await task.consultTransfer(consultTransferPayload);
803
+ const consultTransferResponse = await task.consultTransfer();
763
804
  expect(contactMock.consultTransfer).toHaveBeenCalledWith({
764
805
  interactionId: taskId,
765
- data: consultTransferPayload,
806
+ data: {
807
+ to: taskDataMock.destAgentId,
808
+ destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
809
+ },
766
810
  });
767
811
  expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
768
812
  2,
769
813
  METRIC_EVENT_NAMES.TASK_TRANSFER_SUCCESS,
770
814
  {
771
815
  taskId: taskDataMock.interactionId,
772
- destination: consultTransferPayload.to,
773
- destinationType: consultTransferPayload.destinationType,
816
+ destination: taskDataMock.destAgentId,
817
+ destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
774
818
  isConsultTransfer: true,
775
819
  },
776
820
  ['operational', 'behavioral', 'business']
777
821
  );
778
822
  });
779
823
 
824
+ it('should send DIALNUMBER when task destinationType is DN during consultTransfer', async () => {
825
+ const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
826
+ contactMock.consultTransfer.mockResolvedValue(expectedResponse);
827
+
828
+ // Ensure task data indicates DN scenario
829
+ task.data.destinationType = 'DN' as unknown as string;
830
+
831
+ await task.consultTransfer();
832
+
833
+ expect(contactMock.consultTransfer).toHaveBeenCalledWith({
834
+ interactionId: taskId,
835
+ data: {
836
+ to: taskDataMock.destAgentId,
837
+ destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.DIALNUMBER,
838
+ },
839
+ });
840
+ });
841
+
842
+ it('should send ENTRYPOINT when task destinationType is EPDN during consultTransfer', async () => {
843
+ const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
844
+ contactMock.consultTransfer.mockResolvedValue(expectedResponse);
845
+
846
+ // Ensure task data indicates EP/EPDN scenario
847
+ task.data.destinationType = 'EPDN' as unknown as string;
848
+
849
+ await task.consultTransfer();
850
+
851
+ expect(contactMock.consultTransfer).toHaveBeenCalledWith({
852
+ interactionId: taskId,
853
+ data: {
854
+ to: taskDataMock.destAgentId,
855
+ destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.ENTRYPOINT,
856
+ },
857
+ });
858
+ });
859
+
860
+ it('should keep AGENT when task destinationType is neither DN nor EPDN/ENTRYPOINT', async () => {
861
+ const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
862
+ contactMock.consultTransfer.mockResolvedValue(expectedResponse);
863
+
864
+ // Ensure task data indicates non-DN and non-EP/EPDN scenario
865
+ task.data.destinationType = 'SOMETHING_ELSE' as unknown as string;
866
+
867
+ await task.consultTransfer();
868
+
869
+ expect(contactMock.consultTransfer).toHaveBeenCalledWith({
870
+ interactionId: taskId,
871
+ data: {
872
+ to: taskDataMock.destAgentId,
873
+ destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
874
+ },
875
+ });
876
+ });
877
+
780
878
  it('should do consult transfer to a queue by using the destAgentId from task data', async () => {
781
879
  const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
782
880
  contactMock.consultTransfer.mockResolvedValue(expectedResponse);
@@ -811,6 +909,9 @@ describe('Task', () => {
811
909
  destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.QUEUE,
812
910
  };
813
911
 
912
+ // For this negative case, ensure computed destination is empty
913
+ getDestinationAgentIdSpy.mockReturnValueOnce('');
914
+
814
915
  await expect(
815
916
  taskWithoutDestAgentId.consultTransfer(queueConsultTransferPayload)
816
917
  ).rejects.toThrow('Error while performing consultTransfer');
@@ -829,14 +930,7 @@ describe('Task', () => {
829
930
  expect(contactMock.consult).toHaveBeenCalledWith({interactionId: taskId, data: consultPayload});
830
931
  expect(response).toEqual(expectedResponse);
831
932
 
832
- const error = {
833
- details: {
834
- trackingId: '1234',
835
- data: {
836
- reason: 'Consult Transfer Failed',
837
- },
838
- },
839
- };
933
+ const error = {details: (global as any).makeFailure('Consult Transfer Failed')};
840
934
  contactMock.consultTransfer.mockImplementation(() => {
841
935
  throw error;
842
936
  });
@@ -849,16 +943,24 @@ describe('Task', () => {
849
943
  await expect(task.consultTransfer(consultTransferPayload)).rejects.toThrow(
850
944
  error.details.data.reason
851
945
  );
852
- expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'consultTransfer', TASK_FILE);
946
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'consultTransfer', TASK_FILE);
947
+ const expectedTaskErrorFieldsConsultTransfer = {
948
+ trackingId: error.details.trackingId,
949
+ errorMessage: error.details.data.reason,
950
+ errorType: '',
951
+ errorData: '',
952
+ reasonCode: 0,
953
+ };
853
954
  expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
854
955
  2,
855
956
  METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
856
957
  {
857
958
  taskId: taskDataMock.interactionId,
858
- destination: consultTransferPayload.to,
859
- destinationType: consultTransferPayload.destinationType,
959
+ destination: taskDataMock.destAgentId,
960
+ destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
860
961
  isConsultTransfer: true,
861
962
  error: error.toString(),
963
+ ...expectedTaskErrorFieldsConsultTransfer,
862
964
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
863
965
  },
864
966
  ['operational', 'behavioral', 'business']
@@ -926,14 +1028,7 @@ describe('Task', () => {
926
1028
  });
927
1029
 
928
1030
  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
- };
1031
+ const error = {details: (global as any).makeFailure('Consult Transfer Failed')};
937
1032
  contactMock.blindTransfer.mockImplementation(() => {
938
1033
  throw error;
939
1034
  });
@@ -944,7 +1039,14 @@ describe('Task', () => {
944
1039
  };
945
1040
 
946
1041
  await expect(task.transfer(blindTransferPayload)).rejects.toThrow(error.details.data.reason);
947
- expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'transfer', TASK_FILE);
1042
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'transfer', TASK_FILE);
1043
+ const expectedTaskErrorFieldsTransfer = {
1044
+ trackingId: error.details.trackingId,
1045
+ errorMessage: error.details.data.reason,
1046
+ errorType: '',
1047
+ errorData: '',
1048
+ reasonCode: 0,
1049
+ };
948
1050
  expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
949
1051
  1,
950
1052
  METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
@@ -954,6 +1056,7 @@ describe('Task', () => {
954
1056
  destinationType: blindTransferPayload.destinationType,
955
1057
  isConsultTransfer: false,
956
1058
  error: error.toString(),
1059
+ ...expectedTaskErrorFieldsTransfer,
957
1060
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
958
1061
  },
959
1062
  ['operational', 'behavioral', 'business']
@@ -990,25 +1093,26 @@ describe('Task', () => {
990
1093
  });
991
1094
 
992
1095
  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
- };
1096
+ const error = {details: (global as any).makeFailure('End Failed')};
1001
1097
  contactMock.end.mockImplementation(() => {
1002
1098
  throw error;
1003
1099
  });
1004
1100
 
1005
1101
  await expect(task.end()).rejects.toThrow(error.details.data.reason);
1006
- expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'end', TASK_FILE);
1102
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'end', TASK_FILE);
1103
+ const expectedTaskErrorFieldsEnd = {
1104
+ trackingId: error.details.trackingId,
1105
+ errorMessage: error.details.data.reason,
1106
+ errorType: '',
1107
+ errorData: '',
1108
+ reasonCode: 0,
1109
+ };
1007
1110
  expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1008
1111
  1,
1009
1112
  METRIC_EVENT_NAMES.TASK_END_FAILED,
1010
1113
  {
1011
1114
  taskId: taskDataMock.interactionId,
1115
+ ...expectedTaskErrorFieldsEnd,
1012
1116
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
1013
1117
  },
1014
1118
  ['operational', 'behavioral', 'business']
@@ -1041,14 +1145,7 @@ describe('Task', () => {
1041
1145
  });
1042
1146
 
1043
1147
  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
- };
1148
+ const error = {details: (global as any).makeFailure('Wrapup Failed')};
1052
1149
  contactMock.wrapup.mockImplementation(() => {
1053
1150
  throw error;
1054
1151
  });
@@ -1059,7 +1156,14 @@ describe('Task', () => {
1059
1156
  };
1060
1157
 
1061
1158
  await expect(task.wrapup(wrapupPayload)).rejects.toThrow(error.details.data.reason);
1062
- expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'wrapup', TASK_FILE);
1159
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'wrapup', TASK_FILE);
1160
+ const expectedTaskErrorFieldsWrapup = {
1161
+ trackingId: error.details.trackingId,
1162
+ errorMessage: error.details.data.reason,
1163
+ errorType: '',
1164
+ errorData: '',
1165
+ reasonCode: 0,
1166
+ };
1063
1167
  expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1064
1168
  1,
1065
1169
  METRIC_EVENT_NAMES.TASK_WRAPUP_FAILED,
@@ -1067,6 +1171,7 @@ describe('Task', () => {
1067
1171
  taskId: taskDataMock.interactionId,
1068
1172
  wrapUpCode: wrapupPayload.auxCodeId,
1069
1173
  wrapUpReason: wrapupPayload.wrapUpReason,
1174
+ ...expectedTaskErrorFieldsWrapup,
1070
1175
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
1071
1176
  },
1072
1177
  ['operational', 'behavioral', 'business']
@@ -1124,26 +1229,27 @@ describe('Task', () => {
1124
1229
  });
1125
1230
 
1126
1231
  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
- };
1232
+ const error = {details: (global as any).makeFailure('Pause Recording Failed')};
1135
1233
  contactMock.pauseRecording.mockImplementation(() => {
1136
1234
  throw error;
1137
1235
  });
1138
1236
 
1139
1237
  await expect(task.pauseRecording()).rejects.toThrow(error.details.data.reason);
1140
- expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'pauseRecording', TASK_FILE);
1238
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'pauseRecording', TASK_FILE);
1239
+ const expectedTaskErrorFieldsPause = {
1240
+ trackingId: error.details.trackingId,
1241
+ errorMessage: error.details.data.reason,
1242
+ errorType: '',
1243
+ errorData: '',
1244
+ reasonCode: 0,
1245
+ };
1141
1246
  expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1142
1247
  1,
1143
1248
  METRIC_EVENT_NAMES.TASK_PAUSE_RECORDING_FAILED,
1144
1249
  {
1145
1250
  taskId: taskDataMock.interactionId,
1146
1251
  error: error.toString(),
1252
+ ...expectedTaskErrorFieldsPause,
1147
1253
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
1148
1254
  },
1149
1255
  ['operational', 'behavioral', 'business']
@@ -1204,14 +1310,7 @@ describe('Task', () => {
1204
1310
  });
1205
1311
 
1206
1312
  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
- };
1313
+ const error = {details: (global as any).makeFailure('Resume Recording Failed')};
1215
1314
  contactMock.resumeRecording.mockImplementation(() => {
1216
1315
  throw error;
1217
1316
  });
@@ -1221,13 +1320,21 @@ describe('Task', () => {
1221
1320
  };
1222
1321
 
1223
1322
  await expect(task.resumeRecording(resumePayload)).rejects.toThrow(error.details.data.reason);
1224
- expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'resumeRecording', TASK_FILE);
1323
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'resumeRecording', TASK_FILE);
1324
+ const expectedTaskErrorFieldsResumeRec = {
1325
+ trackingId: error.details.trackingId,
1326
+ errorMessage: error.details.data.reason,
1327
+ errorType: '',
1328
+ errorData: '',
1329
+ reasonCode: 0,
1330
+ };
1225
1331
  expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
1226
1332
  1,
1227
1333
  METRIC_EVENT_NAMES.TASK_RESUME_RECORDING_FAILED,
1228
1334
  {
1229
1335
  taskId: taskDataMock.interactionId,
1230
1336
  error: error.toString(),
1337
+ ...expectedTaskErrorFieldsResumeRec,
1231
1338
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
1232
1339
  },
1233
1340
  ['operational', 'behavioral', 'business']
@@ -1267,7 +1374,7 @@ describe('Task', () => {
1267
1374
  throw error;
1268
1375
  });
1269
1376
  await expect(task.toggleMute()).rejects.toThrow(new Error(error.details.data.reason));
1270
- expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'toggleMute', TASK_FILE);
1377
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'toggleMute', TASK_FILE);
1271
1378
  expect(loggerInfoSpy).toHaveBeenCalledWith(`Toggling mute state`, {
1272
1379
  module: TASK_FILE,
1273
1380
  method: 'toggleMute',
@@ -1471,4 +1578,184 @@ describe('Task', () => {
1471
1578
  });
1472
1579
  });
1473
1580
  });
1581
+
1582
+ describe('Conference methods', () => {
1583
+ beforeEach(() => {
1584
+ contactMock = {
1585
+ consultConference: jest.fn(),
1586
+ exitConference: jest.fn(),
1587
+ conferenceTransfer: jest.fn(),
1588
+ };
1589
+
1590
+ // Re-setup the getDestinationAgentId spy for conference methods
1591
+ getDestinationAgentIdSpy = jest
1592
+ .spyOn(Utils, 'getDestinationAgentId')
1593
+ .mockReturnValue(taskDataMock.destAgentId);
1594
+
1595
+
1596
+ task = new Task(contactMock, webCallingService, taskDataMock, {
1597
+ wrapUpProps: { wrapUpReasonList: [] },
1598
+ autoWrapEnabled: false,
1599
+ autoWrapAfterSeconds: 0
1600
+ }, taskDataMock.agentId);
1601
+ });
1602
+
1603
+ describe('consultConference', () => {
1604
+
1605
+ it('should successfully start conference and emit event', async () => {
1606
+ const mockResponse = {
1607
+ trackingId: 'test-tracking-id',
1608
+ interactionId: taskId,
1609
+ };
1610
+ contactMock.consultConference.mockResolvedValue(mockResponse);
1611
+
1612
+
1613
+ const result = await task.consultConference();
1614
+
1615
+ expect(contactMock.consultConference).toHaveBeenCalledWith({
1616
+ interactionId: taskId,
1617
+ data: {
1618
+ agentId: taskDataMock.agentId, // From task data agent ID
1619
+ to: taskDataMock.destAgentId, // From getDestinationAgentId() using task participants
1620
+ destinationType: 'agent', // From consultation data
1621
+ },
1622
+ });
1623
+ expect(result).toEqual(mockResponse);
1624
+ expect(LoggerProxy.info).toHaveBeenCalledWith(`Initiating consult conference to ${taskDataMock.destAgentId}`, {
1625
+ module: TASK_FILE,
1626
+ method: 'consultConference',
1627
+ interactionId: taskId,
1628
+ });
1629
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Consult conference started successfully', {
1630
+ module: TASK_FILE,
1631
+ method: 'consultConference',
1632
+ interactionId: taskId,
1633
+ });
1634
+ });
1635
+
1636
+ it('should handle basic validation scenarios', async () => {
1637
+ // Agent Desktop logic validates data structure but not participant availability
1638
+ // This test confirms the method works with the Agent Desktop data flow
1639
+ const mockResponse = {
1640
+ trackingId: 'test-tracking-validation',
1641
+ interactionId: taskId,
1642
+ };
1643
+ contactMock.consultConference.mockResolvedValue(mockResponse);
1644
+
1645
+ const result = await task.consultConference();
1646
+ expect(result).toEqual(mockResponse);
1647
+ });
1648
+
1649
+ it('should handle and rethrow contact method errors', async () => {
1650
+ const mockError = new Error('Conference start failed');
1651
+ contactMock.consultConference.mockRejectedValue(mockError);
1652
+ generateTaskErrorObjectSpy.mockReturnValue(mockError);
1653
+
1654
+ await expect(task.consultConference()).rejects.toThrow('Conference start failed');
1655
+ expect(LoggerProxy.error).toHaveBeenCalledWith('Failed to start consult conference', {
1656
+ module: TASK_FILE,
1657
+ method: 'consultConference',
1658
+ interactionId: taskId,
1659
+ });
1660
+ });
1661
+ });
1662
+
1663
+ describe('exitConference', () => {
1664
+ it('should successfully end conference and emit event', async () => {
1665
+ const mockResponse = {
1666
+ trackingId: 'test-tracking-id-end',
1667
+ interactionId: taskId,
1668
+ };
1669
+ contactMock.exitConference.mockResolvedValue(mockResponse);
1670
+
1671
+ const result = await task.exitConference();
1672
+
1673
+ expect(contactMock.exitConference).toHaveBeenCalledWith({
1674
+ interactionId: taskId,
1675
+ });
1676
+ expect(result).toEqual(mockResponse);
1677
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Exiting consult conference', {
1678
+ module: TASK_FILE,
1679
+ method: 'exitConference',
1680
+ interactionId: taskId,
1681
+ });
1682
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Consult conference exited successfully', {
1683
+ module: TASK_FILE,
1684
+ method: 'exitConference',
1685
+ interactionId: taskId,
1686
+ });
1687
+ });
1688
+
1689
+ it('should throw error for invalid interaction ID', async () => {
1690
+ task.data.interactionId = '';
1691
+
1692
+ await expect(task.exitConference()).rejects.toThrow('Error while performing exitConference');
1693
+ expect(contactMock.exitConference).not.toHaveBeenCalled();
1694
+ });
1695
+
1696
+ it('should handle and rethrow contact method errors', async () => {
1697
+ const mockError = new Error('Conference end failed');
1698
+ contactMock.exitConference.mockRejectedValue(mockError);
1699
+ generateTaskErrorObjectSpy.mockReturnValue(mockError);
1700
+
1701
+ await expect(task.exitConference()).rejects.toThrow('Conference end failed');
1702
+ expect(LoggerProxy.error).toHaveBeenCalledWith('Failed to exit consult conference', {
1703
+ module: TASK_FILE,
1704
+ method: 'exitConference',
1705
+ interactionId: taskId,
1706
+ });
1707
+ });
1708
+ });
1709
+
1710
+ // TODO: Uncomment this test section in future PR for Multi-Party Conference support (>3 participants)
1711
+ // Conference transfer tests will be uncommented when implementing enhanced multi-party conference functionality
1712
+ /*
1713
+ describe('transferConference', () => {
1714
+ it('should successfully transfer conference', async () => {
1715
+ const mockResponse = {
1716
+ trackingId: 'test-tracking-id-transfer',
1717
+ interactionId: taskId,
1718
+ };
1719
+ contactMock.conferenceTransfer.mockResolvedValue(mockResponse);
1720
+
1721
+ const result = await task.transferConference();
1722
+
1723
+ expect(contactMock.conferenceTransfer).toHaveBeenCalledWith({
1724
+ interactionId: taskId,
1725
+ });
1726
+ expect(result).toEqual(mockResponse);
1727
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Transferring conference', {
1728
+ module: TASK_FILE,
1729
+ method: 'transferConference',
1730
+ interactionId: taskId,
1731
+ });
1732
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Conference transferred successfully', {
1733
+ module: TASK_FILE,
1734
+ method: 'transferConference',
1735
+ interactionId: taskId,
1736
+ });
1737
+ });
1738
+
1739
+ it('should throw error for invalid interaction ID', async () => {
1740
+ task.data.interactionId = '';
1741
+
1742
+ await expect(task.transferConference()).rejects.toThrow('Error while performing transferConference');
1743
+ expect(contactMock.conferenceTransfer).not.toHaveBeenCalled();
1744
+ });
1745
+
1746
+ it('should handle and rethrow contact method errors', async () => {
1747
+ const mockError = new Error('Conference transfer failed');
1748
+ contactMock.conferenceTransfer.mockRejectedValue(mockError);
1749
+ generateTaskErrorObjectSpy.mockReturnValue(mockError);
1750
+
1751
+ await expect(task.transferConference()).rejects.toThrow('Conference transfer failed');
1752
+ expect(LoggerProxy.error).toHaveBeenCalledWith('Failed to transfer conference', {
1753
+ module: TASK_FILE,
1754
+ method: 'transferConference',
1755
+ interactionId: taskId,
1756
+ });
1757
+ });
1758
+ });
1759
+ */
1760
+ });
1474
1761
  });