@webex/contact-center 3.12.0-next.7 → 3.12.0-next.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cc.js +212 -23
- package/dist/cc.js.map +1 -1
- package/dist/constants.js +2 -0
- package/dist/constants.js.map +1 -1
- package/dist/metrics/behavioral-events.js +26 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +4 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/config/Util.js +1 -1
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/constants.js +1 -1
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/types.js +4 -0
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/core/Utils.js +37 -9
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/task/TaskManager.js +94 -8
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/constants.js +3 -1
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/dialer.js +78 -0
- package/dist/services/task/dialer.js.map +1 -1
- package/dist/services/task/index.js +7 -2
- package/dist/services/task/index.js.map +1 -1
- package/dist/services/task/types.js +56 -0
- package/dist/services/task/types.js.map +1 -1
- package/dist/types/cc.d.ts +61 -0
- package/dist/types/constants.d.ts +2 -0
- package/dist/types/metrics/constants.d.ts +4 -0
- package/dist/types/services/config/types.d.ts +10 -1
- package/dist/types/services/core/Err.d.ts +4 -0
- package/dist/types/services/core/Utils.d.ts +10 -3
- package/dist/types/services/task/constants.d.ts +2 -0
- package/dist/types/services/task/dialer.d.ts +30 -0
- package/dist/types/services/task/types.d.ts +65 -1
- package/dist/types/types.d.ts +2 -0
- package/dist/types.js.map +1 -1
- package/dist/webex.js +1 -1
- package/package.json +9 -9
- package/src/cc.ts +270 -24
- package/src/constants.ts +2 -0
- package/src/metrics/behavioral-events.ts +28 -0
- package/src/metrics/constants.ts +4 -0
- package/src/services/config/Util.ts +1 -1
- package/src/services/config/constants.ts +1 -1
- package/src/services/config/types.ts +6 -1
- package/src/services/core/Err.ts +2 -0
- package/src/services/core/Utils.ts +43 -8
- package/src/services/task/TaskManager.ts +106 -22
- package/src/services/task/constants.ts +2 -0
- package/src/services/task/dialer.ts +80 -0
- package/src/services/task/index.ts +7 -2
- package/src/services/task/types.ts +70 -0
- package/src/types.ts +2 -0
- package/test/unit/spec/cc.ts +210 -20
- package/test/unit/spec/services/config/index.ts +3 -3
- package/test/unit/spec/services/core/Utils.ts +90 -7
- package/test/unit/spec/services/task/TaskManager.ts +252 -7
- package/test/unit/spec/services/task/dialer.ts +190 -0
- package/test/unit/spec/services/task/index.ts +21 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -84,7 +84,12 @@ describe('TaskManager', () => {
|
|
|
84
84
|
onSpy = jest.spyOn(webCallingService, 'on');
|
|
85
85
|
offSpy = jest.spyOn(webCallingService, 'off');
|
|
86
86
|
|
|
87
|
-
taskManager = new TaskManager(
|
|
87
|
+
taskManager = new TaskManager(
|
|
88
|
+
mockApiAIAssistant,
|
|
89
|
+
contactMock,
|
|
90
|
+
webCallingService,
|
|
91
|
+
webSocketManagerMock
|
|
92
|
+
);
|
|
88
93
|
const taskMock = {
|
|
89
94
|
emit: jest.fn(),
|
|
90
95
|
accept: jest.fn(),
|
|
@@ -314,6 +319,10 @@ describe('TaskManager', () => {
|
|
|
314
319
|
TASK_EVENTS.TASK_ASSIGNED,
|
|
315
320
|
taskManager.getTask(taskId)
|
|
316
321
|
);
|
|
322
|
+
expect(taskIncomingSpy).toHaveBeenCalledWith(
|
|
323
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
324
|
+
taskManager.getTask(taskId)
|
|
325
|
+
);
|
|
317
326
|
});
|
|
318
327
|
|
|
319
328
|
it('should handle WebSocket message for AGENT_CONTACT_RESERVED and emit task:incoming for extension case', () => {
|
|
@@ -1146,7 +1155,7 @@ describe('TaskManager', () => {
|
|
|
1146
1155
|
taskManager.setAgentId(agentId);
|
|
1147
1156
|
});
|
|
1148
1157
|
|
|
1149
|
-
it('should set wrapUpRequired to true when agent is in agentsPendingWrapUp array', () => {
|
|
1158
|
+
it('should set wrapUpRequired to true when agent is in agentsPendingWrapUp array for non-campaign tasks', () => {
|
|
1150
1159
|
const task = taskManager.getTask(taskId);
|
|
1151
1160
|
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1152
1161
|
task.data = {
|
|
@@ -1306,7 +1315,7 @@ describe('TaskManager', () => {
|
|
|
1306
1315
|
);
|
|
1307
1316
|
});
|
|
1308
1317
|
|
|
1309
|
-
it('should set wrapUpRequired
|
|
1318
|
+
it('should set wrapUpRequired to true when agent is the only one in agentsPendingWrapUp for non-campaign tasks', () => {
|
|
1310
1319
|
const task = taskManager.getTask(taskId);
|
|
1311
1320
|
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1312
1321
|
task.data = {
|
|
@@ -1338,7 +1347,7 @@ describe('TaskManager', () => {
|
|
|
1338
1347
|
);
|
|
1339
1348
|
});
|
|
1340
1349
|
|
|
1341
|
-
it('should
|
|
1350
|
+
it('should set wrapUpRequired to true for different interaction states when agent is in agentsPendingWrapUp for non-campaign tasks', () => {
|
|
1342
1351
|
const task = taskManager.getTask(taskId);
|
|
1343
1352
|
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1344
1353
|
task.data = {
|
|
@@ -1368,7 +1377,6 @@ describe('TaskManager', () => {
|
|
|
1368
1377
|
|
|
1369
1378
|
webSocketManagerMock.emit('message', JSON.stringify(payloadConnected));
|
|
1370
1379
|
|
|
1371
|
-
// First call should set wrapUpRequired to true
|
|
1372
1380
|
expect(task.updateTaskData).toHaveBeenNthCalledWith(
|
|
1373
1381
|
1,
|
|
1374
1382
|
expect.objectContaining({
|
|
@@ -1391,7 +1399,6 @@ describe('TaskManager', () => {
|
|
|
1391
1399
|
|
|
1392
1400
|
webSocketManagerMock.emit('message', JSON.stringify(payloadHeld));
|
|
1393
1401
|
|
|
1394
|
-
// Second call should also set wrapUpRequired to true
|
|
1395
1402
|
expect(task.updateTaskData).toHaveBeenNthCalledWith(
|
|
1396
1403
|
2,
|
|
1397
1404
|
expect.objectContaining({
|
|
@@ -1399,6 +1406,43 @@ describe('TaskManager', () => {
|
|
|
1399
1406
|
})
|
|
1400
1407
|
);
|
|
1401
1408
|
});
|
|
1409
|
+
|
|
1410
|
+
it('should set wrapUpRequired to false for campaign preview tasks even when agent is in agentsPendingWrapUp', () => {
|
|
1411
|
+
const task = taskManager.getTask(taskId);
|
|
1412
|
+
// Set up task as a campaign preview task via outboundType
|
|
1413
|
+
task.data.interaction = {
|
|
1414
|
+
...task.data.interaction,
|
|
1415
|
+
outboundType: 'STANDARD_PREVIEW_CAMPAIGN',
|
|
1416
|
+
};
|
|
1417
|
+
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1418
|
+
task.data = {
|
|
1419
|
+
...task.data,
|
|
1420
|
+
...newData,
|
|
1421
|
+
};
|
|
1422
|
+
return task;
|
|
1423
|
+
});
|
|
1424
|
+
task.unregisterWebCallListeners = jest.fn();
|
|
1425
|
+
|
|
1426
|
+
const payload = {
|
|
1427
|
+
data: {
|
|
1428
|
+
type: CC_EVENTS.CONTACT_ENDED,
|
|
1429
|
+
interactionId: taskId,
|
|
1430
|
+
interaction: {
|
|
1431
|
+
state: 'connected',
|
|
1432
|
+
mediaType: 'telephony',
|
|
1433
|
+
},
|
|
1434
|
+
agentsPendingWrapUp: [agentId],
|
|
1435
|
+
},
|
|
1436
|
+
};
|
|
1437
|
+
|
|
1438
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1439
|
+
|
|
1440
|
+
expect(task.updateTaskData).toHaveBeenCalledWith(
|
|
1441
|
+
expect.objectContaining({
|
|
1442
|
+
wrapUpRequired: false,
|
|
1443
|
+
})
|
|
1444
|
+
);
|
|
1445
|
+
});
|
|
1402
1446
|
});
|
|
1403
1447
|
|
|
1404
1448
|
it('should remove OUTDIAL task from taskCollection on AGENT_CONTACT_ASSIGN_FAILED when NOT terminated (user-declined)', () => {
|
|
@@ -1497,6 +1541,7 @@ describe('TaskManager', () => {
|
|
|
1497
1541
|
});
|
|
1498
1542
|
|
|
1499
1543
|
const taskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
|
|
1544
|
+
const taskManagerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
1500
1545
|
webSocketManagerMock.emit('message', JSON.stringify(initialConsultingPayload));
|
|
1501
1546
|
webSocketManagerMock.emit('message', JSON.stringify(consultingPayload));
|
|
1502
1547
|
expect(taskManager.getTask(taskId).data.isConsulted).toBe(true);
|
|
@@ -1504,6 +1549,10 @@ describe('TaskManager', () => {
|
|
|
1504
1549
|
TASK_EVENTS.TASK_CONSULT_ACCEPTED,
|
|
1505
1550
|
taskManager.getTask(taskId)
|
|
1506
1551
|
);
|
|
1552
|
+
expect(taskManagerEmitSpy).toHaveBeenCalledWith(
|
|
1553
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
1554
|
+
taskManager.getTask(taskId)
|
|
1555
|
+
);
|
|
1507
1556
|
});
|
|
1508
1557
|
|
|
1509
1558
|
it('should emit TASK_CONSULT_ENDED event on AGENT_CONSULT_ENDED event', () => {
|
|
@@ -1772,6 +1821,7 @@ describe('TaskManager', () => {
|
|
|
1772
1821
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1773
1822
|
taskManager.getTask(taskId).data.isConsulted = false;
|
|
1774
1823
|
const taskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
|
|
1824
|
+
const taskManagerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
1775
1825
|
const consultingPayload = {
|
|
1776
1826
|
data: {
|
|
1777
1827
|
...initalPayload.data,
|
|
@@ -1785,6 +1835,10 @@ describe('TaskManager', () => {
|
|
|
1785
1835
|
TASK_EVENTS.TASK_CONSULTING,
|
|
1786
1836
|
taskManager.getTask(taskId)
|
|
1787
1837
|
);
|
|
1838
|
+
expect(taskManagerEmitSpy).toHaveBeenCalledWith(
|
|
1839
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
1840
|
+
taskManager.getTask(taskId)
|
|
1841
|
+
);
|
|
1788
1842
|
});
|
|
1789
1843
|
|
|
1790
1844
|
it('should emit TASK_END event on AGENT_CONTACT_UNASSIGNED', () => {
|
|
@@ -3294,7 +3348,7 @@ describe('TaskManager', () => {
|
|
|
3294
3348
|
);
|
|
3295
3349
|
});
|
|
3296
3350
|
|
|
3297
|
-
it('should
|
|
3351
|
+
it('should emit TASK_CAMPAIGN_CONTACT_UPDATED and NOT remove task when CampaignContactUpdated is received', () => {
|
|
3298
3352
|
const campaignInteractionId = 'campaign-interaction-123';
|
|
3299
3353
|
|
|
3300
3354
|
// First create a campaign preview task
|
|
@@ -3344,8 +3398,199 @@ describe('TaskManager', () => {
|
|
|
3344
3398
|
// Task should still exist in collection (not removed — non-terminal event)
|
|
3345
3399
|
expect(taskManager['taskCollection'][campaignInteractionId]).toBeDefined();
|
|
3346
3400
|
|
|
3401
|
+
// TASK_CAMPAIGN_CONTACT_UPDATED should have been emitted
|
|
3402
|
+
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
3403
|
+
TASK_EVENTS.TASK_CAMPAIGN_CONTACT_UPDATED,
|
|
3404
|
+
expect.objectContaining({
|
|
3405
|
+
data: expect.objectContaining({
|
|
3406
|
+
interactionId: campaignInteractionId,
|
|
3407
|
+
}),
|
|
3408
|
+
})
|
|
3409
|
+
);
|
|
3410
|
+
|
|
3347
3411
|
// TASK_END should NOT have been emitted
|
|
3348
3412
|
expect(taskEmitSpy).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_END, expect.anything());
|
|
3349
3413
|
});
|
|
3414
|
+
|
|
3415
|
+
it('should emit TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED when CampaignPreviewAcceptFailed is received', () => {
|
|
3416
|
+
const campaignInteractionId = 'campaign-interaction-123';
|
|
3417
|
+
|
|
3418
|
+
// First create a campaign preview task
|
|
3419
|
+
const reservationPayload = {
|
|
3420
|
+
data: {
|
|
3421
|
+
type: CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION,
|
|
3422
|
+
interactionId: campaignInteractionId,
|
|
3423
|
+
agentId: taskDataMock.agentId,
|
|
3424
|
+
orgId: taskDataMock.orgId,
|
|
3425
|
+
trackingId: 'campaign-tracking-456',
|
|
3426
|
+
interaction: {
|
|
3427
|
+
mediaType: 'telephony',
|
|
3428
|
+
callProcessingDetails: {
|
|
3429
|
+
campaignId: 'campaign-789',
|
|
3430
|
+
},
|
|
3431
|
+
},
|
|
3432
|
+
},
|
|
3433
|
+
};
|
|
3434
|
+
|
|
3435
|
+
webSocketManagerMock.emit('message', JSON.stringify(reservationPayload));
|
|
3436
|
+
|
|
3437
|
+
const task = taskManager['taskCollection'][campaignInteractionId];
|
|
3438
|
+
expect(task).toBeDefined();
|
|
3439
|
+
|
|
3440
|
+
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
3441
|
+
|
|
3442
|
+
const failPayload = {
|
|
3443
|
+
data: {
|
|
3444
|
+
type: CC_EVENTS.CAMPAIGN_PREVIEW_ACCEPT_FAILED,
|
|
3445
|
+
interactionId: campaignInteractionId,
|
|
3446
|
+
campaignId: 'campaign-789',
|
|
3447
|
+
reason: 'INTERNAL_ERROR',
|
|
3448
|
+
},
|
|
3449
|
+
};
|
|
3450
|
+
|
|
3451
|
+
webSocketManagerMock.emit('message', JSON.stringify(failPayload));
|
|
3452
|
+
|
|
3453
|
+
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
3454
|
+
TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED,
|
|
3455
|
+
expect.objectContaining({
|
|
3456
|
+
data: expect.objectContaining({interactionId: campaignInteractionId}),
|
|
3457
|
+
})
|
|
3458
|
+
);
|
|
3459
|
+
// Task should still exist (failure is non-terminal)
|
|
3460
|
+
expect(taskManager['taskCollection'][campaignInteractionId]).toBeDefined();
|
|
3461
|
+
|
|
3462
|
+
// Failure payload (reason, campaignId) should be merged into task.data
|
|
3463
|
+
expect(task.data.reason).toBe('INTERNAL_ERROR');
|
|
3464
|
+
|
|
3465
|
+
// Original reservation data must be preserved
|
|
3466
|
+
expect(task.data.interaction).toBeDefined();
|
|
3467
|
+
expect(task.data.interaction.callProcessingDetails.campaignId).toBe('campaign-789');
|
|
3468
|
+
});
|
|
3469
|
+
|
|
3470
|
+
it('should emit TASK_CAMPAIGN_PREVIEW_SKIP_FAILED when CampaignPreviewSkipFailed is received', () => {
|
|
3471
|
+
const campaignInteractionId = 'campaign-interaction-123';
|
|
3472
|
+
|
|
3473
|
+
const reservationPayload = {
|
|
3474
|
+
data: {
|
|
3475
|
+
type: CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION,
|
|
3476
|
+
interactionId: campaignInteractionId,
|
|
3477
|
+
agentId: taskDataMock.agentId,
|
|
3478
|
+
orgId: taskDataMock.orgId,
|
|
3479
|
+
trackingId: 'campaign-tracking-456',
|
|
3480
|
+
interaction: {
|
|
3481
|
+
mediaType: 'telephony',
|
|
3482
|
+
callProcessingDetails: {
|
|
3483
|
+
campaignId: 'campaign-789',
|
|
3484
|
+
},
|
|
3485
|
+
},
|
|
3486
|
+
},
|
|
3487
|
+
};
|
|
3488
|
+
|
|
3489
|
+
webSocketManagerMock.emit('message', JSON.stringify(reservationPayload));
|
|
3490
|
+
|
|
3491
|
+
const task = taskManager['taskCollection'][campaignInteractionId];
|
|
3492
|
+
expect(task).toBeDefined();
|
|
3493
|
+
|
|
3494
|
+
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
3495
|
+
|
|
3496
|
+
const failPayload = {
|
|
3497
|
+
data: {
|
|
3498
|
+
type: CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED,
|
|
3499
|
+
interactionId: campaignInteractionId,
|
|
3500
|
+
campaignId: 'campaign-789',
|
|
3501
|
+
reason: 'INTERNAL_ERROR',
|
|
3502
|
+
},
|
|
3503
|
+
};
|
|
3504
|
+
|
|
3505
|
+
webSocketManagerMock.emit('message', JSON.stringify(failPayload));
|
|
3506
|
+
|
|
3507
|
+
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
3508
|
+
TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_SKIP_FAILED,
|
|
3509
|
+
expect.objectContaining({
|
|
3510
|
+
data: expect.objectContaining({interactionId: campaignInteractionId}),
|
|
3511
|
+
})
|
|
3512
|
+
);
|
|
3513
|
+
expect(taskManager['taskCollection'][campaignInteractionId]).toBeDefined();
|
|
3514
|
+
|
|
3515
|
+
// Failure payload (reason) should be merged into task.data
|
|
3516
|
+
expect(task.data.reason).toBe('INTERNAL_ERROR');
|
|
3517
|
+
|
|
3518
|
+
// Original reservation data must be preserved
|
|
3519
|
+
expect(task.data.interaction).toBeDefined();
|
|
3520
|
+
expect(task.data.interaction.callProcessingDetails.campaignId).toBe('campaign-789');
|
|
3521
|
+
});
|
|
3522
|
+
|
|
3523
|
+
it('should emit TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED when CampaignPreviewRemoveFailed is received', () => {
|
|
3524
|
+
const campaignInteractionId = 'campaign-interaction-123';
|
|
3525
|
+
|
|
3526
|
+
const reservationPayload = {
|
|
3527
|
+
data: {
|
|
3528
|
+
type: CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION,
|
|
3529
|
+
interactionId: campaignInteractionId,
|
|
3530
|
+
agentId: taskDataMock.agentId,
|
|
3531
|
+
orgId: taskDataMock.orgId,
|
|
3532
|
+
trackingId: 'campaign-tracking-456',
|
|
3533
|
+
interaction: {
|
|
3534
|
+
mediaType: 'telephony',
|
|
3535
|
+
callProcessingDetails: {
|
|
3536
|
+
campaignId: 'campaign-789',
|
|
3537
|
+
},
|
|
3538
|
+
},
|
|
3539
|
+
},
|
|
3540
|
+
};
|
|
3541
|
+
|
|
3542
|
+
webSocketManagerMock.emit('message', JSON.stringify(reservationPayload));
|
|
3543
|
+
|
|
3544
|
+
const task = taskManager['taskCollection'][campaignInteractionId];
|
|
3545
|
+
expect(task).toBeDefined();
|
|
3546
|
+
|
|
3547
|
+
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
3548
|
+
|
|
3549
|
+
const failPayload = {
|
|
3550
|
+
data: {
|
|
3551
|
+
type: CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED,
|
|
3552
|
+
interactionId: campaignInteractionId,
|
|
3553
|
+
campaignId: 'campaign-789',
|
|
3554
|
+
reason: 'INTERNAL_ERROR',
|
|
3555
|
+
},
|
|
3556
|
+
};
|
|
3557
|
+
|
|
3558
|
+
webSocketManagerMock.emit('message', JSON.stringify(failPayload));
|
|
3559
|
+
|
|
3560
|
+
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
3561
|
+
TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED,
|
|
3562
|
+
expect.objectContaining({
|
|
3563
|
+
data: expect.objectContaining({interactionId: campaignInteractionId}),
|
|
3564
|
+
})
|
|
3565
|
+
);
|
|
3566
|
+
expect(taskManager['taskCollection'][campaignInteractionId]).toBeDefined();
|
|
3567
|
+
|
|
3568
|
+
// Failure payload (reason) should be merged into task.data
|
|
3569
|
+
expect(task.data.reason).toBe('INTERNAL_ERROR');
|
|
3570
|
+
|
|
3571
|
+
// Original reservation data must be preserved
|
|
3572
|
+
expect(task.data.interaction).toBeDefined();
|
|
3573
|
+
expect(task.data.interaction.callProcessingDetails.campaignId).toBe('campaign-789');
|
|
3574
|
+
});
|
|
3575
|
+
|
|
3576
|
+
it('should not emit campaign preview failure events when task does not exist', () => {
|
|
3577
|
+
const nonExistentId = 'non-existent-interaction';
|
|
3578
|
+
|
|
3579
|
+
const failPayload = {
|
|
3580
|
+
data: {
|
|
3581
|
+
type: CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED,
|
|
3582
|
+
interactionId: nonExistentId,
|
|
3583
|
+
campaignId: 'campaign-789',
|
|
3584
|
+
reason: 'INTERNAL_ERROR',
|
|
3585
|
+
},
|
|
3586
|
+
};
|
|
3587
|
+
|
|
3588
|
+
// Should not throw when task is not found
|
|
3589
|
+
expect(() => {
|
|
3590
|
+
webSocketManagerMock.emit('message', JSON.stringify(failPayload));
|
|
3591
|
+
}).not.toThrow();
|
|
3592
|
+
|
|
3593
|
+
expect(taskManager['taskCollection'][nonExistentId]).toBeUndefined();
|
|
3594
|
+
});
|
|
3350
3595
|
});
|
|
3351
3596
|
});
|
|
@@ -239,5 +239,195 @@ describe('AQM routing dialer', () => {
|
|
|
239
239
|
).rejects.toThrow('Request Timeout');
|
|
240
240
|
});
|
|
241
241
|
});
|
|
242
|
+
|
|
243
|
+
describe('skipPreviewContact', () => {
|
|
244
|
+
it('should construct the correct URL with campaignId and interactionId', () => {
|
|
245
|
+
const dialer = aqmDialer(fakeAqm as any);
|
|
246
|
+
const config = dialer.skipPreviewContact({data: previewPayload}) as any;
|
|
247
|
+
|
|
248
|
+
expect(config.url).toBe(
|
|
249
|
+
`/v1/dialer/campaign/${previewPayload.campaignId}/preview-task/${previewPayload.interactionId}/skip`
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should URL-encode campaignId when it contains reserved characters', () => {
|
|
254
|
+
const dialer = aqmDialer(fakeAqm as any);
|
|
255
|
+
const payloadWithSpecialChars = {
|
|
256
|
+
interactionId: 'interaction-456',
|
|
257
|
+
campaignId: 'My Campaign/Test #1',
|
|
258
|
+
};
|
|
259
|
+
const config = dialer.skipPreviewContact({data: payloadWithSpecialChars}) as any;
|
|
260
|
+
|
|
261
|
+
expect(config.url).toBe(
|
|
262
|
+
`/v1/dialer/campaign/${encodeURIComponent(
|
|
263
|
+
payloadWithSpecialChars.campaignId
|
|
264
|
+
)}/preview-task/${payloadWithSpecialChars.interactionId}/skip`
|
|
265
|
+
);
|
|
266
|
+
expect(config.url).toContain('My%20Campaign%2FTest%20%231');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should call the skipPreviewContact api', () => {
|
|
270
|
+
const fakeAqm = {
|
|
271
|
+
req: () =>
|
|
272
|
+
jest.fn().mockResolvedValue(() => {
|
|
273
|
+
Promise.resolve({data: 'skip preview success'});
|
|
274
|
+
}),
|
|
275
|
+
evt: jest.fn(),
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const dialer = aqmDialer(fakeAqm as any);
|
|
279
|
+
|
|
280
|
+
dialer
|
|
281
|
+
.skipPreviewContact({data: previewPayload})
|
|
282
|
+
.then((response) => {
|
|
283
|
+
expect(response.data).toBe('skip preview success');
|
|
284
|
+
})
|
|
285
|
+
.catch(() => {
|
|
286
|
+
expect(true).toBe(true);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
expect(dialer.skipPreviewContact).toHaveBeenCalled();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should handle network errors', () => {
|
|
293
|
+
const fakeAqm = {
|
|
294
|
+
req: () => jest.fn().mockRejectedValue(new Error('Network Error')),
|
|
295
|
+
evt: jest.fn(),
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const dialer = aqmDialer(fakeAqm as any);
|
|
299
|
+
|
|
300
|
+
return expect(
|
|
301
|
+
dialer.skipPreviewContact({
|
|
302
|
+
data: previewPayload,
|
|
303
|
+
})
|
|
304
|
+
).rejects.toThrow('Network Error');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should handle server errors', () => {
|
|
308
|
+
const fakeAqm = {
|
|
309
|
+
req: () => jest.fn().mockRejectedValue(new Error('Server Error')),
|
|
310
|
+
evt: jest.fn(),
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const dialer = aqmDialer(fakeAqm as any);
|
|
314
|
+
|
|
315
|
+
return expect(
|
|
316
|
+
dialer.skipPreviewContact({
|
|
317
|
+
data: previewPayload,
|
|
318
|
+
})
|
|
319
|
+
).rejects.toThrow('Server Error');
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('should handle timeout scenarios', () => {
|
|
323
|
+
const fakeAqm = {
|
|
324
|
+
req: () => jest.fn().mockRejectedValue(new Error('Request Timeout')),
|
|
325
|
+
evt: jest.fn(),
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const dialer = aqmDialer(fakeAqm as any);
|
|
329
|
+
|
|
330
|
+
return expect(
|
|
331
|
+
dialer.skipPreviewContact({
|
|
332
|
+
data: previewPayload,
|
|
333
|
+
})
|
|
334
|
+
).rejects.toThrow('Request Timeout');
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
describe('removePreviewContact', () => {
|
|
339
|
+
it('should construct the correct URL with campaignId and interactionId', () => {
|
|
340
|
+
const dialer = aqmDialer(fakeAqm as any);
|
|
341
|
+
const config = dialer.removePreviewContact({data: previewPayload}) as any;
|
|
342
|
+
|
|
343
|
+
expect(config.url).toBe(
|
|
344
|
+
`/v1/dialer/campaign/${previewPayload.campaignId}/preview-task/${previewPayload.interactionId}/remove`
|
|
345
|
+
);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('should URL-encode campaignId when it contains reserved characters', () => {
|
|
349
|
+
const dialer = aqmDialer(fakeAqm as any);
|
|
350
|
+
const payloadWithSpecialChars = {
|
|
351
|
+
interactionId: 'interaction-456',
|
|
352
|
+
campaignId: 'My Campaign/Test #1',
|
|
353
|
+
};
|
|
354
|
+
const config = dialer.removePreviewContact({data: payloadWithSpecialChars}) as any;
|
|
355
|
+
|
|
356
|
+
expect(config.url).toBe(
|
|
357
|
+
`/v1/dialer/campaign/${encodeURIComponent(
|
|
358
|
+
payloadWithSpecialChars.campaignId
|
|
359
|
+
)}/preview-task/${payloadWithSpecialChars.interactionId}/remove`
|
|
360
|
+
);
|
|
361
|
+
expect(config.url).toContain('My%20Campaign%2FTest%20%231');
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should call the removePreviewContact api', () => {
|
|
365
|
+
const fakeAqm = {
|
|
366
|
+
req: () =>
|
|
367
|
+
jest.fn().mockResolvedValue(() => {
|
|
368
|
+
Promise.resolve({data: 'remove preview success'});
|
|
369
|
+
}),
|
|
370
|
+
evt: jest.fn(),
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const dialer = aqmDialer(fakeAqm as any);
|
|
374
|
+
|
|
375
|
+
dialer
|
|
376
|
+
.removePreviewContact({data: previewPayload})
|
|
377
|
+
.then((response) => {
|
|
378
|
+
expect(response.data).toBe('remove preview success');
|
|
379
|
+
})
|
|
380
|
+
.catch(() => {
|
|
381
|
+
expect(true).toBe(true);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
expect(dialer.removePreviewContact).toHaveBeenCalled();
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('should handle network errors', () => {
|
|
388
|
+
const fakeAqm = {
|
|
389
|
+
req: () => jest.fn().mockRejectedValue(new Error('Network Error')),
|
|
390
|
+
evt: jest.fn(),
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const dialer = aqmDialer(fakeAqm as any);
|
|
394
|
+
|
|
395
|
+
return expect(
|
|
396
|
+
dialer.removePreviewContact({
|
|
397
|
+
data: previewPayload,
|
|
398
|
+
})
|
|
399
|
+
).rejects.toThrow('Network Error');
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('should handle server errors', () => {
|
|
403
|
+
const fakeAqm = {
|
|
404
|
+
req: () => jest.fn().mockRejectedValue(new Error('Server Error')),
|
|
405
|
+
evt: jest.fn(),
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const dialer = aqmDialer(fakeAqm as any);
|
|
409
|
+
|
|
410
|
+
return expect(
|
|
411
|
+
dialer.removePreviewContact({
|
|
412
|
+
data: previewPayload,
|
|
413
|
+
})
|
|
414
|
+
).rejects.toThrow('Server Error');
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it('should handle timeout scenarios', () => {
|
|
418
|
+
const fakeAqm = {
|
|
419
|
+
req: () => jest.fn().mockRejectedValue(new Error('Request Timeout')),
|
|
420
|
+
evt: jest.fn(),
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const dialer = aqmDialer(fakeAqm as any);
|
|
424
|
+
|
|
425
|
+
return expect(
|
|
426
|
+
dialer.removePreviewContact({
|
|
427
|
+
data: previewPayload,
|
|
428
|
+
})
|
|
429
|
+
).rejects.toThrow('Request Timeout');
|
|
430
|
+
});
|
|
431
|
+
});
|
|
242
432
|
});
|
|
243
433
|
});
|
|
@@ -691,6 +691,27 @@ describe('Task', () => {
|
|
|
691
691
|
);
|
|
692
692
|
});
|
|
693
693
|
|
|
694
|
+
it('should hold using mediaResourceId from interaction.media after recording event wipes top-level mediaResourceId', async () => {
|
|
695
|
+
// Set a DIFFERENT top-level mediaResourceId so we can distinguish the two sources
|
|
696
|
+
const staleTopLevelId = 'stale-top-level-media-resource-id';
|
|
697
|
+
const correctMediaId = task.data.interaction.media[task.data.interaction.mainInteractionId].mediaResourceId;
|
|
698
|
+
task.data.mediaResourceId = staleTopLevelId;
|
|
699
|
+
|
|
700
|
+
// Simulate recording event wiping top-level mediaResourceId (as reconcileData does)
|
|
701
|
+
delete task.data.mediaResourceId;
|
|
702
|
+
|
|
703
|
+
const expectedResponse: TaskResponse = {data: {interactionId: taskId}} as AgentContact;
|
|
704
|
+
contactMock.hold.mockResolvedValue(expectedResponse);
|
|
705
|
+
|
|
706
|
+
await task.hold();
|
|
707
|
+
|
|
708
|
+
// hold() should read from interaction.media, not the (now deleted) top-level field
|
|
709
|
+
expect(contactMock.hold).toHaveBeenCalledWith({
|
|
710
|
+
interactionId: taskId,
|
|
711
|
+
data: {mediaResourceId: correctMediaId},
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
|
|
694
715
|
it('should handle errors in hold method', async () => {
|
|
695
716
|
const error = {details: (global as any).makeFailure('Hold Failed')};
|
|
696
717
|
contactMock.hold.mockImplementation(() => {
|