@webex/contact-center 3.12.0-next.2 → 3.12.0-next.21
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 +132 -0
- 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/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/task/TaskManager.js +90 -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 +44 -0
- package/dist/services/task/types.js.map +1 -1
- package/dist/types/cc.d.ts +44 -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 +8 -0
- package/dist/types/services/core/Err.d.ts +4 -0
- 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 +53 -1
- package/dist/webex.js +1 -1
- package/package.json +8 -8
- package/src/cc.ts +160 -0
- 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/constants.ts +1 -1
- package/src/services/config/types.ts +4 -0
- package/src/services/core/Err.ts +2 -0
- package/src/services/task/TaskManager.ts +102 -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 +56 -0
- package/test/unit/spec/cc.ts +130 -0
- package/test/unit/spec/services/config/index.ts +1 -1
- package/test/unit/spec/services/task/TaskManager.ts +238 -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
package/test/unit/spec/cc.ts
CHANGED
|
@@ -139,6 +139,8 @@ describe('webex.cc', () => {
|
|
|
139
139
|
dialer: {
|
|
140
140
|
startOutdial: jest.fn(),
|
|
141
141
|
acceptPreviewContact: jest.fn(),
|
|
142
|
+
skipPreviewContact: jest.fn(),
|
|
143
|
+
removePreviewContact: jest.fn(),
|
|
142
144
|
},
|
|
143
145
|
apiAIAssistant: {
|
|
144
146
|
sendEvent: jest.fn(),
|
|
@@ -2320,4 +2322,132 @@ describe('webex.cc', () => {
|
|
|
2320
2322
|
expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'acceptPreviewContact', CC_FILE);
|
|
2321
2323
|
});
|
|
2322
2324
|
});
|
|
2325
|
+
|
|
2326
|
+
describe('skipPreviewContact', () => {
|
|
2327
|
+
const previewPayload = {
|
|
2328
|
+
interactionId: 'interaction-123',
|
|
2329
|
+
campaignId: 'campaign-456',
|
|
2330
|
+
};
|
|
2331
|
+
|
|
2332
|
+
it('should skip preview contact successfully', async () => {
|
|
2333
|
+
const mockResponse = {trackingId: 'track-123'} as AgentContact;
|
|
2334
|
+
|
|
2335
|
+
const skipPreviewContactMock = jest
|
|
2336
|
+
.spyOn(webex.cc.services.dialer, 'skipPreviewContact')
|
|
2337
|
+
.mockResolvedValue(mockResponse);
|
|
2338
|
+
|
|
2339
|
+
const result = await webex.cc.skipPreviewContact(previewPayload);
|
|
2340
|
+
|
|
2341
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Skipping campaign preview contact', {
|
|
2342
|
+
module: CC_FILE,
|
|
2343
|
+
method: 'skipPreviewContact',
|
|
2344
|
+
});
|
|
2345
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
2346
|
+
'Campaign preview contact skipped successfully',
|
|
2347
|
+
{
|
|
2348
|
+
module: CC_FILE,
|
|
2349
|
+
method: 'skipPreviewContact',
|
|
2350
|
+
trackingId: 'track-123',
|
|
2351
|
+
interactionId: previewPayload.interactionId,
|
|
2352
|
+
}
|
|
2353
|
+
);
|
|
2354
|
+
|
|
2355
|
+
expect(skipPreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
|
|
2356
|
+
expect(result).toEqual(mockResponse);
|
|
2357
|
+
});
|
|
2358
|
+
|
|
2359
|
+
it('should handle error during skipPreviewContact', async () => {
|
|
2360
|
+
getErrorDetailsSpy.mockRestore();
|
|
2361
|
+
getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
|
|
2362
|
+
|
|
2363
|
+
const error = {
|
|
2364
|
+
details: {
|
|
2365
|
+
trackingId: '1234',
|
|
2366
|
+
data: {
|
|
2367
|
+
reason: 'Error while performing skipPreviewContact',
|
|
2368
|
+
},
|
|
2369
|
+
},
|
|
2370
|
+
};
|
|
2371
|
+
|
|
2372
|
+
jest.spyOn(webex.cc.services.dialer, 'skipPreviewContact').mockRejectedValue(error);
|
|
2373
|
+
|
|
2374
|
+
await expect(webex.cc.skipPreviewContact(previewPayload)).rejects.toThrow(
|
|
2375
|
+
error.details.data.reason
|
|
2376
|
+
);
|
|
2377
|
+
|
|
2378
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Skipping campaign preview contact', {
|
|
2379
|
+
module: CC_FILE,
|
|
2380
|
+
method: 'skipPreviewContact',
|
|
2381
|
+
});
|
|
2382
|
+
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
2383
|
+
`skipPreviewContact failed with reason: ${error.details.data.reason}`,
|
|
2384
|
+
{module: CC_FILE, method: 'skipPreviewContact', trackingId: error.details.trackingId}
|
|
2385
|
+
);
|
|
2386
|
+
expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'skipPreviewContact', CC_FILE);
|
|
2387
|
+
});
|
|
2388
|
+
});
|
|
2389
|
+
|
|
2390
|
+
describe('removePreviewContact', () => {
|
|
2391
|
+
const previewPayload = {
|
|
2392
|
+
interactionId: 'interaction-123',
|
|
2393
|
+
campaignId: 'campaign-456',
|
|
2394
|
+
};
|
|
2395
|
+
|
|
2396
|
+
it('should remove preview contact successfully', async () => {
|
|
2397
|
+
const mockResponse = {trackingId: 'track-123'} as AgentContact;
|
|
2398
|
+
|
|
2399
|
+
const removePreviewContactMock = jest
|
|
2400
|
+
.spyOn(webex.cc.services.dialer, 'removePreviewContact')
|
|
2401
|
+
.mockResolvedValue(mockResponse);
|
|
2402
|
+
|
|
2403
|
+
const result = await webex.cc.removePreviewContact(previewPayload);
|
|
2404
|
+
|
|
2405
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Removing campaign preview contact', {
|
|
2406
|
+
module: CC_FILE,
|
|
2407
|
+
method: 'removePreviewContact',
|
|
2408
|
+
});
|
|
2409
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
2410
|
+
'Campaign preview contact removed successfully',
|
|
2411
|
+
{
|
|
2412
|
+
module: CC_FILE,
|
|
2413
|
+
method: 'removePreviewContact',
|
|
2414
|
+
trackingId: 'track-123',
|
|
2415
|
+
interactionId: previewPayload.interactionId,
|
|
2416
|
+
}
|
|
2417
|
+
);
|
|
2418
|
+
|
|
2419
|
+
expect(removePreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
|
|
2420
|
+
expect(result).toEqual(mockResponse);
|
|
2421
|
+
});
|
|
2422
|
+
|
|
2423
|
+
it('should handle error during removePreviewContact', async () => {
|
|
2424
|
+
getErrorDetailsSpy.mockRestore();
|
|
2425
|
+
getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
|
|
2426
|
+
|
|
2427
|
+
const error = {
|
|
2428
|
+
details: {
|
|
2429
|
+
trackingId: '1234',
|
|
2430
|
+
data: {
|
|
2431
|
+
reason: 'Error while performing removePreviewContact',
|
|
2432
|
+
},
|
|
2433
|
+
},
|
|
2434
|
+
};
|
|
2435
|
+
|
|
2436
|
+
jest.spyOn(webex.cc.services.dialer, 'removePreviewContact').mockRejectedValue(error);
|
|
2437
|
+
|
|
2438
|
+
await expect(webex.cc.removePreviewContact(previewPayload)).rejects.toThrow(
|
|
2439
|
+
error.details.data.reason
|
|
2440
|
+
);
|
|
2441
|
+
|
|
2442
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Removing campaign preview contact', {
|
|
2443
|
+
module: CC_FILE,
|
|
2444
|
+
method: 'removePreviewContact',
|
|
2445
|
+
});
|
|
2446
|
+
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
2447
|
+
`removePreviewContact failed with reason: ${error.details.data.reason}`,
|
|
2448
|
+
{module: CC_FILE, method: 'removePreviewContact', trackingId: error.details.trackingId}
|
|
2449
|
+
);
|
|
2450
|
+
expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'removePreviewContact', CC_FILE);
|
|
2451
|
+
});
|
|
2452
|
+
});
|
|
2323
2453
|
});
|
|
@@ -263,7 +263,7 @@ describe('AgentConfigService', () => {
|
|
|
263
263
|
|
|
264
264
|
expect(mockWebexRequest.request).toHaveBeenCalledWith({
|
|
265
265
|
service: mockWccAPIURL,
|
|
266
|
-
resource: `organization/${mockOrgId}/v2/auxiliary-code?page=${page}&pageSize=${pageSize}&filter=id=in=(${filter})&attributes=${attributes}`,
|
|
266
|
+
resource: `organization/${mockOrgId}/v2/auxiliary-code?page=${page}&pageSize=${pageSize}&filter=id=in=(${filter})&attributes=${attributes}&desktopProfileFilter=true`,
|
|
267
267
|
method: 'GET',
|
|
268
268
|
});
|
|
269
269
|
expect(result).toEqual(mockResponse.body);
|
|
@@ -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(),
|
|
@@ -1146,7 +1151,7 @@ describe('TaskManager', () => {
|
|
|
1146
1151
|
taskManager.setAgentId(agentId);
|
|
1147
1152
|
});
|
|
1148
1153
|
|
|
1149
|
-
it('should set wrapUpRequired to true when agent is in agentsPendingWrapUp array', () => {
|
|
1154
|
+
it('should set wrapUpRequired to true when agent is in agentsPendingWrapUp array for non-campaign tasks', () => {
|
|
1150
1155
|
const task = taskManager.getTask(taskId);
|
|
1151
1156
|
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1152
1157
|
task.data = {
|
|
@@ -1306,7 +1311,7 @@ describe('TaskManager', () => {
|
|
|
1306
1311
|
);
|
|
1307
1312
|
});
|
|
1308
1313
|
|
|
1309
|
-
it('should set wrapUpRequired
|
|
1314
|
+
it('should set wrapUpRequired to true when agent is the only one in agentsPendingWrapUp for non-campaign tasks', () => {
|
|
1310
1315
|
const task = taskManager.getTask(taskId);
|
|
1311
1316
|
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1312
1317
|
task.data = {
|
|
@@ -1338,7 +1343,7 @@ describe('TaskManager', () => {
|
|
|
1338
1343
|
);
|
|
1339
1344
|
});
|
|
1340
1345
|
|
|
1341
|
-
it('should
|
|
1346
|
+
it('should set wrapUpRequired to true for different interaction states when agent is in agentsPendingWrapUp for non-campaign tasks', () => {
|
|
1342
1347
|
const task = taskManager.getTask(taskId);
|
|
1343
1348
|
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1344
1349
|
task.data = {
|
|
@@ -1368,7 +1373,6 @@ describe('TaskManager', () => {
|
|
|
1368
1373
|
|
|
1369
1374
|
webSocketManagerMock.emit('message', JSON.stringify(payloadConnected));
|
|
1370
1375
|
|
|
1371
|
-
// First call should set wrapUpRequired to true
|
|
1372
1376
|
expect(task.updateTaskData).toHaveBeenNthCalledWith(
|
|
1373
1377
|
1,
|
|
1374
1378
|
expect.objectContaining({
|
|
@@ -1391,7 +1395,6 @@ describe('TaskManager', () => {
|
|
|
1391
1395
|
|
|
1392
1396
|
webSocketManagerMock.emit('message', JSON.stringify(payloadHeld));
|
|
1393
1397
|
|
|
1394
|
-
// Second call should also set wrapUpRequired to true
|
|
1395
1398
|
expect(task.updateTaskData).toHaveBeenNthCalledWith(
|
|
1396
1399
|
2,
|
|
1397
1400
|
expect.objectContaining({
|
|
@@ -1399,6 +1402,43 @@ describe('TaskManager', () => {
|
|
|
1399
1402
|
})
|
|
1400
1403
|
);
|
|
1401
1404
|
});
|
|
1405
|
+
|
|
1406
|
+
it('should set wrapUpRequired to false for campaign preview tasks even when agent is in agentsPendingWrapUp', () => {
|
|
1407
|
+
const task = taskManager.getTask(taskId);
|
|
1408
|
+
// Set up task as a campaign preview task via outboundType
|
|
1409
|
+
task.data.interaction = {
|
|
1410
|
+
...task.data.interaction,
|
|
1411
|
+
outboundType: 'STANDARD_PREVIEW_CAMPAIGN',
|
|
1412
|
+
};
|
|
1413
|
+
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1414
|
+
task.data = {
|
|
1415
|
+
...task.data,
|
|
1416
|
+
...newData,
|
|
1417
|
+
};
|
|
1418
|
+
return task;
|
|
1419
|
+
});
|
|
1420
|
+
task.unregisterWebCallListeners = jest.fn();
|
|
1421
|
+
|
|
1422
|
+
const payload = {
|
|
1423
|
+
data: {
|
|
1424
|
+
type: CC_EVENTS.CONTACT_ENDED,
|
|
1425
|
+
interactionId: taskId,
|
|
1426
|
+
interaction: {
|
|
1427
|
+
state: 'connected',
|
|
1428
|
+
mediaType: 'telephony',
|
|
1429
|
+
},
|
|
1430
|
+
agentsPendingWrapUp: [agentId],
|
|
1431
|
+
},
|
|
1432
|
+
};
|
|
1433
|
+
|
|
1434
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1435
|
+
|
|
1436
|
+
expect(task.updateTaskData).toHaveBeenCalledWith(
|
|
1437
|
+
expect.objectContaining({
|
|
1438
|
+
wrapUpRequired: false,
|
|
1439
|
+
})
|
|
1440
|
+
);
|
|
1441
|
+
});
|
|
1402
1442
|
});
|
|
1403
1443
|
|
|
1404
1444
|
it('should remove OUTDIAL task from taskCollection on AGENT_CONTACT_ASSIGN_FAILED when NOT terminated (user-declined)', () => {
|
|
@@ -3294,7 +3334,7 @@ describe('TaskManager', () => {
|
|
|
3294
3334
|
);
|
|
3295
3335
|
});
|
|
3296
3336
|
|
|
3297
|
-
it('should
|
|
3337
|
+
it('should emit TASK_CAMPAIGN_CONTACT_UPDATED and NOT remove task when CampaignContactUpdated is received', () => {
|
|
3298
3338
|
const campaignInteractionId = 'campaign-interaction-123';
|
|
3299
3339
|
|
|
3300
3340
|
// First create a campaign preview task
|
|
@@ -3344,8 +3384,199 @@ describe('TaskManager', () => {
|
|
|
3344
3384
|
// Task should still exist in collection (not removed — non-terminal event)
|
|
3345
3385
|
expect(taskManager['taskCollection'][campaignInteractionId]).toBeDefined();
|
|
3346
3386
|
|
|
3387
|
+
// TASK_CAMPAIGN_CONTACT_UPDATED should have been emitted
|
|
3388
|
+
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
3389
|
+
TASK_EVENTS.TASK_CAMPAIGN_CONTACT_UPDATED,
|
|
3390
|
+
expect.objectContaining({
|
|
3391
|
+
data: expect.objectContaining({
|
|
3392
|
+
interactionId: campaignInteractionId,
|
|
3393
|
+
}),
|
|
3394
|
+
})
|
|
3395
|
+
);
|
|
3396
|
+
|
|
3347
3397
|
// TASK_END should NOT have been emitted
|
|
3348
3398
|
expect(taskEmitSpy).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_END, expect.anything());
|
|
3349
3399
|
});
|
|
3400
|
+
|
|
3401
|
+
it('should emit TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED when CampaignPreviewAcceptFailed is received', () => {
|
|
3402
|
+
const campaignInteractionId = 'campaign-interaction-123';
|
|
3403
|
+
|
|
3404
|
+
// First create a campaign preview task
|
|
3405
|
+
const reservationPayload = {
|
|
3406
|
+
data: {
|
|
3407
|
+
type: CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION,
|
|
3408
|
+
interactionId: campaignInteractionId,
|
|
3409
|
+
agentId: taskDataMock.agentId,
|
|
3410
|
+
orgId: taskDataMock.orgId,
|
|
3411
|
+
trackingId: 'campaign-tracking-456',
|
|
3412
|
+
interaction: {
|
|
3413
|
+
mediaType: 'telephony',
|
|
3414
|
+
callProcessingDetails: {
|
|
3415
|
+
campaignId: 'campaign-789',
|
|
3416
|
+
},
|
|
3417
|
+
},
|
|
3418
|
+
},
|
|
3419
|
+
};
|
|
3420
|
+
|
|
3421
|
+
webSocketManagerMock.emit('message', JSON.stringify(reservationPayload));
|
|
3422
|
+
|
|
3423
|
+
const task = taskManager['taskCollection'][campaignInteractionId];
|
|
3424
|
+
expect(task).toBeDefined();
|
|
3425
|
+
|
|
3426
|
+
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
3427
|
+
|
|
3428
|
+
const failPayload = {
|
|
3429
|
+
data: {
|
|
3430
|
+
type: CC_EVENTS.CAMPAIGN_PREVIEW_ACCEPT_FAILED,
|
|
3431
|
+
interactionId: campaignInteractionId,
|
|
3432
|
+
campaignId: 'campaign-789',
|
|
3433
|
+
reason: 'INTERNAL_ERROR',
|
|
3434
|
+
},
|
|
3435
|
+
};
|
|
3436
|
+
|
|
3437
|
+
webSocketManagerMock.emit('message', JSON.stringify(failPayload));
|
|
3438
|
+
|
|
3439
|
+
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
3440
|
+
TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED,
|
|
3441
|
+
expect.objectContaining({
|
|
3442
|
+
data: expect.objectContaining({interactionId: campaignInteractionId}),
|
|
3443
|
+
})
|
|
3444
|
+
);
|
|
3445
|
+
// Task should still exist (failure is non-terminal)
|
|
3446
|
+
expect(taskManager['taskCollection'][campaignInteractionId]).toBeDefined();
|
|
3447
|
+
|
|
3448
|
+
// Failure payload (reason, campaignId) should be merged into task.data
|
|
3449
|
+
expect(task.data.reason).toBe('INTERNAL_ERROR');
|
|
3450
|
+
|
|
3451
|
+
// Original reservation data must be preserved
|
|
3452
|
+
expect(task.data.interaction).toBeDefined();
|
|
3453
|
+
expect(task.data.interaction.callProcessingDetails.campaignId).toBe('campaign-789');
|
|
3454
|
+
});
|
|
3455
|
+
|
|
3456
|
+
it('should emit TASK_CAMPAIGN_PREVIEW_SKIP_FAILED when CampaignPreviewSkipFailed is received', () => {
|
|
3457
|
+
const campaignInteractionId = 'campaign-interaction-123';
|
|
3458
|
+
|
|
3459
|
+
const reservationPayload = {
|
|
3460
|
+
data: {
|
|
3461
|
+
type: CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION,
|
|
3462
|
+
interactionId: campaignInteractionId,
|
|
3463
|
+
agentId: taskDataMock.agentId,
|
|
3464
|
+
orgId: taskDataMock.orgId,
|
|
3465
|
+
trackingId: 'campaign-tracking-456',
|
|
3466
|
+
interaction: {
|
|
3467
|
+
mediaType: 'telephony',
|
|
3468
|
+
callProcessingDetails: {
|
|
3469
|
+
campaignId: 'campaign-789',
|
|
3470
|
+
},
|
|
3471
|
+
},
|
|
3472
|
+
},
|
|
3473
|
+
};
|
|
3474
|
+
|
|
3475
|
+
webSocketManagerMock.emit('message', JSON.stringify(reservationPayload));
|
|
3476
|
+
|
|
3477
|
+
const task = taskManager['taskCollection'][campaignInteractionId];
|
|
3478
|
+
expect(task).toBeDefined();
|
|
3479
|
+
|
|
3480
|
+
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
3481
|
+
|
|
3482
|
+
const failPayload = {
|
|
3483
|
+
data: {
|
|
3484
|
+
type: CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED,
|
|
3485
|
+
interactionId: campaignInteractionId,
|
|
3486
|
+
campaignId: 'campaign-789',
|
|
3487
|
+
reason: 'INTERNAL_ERROR',
|
|
3488
|
+
},
|
|
3489
|
+
};
|
|
3490
|
+
|
|
3491
|
+
webSocketManagerMock.emit('message', JSON.stringify(failPayload));
|
|
3492
|
+
|
|
3493
|
+
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
3494
|
+
TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_SKIP_FAILED,
|
|
3495
|
+
expect.objectContaining({
|
|
3496
|
+
data: expect.objectContaining({interactionId: campaignInteractionId}),
|
|
3497
|
+
})
|
|
3498
|
+
);
|
|
3499
|
+
expect(taskManager['taskCollection'][campaignInteractionId]).toBeDefined();
|
|
3500
|
+
|
|
3501
|
+
// Failure payload (reason) should be merged into task.data
|
|
3502
|
+
expect(task.data.reason).toBe('INTERNAL_ERROR');
|
|
3503
|
+
|
|
3504
|
+
// Original reservation data must be preserved
|
|
3505
|
+
expect(task.data.interaction).toBeDefined();
|
|
3506
|
+
expect(task.data.interaction.callProcessingDetails.campaignId).toBe('campaign-789');
|
|
3507
|
+
});
|
|
3508
|
+
|
|
3509
|
+
it('should emit TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED when CampaignPreviewRemoveFailed is received', () => {
|
|
3510
|
+
const campaignInteractionId = 'campaign-interaction-123';
|
|
3511
|
+
|
|
3512
|
+
const reservationPayload = {
|
|
3513
|
+
data: {
|
|
3514
|
+
type: CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION,
|
|
3515
|
+
interactionId: campaignInteractionId,
|
|
3516
|
+
agentId: taskDataMock.agentId,
|
|
3517
|
+
orgId: taskDataMock.orgId,
|
|
3518
|
+
trackingId: 'campaign-tracking-456',
|
|
3519
|
+
interaction: {
|
|
3520
|
+
mediaType: 'telephony',
|
|
3521
|
+
callProcessingDetails: {
|
|
3522
|
+
campaignId: 'campaign-789',
|
|
3523
|
+
},
|
|
3524
|
+
},
|
|
3525
|
+
},
|
|
3526
|
+
};
|
|
3527
|
+
|
|
3528
|
+
webSocketManagerMock.emit('message', JSON.stringify(reservationPayload));
|
|
3529
|
+
|
|
3530
|
+
const task = taskManager['taskCollection'][campaignInteractionId];
|
|
3531
|
+
expect(task).toBeDefined();
|
|
3532
|
+
|
|
3533
|
+
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
3534
|
+
|
|
3535
|
+
const failPayload = {
|
|
3536
|
+
data: {
|
|
3537
|
+
type: CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED,
|
|
3538
|
+
interactionId: campaignInteractionId,
|
|
3539
|
+
campaignId: 'campaign-789',
|
|
3540
|
+
reason: 'INTERNAL_ERROR',
|
|
3541
|
+
},
|
|
3542
|
+
};
|
|
3543
|
+
|
|
3544
|
+
webSocketManagerMock.emit('message', JSON.stringify(failPayload));
|
|
3545
|
+
|
|
3546
|
+
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
3547
|
+
TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED,
|
|
3548
|
+
expect.objectContaining({
|
|
3549
|
+
data: expect.objectContaining({interactionId: campaignInteractionId}),
|
|
3550
|
+
})
|
|
3551
|
+
);
|
|
3552
|
+
expect(taskManager['taskCollection'][campaignInteractionId]).toBeDefined();
|
|
3553
|
+
|
|
3554
|
+
// Failure payload (reason) should be merged into task.data
|
|
3555
|
+
expect(task.data.reason).toBe('INTERNAL_ERROR');
|
|
3556
|
+
|
|
3557
|
+
// Original reservation data must be preserved
|
|
3558
|
+
expect(task.data.interaction).toBeDefined();
|
|
3559
|
+
expect(task.data.interaction.callProcessingDetails.campaignId).toBe('campaign-789');
|
|
3560
|
+
});
|
|
3561
|
+
|
|
3562
|
+
it('should not emit campaign preview failure events when task does not exist', () => {
|
|
3563
|
+
const nonExistentId = 'non-existent-interaction';
|
|
3564
|
+
|
|
3565
|
+
const failPayload = {
|
|
3566
|
+
data: {
|
|
3567
|
+
type: CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED,
|
|
3568
|
+
interactionId: nonExistentId,
|
|
3569
|
+
campaignId: 'campaign-789',
|
|
3570
|
+
reason: 'INTERNAL_ERROR',
|
|
3571
|
+
},
|
|
3572
|
+
};
|
|
3573
|
+
|
|
3574
|
+
// Should not throw when task is not found
|
|
3575
|
+
expect(() => {
|
|
3576
|
+
webSocketManagerMock.emit('message', JSON.stringify(failPayload));
|
|
3577
|
+
}).not.toThrow();
|
|
3578
|
+
|
|
3579
|
+
expect(taskManager['taskCollection'][nonExistentId]).toBeUndefined();
|
|
3580
|
+
});
|
|
3350
3581
|
});
|
|
3351
3582
|
});
|
|
@@ -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(() => {
|