@webex/contact-center 3.9.0-next.9 → 3.10.0-next.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 (95) hide show
  1. package/dist/cc.js +182 -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/behavioral-events.js +89 -0
  10. package/dist/metrics/behavioral-events.js.map +1 -1
  11. package/dist/metrics/constants.js +30 -2
  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 +36 -2
  20. package/dist/services/config/constants.js.map +1 -1
  21. package/dist/services/config/index.js +29 -21
  22. package/dist/services/config/index.js.map +1 -1
  23. package/dist/services/config/types.js +33 -1
  24. package/dist/services/config/types.js.map +1 -1
  25. package/dist/services/core/Utils.js +42 -1
  26. package/dist/services/core/Utils.js.map +1 -1
  27. package/dist/services/task/TaskManager.js +113 -3
  28. package/dist/services/task/TaskManager.js.map +1 -1
  29. package/dist/services/task/TaskUtils.js +76 -0
  30. package/dist/services/task/TaskUtils.js.map +1 -0
  31. package/dist/services/task/constants.js +26 -1
  32. package/dist/services/task/constants.js.map +1 -1
  33. package/dist/services/task/contact.js +86 -0
  34. package/dist/services/task/contact.js.map +1 -1
  35. package/dist/services/task/index.js +273 -16
  36. package/dist/services/task/index.js.map +1 -1
  37. package/dist/services/task/types.js +14 -0
  38. package/dist/services/task/types.js.map +1 -1
  39. package/dist/types/cc.d.ts +115 -35
  40. package/dist/types/constants.d.ts +1 -0
  41. package/dist/types/index.d.ts +3 -2
  42. package/dist/types/metrics/constants.d.ts +24 -1
  43. package/dist/types/services/AddressBook.d.ts +74 -0
  44. package/dist/types/services/EntryPoint.d.ts +67 -0
  45. package/dist/types/services/Queue.d.ts +76 -0
  46. package/dist/types/services/config/constants.d.ts +35 -1
  47. package/dist/types/services/config/index.d.ts +6 -9
  48. package/dist/types/services/config/types.d.ts +79 -58
  49. package/dist/types/services/core/Utils.d.ts +14 -1
  50. package/dist/types/services/task/TaskUtils.d.ts +28 -0
  51. package/dist/types/services/task/constants.d.ts +23 -0
  52. package/dist/types/services/task/contact.d.ts +10 -0
  53. package/dist/types/services/task/index.d.ts +84 -3
  54. package/dist/types/services/task/types.d.ts +233 -21
  55. package/dist/types/types.d.ts +162 -0
  56. package/dist/types/utils/PageCache.d.ts +173 -0
  57. package/dist/types.js +17 -0
  58. package/dist/types.js.map +1 -1
  59. package/dist/utils/PageCache.js +192 -0
  60. package/dist/utils/PageCache.js.map +1 -0
  61. package/dist/webex.js +1 -1
  62. package/package.json +9 -8
  63. package/src/cc.ts +206 -52
  64. package/src/constants.ts +1 -0
  65. package/src/index.ts +16 -2
  66. package/src/logger-proxy.ts +24 -1
  67. package/src/metrics/behavioral-events.ts +94 -0
  68. package/src/metrics/constants.ts +34 -1
  69. package/src/services/AddressBook.ts +291 -0
  70. package/src/services/EntryPoint.ts +241 -0
  71. package/src/services/Queue.ts +277 -0
  72. package/src/services/config/constants.ts +42 -2
  73. package/src/services/config/index.ts +30 -30
  74. package/src/services/config/types.ts +59 -58
  75. package/src/services/core/Utils.ts +44 -0
  76. package/src/services/task/TaskManager.ts +122 -5
  77. package/src/services/task/TaskUtils.ts +81 -0
  78. package/src/services/task/constants.ts +25 -0
  79. package/src/services/task/contact.ts +80 -0
  80. package/src/services/task/index.ts +338 -15
  81. package/src/services/task/types.ts +251 -20
  82. package/src/types.ts +180 -0
  83. package/src/utils/PageCache.ts +252 -0
  84. package/test/unit/spec/cc.ts +282 -85
  85. package/test/unit/spec/metrics/behavioral-events.ts +42 -0
  86. package/test/unit/spec/services/AddressBook.ts +332 -0
  87. package/test/unit/spec/services/EntryPoint.ts +259 -0
  88. package/test/unit/spec/services/Queue.ts +323 -0
  89. package/test/unit/spec/services/config/index.ts +279 -65
  90. package/test/unit/spec/services/task/TaskManager.ts +382 -0
  91. package/test/unit/spec/services/task/TaskUtils.ts +131 -0
  92. package/test/unit/spec/services/task/contact.ts +31 -1
  93. package/test/unit/spec/services/task/index.ts +359 -8
  94. package/umd/contact-center.min.js +2 -2
  95. package/umd/contact-center.min.js.map +1 -1
@@ -75,6 +75,9 @@ describe('Task', () => {
75
75
  wrapup: jest.fn().mockResolvedValue({}),
76
76
  pauseRecording: jest.fn().mockResolvedValue({}),
77
77
  resumeRecording: jest.fn().mockResolvedValue({}),
78
+ consultConference: jest.fn().mockResolvedValue({}),
79
+ exitConference: jest.fn().mockResolvedValue({}),
80
+ conferenceTransfer: jest.fn().mockResolvedValue({}),
78
81
  };
79
82
 
80
83
  mockMetricsManager = {
@@ -214,7 +217,7 @@ describe('Task', () => {
214
217
  });
215
218
 
216
219
  describe('updateTaskData cases', () => {
217
- it('test updating the task data by overwrite', async () => {
220
+ it('updates the task data by overwrite', async () => {
218
221
  const newData = {
219
222
  type: CC_EVENTS.AGENT_CONTACT_ASSIGNED,
220
223
  agentId: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
@@ -263,12 +266,12 @@ describe('Task', () => {
263
266
  expect(task.data).toEqual(newData);
264
267
  });
265
268
 
266
- it('test updating the task data by merging', async () => {
269
+ it('updates the task data by merging with key removal', async () => {
267
270
  const newData = {
268
- // ...taskDataMock, // Purposefully omit this to test scenario when other keys isn't present
271
+ // Purposefully omit other keys to test remove and merge behavior
269
272
  isConsulting: true, // Add a new custom key to test persistence
270
273
  interaction: {
271
- // ...taskDataMock.interaction, // Purposefully omit this to test scenario when a nested key isn't present
274
+ // Purposefully omit other interaction keys to test removal
272
275
  media: {
273
276
  '58a45567-4e61-4f4b-a580-5bc86357bef0': {
274
277
  holdTimestamp: null,
@@ -295,11 +298,12 @@ describe('Task', () => {
295
298
  },
296
299
  };
297
300
 
301
+ // The reconcileData method removes keys from oldData that are not in newData
302
+ // This means only keys present in newData will remain in the final result
298
303
  const expectedData: TaskData = {
299
- ...taskDataMock,
300
- isConsulting: true,
304
+ isConsulting: true, // New key is added
301
305
  interaction: {
302
- ...taskDataMock.interaction,
306
+ // Only the media key from newData.interaction remains
303
307
  media: {
304
308
  '58a45567-4e61-4f4b-a580-5bc86357bef0': {
305
309
  holdTimestamp: null,
@@ -332,6 +336,60 @@ describe('Task', () => {
332
336
 
333
337
  expect(task.data).toEqual(expectedData);
334
338
  });
339
+
340
+ it('updates the task data by merging and preserving existing keys', async () => {
341
+ const newData = {
342
+ ...taskDataMock, // Include all existing keys to test merge without removal
343
+ isConsulting: true, // Add a new custom key
344
+ interaction: {
345
+ ...taskDataMock.interaction, // Include existing interaction data
346
+ media: {
347
+ ...taskDataMock.interaction.media, // Include existing media
348
+ '58a45567-4e61-4f4b-a580-5bc86357bef0': {
349
+ holdTimestamp: null,
350
+ isHold: true,
351
+ mType: 'consult',
352
+ mediaMgr: 'callmm',
353
+ mediaResourceId: '58a45567-4e61-4f4b-a580-5bc86357bef0',
354
+ mediaType: 'telephony',
355
+ participants: [
356
+ 'f520d6b5-28ad-4f2f-b83e-781bb64af617',
357
+ '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
358
+ ],
359
+ },
360
+ },
361
+ },
362
+ };
363
+
364
+ const expectedData: TaskData = {
365
+ ...taskDataMock,
366
+ isConsulting: true,
367
+ interaction: {
368
+ ...taskDataMock.interaction,
369
+ media: {
370
+ ...taskDataMock.interaction.media,
371
+ '58a45567-4e61-4f4b-a580-5bc86357bef0': {
372
+ holdTimestamp: null,
373
+ isHold: true,
374
+ mType: 'consult',
375
+ mediaMgr: 'callmm',
376
+ mediaResourceId: '58a45567-4e61-4f4b-a580-5bc86357bef0',
377
+ mediaType: 'telephony',
378
+ participants: [
379
+ 'f520d6b5-28ad-4f2f-b83e-781bb64af617',
380
+ '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
381
+ ],
382
+ },
383
+ },
384
+ },
385
+ };
386
+
387
+ expect(task.data).toEqual(taskDataMock);
388
+ const shouldOverwrite = false;
389
+ task.updateTaskData(newData, shouldOverwrite);
390
+
391
+ expect(task.data).toEqual(expectedData);
392
+ });
335
393
  });
336
394
 
337
395
  it('should accept a task and answer call when using BROWSER login option', async () => {
@@ -567,6 +625,40 @@ describe('Task', () => {
567
625
  );
568
626
  });
569
627
 
628
+ it('should hold the task with custom mediaResourceId and return the expected response', async () => {
629
+ const customMediaResourceId = 'custom-media-resource-id-123';
630
+ const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
631
+ contactMock.hold.mockResolvedValue(expectedResponse);
632
+
633
+ const response = await task.hold(customMediaResourceId);
634
+
635
+ expect(contactMock.hold).toHaveBeenCalledWith({
636
+ interactionId: taskId,
637
+ data: {mediaResourceId: customMediaResourceId},
638
+ });
639
+ expect(response).toEqual(expectedResponse);
640
+ expect(loggerInfoSpy).toHaveBeenCalledWith(`Holding task`, {
641
+ module: TASK_FILE,
642
+ method: 'hold',
643
+ interactionId: task.data.interactionId,
644
+ });
645
+ expect(loggerLogSpy).toHaveBeenCalledWith(`Task placed on hold successfully`, {
646
+ module: TASK_FILE,
647
+ method: 'hold',
648
+ interactionId: task.data.interactionId,
649
+ });
650
+ expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
651
+ 1,
652
+ METRIC_EVENT_NAMES.TASK_HOLD_SUCCESS,
653
+ {
654
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(expectedResponse),
655
+ taskId: taskDataMock.interactionId,
656
+ mediaResourceId: customMediaResourceId,
657
+ },
658
+ ['operational', 'behavioral']
659
+ );
660
+ });
661
+
570
662
  it('should handle errors in hold method', async () => {
571
663
  const error = {details: (global as any).makeFailure('Hold Failed')};
572
664
  contactMock.hold.mockImplementation(() => {
@@ -596,6 +688,36 @@ describe('Task', () => {
596
688
  );
597
689
  });
598
690
 
691
+ it('should handle errors in hold method with custom mediaResourceId', async () => {
692
+ const customMediaResourceId = 'custom-media-resource-id-456';
693
+ const error = {details: (global as any).makeFailure('Hold Failed with custom mediaResourceId')};
694
+ contactMock.hold.mockImplementation(() => {
695
+ throw error;
696
+ });
697
+
698
+ await expect(task.hold(customMediaResourceId)).rejects.toThrow(error.details.data.reason);
699
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'hold', TASK_FILE);
700
+ const expectedTaskErrorFieldsHold = {
701
+ trackingId: error.details.trackingId,
702
+ errorMessage: error.details.data.reason,
703
+ errorType: '',
704
+ errorData: '',
705
+ reasonCode: 0,
706
+ };
707
+ expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
708
+ 1,
709
+ METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
710
+ {
711
+ taskId: taskDataMock.interactionId,
712
+ mediaResourceId: customMediaResourceId,
713
+ error: error.toString(),
714
+ ...expectedTaskErrorFieldsHold,
715
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
716
+ },
717
+ ['operational', 'behavioral']
718
+ );
719
+ });
720
+
599
721
  it('should resume the task and return the expected response', async () => {
600
722
  const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
601
723
  contactMock.unHold.mockResolvedValue(expectedResponse);
@@ -620,6 +742,29 @@ describe('Task', () => {
620
742
  );
621
743
  });
622
744
 
745
+ it('should resume the task with custom mediaResourceId and return the expected response', async () => {
746
+ const customMediaResourceId = 'custom-media-resource-id-789';
747
+ const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
748
+ contactMock.unHold.mockResolvedValue(expectedResponse);
749
+ const response = await task.resume(customMediaResourceId);
750
+ expect(contactMock.unHold).toHaveBeenCalledWith({
751
+ interactionId: taskId,
752
+ data: {mediaResourceId: customMediaResourceId},
753
+ });
754
+ expect(response).toEqual(expectedResponse);
755
+ expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
756
+ 1,
757
+ METRIC_EVENT_NAMES.TASK_RESUME_SUCCESS,
758
+ {
759
+ taskId: taskDataMock.interactionId,
760
+ mainInteractionId: taskDataMock.interaction.mainInteractionId,
761
+ mediaResourceId: customMediaResourceId,
762
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(expectedResponse),
763
+ },
764
+ ['operational', 'behavioral']
765
+ );
766
+ });
767
+
623
768
  it('should handle errors in resume method', async () => {
624
769
  const error = {details: (global as any).makeFailure('Resume Failed')};
625
770
  contactMock.unHold.mockImplementation(() => {
@@ -651,6 +796,36 @@ describe('Task', () => {
651
796
  );
652
797
  });
653
798
 
799
+ it('should handle errors in resume method with custom mediaResourceId', async () => {
800
+ const customMediaResourceId = 'custom-media-resource-id-999';
801
+ const error = {details: (global as any).makeFailure('Resume Failed with custom mediaResourceId')};
802
+ contactMock.unHold.mockImplementation(() => {
803
+ throw error;
804
+ });
805
+
806
+ await expect(task.resume(customMediaResourceId)).rejects.toThrow(error.details.data.reason);
807
+ expect(generateTaskErrorObjectSpy).toHaveBeenCalledWith(error, 'resume', TASK_FILE);
808
+ const expectedTaskErrorFieldsResume = {
809
+ trackingId: error.details.trackingId,
810
+ errorMessage: error.details.data.reason,
811
+ errorType: '',
812
+ errorData: '',
813
+ reasonCode: 0,
814
+ };
815
+ expect(mockMetricsManager.trackEvent).toHaveBeenNthCalledWith(
816
+ 1,
817
+ METRIC_EVENT_NAMES.TASK_RESUME_FAILED,
818
+ {
819
+ taskId: taskDataMock.interactionId,
820
+ mainInteractionId: taskDataMock.interaction.mainInteractionId,
821
+ mediaResourceId: customMediaResourceId,
822
+ ...expectedTaskErrorFieldsResume,
823
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details),
824
+ },
825
+ ['operational', 'behavioral']
826
+ );
827
+ });
828
+
654
829
  it('should initiate a consult call and return the expected response', async () => {
655
830
  const consultPayload = {
656
831
  to: '1234',
@@ -671,8 +846,8 @@ describe('Task', () => {
671
846
  expect(loggerLogSpy).toHaveBeenCalledWith(`Consult started successfully to ${consultPayload.to}`, {
672
847
  module: TASK_FILE,
673
848
  method: 'consult',
674
- trackingId: expectedResponse.trackingId,
675
849
  interactionId: task.data.interactionId,
850
+ trackingId: '1234',
676
851
  });
677
852
  expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
678
853
  METRIC_EVENT_NAMES.TASK_CONSULT_START_SUCCESS,
@@ -1575,4 +1750,180 @@ describe('Task', () => {
1575
1750
  });
1576
1751
  });
1577
1752
  });
1753
+
1754
+ describe('Conference methods', () => {
1755
+ beforeEach(() => {
1756
+ contactMock = {
1757
+ consultConference: jest.fn(),
1758
+ exitConference: jest.fn(),
1759
+ conferenceTransfer: jest.fn(),
1760
+ };
1761
+
1762
+ // Re-setup the getDestinationAgentId spy for conference methods
1763
+ getDestinationAgentIdSpy = jest
1764
+ .spyOn(Utils, 'getDestinationAgentId')
1765
+ .mockReturnValue(taskDataMock.destAgentId);
1766
+
1767
+
1768
+ task = new Task(contactMock, webCallingService, taskDataMock, {
1769
+ wrapUpProps: { wrapUpReasonList: [] },
1770
+ autoWrapEnabled: false,
1771
+ autoWrapAfterSeconds: 0
1772
+ }, taskDataMock.agentId);
1773
+ });
1774
+
1775
+ describe('consultConference', () => {
1776
+
1777
+ it('should successfully start conference and emit event', async () => {
1778
+ const mockResponse = {
1779
+ trackingId: 'test-tracking-id',
1780
+ interactionId: taskId,
1781
+ };
1782
+ contactMock.consultConference.mockResolvedValue(mockResponse);
1783
+
1784
+
1785
+ const result = await task.consultConference();
1786
+
1787
+ expect(contactMock.consultConference).toHaveBeenCalledWith({
1788
+ interactionId: taskId,
1789
+ data: {
1790
+ agentId: taskDataMock.agentId, // From task data agent ID
1791
+ to: taskDataMock.destAgentId, // From getDestinationAgentId() using task participants
1792
+ destinationType: 'agent', // From consultation data
1793
+ },
1794
+ });
1795
+ expect(result).toEqual(mockResponse);
1796
+ expect(LoggerProxy.info).toHaveBeenCalledWith(`Initiating consult conference to ${taskDataMock.destAgentId}`, {
1797
+ module: TASK_FILE,
1798
+ method: 'consultConference',
1799
+ interactionId: taskId,
1800
+ });
1801
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Consult conference started successfully', {
1802
+ module: TASK_FILE,
1803
+ method: 'consultConference',
1804
+ interactionId: taskId,
1805
+ });
1806
+ });
1807
+
1808
+ it('should handle basic validation scenarios', async () => {
1809
+ // Agent Desktop logic validates data structure but not participant availability
1810
+ // This test confirms the method works with the Agent Desktop data flow
1811
+ const mockResponse = {
1812
+ trackingId: 'test-tracking-validation',
1813
+ interactionId: taskId,
1814
+ };
1815
+ contactMock.consultConference.mockResolvedValue(mockResponse);
1816
+
1817
+ const result = await task.consultConference();
1818
+ expect(result).toEqual(mockResponse);
1819
+ });
1820
+
1821
+ it('should handle and rethrow contact method errors', async () => {
1822
+ const mockError = new Error('Conference start failed');
1823
+ contactMock.consultConference.mockRejectedValue(mockError);
1824
+ generateTaskErrorObjectSpy.mockReturnValue(mockError);
1825
+
1826
+ await expect(task.consultConference()).rejects.toThrow('Conference start failed');
1827
+ expect(LoggerProxy.error).toHaveBeenCalledWith('Failed to start consult conference', {
1828
+ module: TASK_FILE,
1829
+ method: 'consultConference',
1830
+ interactionId: taskId,
1831
+ });
1832
+ });
1833
+ });
1834
+
1835
+ describe('exitConference', () => {
1836
+ it('should successfully end conference and emit event', async () => {
1837
+ const mockResponse = {
1838
+ trackingId: 'test-tracking-id-end',
1839
+ interactionId: taskId,
1840
+ };
1841
+ contactMock.exitConference.mockResolvedValue(mockResponse);
1842
+
1843
+ const result = await task.exitConference();
1844
+
1845
+ expect(contactMock.exitConference).toHaveBeenCalledWith({
1846
+ interactionId: taskId,
1847
+ });
1848
+ expect(result).toEqual(mockResponse);
1849
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Exiting consult conference', {
1850
+ module: TASK_FILE,
1851
+ method: 'exitConference',
1852
+ interactionId: taskId,
1853
+ });
1854
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Consult conference exited successfully', {
1855
+ module: TASK_FILE,
1856
+ method: 'exitConference',
1857
+ interactionId: taskId,
1858
+ });
1859
+ });
1860
+
1861
+ it('should throw error for invalid interaction ID', async () => {
1862
+ task.data.interactionId = '';
1863
+
1864
+ await expect(task.exitConference()).rejects.toThrow('Error while performing exitConference');
1865
+ expect(contactMock.exitConference).not.toHaveBeenCalled();
1866
+ });
1867
+
1868
+ it('should handle and rethrow contact method errors', async () => {
1869
+ const mockError = new Error('Conference end failed');
1870
+ contactMock.exitConference.mockRejectedValue(mockError);
1871
+ generateTaskErrorObjectSpy.mockReturnValue(mockError);
1872
+
1873
+ await expect(task.exitConference()).rejects.toThrow('Conference end failed');
1874
+ expect(LoggerProxy.error).toHaveBeenCalledWith('Failed to exit consult conference', {
1875
+ module: TASK_FILE,
1876
+ method: 'exitConference',
1877
+ interactionId: taskId,
1878
+ });
1879
+ });
1880
+ });
1881
+
1882
+ describe('transferConference', () => {
1883
+ it('should successfully transfer conference', async () => {
1884
+ const mockResponse = {
1885
+ trackingId: 'test-tracking-id-transfer',
1886
+ interactionId: taskId,
1887
+ };
1888
+ contactMock.conferenceTransfer.mockResolvedValue(mockResponse);
1889
+
1890
+ const result = await task.transferConference();
1891
+
1892
+ expect(contactMock.conferenceTransfer).toHaveBeenCalledWith({
1893
+ interactionId: taskId,
1894
+ });
1895
+ expect(result).toEqual(mockResponse);
1896
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Transferring conference', {
1897
+ module: TASK_FILE,
1898
+ method: 'transferConference',
1899
+ interactionId: taskId,
1900
+ });
1901
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Conference transferred successfully', {
1902
+ module: TASK_FILE,
1903
+ method: 'transferConference',
1904
+ interactionId: taskId,
1905
+ });
1906
+ });
1907
+
1908
+ it('should throw error for invalid interaction ID', async () => {
1909
+ task.data.interactionId = '';
1910
+
1911
+ await expect(task.transferConference()).rejects.toThrow('Error while performing transferConference');
1912
+ expect(contactMock.conferenceTransfer).not.toHaveBeenCalled();
1913
+ });
1914
+
1915
+ it('should handle and rethrow contact method errors', async () => {
1916
+ const mockError = new Error('Conference transfer failed');
1917
+ contactMock.conferenceTransfer.mockRejectedValue(mockError);
1918
+ generateTaskErrorObjectSpy.mockReturnValue(mockError);
1919
+
1920
+ await expect(task.transferConference()).rejects.toThrow('Conference transfer failed');
1921
+ expect(LoggerProxy.error).toHaveBeenCalledWith('Failed to transfer conference', {
1922
+ module: TASK_FILE,
1923
+ method: 'transferConference',
1924
+ interactionId: taskId,
1925
+ });
1926
+ });
1927
+ });
1928
+ });
1578
1929
  });