@webex/contact-center 3.11.0-next.21 → 3.11.0-next.23

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 (54) hide show
  1. package/dist/cc.js +71 -0
  2. package/dist/cc.js.map +1 -1
  3. package/dist/constants.js +2 -1
  4. package/dist/constants.js.map +1 -1
  5. package/dist/metrics/behavioral-events.js +13 -0
  6. package/dist/metrics/behavioral-events.js.map +1 -1
  7. package/dist/metrics/constants.js +4 -1
  8. package/dist/metrics/constants.js.map +1 -1
  9. package/dist/services/config/types.js +7 -1
  10. package/dist/services/config/types.js.map +1 -1
  11. package/dist/services/core/Err.js.map +1 -1
  12. package/dist/services/core/aqm-reqs.js +92 -17
  13. package/dist/services/core/aqm-reqs.js.map +1 -1
  14. package/dist/services/task/TaskManager.js +60 -3
  15. package/dist/services/task/TaskManager.js.map +1 -1
  16. package/dist/services/task/TaskUtils.js +16 -3
  17. package/dist/services/task/TaskUtils.js.map +1 -1
  18. package/dist/services/task/constants.js +5 -1
  19. package/dist/services/task/constants.js.map +1 -1
  20. package/dist/services/task/dialer.js +51 -0
  21. package/dist/services/task/dialer.js.map +1 -1
  22. package/dist/services/task/types.js +15 -0
  23. package/dist/services/task/types.js.map +1 -1
  24. package/dist/types/cc.d.ts +31 -1
  25. package/dist/types/constants.d.ts +1 -0
  26. package/dist/types/metrics/constants.d.ts +2 -0
  27. package/dist/types/services/config/types.d.ts +12 -0
  28. package/dist/types/services/core/Err.d.ts +2 -0
  29. package/dist/types/services/core/aqm-reqs.d.ts +49 -0
  30. package/dist/types/services/task/TaskUtils.d.ts +8 -0
  31. package/dist/types/services/task/constants.d.ts +4 -0
  32. package/dist/types/services/task/dialer.d.ts +15 -0
  33. package/dist/types/services/task/types.d.ts +23 -1
  34. package/dist/types.js.map +1 -1
  35. package/dist/webex.js +1 -1
  36. package/package.json +2 -2
  37. package/src/cc.ts +99 -1
  38. package/src/constants.ts +1 -0
  39. package/src/metrics/behavioral-events.ts +14 -0
  40. package/src/metrics/constants.ts +4 -0
  41. package/src/services/config/types.ts +6 -0
  42. package/src/services/core/Err.ts +1 -0
  43. package/src/services/core/aqm-reqs.ts +100 -22
  44. package/src/services/task/TaskManager.ts +75 -3
  45. package/src/services/task/TaskUtils.ts +12 -0
  46. package/src/services/task/constants.ts +4 -0
  47. package/src/services/task/dialer.ts +56 -1
  48. package/src/services/task/types.ts +24 -0
  49. package/src/types.ts +2 -1
  50. package/test/unit/spec/cc.ts +65 -0
  51. package/test/unit/spec/services/task/TaskManager.ts +281 -105
  52. package/test/unit/spec/services/task/dialer.ts +198 -112
  53. package/umd/contact-center.min.js +2 -2
  54. package/umd/contact-center.min.js.map +1 -1
@@ -102,14 +102,15 @@ describe('TaskManager', () => {
102
102
 
103
103
  incomingCallCb(mockCall);
104
104
 
105
- expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_INCOMING, taskManager.getTask(taskId));
105
+ expect(taskEmitSpy).toHaveBeenCalledWith(
106
+ TASK_EVENTS.TASK_INCOMING,
107
+ taskManager.getTask(taskId)
108
+ );
106
109
  });
107
110
 
108
111
  it('should re-emit task related events', () => {
109
112
  const dummyPayload = {
110
- data: {...taskDataMock,
111
- type: CC_TASK_EVENTS.AGENT_CONSULTING,
112
- },
113
+ data: {...taskDataMock, type: CC_TASK_EVENTS.AGENT_CONSULTING},
113
114
  };
114
115
  webSocketManagerMock.emit('message', JSON.stringify({data: taskDataMock}));
115
116
  const taskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
@@ -187,7 +188,10 @@ describe('TaskManager', () => {
187
188
  },
188
189
  };
189
190
 
190
- const currentTaskAssignedSpy = jest.spyOn(taskManager.getTask(payload.data.interactionId), 'emit');
191
+ const currentTaskAssignedSpy = jest.spyOn(
192
+ taskManager.getTask(payload.data.interactionId),
193
+ 'emit'
194
+ );
191
195
 
192
196
  webSocketManagerMock.emit('message', JSON.stringify(assignedPayload));
193
197
 
@@ -311,7 +315,10 @@ describe('TaskManager', () => {
311
315
  webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
312
316
 
313
317
  const taskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
314
- const webCallListenerSpy = jest.spyOn(taskManager.getTask(taskId), 'unregisterWebCallListeners');
318
+ const webCallListenerSpy = jest.spyOn(
319
+ taskManager.getTask(taskId),
320
+ 'unregisterWebCallListeners'
321
+ );
315
322
  const callOffSpy = jest.spyOn(mockCall, 'off');
316
323
  const payload = {
317
324
  data: {
@@ -331,11 +338,9 @@ describe('TaskManager', () => {
331
338
  };
332
339
 
333
340
  taskManager.getTask(taskId).data = payload.data;
334
- const task = taskManager.getTask(taskId)
341
+ const task = taskManager.getTask(taskId);
335
342
  webSocketManagerMock.emit('message', JSON.stringify(payload));
336
- expect(taskEmitSpy).toHaveBeenCalledWith(
337
- TASK_EVENTS.TASK_END, task
338
- );
343
+ expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_END, task);
339
344
  expect(webCallListenerSpy).toHaveBeenCalledWith();
340
345
  expect(callOffSpy).toHaveBeenCalledWith(
341
346
  CALL_EVENT_KEYS.REMOTE_MEDIA,
@@ -371,55 +376,42 @@ describe('TaskManager', () => {
371
376
 
372
377
  taskManager.getTask(taskId).updateTaskData(payload.data);
373
378
  webSocketManagerMock.emit('message', JSON.stringify(payload));
374
- expect(taskEmitSpy).toHaveBeenCalledWith(
375
- CC_EVENTS.CONTACT_ENDED,
376
- { ...payload.data}
377
- );
378
- expect(taskEmitSpy).toHaveBeenCalledWith(
379
- TASK_EVENTS.TASK_END,
380
- taskManager.getTask(taskId)
381
- );
379
+ expect(taskEmitSpy).toHaveBeenCalledWith(CC_EVENTS.CONTACT_ENDED, {...payload.data});
380
+ expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_END, taskManager.getTask(taskId));
382
381
  });
383
382
 
384
383
  it('should emit TASK_REJECT event on AGENT_INVITE_FAILED event', () => {
385
384
  webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
386
385
 
387
- const taskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
388
- const metricsTrackSpy = jest.spyOn(taskManager.metricsManager, 'trackEvent');
389
- const payload = {
390
- data: {
391
- type: CC_EVENTS.AGENT_INVITE_FAILED,
392
- agentId: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
393
- eventTime: 1733211616959,
394
- eventType: 'RoutingMessage',
395
- interaction: {state: 'connected'},
396
- interactionId: taskId,
397
- orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a',
398
- trackingId: '575c0ec2-618c-42af-a61c-53aeb0a221ee',
399
- mediaResourceId: '0ae913a4-c857-4705-8d49-76dd3dde75e4',
400
- destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
401
- owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
402
- queueMgr: 'aqm',
403
- reason: 'INVITE_FAILED',
404
- },
405
- };
386
+ const taskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
387
+ const metricsTrackSpy = jest.spyOn(taskManager.metricsManager, 'trackEvent');
388
+ const payload = {
389
+ data: {
390
+ type: CC_EVENTS.AGENT_INVITE_FAILED,
391
+ agentId: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
392
+ eventTime: 1733211616959,
393
+ eventType: 'RoutingMessage',
394
+ interaction: {state: 'connected'},
395
+ interactionId: taskId,
396
+ orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a',
397
+ trackingId: '575c0ec2-618c-42af-a61c-53aeb0a221ee',
398
+ mediaResourceId: '0ae913a4-c857-4705-8d49-76dd3dde75e4',
399
+ destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
400
+ owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
401
+ queueMgr: 'aqm',
402
+ reason: 'INVITE_FAILED',
403
+ },
404
+ };
406
405
 
407
- taskManager.getTask(taskId).updateTaskData(payload.data);
408
- webSocketManagerMock.emit('message', JSON.stringify(payload));
409
- expect(taskEmitSpy).toHaveBeenCalledWith(
410
- CC_EVENTS.AGENT_INVITE_FAILED,
411
- { ...payload.data}
412
- );
413
- expect(taskEmitSpy).toHaveBeenCalledWith(
414
- TASK_EVENTS.TASK_REJECT,
415
- payload.data.reason
416
- );
417
- // Verify the correct metric event name is used for AGENT_INVITE_FAILED
418
- expect(metricsTrackSpy).toHaveBeenCalled();
419
- expect(metricsTrackSpy.mock.calls[0][0]).toBe('Agent Invite Failed');
406
+ taskManager.getTask(taskId).updateTaskData(payload.data);
407
+ webSocketManagerMock.emit('message', JSON.stringify(payload));
408
+ expect(taskEmitSpy).toHaveBeenCalledWith(CC_EVENTS.AGENT_INVITE_FAILED, {...payload.data});
409
+ expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_REJECT, payload.data.reason);
410
+ // Verify the correct metric event name is used for AGENT_INVITE_FAILED
411
+ expect(metricsTrackSpy).toHaveBeenCalled();
412
+ expect(metricsTrackSpy.mock.calls[0][0]).toBe('Agent Invite Failed');
420
413
  });
421
414
 
422
-
423
415
  it('should not emit TASK_HYDRATE if task is already present in taskManager', () => {
424
416
  const payload = {
425
417
  data: {
@@ -492,9 +484,9 @@ describe('TaskManager', () => {
492
484
  mediaType: 'telephony',
493
485
  state: 'conference',
494
486
  participants: {
495
- [testAgentId]: { pType: 'Agent', hasLeft: false },
496
- 'agent-2': { pType: 'Agent', hasLeft: false },
497
- 'customer-1': { pType: 'Customer', hasLeft: false },
487
+ [testAgentId]: {pType: 'Agent', hasLeft: false},
488
+ 'agent-2': {pType: 'Agent', hasLeft: false},
489
+ 'customer-1': {pType: 'Customer', hasLeft: false},
498
490
  },
499
491
  media: {
500
492
  [taskId]: {
@@ -526,8 +518,8 @@ describe('TaskManager', () => {
526
518
  mediaType: 'telephony',
527
519
  state: 'connected',
528
520
  participants: {
529
- [testAgentId]: { pType: 'Agent', hasLeft: false },
530
- 'customer-1': { pType: 'Customer', hasLeft: false },
521
+ [testAgentId]: {pType: 'Agent', hasLeft: false},
522
+ 'customer-1': {pType: 'Customer', hasLeft: false},
531
523
  },
532
524
  media: {
533
525
  [taskId]: {
@@ -563,7 +555,7 @@ describe('TaskManager', () => {
563
555
  destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
564
556
  owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
565
557
  queueMgr: 'aqm',
566
- wrapUpRequired: true
558
+ wrapUpRequired: true,
567
559
  },
568
560
  };
569
561
 
@@ -716,7 +708,9 @@ describe('TaskManager', () => {
716
708
 
717
709
  const task = taskManager.getTask(taskId);
718
710
  const taskEmitSpy = jest.spyOn(task, 'emit');
719
- const taskAcceptSpy = jest.spyOn(task, 'accept').mockRejectedValue(new Error('Accept failed'));
711
+ const taskAcceptSpy = jest
712
+ .spyOn(task, 'accept')
713
+ .mockRejectedValue(new Error('Accept failed'));
720
714
 
721
715
  // Step 2: Trigger AGENT_OFFER_CONTACT with auto-answer (will fail)
722
716
  const autoAnswerPayload = {
@@ -814,7 +808,10 @@ describe('TaskManager', () => {
814
808
  expect(taskAcceptSpy).not.toHaveBeenCalled();
815
809
 
816
810
  // Verify TASK_AUTO_ANSWERED event was NOT emitted
817
- expect(taskEmitSpy).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_AUTO_ANSWERED, expect.anything());
811
+ expect(taskEmitSpy).not.toHaveBeenCalledWith(
812
+ TASK_EVENTS.TASK_AUTO_ANSWERED,
813
+ expect.anything()
814
+ );
818
815
  });
819
816
  });
820
817
 
@@ -1257,7 +1254,8 @@ describe('TaskManager', () => {
1257
1254
  webSocketManagerMock.emit('message', JSON.stringify(payloadConnected));
1258
1255
 
1259
1256
  // First call should set wrapUpRequired to true
1260
- expect(task.updateTaskData).toHaveBeenNthCalledWith(1,
1257
+ expect(task.updateTaskData).toHaveBeenNthCalledWith(
1258
+ 1,
1261
1259
  expect.objectContaining({
1262
1260
  wrapUpRequired: true,
1263
1261
  })
@@ -1279,13 +1277,13 @@ describe('TaskManager', () => {
1279
1277
  webSocketManagerMock.emit('message', JSON.stringify(payloadHeld));
1280
1278
 
1281
1279
  // Second call should also set wrapUpRequired to true
1282
- expect(task.updateTaskData).toHaveBeenNthCalledWith(2,
1280
+ expect(task.updateTaskData).toHaveBeenNthCalledWith(
1281
+ 2,
1283
1282
  expect.objectContaining({
1284
1283
  wrapUpRequired: true,
1285
1284
  })
1286
1285
  );
1287
1286
  });
1288
-
1289
1287
  });
1290
1288
 
1291
1289
  it('should remove OUTDIAL task from taskCollection on AGENT_CONTACT_ASSIGN_FAILED when NOT terminated (user-declined)', () => {
@@ -1406,7 +1404,10 @@ describe('TaskManager', () => {
1406
1404
  const taskUpdateTaskDataSpy = jest.spyOn(taskManager.getTask(taskId), 'updateTaskData');
1407
1405
  webSocketManagerMock.emit('message', JSON.stringify(payload));
1408
1406
  expect(taskUpdateTaskDataSpy).toHaveBeenCalledWith(payload.data);
1409
- expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULT_END, taskManager.getTask(taskId));
1407
+ expect(taskEmitSpy).toHaveBeenCalledWith(
1408
+ TASK_EVENTS.TASK_CONSULT_END,
1409
+ taskManager.getTask(taskId)
1410
+ );
1410
1411
  });
1411
1412
 
1412
1413
  it('should emit TASK_CONSULT_ENDED event and remove currentTask when on AGENT_CONSULT_ENDED event when requested for a consult', () => {
@@ -1587,7 +1588,10 @@ describe('TaskManager', () => {
1587
1588
  webSocketManagerMock.emit('message', JSON.stringify(assignFailedPayload));
1588
1589
 
1589
1590
  expect(taskUpdateDataSpy).toHaveBeenCalledWith(assignFailedPayload.data);
1590
- expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_REJECT, assignFailedPayload.data.reason);
1591
+ expect(taskEmitSpy).toHaveBeenCalledWith(
1592
+ TASK_EVENTS.TASK_REJECT,
1593
+ assignFailedPayload.data.reason
1594
+ );
1591
1595
  // Verify the correct metric event name is used for AGENT_CONTACT_ASSIGN_FAILED
1592
1596
  expect(metricsTrackSpy).toHaveBeenCalled();
1593
1597
  expect(metricsTrackSpy.mock.calls[0][0]).toBe('Agent Contact Assign Failed');
@@ -1662,7 +1666,10 @@ describe('TaskManager', () => {
1662
1666
  };
1663
1667
  webSocketManagerMock.emit('message', JSON.stringify(consultingPayload));
1664
1668
  expect(taskEmitSpy).toHaveBeenCalledWith(CC_EVENTS.AGENT_CONSULTING, consultingPayload.data);
1665
- expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULTING, taskManager.getTask(taskId));
1669
+ expect(taskEmitSpy).toHaveBeenCalledWith(
1670
+ TASK_EVENTS.TASK_CONSULTING,
1671
+ taskManager.getTask(taskId)
1672
+ );
1666
1673
  });
1667
1674
 
1668
1675
  it('should emit TASK_END event on AGENT_CONTACT_UNASSIGNED', () => {
@@ -1685,7 +1692,10 @@ describe('TaskManager', () => {
1685
1692
  },
1686
1693
  };
1687
1694
  webSocketManagerMock.emit('message', JSON.stringify(unassignedPayload));
1688
- expect(taskEmitSpy).toHaveBeenCalledWith(CC_EVENTS.AGENT_CONTACT_UNASSIGNED, unassignedPayload.data);
1695
+ expect(taskEmitSpy).toHaveBeenCalledWith(
1696
+ CC_EVENTS.AGENT_CONTACT_UNASSIGNED,
1697
+ unassignedPayload.data
1698
+ );
1689
1699
  expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_END, taskManager.getTask(taskId));
1690
1700
  });
1691
1701
 
@@ -1694,7 +1704,7 @@ describe('TaskManager', () => {
1694
1704
  const chatPayload = {
1695
1705
  data: {
1696
1706
  ...initalPayload.data,
1697
- interaction: { mediaType: 'chat' },
1707
+ interaction: {mediaType: 'chat'},
1698
1708
  },
1699
1709
  };
1700
1710
 
@@ -1716,7 +1726,7 @@ describe('TaskManager', () => {
1716
1726
  const emailPayload = {
1717
1727
  data: {
1718
1728
  ...initalPayload.data,
1719
- interaction: { mediaType: 'email' },
1729
+ interaction: {mediaType: 'email'},
1720
1730
  },
1721
1731
  };
1722
1732
 
@@ -1739,7 +1749,7 @@ describe('TaskManager', () => {
1739
1749
  data: {
1740
1750
  ...initalPayload.data,
1741
1751
  type: CC_EVENTS.AGENT_CONTACT_RESERVED,
1742
- interaction: { mediaType: 'chat' },
1752
+ interaction: {mediaType: 'chat'},
1743
1753
  },
1744
1754
  };
1745
1755
 
@@ -1771,7 +1781,7 @@ describe('TaskManager', () => {
1771
1781
  data: {
1772
1782
  ...chatReservedPayload.data,
1773
1783
  type: CC_EVENTS.CONTACT_ENDED,
1774
- interaction: { mediaType: 'chat', state: 'new' }, // Change to 'new' state
1784
+ interaction: {mediaType: 'chat', state: 'new'}, // Change to 'new' state
1775
1785
  },
1776
1786
  };
1777
1787
 
@@ -1788,7 +1798,7 @@ describe('TaskManager', () => {
1788
1798
  data: {
1789
1799
  ...initalPayload.data,
1790
1800
  interactionId: 'telephony-task-id',
1791
- interaction: { mediaType: 'telephony' },
1801
+ interaction: {mediaType: 'telephony'},
1792
1802
  },
1793
1803
  };
1794
1804
 
@@ -1796,7 +1806,7 @@ describe('TaskManager', () => {
1796
1806
  data: {
1797
1807
  ...initalPayload.data,
1798
1808
  interactionId: 'chat-task-id',
1799
- interaction: { mediaType: 'chat' },
1809
+ interaction: {mediaType: 'chat'},
1800
1810
  },
1801
1811
  };
1802
1812
 
@@ -1804,7 +1814,7 @@ describe('TaskManager', () => {
1804
1814
  data: {
1805
1815
  ...initalPayload.data,
1806
1816
  interactionId: 'email-task-id',
1807
- interaction: { mediaType: 'email' },
1817
+ interaction: {mediaType: 'email'},
1808
1818
  },
1809
1819
  };
1810
1820
 
@@ -1819,9 +1829,15 @@ describe('TaskManager', () => {
1819
1829
  expect(taskManager.getAllTasks()).toHaveProperty(emailPayload.data.interactionId);
1820
1830
 
1821
1831
  // Verify the task media types are correctly set
1822
- expect(taskManager.getTask(telephonyPayload.data.interactionId).data.interaction.mediaType).toBe('telephony');
1823
- expect(taskManager.getTask(chatPayload.data.interactionId).data.interaction.mediaType).toBe('chat');
1824
- expect(taskManager.getTask(emailPayload.data.interactionId).data.interaction.mediaType).toBe('email');
1832
+ expect(
1833
+ taskManager.getTask(telephonyPayload.data.interactionId).data.interaction.mediaType
1834
+ ).toBe('telephony');
1835
+ expect(taskManager.getTask(chatPayload.data.interactionId).data.interaction.mediaType).toBe(
1836
+ 'chat'
1837
+ );
1838
+ expect(taskManager.getTask(emailPayload.data.interactionId).data.interaction.mediaType).toBe(
1839
+ 'email'
1840
+ );
1825
1841
  });
1826
1842
 
1827
1843
  it('should properly handle one task ending when multiple tasks are active', () => {
@@ -1830,7 +1846,7 @@ describe('TaskManager', () => {
1830
1846
  data: {
1831
1847
  ...initalPayload.data,
1832
1848
  interactionId: 'task-id-1',
1833
- interaction: { mediaType: 'telephony' },
1849
+ interaction: {mediaType: 'telephony'},
1834
1850
  },
1835
1851
  };
1836
1852
 
@@ -1838,7 +1854,7 @@ describe('TaskManager', () => {
1838
1854
  data: {
1839
1855
  ...initalPayload.data,
1840
1856
  interactionId: 'task-id-2',
1841
- interaction: { mediaType: 'chat' },
1857
+ interaction: {mediaType: 'chat'},
1842
1858
  },
1843
1859
  };
1844
1860
 
@@ -1846,7 +1862,7 @@ describe('TaskManager', () => {
1846
1862
  data: {
1847
1863
  ...initalPayload.data,
1848
1864
  interactionId: 'task-id-3',
1849
- interaction: { mediaType: 'email' },
1865
+ interaction: {mediaType: 'email'},
1850
1866
  },
1851
1867
  };
1852
1868
 
@@ -1873,7 +1889,7 @@ describe('TaskManager', () => {
1873
1889
  data: {
1874
1890
  ...task2Payload.data,
1875
1891
  type: CC_EVENTS.CONTACT_ENDED,
1876
- interaction: { mediaType: 'chat', state: 'new' }, // Using 'new' to trigger cleanup
1892
+ interaction: {mediaType: 'chat', state: 'new'}, // Using 'new' to trigger cleanup
1877
1893
  },
1878
1894
  };
1879
1895
 
@@ -1899,7 +1915,7 @@ describe('TaskManager', () => {
1899
1915
  data: {
1900
1916
  ...task3Payload.data,
1901
1917
  type: CC_EVENTS.CONTACT_ENDED,
1902
- interaction: { mediaType: 'email', state: 'connected' }, // Using 'connected' to NOT trigger cleanup
1918
+ interaction: {mediaType: 'email', state: 'connected'}, // Using 'connected' to NOT trigger cleanup
1903
1919
  },
1904
1920
  };
1905
1921
 
@@ -1957,16 +1973,16 @@ describe('TaskManager', () => {
1957
1973
 
1958
1974
  it('should update task data on AGENT_WRAPUP event', () => {
1959
1975
  const payload = {
1960
- data: {
1961
- type: CC_EVENTS.AGENT_WRAPUP,
1962
- interactionId: taskId,
1963
- wrapUpRequired: true,
1964
- },
1976
+ data: {
1977
+ type: CC_EVENTS.AGENT_WRAPUP,
1978
+ interactionId: taskId,
1979
+ wrapUpRequired: true,
1980
+ },
1965
1981
  };
1966
1982
  const task = taskManager.getTask(taskId);
1967
1983
  const updateSpy = jest.spyOn(task, 'updateTaskData').mockImplementation((data) => {
1968
- task.data = { ...(task.data || {}), ...(data || {}) };
1969
- return task;
1984
+ task.data = {...(task.data || {}), ...(data || {})};
1985
+ return task;
1970
1986
  });
1971
1987
  webSocketManagerMock.emit('message', JSON.stringify(payload));
1972
1988
  expect(updateSpy).toHaveBeenCalledWith(payload.data);
@@ -1981,7 +1997,7 @@ describe('TaskManager', () => {
1981
1997
  data: {
1982
1998
  type: CC_EVENTS.AGENT_CONTACT_UNASSIGNED,
1983
1999
  agentId: initalPayload.data.agentId,
1984
- interaction: { mediaType: 'telephony' },
2000
+ interaction: {mediaType: 'telephony'},
1985
2001
  interactionId: initalPayload.data.interactionId,
1986
2002
  orgId: initalPayload.data.orgId,
1987
2003
  trackingId: initalPayload.data.trackingId,
@@ -2000,7 +2016,7 @@ describe('TaskManager', () => {
2000
2016
  data: {
2001
2017
  type: CC_EVENTS.AGENT_WRAPUP,
2002
2018
  interactionId: taskId,
2003
- interaction: { mediaType: 'telephony' },
2019
+ interaction: {mediaType: 'telephony'},
2004
2020
  },
2005
2021
  };
2006
2022
  webSocketManagerMock.emit('message', JSON.stringify(wrapupPayload));
@@ -2017,7 +2033,7 @@ describe('TaskManager', () => {
2017
2033
  data: {
2018
2034
  type: CC_EVENTS.AGENT_VTEAM_TRANSFERRED,
2019
2035
  agentId: initalPayload.data.agentId,
2020
- interaction: { mediaType: 'telephony' },
2036
+ interaction: {mediaType: 'telephony'},
2021
2037
  interactionId: initalPayload.data.interactionId,
2022
2038
  orgId: initalPayload.data.orgId,
2023
2039
  trackingId: initalPayload.data.trackingId,
@@ -2036,7 +2052,7 @@ describe('TaskManager', () => {
2036
2052
  data: {
2037
2053
  type: CC_EVENTS.AGENT_WRAPUP,
2038
2054
  interactionId: taskId,
2039
- interaction: { mediaType: 'telephony' },
2055
+ interaction: {mediaType: 'telephony'},
2040
2056
  },
2041
2057
  };
2042
2058
  webSocketManagerMock.emit('message', JSON.stringify(wrapupPayload));
@@ -2069,11 +2085,11 @@ describe('TaskManager', () => {
2069
2085
  taskManager.setAgentId(agentId);
2070
2086
 
2071
2087
  task = {
2072
- data: { interactionId: taskId },
2088
+ data: {interactionId: taskId},
2073
2089
  emit: jest.fn(),
2074
2090
  updateTaskData: jest.fn().mockImplementation((updatedData) => {
2075
2091
  // Mock the updateTaskData method to actually update task.data
2076
- task.data = { ...task.data, ...updatedData };
2092
+ task.data = {...task.data, ...updatedData};
2077
2093
  return task;
2078
2094
  }),
2079
2095
  };
@@ -2135,8 +2151,8 @@ describe('TaskManager', () => {
2135
2151
  participantType: 'agent',
2136
2152
  interaction: {
2137
2153
  participants: {
2138
- [agentId]: { pType: 'Agent', hasLeft: false },
2139
- 'new-participant-123': { pType: 'Agent', hasLeft: false },
2154
+ [agentId]: {pType: 'Agent', hasLeft: false},
2155
+ 'new-participant-123': {pType: 'Agent', hasLeft: false},
2140
2156
  },
2141
2157
  media: {
2142
2158
  [taskId]: {
@@ -2163,10 +2179,10 @@ describe('TaskManager', () => {
2163
2179
  participantId: 'new-agent-789',
2164
2180
  interaction: {
2165
2181
  participants: {
2166
- [agentId]: { pType: 'Agent', hasLeft: false },
2167
- 'agent-2': { pType: 'Agent', hasLeft: false },
2168
- 'new-agent-789': { pType: 'Agent', hasLeft: false },
2169
- 'customer-1': { pType: 'Customer', hasLeft: false },
2182
+ [agentId]: {pType: 'Agent', hasLeft: false},
2183
+ 'agent-2': {pType: 'Agent', hasLeft: false},
2184
+ 'new-agent-789': {pType: 'Agent', hasLeft: false},
2185
+ 'customer-1': {pType: 'Customer', hasLeft: false},
2170
2186
  },
2171
2187
  media: {
2172
2188
  [taskId]: {
@@ -2204,9 +2220,9 @@ describe('TaskManager', () => {
2204
2220
  interactionId: taskId,
2205
2221
  interaction: {
2206
2222
  participants: {
2207
- [agentId]: { pType: 'Agent', hasLeft: false },
2208
- 'agent-2': { pType: 'Agent', hasLeft: true }, // This agent left
2209
- 'customer-1': { pType: 'Customer', hasLeft: false },
2223
+ [agentId]: {pType: 'Agent', hasLeft: false},
2224
+ 'agent-2': {pType: 'Agent', hasLeft: true}, // This agent left
2225
+ 'customer-1': {pType: 'Customer', hasLeft: false},
2210
2226
  },
2211
2227
  media: {
2212
2228
  [taskId]: {
@@ -2508,7 +2524,7 @@ describe('TaskManager', () => {
2508
2524
  it('should only update task for matching interactionId', () => {
2509
2525
  const otherTaskId = 'other-task-id';
2510
2526
  const otherTask = {
2511
- data: { interactionId: otherTaskId },
2527
+ data: {interactionId: otherTaskId},
2512
2528
  emit: jest.fn(),
2513
2529
  };
2514
2530
  taskManager.taskCollection[otherTaskId] = otherTask;
@@ -3056,5 +3072,165 @@ describe('TaskManager', () => {
3056
3072
  });
3057
3073
  });
3058
3074
 
3059
- });
3075
+ describe('Campaign Preview Reservation', () => {
3076
+ it('should create a task and emit TASK_CAMPAIGN_PREVIEW_RESERVATION when AgentOfferCampaignReservation is received', () => {
3077
+ const campaignPayload = {
3078
+ data: {
3079
+ type: CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION,
3080
+ interactionId: 'campaign-interaction-123',
3081
+ agentId: taskDataMock.agentId,
3082
+ orgId: taskDataMock.orgId,
3083
+ trackingId: 'campaign-tracking-456',
3084
+ interaction: {
3085
+ mediaType: 'telephony',
3086
+ callProcessingDetails: {
3087
+ campaignId: 'campaign-789',
3088
+ },
3089
+ },
3090
+ },
3091
+ };
3092
+
3093
+ const managerEmitSpy = jest.spyOn(taskManager, 'emit');
3094
+
3095
+ webSocketManagerMock.emit('message', JSON.stringify(campaignPayload));
3096
+
3097
+ // Should emit with a task object (not raw data)
3098
+ expect(managerEmitSpy).toHaveBeenCalledWith(
3099
+ TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
3100
+ expect.objectContaining({
3101
+ data: expect.objectContaining({
3102
+ interactionId: 'campaign-interaction-123',
3103
+ type: CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION,
3104
+ wrapUpRequired: false,
3105
+ isAutoAnswering: false,
3106
+ }),
3107
+ })
3108
+ );
3109
+
3110
+ // Task should be in the collection so subsequent events (e.g. AGENT_CONTACT_ASSIGNED) can find it
3111
+ expect(taskManager['taskCollection']['campaign-interaction-123']).toBeDefined();
3112
+ });
3113
+
3114
+ it('should not emit TASK_INCOMING for campaign preview reservation when incoming WebRTC call arrives', () => {
3115
+ const campaignPayload = {
3116
+ data: {
3117
+ type: CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION,
3118
+ interactionId: 'campaign-interaction-123',
3119
+ agentId: taskDataMock.agentId,
3120
+ orgId: taskDataMock.orgId,
3121
+ trackingId: 'campaign-tracking-456',
3122
+ interaction: {
3123
+ mediaType: 'telephony',
3124
+ callProcessingDetails: {
3125
+ campaignId: 'campaign-789',
3126
+ },
3127
+ },
3128
+ },
3129
+ };
3130
+
3131
+ // Remove the default task so only the campaign preview task is in the collection
3132
+ delete taskManager['taskCollection'][taskId];
3133
+
3134
+ // Create campaign preview task via the reservation event
3135
+ webSocketManagerMock.emit('message', JSON.stringify(campaignPayload));
3136
+
3137
+ const managerEmitSpy = jest.spyOn(taskManager, 'emit');
3138
+
3139
+ // Simulate an incoming WebRTC call
3140
+ const incomingCallCb = onSpy.mock.calls[0][1];
3141
+ incomingCallCb(mockCall);
3142
+
3143
+ // TASK_INCOMING should NOT be emitted because the only telephony task is a campaign preview
3144
+ expect(managerEmitSpy).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_INCOMING, expect.anything());
3145
+ });
3146
+
3147
+ it('should update existing task when AgentOfferCampaignReservation is received for known interactionId', () => {
3148
+ const campaignPayload = {
3149
+ data: {
3150
+ type: CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION,
3151
+ interactionId: 'campaign-interaction-123',
3152
+ agentId: taskDataMock.agentId,
3153
+ orgId: taskDataMock.orgId,
3154
+ trackingId: 'campaign-tracking-456',
3155
+ interaction: {
3156
+ mediaType: 'telephony',
3157
+ callProcessingDetails: {
3158
+ campaignId: 'campaign-789',
3159
+ },
3160
+ },
3161
+ },
3162
+ };
3163
+
3164
+ // Send the first reservation to create the task
3165
+ webSocketManagerMock.emit('message', JSON.stringify(campaignPayload));
3060
3166
 
3167
+ const managerEmitSpy = jest.spyOn(taskManager, 'emit');
3168
+
3169
+ // Send a second reservation for the same interactionId
3170
+ webSocketManagerMock.emit('message', JSON.stringify(campaignPayload));
3171
+
3172
+ expect(managerEmitSpy).toHaveBeenCalledWith(
3173
+ TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
3174
+ expect.objectContaining({
3175
+ data: expect.objectContaining({
3176
+ interactionId: 'campaign-interaction-123',
3177
+ }),
3178
+ })
3179
+ );
3180
+ });
3181
+
3182
+ it('should update task data but NOT remove task when CampaignContactUpdated is received', () => {
3183
+ const campaignInteractionId = 'campaign-interaction-123';
3184
+
3185
+ // First create a campaign preview task
3186
+ const reservationPayload = {
3187
+ data: {
3188
+ type: CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION,
3189
+ interactionId: campaignInteractionId,
3190
+ agentId: taskDataMock.agentId,
3191
+ orgId: taskDataMock.orgId,
3192
+ trackingId: 'campaign-tracking-456',
3193
+ interaction: {
3194
+ mediaType: 'telephony',
3195
+ callProcessingDetails: {
3196
+ campaignId: 'campaign-789',
3197
+ },
3198
+ },
3199
+ },
3200
+ };
3201
+
3202
+ webSocketManagerMock.emit('message', JSON.stringify(reservationPayload));
3203
+
3204
+ // Verify task exists in collection
3205
+ const task = taskManager['taskCollection'][campaignInteractionId];
3206
+ expect(task).toBeDefined();
3207
+
3208
+ const taskEmitSpy = jest.spyOn(task, 'emit');
3209
+
3210
+ // Now send CampaignContactUpdated
3211
+ const campaignContactUpdatedPayload = {
3212
+ data: {
3213
+ type: CC_EVENTS.CAMPAIGN_CONTACT_UPDATED,
3214
+ interactionId: campaignInteractionId,
3215
+ agentId: taskDataMock.agentId,
3216
+ orgId: taskDataMock.orgId,
3217
+ interaction: {
3218
+ mediaType: 'telephony',
3219
+ state: 'new',
3220
+ callProcessingDetails: {
3221
+ campaignId: 'campaign-789',
3222
+ },
3223
+ },
3224
+ },
3225
+ };
3226
+
3227
+ webSocketManagerMock.emit('message', JSON.stringify(campaignContactUpdatedPayload));
3228
+
3229
+ // Task should still exist in collection (not removed — non-terminal event)
3230
+ expect(taskManager['taskCollection'][campaignInteractionId]).toBeDefined();
3231
+
3232
+ // TASK_END should NOT have been emitted
3233
+ expect(taskEmitSpy).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_END, expect.anything());
3234
+ });
3235
+ });
3236
+ });