@webex/contact-center 3.12.0-next.9 → 3.12.0-task-refactor.2
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/AGENTS.md +438 -0
- package/ai-docs/README.md +131 -0
- package/ai-docs/RULES.md +455 -0
- package/ai-docs/patterns/event-driven-patterns.md +485 -0
- package/ai-docs/patterns/testing-patterns.md +480 -0
- package/ai-docs/patterns/typescript-patterns.md +365 -0
- package/ai-docs/templates/README.md +102 -0
- package/ai-docs/templates/documentation/create-agents-md.md +240 -0
- package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
- package/ai-docs/templates/existing-service/bug-fix.md +254 -0
- package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
- package/ai-docs/templates/new-method/00-master.md +80 -0
- package/ai-docs/templates/new-method/01-requirements.md +232 -0
- package/ai-docs/templates/new-method/02-implementation.md +295 -0
- package/ai-docs/templates/new-method/03-tests.md +201 -0
- package/ai-docs/templates/new-method/04-validation.md +141 -0
- package/ai-docs/templates/new-service/00-master.md +109 -0
- package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
- package/ai-docs/templates/new-service/02-code-generation.md +346 -0
- package/ai-docs/templates/new-service/03-integration.md +178 -0
- package/ai-docs/templates/new-service/04-test-generation.md +205 -0
- package/ai-docs/templates/new-service/05-validation.md +145 -0
- package/dist/cc.js +65 -123
- package/dist/cc.js.map +1 -1
- package/dist/constants.js +13 -2
- package/dist/constants.js.map +1 -1
- package/dist/index.js +13 -5
- package/dist/index.js.map +1 -1
- package/dist/metrics/behavioral-events.js +26 -13
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +7 -6
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/ApiAiAssistant.js +0 -3
- package/dist/services/ApiAiAssistant.js.map +1 -1
- package/dist/services/config/Util.js +2 -3
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/types.js +16 -14
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/constants.js +0 -1
- package/dist/services/constants.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/core/Utils.js +79 -55
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/core/aqm-reqs.js +17 -92
- package/dist/services/core/aqm-reqs.js.map +1 -1
- package/dist/services/core/websocket/WebSocketManager.js +5 -25
- package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
- package/dist/services/core/websocket/types.js.map +1 -1
- package/dist/services/index.js +1 -2
- package/dist/services/index.js.map +1 -1
- package/dist/services/task/Task.js +644 -0
- package/dist/services/task/Task.js.map +1 -0
- package/dist/services/task/TaskFactory.js +45 -0
- package/dist/services/task/TaskFactory.js.map +1 -0
- package/dist/services/task/TaskManager.js +570 -535
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +132 -28
- package/dist/services/task/TaskUtils.js.map +1 -1
- package/dist/services/task/constants.js +7 -6
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/dialer.js +0 -51
- package/dist/services/task/dialer.js.map +1 -1
- package/dist/services/task/digital/Digital.js +77 -0
- package/dist/services/task/digital/Digital.js.map +1 -0
- package/dist/services/task/state-machine/TaskStateMachine.js +634 -0
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
- package/dist/services/task/state-machine/actions.js +372 -0
- package/dist/services/task/state-machine/actions.js.map +1 -0
- package/dist/services/task/state-machine/constants.js +139 -0
- package/dist/services/task/state-machine/constants.js.map +1 -0
- package/dist/services/task/state-machine/guards.js +263 -0
- package/dist/services/task/state-machine/guards.js.map +1 -0
- package/dist/services/task/state-machine/index.js +53 -0
- package/dist/services/task/state-machine/index.js.map +1 -0
- package/dist/services/task/state-machine/types.js +54 -0
- package/dist/services/task/state-machine/types.js.map +1 -0
- package/dist/services/task/state-machine/uiControlsComputer.js +377 -0
- package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
- package/dist/services/task/taskDataNormalizer.js +99 -0
- package/dist/services/task/taskDataNormalizer.js.map +1 -0
- package/dist/services/task/types.js +157 -18
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +1031 -0
- package/dist/services/task/voice/Voice.js.map +1 -0
- package/dist/services/task/voice/WebRTC.js +149 -0
- package/dist/services/task/voice/WebRTC.js.map +1 -0
- package/dist/types/cc.d.ts +4 -33
- package/dist/types/constants.d.ts +13 -2
- package/dist/types/index.d.ts +11 -5
- package/dist/types/metrics/constants.d.ts +5 -3
- package/dist/types/services/ApiAiAssistant.d.ts +1 -1
- package/dist/types/services/config/types.d.ts +97 -25
- package/dist/types/services/core/Err.d.ts +0 -2
- package/dist/types/services/core/Utils.d.ts +25 -23
- package/dist/types/services/core/aqm-reqs.d.ts +0 -49
- package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -1
- package/dist/types/services/core/websocket/connection-service.d.ts +0 -1
- package/dist/types/services/core/websocket/types.d.ts +1 -1
- package/dist/types/services/index.d.ts +1 -1
- package/dist/types/services/task/Task.d.ts +146 -0
- package/dist/types/services/task/TaskFactory.d.ts +12 -0
- package/dist/types/services/task/TaskUtils.d.ts +39 -8
- package/dist/types/services/task/constants.d.ts +5 -4
- package/dist/types/services/task/dialer.d.ts +0 -15
- package/dist/types/services/task/digital/Digital.d.ts +22 -0
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +906 -0
- package/dist/types/services/task/state-machine/actions.d.ts +8 -0
- package/dist/types/services/task/state-machine/constants.d.ts +91 -0
- package/dist/types/services/task/state-machine/guards.d.ts +78 -0
- package/dist/types/services/task/state-machine/index.d.ts +13 -0
- package/dist/types/services/task/state-machine/types.d.ts +256 -0
- package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
- package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
- package/dist/types/services/task/types.d.ts +539 -88
- package/dist/types/services/task/voice/Voice.d.ts +183 -0
- package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
- package/dist/types/types.d.ts +68 -0
- package/dist/types/webex.d.ts +1 -0
- package/dist/types.js +70 -0
- package/dist/types.js.map +1 -1
- package/dist/webex.js +14 -2
- package/dist/webex.js.map +1 -1
- package/package.json +14 -11
- package/src/cc.ts +91 -177
- package/src/constants.ts +13 -2
- package/src/index.ts +14 -5
- package/src/metrics/ai-docs/AGENTS.md +348 -0
- package/src/metrics/ai-docs/ARCHITECTURE.md +336 -0
- package/src/metrics/behavioral-events.ts +28 -14
- package/src/metrics/constants.ts +7 -8
- package/src/services/ApiAiAssistant.ts +2 -4
- package/src/services/agent/ai-docs/AGENTS.md +238 -0
- package/src/services/agent/ai-docs/ARCHITECTURE.md +302 -0
- package/src/services/ai-docs/AGENTS.md +384 -0
- package/src/services/config/Util.ts +2 -3
- package/src/services/config/ai-docs/AGENTS.md +253 -0
- package/src/services/config/ai-docs/ARCHITECTURE.md +424 -0
- package/src/services/config/types.ts +108 -20
- package/src/services/constants.ts +0 -1
- package/src/services/core/Err.ts +0 -1
- package/src/services/core/Utils.ts +90 -67
- package/src/services/core/ai-docs/AGENTS.md +379 -0
- package/src/services/core/ai-docs/ARCHITECTURE.md +696 -0
- package/src/services/core/aqm-reqs.ts +22 -100
- package/src/services/core/websocket/WebSocketManager.ts +4 -23
- package/src/services/core/websocket/types.ts +1 -1
- package/src/services/index.ts +1 -2
- package/src/services/task/Task.ts +785 -0
- package/src/services/task/TaskFactory.ts +55 -0
- package/src/services/task/TaskManager.ts +579 -633
- package/src/services/task/TaskUtils.ts +175 -31
- package/src/services/task/ai-docs/AGENTS.md +448 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +573 -0
- package/src/services/task/constants.ts +5 -4
- package/src/services/task/dialer.ts +1 -56
- package/src/services/task/digital/Digital.ts +95 -0
- package/src/services/task/state-machine/TaskStateMachine.ts +793 -0
- package/src/services/task/state-machine/actions.ts +422 -0
- package/src/services/task/state-machine/ai-docs/AGENTS.md +495 -0
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1135 -0
- package/src/services/task/state-machine/constants.ts +150 -0
- package/src/services/task/state-machine/guards.ts +303 -0
- package/src/services/task/state-machine/index.ts +28 -0
- package/src/services/task/state-machine/types.ts +228 -0
- package/src/services/task/state-machine/uiControlsComputer.ts +542 -0
- package/src/services/task/taskDataNormalizer.ts +137 -0
- package/src/services/task/types.ts +641 -95
- package/src/services/task/voice/Voice.ts +1255 -0
- package/src/services/task/voice/WebRTC.ts +187 -0
- package/src/types.ts +88 -5
- package/src/utils/AGENTS.md +276 -0
- package/src/webex.js +2 -0
- package/test/unit/spec/cc.ts +59 -142
- package/test/unit/spec/logger-proxy.ts +70 -0
- package/test/unit/spec/services/ApiAiAssistant.ts +17 -0
- package/test/unit/spec/services/config/index.ts +26 -55
- package/test/unit/spec/services/core/Utils.ts +103 -52
- package/test/unit/spec/services/core/websocket/WebSocketManager.ts +48 -112
- package/test/unit/spec/services/core/websocket/connection-service.ts +5 -4
- package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
- package/test/unit/spec/services/task/Task.ts +416 -0
- package/test/unit/spec/services/task/TaskFactory.ts +62 -0
- package/test/unit/spec/services/task/TaskManager.ts +781 -1735
- package/test/unit/spec/services/task/TaskUtils.ts +125 -0
- package/test/unit/spec/services/task/dialer.ts +112 -198
- package/test/unit/spec/services/task/digital/Digital.ts +105 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +473 -0
- package/test/unit/spec/services/task/state-machine/guards.ts +288 -0
- package/test/unit/spec/services/task/state-machine/types.ts +18 -0
- package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +147 -0
- package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
- package/test/unit/spec/services/task/voice/Voice.ts +587 -0
- package/test/unit/spec/services/task/voice/WebRTC.ts +242 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
- package/dist/services/task/index.js +0 -1525
- package/dist/services/task/index.js.map +0 -1
- package/dist/types/services/task/index.d.ts +0 -650
- package/src/services/task/index.ts +0 -1801
- package/test/unit/spec/services/task/index.ts +0 -2184
|
@@ -8,6 +8,11 @@ import {
|
|
|
8
8
|
isDigitalOutbound,
|
|
9
9
|
hasAgentInitiatedOutdial,
|
|
10
10
|
shouldAutoAnswerTask,
|
|
11
|
+
getIsCustomerInCall,
|
|
12
|
+
getConferenceParticipantsCount,
|
|
13
|
+
isSecondaryAgent,
|
|
14
|
+
isSecondaryEpDnAgent,
|
|
15
|
+
getConsultMediaResourceId,
|
|
11
16
|
} from '../../../../../src/services/task/TaskUtils';
|
|
12
17
|
import {ITask, Interaction, TaskData} from '../../../../../src/services/task/types';
|
|
13
18
|
import {LoginOption} from '../../../../../src/types';
|
|
@@ -430,4 +435,124 @@ describe('TaskUtils', () => {
|
|
|
430
435
|
});
|
|
431
436
|
});
|
|
432
437
|
});
|
|
438
|
+
|
|
439
|
+
// Additional coverage for conference/consult utility functions
|
|
440
|
+
describe('Conference Utility Functions', () => {
|
|
441
|
+
const interactionId = 'interaction-123';
|
|
442
|
+
const createInteraction = (media: any = {}, participants: any = {}) =>
|
|
443
|
+
({interactionId, mainInteractionId: interactionId, media, participants}) as any;
|
|
444
|
+
|
|
445
|
+
it('getIsCustomerInCall returns true when customer active', () => {
|
|
446
|
+
const interaction = createInteraction(
|
|
447
|
+
{[interactionId]: {mType: 'mainCall', participants: ['c1']}},
|
|
448
|
+
{'c1': {pType: 'Customer', hasLeft: false}}
|
|
449
|
+
);
|
|
450
|
+
expect(getIsCustomerInCall(interaction, interactionId)).toBe(true);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('getConferenceParticipantsCount counts active agents only', () => {
|
|
454
|
+
const interaction = createInteraction(
|
|
455
|
+
{[interactionId]: {mType: 'mainCall', participants: ['a1', 'a2', 'c1']}},
|
|
456
|
+
{'a1': {pType: 'Agent', hasLeft: false}, 'a2': {pType: 'Agent', hasLeft: false}, 'c1': {pType: 'Customer', hasLeft: false}}
|
|
457
|
+
);
|
|
458
|
+
expect(getConferenceParticipantsCount(interaction, interactionId)).toBe(2);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('isSecondaryAgent returns true for consult with parentInteractionId', () => {
|
|
462
|
+
const interaction = createInteraction();
|
|
463
|
+
interaction.callProcessingDetails = {relationshipType: 'consult', parentInteractionId: 'parent-456'};
|
|
464
|
+
expect(isSecondaryAgent(interaction)).toBe(true);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('isSecondaryEpDnAgent returns true for telephony secondary agent', () => {
|
|
468
|
+
const interaction = createInteraction();
|
|
469
|
+
interaction.mediaType = 'telephony';
|
|
470
|
+
interaction.callProcessingDetails = {relationshipType: 'consult', parentInteractionId: 'parent-456'};
|
|
471
|
+
expect(isSecondaryEpDnAgent(interaction)).toBe(true);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
describe('getConsultMediaResourceId', () => {
|
|
476
|
+
it('returns consultMediaResourceId directly when provided', () => {
|
|
477
|
+
const result = getConsultMediaResourceId(undefined, 'consult-media-1', 'agent1');
|
|
478
|
+
expect(result).toBe('consult-media-1');
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it('returns undefined when no interaction and no consultMediaResourceId', () => {
|
|
482
|
+
const result = getConsultMediaResourceId(undefined, undefined, 'agent1');
|
|
483
|
+
expect(result).toBeUndefined();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('returns undefined when no agentId and no consultMediaResourceId', () => {
|
|
487
|
+
const interaction = {media: {}} as any;
|
|
488
|
+
const result = getConsultMediaResourceId(interaction, undefined, undefined);
|
|
489
|
+
expect(result).toBeUndefined();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('finds consult media leg by mType and agent participation', () => {
|
|
493
|
+
const interaction = {
|
|
494
|
+
media: {
|
|
495
|
+
'main-media': {
|
|
496
|
+
mediaResourceId: 'main-media',
|
|
497
|
+
mType: 'mainCall',
|
|
498
|
+
participants: ['agent1', 'customer1'],
|
|
499
|
+
},
|
|
500
|
+
'consult-media': {
|
|
501
|
+
mediaResourceId: 'consult-media',
|
|
502
|
+
mType: 'consult',
|
|
503
|
+
participants: ['agent1', 'agent2'],
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
} as any;
|
|
507
|
+
const result = getConsultMediaResourceId(interaction, undefined, 'agent1');
|
|
508
|
+
expect(result).toBe('consult-media');
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it('returns undefined when no consult media leg matches the agent', () => {
|
|
512
|
+
const interaction = {
|
|
513
|
+
media: {
|
|
514
|
+
'main-media': {
|
|
515
|
+
mediaResourceId: 'main-media',
|
|
516
|
+
mType: 'mainCall',
|
|
517
|
+
participants: ['agent1', 'customer1'],
|
|
518
|
+
},
|
|
519
|
+
'consult-media': {
|
|
520
|
+
mediaResourceId: 'consult-media',
|
|
521
|
+
mType: 'consult',
|
|
522
|
+
participants: ['agent2', 'agent3'],
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
} as any;
|
|
526
|
+
const result = getConsultMediaResourceId(interaction, undefined, 'agent1');
|
|
527
|
+
expect(result).toBeUndefined();
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it('returns undefined when media has no consult type entries', () => {
|
|
531
|
+
const interaction = {
|
|
532
|
+
media: {
|
|
533
|
+
'main-media': {
|
|
534
|
+
mediaResourceId: 'main-media',
|
|
535
|
+
mType: 'mainCall',
|
|
536
|
+
participants: ['agent1', 'customer1'],
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
} as any;
|
|
540
|
+
const result = getConsultMediaResourceId(interaction, undefined, 'agent1');
|
|
541
|
+
expect(result).toBeUndefined();
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('prioritizes direct consultMediaResourceId over interaction search', () => {
|
|
545
|
+
const interaction = {
|
|
546
|
+
media: {
|
|
547
|
+
'consult-media': {
|
|
548
|
+
mediaResourceId: 'consult-media',
|
|
549
|
+
mType: 'consult',
|
|
550
|
+
participants: ['agent1', 'agent2'],
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
} as any;
|
|
554
|
+
const result = getConsultMediaResourceId(interaction, 'direct-id', 'agent1');
|
|
555
|
+
expect(result).toBe('direct-id');
|
|
556
|
+
});
|
|
557
|
+
});
|
|
433
558
|
});
|
|
@@ -1,243 +1,157 @@
|
|
|
1
|
-
import AqmReqs from
|
|
2
|
-
import aqmDialer from
|
|
1
|
+
import AqmReqs from "../../../../../src/services/core/aqm-reqs";
|
|
2
|
+
import aqmDialer from "../../../../../src/services/task/dialer";
|
|
3
3
|
|
|
4
4
|
jest.mock('../../../../../src/services/core/Utils', () => ({
|
|
5
|
+
|
|
5
6
|
createErrDetailsObject: jest.fn(),
|
|
6
7
|
getRoutingHost: jest.fn(),
|
|
8
|
+
|
|
7
9
|
}));
|
|
8
10
|
|
|
9
11
|
jest.mock('../../../../../src/services/core/aqm-reqs');
|
|
10
12
|
|
|
11
13
|
describe('AQM routing dialer', () => {
|
|
14
|
+
|
|
12
15
|
let fakeAqm: jest.Mocked<AqmReqs>;
|
|
13
16
|
|
|
14
17
|
beforeEach(() => {
|
|
18
|
+
|
|
15
19
|
jest.clearAllMocks();
|
|
16
20
|
|
|
17
21
|
fakeAqm = new AqmReqs() as jest.Mocked<AqmReqs>;
|
|
18
22
|
fakeAqm.reqEmpty = jest.fn().mockImplementation((fn) => fn);
|
|
19
23
|
fakeAqm.req = jest.fn().mockImplementation((fn) => fn);
|
|
20
|
-
});
|
|
21
24
|
|
|
22
|
-
describe('Routing outbound dial', () => {
|
|
23
|
-
it('should call the startdial api', () => {
|
|
24
|
-
const fakeAqm = {
|
|
25
|
-
req: () =>
|
|
26
|
-
jest.fn().mockResolvedValue(() => {
|
|
27
|
-
Promise.resolve({data: 'outdial success'});
|
|
28
|
-
}),
|
|
29
|
-
evt: jest.fn(),
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const dialer = aqmDialer(fakeAqm as any);
|
|
33
|
-
|
|
34
|
-
dialer
|
|
35
|
-
.startOutdial({
|
|
36
|
-
data: {
|
|
37
|
-
entryPointId: '1212312',
|
|
38
|
-
destination: '+142356',
|
|
39
|
-
direction: 'OUTBOUND',
|
|
40
|
-
attributes: {},
|
|
41
|
-
mediaType: 'telephony',
|
|
42
|
-
outboundType: 'OUTDIAL',
|
|
43
|
-
},
|
|
44
|
-
})
|
|
45
|
-
.then((response) => {
|
|
46
|
-
expect(response.data).toBe('outdial success');
|
|
47
|
-
})
|
|
48
|
-
.catch(() => {
|
|
49
|
-
expect(true).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
expect(dialer.startOutdial).toHaveBeenCalled();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should handle network errors', () => {
|
|
56
|
-
const fakeAqm = {
|
|
57
|
-
req: () => jest.fn().mockRejectedValue(new Error('Network Error')),
|
|
58
|
-
evt: jest.fn(),
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const dialer = aqmDialer(fakeAqm as any);
|
|
62
|
-
|
|
63
|
-
return expect(
|
|
64
|
-
dialer.startOutdial({
|
|
65
|
-
data: {
|
|
66
|
-
entryPointId: '1212312',
|
|
67
|
-
destination: '+142356',
|
|
68
|
-
direction: 'OUTBOUND',
|
|
69
|
-
attributes: {},
|
|
70
|
-
mediaType: 'telephony',
|
|
71
|
-
outboundType: 'OUTDIAL',
|
|
72
|
-
},
|
|
73
|
-
})
|
|
74
|
-
).rejects.toThrow('Network Error');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('should handle invalid payload', () => {
|
|
78
|
-
const fakeAqm = {
|
|
79
|
-
req: () => jest.fn().mockRejectedValue(new Error('Invalid Payload in request')),
|
|
80
|
-
evt: jest.fn(),
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const dialer = aqmDialer(fakeAqm as any);
|
|
84
|
-
|
|
85
|
-
return expect(
|
|
86
|
-
dialer.startOutdial({
|
|
87
|
-
data: {
|
|
88
|
-
entryPointId: '',
|
|
89
|
-
destination: '',
|
|
90
|
-
direction: 'OUTBOUND',
|
|
91
|
-
attributes: {},
|
|
92
|
-
mediaType: 'telephony',
|
|
93
|
-
outboundType: 'OUTDIAL',
|
|
94
|
-
},
|
|
95
|
-
})
|
|
96
|
-
).rejects.toThrow('Invalid Payload in request');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('should handle servers errors', () => {
|
|
100
|
-
const fakeAqm = {
|
|
101
|
-
req: () => jest.fn().mockRejectedValue(new Error('Server Error')),
|
|
102
|
-
evt: jest.fn(),
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const dialer = aqmDialer(fakeAqm as any);
|
|
106
|
-
return expect(
|
|
107
|
-
dialer.startOutdial({
|
|
108
|
-
data: {
|
|
109
|
-
entryPointId: '123456',
|
|
110
|
-
destination: '+142356',
|
|
111
|
-
direction: 'OUTBOUND',
|
|
112
|
-
attributes: {},
|
|
113
|
-
mediaType: 'telephony',
|
|
114
|
-
outboundType: 'OUTDIAL',
|
|
115
|
-
},
|
|
116
|
-
})
|
|
117
|
-
).rejects.toThrow('Server Error');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should handle Timeout scenarios', () => {
|
|
121
|
-
const fakeAqm = {
|
|
122
|
-
req: () => jest.fn().mockRejectedValue(new Error('Request Timeout')),
|
|
123
|
-
evt: jest.fn(),
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const dialer = aqmDialer(fakeAqm as any);
|
|
127
|
-
return expect(
|
|
128
|
-
dialer.startOutdial({
|
|
129
|
-
data: {
|
|
130
|
-
entryPointId: '12345',
|
|
131
|
-
destination: '+123456',
|
|
132
|
-
direction: 'OUTBOUND',
|
|
133
|
-
attributes: {},
|
|
134
|
-
mediaType: 'telephony',
|
|
135
|
-
outboundType: 'OUTDIAL',
|
|
136
|
-
},
|
|
137
|
-
})
|
|
138
|
-
).rejects.toThrow('Request Timeout');
|
|
139
|
-
});
|
|
140
25
|
});
|
|
141
26
|
|
|
142
|
-
|
|
143
|
-
const previewPayload = {
|
|
144
|
-
interactionId: 'interaction-123',
|
|
145
|
-
campaignId: 'TestCampaignPreview',
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
describe('acceptPreviewContact', () => {
|
|
149
|
-
it('should construct the correct URL with campaignId and interactionId', () => {
|
|
150
|
-
const dialer = aqmDialer(fakeAqm as any);
|
|
151
|
-
const config = dialer.acceptPreviewContact({data: previewPayload}) as any;
|
|
152
|
-
|
|
153
|
-
expect(config.url).toBe(
|
|
154
|
-
`/v1/dialer/campaign/${previewPayload.campaignId}/preview-task/${previewPayload.interactionId}/accept`
|
|
155
|
-
);
|
|
156
|
-
});
|
|
27
|
+
describe("Routing outbound dial", () => {
|
|
157
28
|
|
|
158
|
-
|
|
159
|
-
const dialer = aqmDialer(fakeAqm as any);
|
|
160
|
-
const payloadWithSpecialChars = {
|
|
161
|
-
interactionId: 'interaction-456',
|
|
162
|
-
campaignId: 'My Campaign/Test #1',
|
|
163
|
-
};
|
|
164
|
-
const config = dialer.acceptPreviewContact({data: payloadWithSpecialChars}) as any;
|
|
165
|
-
|
|
166
|
-
expect(config.url).toBe(
|
|
167
|
-
`/v1/dialer/campaign/${encodeURIComponent(
|
|
168
|
-
payloadWithSpecialChars.campaignId
|
|
169
|
-
)}/preview-task/${payloadWithSpecialChars.interactionId}/accept`
|
|
170
|
-
);
|
|
171
|
-
expect(config.url).toContain('My%20Campaign%2FTest%20%231');
|
|
172
|
-
});
|
|
29
|
+
it("should call the startdial api", () => {
|
|
173
30
|
|
|
174
|
-
it('should call the acceptPreviewContact api', () => {
|
|
175
31
|
const fakeAqm = {
|
|
176
32
|
req: () =>
|
|
177
33
|
jest.fn().mockResolvedValue(() => {
|
|
178
|
-
Promise.resolve({data:
|
|
34
|
+
Promise.resolve({ data: "outdial success" });
|
|
179
35
|
}),
|
|
180
|
-
evt: jest.fn()
|
|
36
|
+
evt: jest.fn()
|
|
181
37
|
};
|
|
182
|
-
|
|
38
|
+
|
|
183
39
|
const dialer = aqmDialer(fakeAqm as any);
|
|
184
40
|
|
|
185
41
|
dialer
|
|
186
|
-
.
|
|
187
|
-
|
|
188
|
-
|
|
42
|
+
.startOutdial({
|
|
43
|
+
data: {
|
|
44
|
+
entryPointId: "1212312",
|
|
45
|
+
destination: "+142356",
|
|
46
|
+
direction: "OUTBOUND",
|
|
47
|
+
attributes: {},
|
|
48
|
+
mediaType: "telephony",
|
|
49
|
+
outboundType: "OUTDIAL"
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
.then(response => {
|
|
53
|
+
expect(response.data).toBe("outdial success");
|
|
189
54
|
})
|
|
190
55
|
.catch(() => {
|
|
191
56
|
expect(true).toBe(true);
|
|
192
57
|
});
|
|
193
|
-
|
|
194
|
-
|
|
58
|
+
|
|
59
|
+
expect(dialer.startOutdial).toHaveBeenCalled();
|
|
60
|
+
|
|
195
61
|
});
|
|
196
62
|
|
|
197
|
-
it(
|
|
198
|
-
const fakeAqm = {
|
|
199
|
-
req: () => jest.fn().mockRejectedValue(new Error('Network Error')),
|
|
200
|
-
evt: jest.fn(),
|
|
201
|
-
};
|
|
63
|
+
it("should handle network errors", () => {
|
|
202
64
|
|
|
203
|
-
|
|
65
|
+
const fakeAqm = {
|
|
66
|
+
req: () => jest.fn().mockRejectedValue(new Error("Network Error")),
|
|
67
|
+
evt: jest.fn()
|
|
68
|
+
};
|
|
204
69
|
|
|
205
|
-
|
|
206
|
-
dialer.acceptPreviewContact({
|
|
207
|
-
data: previewPayload,
|
|
208
|
-
})
|
|
209
|
-
).rejects.toThrow('Network Error');
|
|
210
|
-
});
|
|
70
|
+
const dialer = aqmDialer(fakeAqm as any);
|
|
211
71
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
72
|
+
return expect(dialer.startOutdial({
|
|
73
|
+
|
|
74
|
+
data: {
|
|
75
|
+
entryPointId: "1212312",
|
|
76
|
+
destination: "+142356",
|
|
77
|
+
direction: "OUTBOUND",
|
|
78
|
+
attributes: {},
|
|
79
|
+
mediaType: "telephony",
|
|
80
|
+
outboundType: "OUTDIAL"
|
|
81
|
+
}
|
|
217
82
|
|
|
218
|
-
|
|
83
|
+
})).rejects.toThrow("Network Error");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should handle invalid payload", () => {
|
|
219
87
|
|
|
220
|
-
|
|
221
|
-
dialer.acceptPreviewContact({
|
|
222
|
-
data: previewPayload,
|
|
223
|
-
})
|
|
224
|
-
).rejects.toThrow('Server Error');
|
|
225
|
-
});
|
|
88
|
+
const fakeAqm = {
|
|
226
89
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
req: () => jest.fn().mockRejectedValue(new Error('Request Timeout')),
|
|
230
|
-
evt: jest.fn(),
|
|
231
|
-
};
|
|
90
|
+
req: () => jest.fn().mockRejectedValue(new Error("Invalid Payload in request")),
|
|
91
|
+
evt: jest.fn()
|
|
232
92
|
|
|
233
|
-
|
|
93
|
+
};
|
|
234
94
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
95
|
+
const dialer = aqmDialer(fakeAqm as any);
|
|
96
|
+
|
|
97
|
+
return expect(dialer.startOutdial({
|
|
98
|
+
|
|
99
|
+
data: {
|
|
100
|
+
entryPointId: "",
|
|
101
|
+
destination: "",
|
|
102
|
+
direction: "OUTBOUND",
|
|
103
|
+
attributes: {},
|
|
104
|
+
mediaType: "telephony",
|
|
105
|
+
outboundType: "OUTDIAL"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
})).rejects.toThrow("Invalid Payload in request");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
it("should handle servers errors", () => {
|
|
113
|
+
|
|
114
|
+
const fakeAqm = {
|
|
115
|
+
req: () => jest.fn().mockRejectedValue(new Error("Server Error")),
|
|
116
|
+
evt: jest.fn()
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const dialer = aqmDialer(fakeAqm as any);
|
|
120
|
+
return expect(dialer.startOutdial({
|
|
121
|
+
|
|
122
|
+
data: {
|
|
123
|
+
entryPointId: "123456",
|
|
124
|
+
destination: "+142356",
|
|
125
|
+
direction: "OUTBOUND",
|
|
126
|
+
attributes: {},
|
|
127
|
+
mediaType: "telephony",
|
|
128
|
+
outboundType: "OUTDIAL"
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
})).rejects.toThrow("Server Error");
|
|
132
|
+
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should handle Timeout scenarios", () => {
|
|
136
|
+
|
|
137
|
+
const fakeAqm = {
|
|
138
|
+
req: () => jest.fn().mockRejectedValue(new Error("Request Timeout")),
|
|
139
|
+
evt: jest.fn()
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const dialer = aqmDialer(fakeAqm as any);
|
|
143
|
+
return expect(dialer.startOutdial({
|
|
144
|
+
|
|
145
|
+
data: {
|
|
146
|
+
entryPointId: "12345",
|
|
147
|
+
destination: "+123456",
|
|
148
|
+
direction: "OUTBOUND",
|
|
149
|
+
attributes: {},
|
|
150
|
+
mediaType: "telephony",
|
|
151
|
+
outboundType: "OUTDIAL"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
})).rejects.toThrow("Request Timeout");
|
|
155
|
+
});
|
|
156
|
+
});
|
|
243
157
|
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import Digital from '../../../../../../src/services/task/digital/Digital';
|
|
2
|
+
import {MEDIA_CHANNEL, TaskData, TaskResponse} from '../../../../../../src/services/task/types';
|
|
3
|
+
import {TaskEvent, TaskEventPayload} from '../../../../../../src/services/task/state-machine';
|
|
4
|
+
|
|
5
|
+
jest.mock('../../../../../../src/services/core/WebexRequest', () => ({
|
|
6
|
+
__esModule: true,
|
|
7
|
+
default: {
|
|
8
|
+
getInstance: () => ({ uploadLogs: jest.fn() }),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
const sendStateEvents = (task: Digital, events: TaskEventPayload[]) => {
|
|
13
|
+
events.forEach((event) => {
|
|
14
|
+
if (!event) {
|
|
15
|
+
throw new Error('Task event payload is required');
|
|
16
|
+
}
|
|
17
|
+
task.stateMachineService?.send(event);
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe('Digital Task', () => {
|
|
22
|
+
const dummyData = {
|
|
23
|
+
interactionId: 'dig1',
|
|
24
|
+
interaction: {isTerminated: false, mediaType: MEDIA_CHANNEL.CHAT},
|
|
25
|
+
} as TaskData;
|
|
26
|
+
let dummyContact: { accept: jest.Mock<Promise<TaskResponse>> };
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
dummyContact = {
|
|
30
|
+
accept: jest.fn().mockResolvedValue({ status: 'ok' }),
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('accept() calls contact.accept with interactionId', async () => {
|
|
35
|
+
const task = new Digital(dummyContact, dummyData);
|
|
36
|
+
const res = await task.accept();
|
|
37
|
+
expect(dummyContact.accept).toHaveBeenCalledWith({ interactionId: 'dig1' });
|
|
38
|
+
expect(res).toEqual({ status: 'ok' });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('accept() throws an error when contact.accept rejects', async () => {
|
|
42
|
+
const error = new Error('Error while performing accept');
|
|
43
|
+
(dummyContact.accept as jest.Mock).mockRejectedValue(error);
|
|
44
|
+
const task = new Digital(dummyContact, dummyData);
|
|
45
|
+
await expect(task.accept()).rejects.toThrow('Error while performing accept');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('constructor shows accept when offered', () => {
|
|
49
|
+
const task = new Digital(dummyContact, dummyData);
|
|
50
|
+
sendStateEvents(task, [{type: TaskEvent.TASK_INCOMING, taskData: dummyData}]);
|
|
51
|
+
expect(task.uiControls.main.accept.isVisible).toBe(true);
|
|
52
|
+
expect(task.uiControls.main.accept.isEnabled).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('UI controls derived from state machine events', () => {
|
|
56
|
+
it('connected state shows transfer and end', () => {
|
|
57
|
+
const task = new Digital(dummyContact, dummyData);
|
|
58
|
+
sendStateEvents(task, [
|
|
59
|
+
{type: TaskEvent.TASK_INCOMING, taskData: dummyData},
|
|
60
|
+
{type: TaskEvent.ASSIGN, taskData: dummyData},
|
|
61
|
+
]);
|
|
62
|
+
expect(task.uiControls.main.accept.isVisible).toBe(false);
|
|
63
|
+
expect(task.uiControls.main.transfer.isVisible).toBe(true);
|
|
64
|
+
expect(task.uiControls.main.end.isVisible).toBe(true);
|
|
65
|
+
expect(task.uiControls.main.wrapup.isVisible).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('wrapup state hides transfer/end and shows wrapup button', () => {
|
|
69
|
+
const task = new Digital(dummyContact, dummyData);
|
|
70
|
+
sendStateEvents(task, [
|
|
71
|
+
{type: TaskEvent.TASK_INCOMING, taskData: dummyData},
|
|
72
|
+
{type: TaskEvent.ASSIGN, taskData: dummyData},
|
|
73
|
+
{type: TaskEvent.TASK_WRAPUP},
|
|
74
|
+
]);
|
|
75
|
+
expect(task.uiControls.main.transfer.isVisible).toBe(false);
|
|
76
|
+
expect(task.uiControls.main.end.isVisible).toBe(false);
|
|
77
|
+
expect(task.uiControls.main.wrapup.isVisible).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('terminated interaction toggles wrapup visibility even before END event', () => {
|
|
81
|
+
const task = new Digital(dummyContact, dummyData);
|
|
82
|
+
const terminatedData = {
|
|
83
|
+
...dummyData,
|
|
84
|
+
interaction: {...(dummyData.interaction as any), isTerminated: true},
|
|
85
|
+
} as TaskData;
|
|
86
|
+
task.updateTaskData(terminatedData);
|
|
87
|
+
sendStateEvents(task, [
|
|
88
|
+
{type: TaskEvent.TASK_INCOMING, taskData: dummyData},
|
|
89
|
+
{type: TaskEvent.ASSIGN, taskData: terminatedData},
|
|
90
|
+
]);
|
|
91
|
+
expect(task.uiControls.main.wrapup.isVisible).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('rona hides accept controls', () => {
|
|
95
|
+
const task = new Digital(dummyContact, dummyData);
|
|
96
|
+
sendStateEvents(task, [
|
|
97
|
+
{type: TaskEvent.TASK_INCOMING, taskData: dummyData},
|
|
98
|
+
{type: TaskEvent.RONA},
|
|
99
|
+
]);
|
|
100
|
+
expect(task.uiControls.main.accept.isVisible).toBe(false);
|
|
101
|
+
expect(task.uiControls.main.transfer.isVisible).toBe(false);
|
|
102
|
+
expect(task.uiControls.main.end.isVisible).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|