@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
|
@@ -5,16 +5,21 @@ import {CALL_EVENT_KEYS, CallingClientConfig, LINE_EVENTS} from '@webex/calling'
|
|
|
5
5
|
import {CC_AGENT_EVENTS, CC_EVENTS} from '../../../../../src/services/config/types';
|
|
6
6
|
import TaskManager from '../../../../../src/services/task/TaskManager';
|
|
7
7
|
import * as contact from '../../../../../src/services/task/contact';
|
|
8
|
-
import Task from '../../../../../src/services/task';
|
|
8
|
+
import Task from '../../../../../src/services/task/Task';
|
|
9
9
|
import {TASK_EVENTS} from '../../../../../src/services/task/types';
|
|
10
|
+
import {TaskEvent} from '../../../../../src/services/task/state-machine';
|
|
11
|
+
import WebRTC from '../../../../../src/services/task/voice/WebRTC';
|
|
12
|
+
import {Profile} from '../../../../../src/services/config/types';
|
|
10
13
|
import WebCallingService from '../../../../../src/services/WebCallingService';
|
|
11
14
|
import config from '../../../../../src/config';
|
|
12
15
|
import {CC_TASK_EVENTS} from '../../../../../src/services/config/types';
|
|
16
|
+
import TaskFactory from '../../../../../src/services/task/TaskFactory';
|
|
13
17
|
|
|
14
18
|
describe('TaskManager', () => {
|
|
15
19
|
let mockCall;
|
|
16
20
|
let mockApiAIAssistant;
|
|
17
21
|
let webSocketManagerMock;
|
|
22
|
+
let rtdWebSocketManagerMock;
|
|
18
23
|
let onSpy;
|
|
19
24
|
let offSpy;
|
|
20
25
|
let taskManager;
|
|
@@ -24,6 +29,104 @@ describe('TaskManager', () => {
|
|
|
24
29
|
let webex: WebexSDK;
|
|
25
30
|
const taskId = '0ae913a4-c857-4705-8d49-76dd3dde75e4';
|
|
26
31
|
|
|
32
|
+
const createMockTask = (data = taskDataMock) => {
|
|
33
|
+
const task = new EventEmitter() as any;
|
|
34
|
+
|
|
35
|
+
const updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
36
|
+
task.data = {...task.data, ...newData};
|
|
37
|
+
return task;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
Object.assign(task, {
|
|
41
|
+
data,
|
|
42
|
+
accept: jest.fn(),
|
|
43
|
+
decline: jest.fn(),
|
|
44
|
+
updateTaskData,
|
|
45
|
+
unregisterWebCallListeners: jest.fn(),
|
|
46
|
+
cancelAutoWrapupTimer: jest.fn(),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const taskEventMap: Partial<Record<TaskEvent, string>> = {
|
|
50
|
+
[TaskEvent.TASK_INCOMING]: TASK_EVENTS.TASK_INCOMING,
|
|
51
|
+
[TaskEvent.TASK_OFFERED]: TASK_EVENTS.TASK_OFFER_CONTACT,
|
|
52
|
+
[TaskEvent.OFFER_CONSULT]: TASK_EVENTS.TASK_OFFER_CONSULT,
|
|
53
|
+
[TaskEvent.HYDRATE]: TASK_EVENTS.TASK_HYDRATE,
|
|
54
|
+
[TaskEvent.ASSIGN]: TASK_EVENTS.TASK_ASSIGNED,
|
|
55
|
+
[TaskEvent.HOLD_SUCCESS]: TASK_EVENTS.TASK_HOLD,
|
|
56
|
+
[TaskEvent.UNHOLD_SUCCESS]: TASK_EVENTS.TASK_RESUME,
|
|
57
|
+
[TaskEvent.CONSULT_CREATED]: TASK_EVENTS.TASK_CONSULT_CREATED,
|
|
58
|
+
[TaskEvent.CONSULTING_ACTIVE]: TASK_EVENTS.TASK_CONSULT_ACCEPTED,
|
|
59
|
+
[TaskEvent.CONSULT_END]: TASK_EVENTS.TASK_CONSULT_END,
|
|
60
|
+
[TaskEvent.CONSULT_FAILED]: CC_EVENTS.AGENT_CONSULT_FAILED,
|
|
61
|
+
[TaskEvent.CTQ_CANCEL]: TASK_EVENTS.TASK_CONSULT_QUEUE_CANCELLED,
|
|
62
|
+
[TaskEvent.CTQ_CANCEL_FAILED]: TASK_EVENTS.TASK_CONSULT_QUEUE_FAILED,
|
|
63
|
+
[TaskEvent.END]: TASK_EVENTS.TASK_END,
|
|
64
|
+
[TaskEvent.CONTACT_ENDED]: TASK_EVENTS.TASK_END,
|
|
65
|
+
[TaskEvent.ASSIGN_FAILED]: TASK_EVENTS.TASK_REJECT,
|
|
66
|
+
[TaskEvent.INVITE_FAILED]: TASK_EVENTS.TASK_REJECT,
|
|
67
|
+
[TaskEvent.RONA]: TASK_EVENTS.TASK_REJECT,
|
|
68
|
+
[TaskEvent.OUTBOUND_FAILED]: TASK_EVENTS.TASK_OUTDIAL_FAILED,
|
|
69
|
+
[TaskEvent.RECORDING_STARTED]: TASK_EVENTS.TASK_RECORDING_STARTED,
|
|
70
|
+
[TaskEvent.PAUSE_RECORDING]: TASK_EVENTS.TASK_RECORDING_PAUSED,
|
|
71
|
+
[TaskEvent.RESUME_RECORDING]: TASK_EVENTS.TASK_RECORDING_RESUMED,
|
|
72
|
+
[TaskEvent.WRAPUP_COMPLETE]: TASK_EVENTS.TASK_WRAPPEDUP,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
task.sendStateMachineEvent = jest.fn().mockImplementation((event) => {
|
|
76
|
+
if (event.taskData) {
|
|
77
|
+
task.updateTaskData(event.taskData);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const mappedEvent = taskEventMap[event.type as TaskEvent];
|
|
81
|
+
if (mappedEvent) {
|
|
82
|
+
if (
|
|
83
|
+
[TaskEvent.ASSIGN_FAILED, TaskEvent.RONA, TaskEvent.INVITE_FAILED].includes(
|
|
84
|
+
event.type as TaskEvent
|
|
85
|
+
)
|
|
86
|
+
) {
|
|
87
|
+
task.emit(mappedEvent, event.reason ?? event.taskData?.reason);
|
|
88
|
+
} else if (event.type === TaskEvent.OUTBOUND_FAILED) {
|
|
89
|
+
task.emit(mappedEvent, event.reason);
|
|
90
|
+
} else {
|
|
91
|
+
task.emit(mappedEvent, task);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Auto-answer is now handled at the Task layer (triggered by state machine actions)
|
|
96
|
+
if (
|
|
97
|
+
[TaskEvent.TASK_OFFERED, TaskEvent.OFFER_CONSULT].includes(event.type as TaskEvent) &&
|
|
98
|
+
(event.taskData?.isAutoAnswering === true || event.taskData?.isAutoAnswering === 'true')
|
|
99
|
+
) {
|
|
100
|
+
Promise.resolve(task.accept())
|
|
101
|
+
.then(() => {
|
|
102
|
+
task.emit(TASK_EVENTS.TASK_AUTO_ANSWERED, task);
|
|
103
|
+
})
|
|
104
|
+
.catch(() => undefined);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Cleanup is now emitted by state machine actions (Task layer).
|
|
108
|
+
// Simulate the TASK_CLEANUP emission for unit tests using mock tasks.
|
|
109
|
+
const eventType = event.type as TaskEvent;
|
|
110
|
+
const shouldCleanup =
|
|
111
|
+
eventType === TaskEvent.CONTACT_ENDED ||
|
|
112
|
+
eventType === TaskEvent.END ||
|
|
113
|
+
eventType === TaskEvent.TASK_WRAPUP ||
|
|
114
|
+
eventType === TaskEvent.WRAPUP_COMPLETE ||
|
|
115
|
+
eventType === TaskEvent.ASSIGN_FAILED ||
|
|
116
|
+
eventType === TaskEvent.INVITE_FAILED ||
|
|
117
|
+
eventType === TaskEvent.RONA ||
|
|
118
|
+
eventType === TaskEvent.OUTBOUND_FAILED ||
|
|
119
|
+
(eventType === TaskEvent.CONSULT_END && task.data?.isConsulted === true);
|
|
120
|
+
|
|
121
|
+
if (shouldCleanup) {
|
|
122
|
+
const removeFromCollection = eventType !== TaskEvent.CONTACT_ENDED;
|
|
123
|
+
task.emit(TASK_EVENTS.TASK_CLEANUP, task, {removeFromCollection});
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return task;
|
|
128
|
+
};
|
|
129
|
+
|
|
27
130
|
taskDataMock = {
|
|
28
131
|
type: CC_EVENTS.AGENT_CONTACT_RESERVED,
|
|
29
132
|
agentId: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
@@ -43,18 +146,21 @@ describe('TaskManager', () => {
|
|
|
43
146
|
data: taskDataMock,
|
|
44
147
|
};
|
|
45
148
|
|
|
149
|
+
const expectLastStateMachineEvent = (
|
|
150
|
+
spy: jest.SpyInstance | jest.Mock,
|
|
151
|
+
expectedType: TaskEvent
|
|
152
|
+
) => {
|
|
153
|
+
expect(spy).toHaveBeenCalled();
|
|
154
|
+
const lastCall = spy.mock.calls[spy.mock.calls.length - 1] || [];
|
|
155
|
+
const event = lastCall[3]?.type ? lastCall[3] : lastCall[0];
|
|
156
|
+
expect(event?.type).toBe(expectedType);
|
|
157
|
+
return event;
|
|
158
|
+
};
|
|
159
|
+
|
|
46
160
|
beforeEach(() => {
|
|
47
161
|
contactMock = contact;
|
|
48
162
|
webSocketManagerMock = new EventEmitter();
|
|
49
|
-
|
|
50
|
-
sendEvent: jest.fn().mockResolvedValue({}),
|
|
51
|
-
setAIFeatureFlags: jest.fn(),
|
|
52
|
-
aiFeature: {
|
|
53
|
-
realtimeTranscripts: {
|
|
54
|
-
enable: true,
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
};
|
|
163
|
+
rtdWebSocketManagerMock = new EventEmitter();
|
|
58
164
|
|
|
59
165
|
webex = {
|
|
60
166
|
logger: {
|
|
@@ -84,20 +190,25 @@ describe('TaskManager', () => {
|
|
|
84
190
|
onSpy = jest.spyOn(webCallingService, 'on');
|
|
85
191
|
offSpy = jest.spyOn(webCallingService, 'off');
|
|
86
192
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
emit: jest.fn(),
|
|
90
|
-
accept: jest.fn(),
|
|
91
|
-
decline: jest.fn(),
|
|
92
|
-
updateTaskData: jest.fn().mockImplementation((updatedData) => {
|
|
93
|
-
taskMock.data = {...taskMock.data, ...updatedData};
|
|
94
|
-
return taskMock;
|
|
95
|
-
}),
|
|
96
|
-
data: taskDataMock,
|
|
193
|
+
mockApiAIAssistant = {
|
|
194
|
+
sendEvent: jest.fn().mockResolvedValue({}),
|
|
97
195
|
};
|
|
98
|
-
|
|
99
|
-
taskManager
|
|
196
|
+
|
|
197
|
+
taskManager = new TaskManager(
|
|
198
|
+
mockApiAIAssistant as any,
|
|
199
|
+
contactMock,
|
|
200
|
+
webCallingService,
|
|
201
|
+
webSocketManagerMock as any,
|
|
202
|
+
rtdWebSocketManagerMock as any
|
|
203
|
+
);
|
|
204
|
+
taskManager.taskCollection[taskId] = createMockTask(taskDataMock);
|
|
205
|
+
(taskManager as any).setupTaskListeners?.(taskManager.taskCollection[taskId]);
|
|
100
206
|
taskManager.call = mockCall;
|
|
207
|
+
taskManager.setAgentId('test-agent-id');
|
|
208
|
+
|
|
209
|
+
jest
|
|
210
|
+
.spyOn(TaskFactory, 'createTask')
|
|
211
|
+
.mockImplementation((contact, webCallingService, data, configFlags) => createMockTask(data));
|
|
101
212
|
});
|
|
102
213
|
|
|
103
214
|
afterEach(() => {
|
|
@@ -108,7 +219,8 @@ describe('TaskManager', () => {
|
|
|
108
219
|
it('should initialize TaskManager and register listeners', () => {
|
|
109
220
|
webSocketManagerMock.emit('message', JSON.stringify({data: taskDataMock}));
|
|
110
221
|
const incomingCallCb = onSpy.mock.calls[0][1];
|
|
111
|
-
const
|
|
222
|
+
const incomingHandler = jest.fn();
|
|
223
|
+
taskManager.on(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
112
224
|
|
|
113
225
|
expect(taskManager).toBeInstanceOf(TaskManager);
|
|
114
226
|
expect(webCallingService.listenerCount(LINE_EVENTS.INCOMING_CALL)).toBe(1);
|
|
@@ -117,10 +229,8 @@ describe('TaskManager', () => {
|
|
|
117
229
|
|
|
118
230
|
incomingCallCb(mockCall);
|
|
119
231
|
|
|
120
|
-
expect(
|
|
121
|
-
|
|
122
|
-
taskManager.getTask(taskId)
|
|
123
|
-
);
|
|
232
|
+
expect(incomingHandler).toHaveBeenCalledWith(taskManager.getTask(taskId));
|
|
233
|
+
taskManager.off(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
124
234
|
});
|
|
125
235
|
|
|
126
236
|
it('should re-emit task related events', () => {
|
|
@@ -136,15 +246,19 @@ describe('TaskManager', () => {
|
|
|
136
246
|
|
|
137
247
|
webSocketManagerMock.emit('message', JSON.stringify(dummyPayload));
|
|
138
248
|
|
|
139
|
-
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
249
|
+
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
250
|
+
TASK_EVENTS.TASK_CONSULT_ACCEPTED,
|
|
251
|
+
taskManager.getTask(taskId)
|
|
252
|
+
);
|
|
140
253
|
});
|
|
141
254
|
|
|
142
255
|
it('should invoke sendEvent for configured start/stop backend events', () => {
|
|
256
|
+
const interactionId = taskId;
|
|
143
257
|
const message = (type: CC_EVENTS) =>
|
|
144
258
|
JSON.stringify({
|
|
145
259
|
data: {
|
|
146
260
|
...taskDataMock,
|
|
147
|
-
|
|
261
|
+
interactionId,
|
|
148
262
|
type,
|
|
149
263
|
},
|
|
150
264
|
});
|
|
@@ -153,67 +267,85 @@ describe('TaskManager', () => {
|
|
|
153
267
|
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULTING));
|
|
154
268
|
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULT_CONFERENCED));
|
|
155
269
|
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULT_ENDED));
|
|
156
|
-
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_WRAPUP));
|
|
157
270
|
webSocketManagerMock.emit('message', message(CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE));
|
|
271
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_WRAPUP));
|
|
158
272
|
|
|
159
273
|
expect(mockApiAIAssistant.sendEvent).toHaveBeenCalledTimes(6);
|
|
160
274
|
expect(mockApiAIAssistant.sendEvent).toHaveBeenCalledWith(
|
|
161
275
|
'test-agent-id',
|
|
162
|
-
|
|
276
|
+
interactionId,
|
|
163
277
|
'CUSTOM_EVENT',
|
|
164
278
|
'GET_TRANSCRIPTS',
|
|
165
279
|
'START'
|
|
166
280
|
);
|
|
167
281
|
expect(mockApiAIAssistant.sendEvent).toHaveBeenCalledWith(
|
|
168
282
|
'test-agent-id',
|
|
169
|
-
|
|
283
|
+
interactionId,
|
|
170
284
|
'CUSTOM_EVENT',
|
|
171
285
|
'GET_TRANSCRIPTS',
|
|
172
286
|
'STOP'
|
|
173
287
|
);
|
|
174
288
|
});
|
|
175
289
|
|
|
176
|
-
it('should not invoke sendEvent
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
290
|
+
it('should not invoke sendEvent when realtime transcripts are disabled in aiFeature', () => {
|
|
291
|
+
taskManager.setConfigFlags({
|
|
292
|
+
isEndTaskEnabled: true,
|
|
293
|
+
isEndConsultEnabled: true,
|
|
294
|
+
webRtcEnabled: true,
|
|
295
|
+
autoWrapup: false,
|
|
296
|
+
aiFeature: {
|
|
297
|
+
id: 'ai-feature-1',
|
|
298
|
+
realtimeTranscripts: {
|
|
299
|
+
enable: false,
|
|
300
|
+
},
|
|
180
301
|
},
|
|
181
|
-
};
|
|
182
|
-
mockApiAIAssistant.setAIFeatureFlags(mockApiAIAssistant.aiFeature);
|
|
302
|
+
});
|
|
183
303
|
|
|
184
304
|
const message = (type: CC_EVENTS) =>
|
|
185
305
|
JSON.stringify({
|
|
186
306
|
data: {
|
|
187
307
|
...taskDataMock,
|
|
188
|
-
taskId,
|
|
308
|
+
interactionId: taskId,
|
|
189
309
|
type,
|
|
190
310
|
},
|
|
191
311
|
});
|
|
192
312
|
|
|
193
313
|
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONTACT_ASSIGNED));
|
|
194
314
|
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULTING));
|
|
195
|
-
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULT_CONFERENCED));
|
|
196
|
-
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULT_ENDED));
|
|
197
|
-
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_WRAPUP));
|
|
198
|
-
webSocketManagerMock.emit('message', message(CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE));
|
|
199
315
|
|
|
200
316
|
expect(mockApiAIAssistant.sendEvent).not.toHaveBeenCalled();
|
|
201
317
|
});
|
|
202
318
|
|
|
203
|
-
it('should emit REAL_TIME_TRANSCRIPTION from
|
|
319
|
+
it('should emit REAL_TIME_TRANSCRIPTION from RTD websocket payload', () => {
|
|
204
320
|
const task = taskManager.getTask(taskId);
|
|
205
321
|
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
206
322
|
const realtimePayload = {
|
|
207
323
|
data: {
|
|
208
|
-
|
|
209
|
-
type: CC_EVENTS.REAL_TIME_TRANSCRIPTION,
|
|
324
|
+
agentId: 'test-agent-id',
|
|
210
325
|
data: {
|
|
211
|
-
content: '
|
|
326
|
+
content: 'Thank you. Okay.',
|
|
327
|
+
conversationId: taskId,
|
|
328
|
+
isFinal: true,
|
|
329
|
+
languageCode: 'en-US',
|
|
330
|
+
messageId: '1',
|
|
331
|
+
orgId: 'org-id',
|
|
332
|
+
publishTimestamp: 1773807297475,
|
|
333
|
+
role: 'AGENT',
|
|
334
|
+
trackingId: 'tracking-id',
|
|
335
|
+
utteranceId: 'utterance-id',
|
|
336
|
+
},
|
|
337
|
+
notifDetails: {
|
|
338
|
+
actionEvent: 'REAL_TIME_TRANSCRIPTION',
|
|
212
339
|
},
|
|
340
|
+
notifType: 'REAL_TIME_TRANSCRIPTION',
|
|
341
|
+
orgId: 'org-id',
|
|
213
342
|
},
|
|
343
|
+
orgId: 'org-id',
|
|
344
|
+
trackingId: 'notifs_tracking-id',
|
|
345
|
+
type: 'REAL_TIME_TRANSCRIPTION',
|
|
214
346
|
};
|
|
215
347
|
|
|
216
|
-
|
|
348
|
+
taskManager.handleRealtimeWebsocketEvent(JSON.stringify(realtimePayload));
|
|
217
349
|
|
|
218
350
|
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
219
351
|
CC_EVENTS.REAL_TIME_TRANSCRIPTION,
|
|
@@ -221,22 +353,37 @@ describe('TaskManager', () => {
|
|
|
221
353
|
);
|
|
222
354
|
});
|
|
223
355
|
|
|
224
|
-
it('should
|
|
225
|
-
const task = taskManager.getTask(taskId);
|
|
226
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
356
|
+
it('should ignore RTD transcript events when task is not found', () => {
|
|
227
357
|
const realtimePayload = {
|
|
228
358
|
data: {
|
|
229
|
-
notifType: CC_EVENTS.REAL_TIME_TRANSCRIPTION,
|
|
230
359
|
data: {
|
|
231
|
-
|
|
232
|
-
|
|
360
|
+
content: 'Thank you. Okay.',
|
|
361
|
+
conversationId: 'missing-task-id',
|
|
362
|
+
isFinal: true,
|
|
363
|
+
languageCode: 'en-US',
|
|
364
|
+
messageId: '1',
|
|
365
|
+
orgId: 'org-id',
|
|
366
|
+
publishTimestamp: 1773807297475,
|
|
367
|
+
role: 'AGENT',
|
|
368
|
+
trackingId: 'tracking-id',
|
|
369
|
+
utteranceId: 'utterance-id',
|
|
233
370
|
},
|
|
371
|
+
notifDetails: {
|
|
372
|
+
actionEvent: 'REAL_TIME_TRANSCRIPTION',
|
|
373
|
+
},
|
|
374
|
+
notifType: 'REAL_TIME_TRANSCRIPTION',
|
|
375
|
+
orgId: 'org-id',
|
|
234
376
|
},
|
|
377
|
+
orgId: 'org-id',
|
|
378
|
+
trackingId: 'notifs_tracking-id',
|
|
379
|
+
type: 'REAL_TIME_TRANSCRIPTION',
|
|
235
380
|
};
|
|
236
381
|
|
|
382
|
+
const existingTaskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
|
|
383
|
+
|
|
237
384
|
taskManager.handleRealtimeWebsocketEvent(JSON.stringify(realtimePayload));
|
|
238
385
|
|
|
239
|
-
expect(
|
|
386
|
+
expect(existingTaskEmitSpy).not.toHaveBeenCalled();
|
|
240
387
|
});
|
|
241
388
|
|
|
242
389
|
it('should not re-emit agent related events', () => {
|
|
@@ -254,7 +401,7 @@ describe('TaskManager', () => {
|
|
|
254
401
|
|
|
255
402
|
webSocketManagerMock.emit('message', JSON.stringify(dummyPayload));
|
|
256
403
|
|
|
257
|
-
expect(taskEmitSpy).not.
|
|
404
|
+
expect(taskEmitSpy).not.toHaveBeenCalled();
|
|
258
405
|
});
|
|
259
406
|
|
|
260
407
|
it('should handle WebSocket message for AGENT_CONTACT_RESERVED and emit task:incoming for browser case', () => {
|
|
@@ -275,14 +422,12 @@ describe('TaskManager', () => {
|
|
|
275
422
|
},
|
|
276
423
|
};
|
|
277
424
|
|
|
278
|
-
const
|
|
425
|
+
const incomingHandler = jest.fn();
|
|
426
|
+
taskManager.on(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
279
427
|
|
|
280
428
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
281
429
|
|
|
282
|
-
expect(
|
|
283
|
-
TASK_EVENTS.TASK_INCOMING,
|
|
284
|
-
taskManager.getTask(payload.data.interactionId)
|
|
285
|
-
);
|
|
430
|
+
expect(incomingHandler).toHaveBeenCalledWith(taskManager.getTask(payload.data.interactionId));
|
|
286
431
|
expect(taskManager.getTask(payload.data.interactionId)).toBe(taskManager.getTask(taskId));
|
|
287
432
|
expect(taskManager.getAllTasks()).toHaveProperty(payload.data.interactionId);
|
|
288
433
|
|
|
@@ -335,16 +480,64 @@ describe('TaskManager', () => {
|
|
|
335
480
|
},
|
|
336
481
|
};
|
|
337
482
|
|
|
338
|
-
const
|
|
483
|
+
const incomingHandler = jest.fn();
|
|
484
|
+
taskManager.on(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
339
485
|
|
|
340
486
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
341
487
|
|
|
342
|
-
expect(
|
|
343
|
-
TASK_EVENTS.TASK_INCOMING,
|
|
344
|
-
taskManager.getTask(taskId)
|
|
345
|
-
);
|
|
488
|
+
expect(incomingHandler).toHaveBeenCalledWith(taskManager.getTask(taskId));
|
|
346
489
|
expect(taskManager.getTask(payload.data.interactionId)).toBe(taskManager.getTask(taskId));
|
|
347
490
|
expect(taskManager.getAllTasks()).toHaveProperty(payload.data.interactionId);
|
|
491
|
+
taskManager.off(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('should send mapped events through the state machine without duplicate updates', () => {
|
|
495
|
+
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
496
|
+
const task = taskManager.getTask(taskId);
|
|
497
|
+
const updateSpy = task.updateTaskData as jest.Mock;
|
|
498
|
+
updateSpy.mockClear();
|
|
499
|
+
const sendSpy = task.sendStateMachineEvent as jest.Mock;
|
|
500
|
+
sendSpy.mockClear();
|
|
501
|
+
const cleanupSpy = jest.spyOn(taskManager as any, 'handleTaskCleanup');
|
|
502
|
+
|
|
503
|
+
const assignFailedPayload = {
|
|
504
|
+
data: {
|
|
505
|
+
...initalPayload.data,
|
|
506
|
+
type: CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED,
|
|
507
|
+
reason: 'ASSIGN_FAILED',
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
webSocketManagerMock.emit('message', JSON.stringify(assignFailedPayload));
|
|
512
|
+
|
|
513
|
+
const stateMachineEvent = expectLastStateMachineEvent(sendSpy, TaskEvent.ASSIGN_FAILED);
|
|
514
|
+
expect(stateMachineEvent).toEqual({
|
|
515
|
+
type: TaskEvent.ASSIGN_FAILED,
|
|
516
|
+
reason: assignFailedPayload.data.reason,
|
|
517
|
+
});
|
|
518
|
+
expect(updateSpy).toHaveBeenCalledWith(assignFailedPayload.data);
|
|
519
|
+
expect(cleanupSpy).toHaveBeenCalledWith(task);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it('should update task data directly when no state machine mapping exists', () => {
|
|
523
|
+
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
524
|
+
const task = taskManager.getTask(taskId);
|
|
525
|
+
const updateSpy = task.updateTaskData as jest.Mock;
|
|
526
|
+
updateSpy.mockClear();
|
|
527
|
+
const sendSpy = task.sendStateMachineEvent as jest.Mock;
|
|
528
|
+
sendSpy.mockClear();
|
|
529
|
+
|
|
530
|
+
const participantMovedPayload = {
|
|
531
|
+
data: {
|
|
532
|
+
...initalPayload.data,
|
|
533
|
+
type: CC_EVENTS.CONSULTED_PARTICIPANT_MOVING,
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
webSocketManagerMock.emit('message', JSON.stringify(participantMovedPayload));
|
|
538
|
+
|
|
539
|
+
expect(sendSpy).not.toHaveBeenCalled();
|
|
540
|
+
expect(updateSpy).toHaveBeenCalledWith(participantMovedPayload.data);
|
|
348
541
|
});
|
|
349
542
|
|
|
350
543
|
it('should return task by ID', () => {
|
|
@@ -429,12 +622,30 @@ describe('TaskManager', () => {
|
|
|
429
622
|
it('test call listeners being switched off on call end', () => {
|
|
430
623
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
431
624
|
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
);
|
|
625
|
+
const webrtcTask = new WebRTC(contactMock, webCallingService, taskDataMock, {
|
|
626
|
+
isEndTaskEnabled: true,
|
|
627
|
+
isEndConsultEnabled: true,
|
|
628
|
+
});
|
|
629
|
+
(taskManager as any).taskCollection[taskId] = webrtcTask;
|
|
630
|
+
// TaskManager must listen to task-level cleanup events emitted by the state machine.
|
|
631
|
+
// This is normally wired when TaskManager creates the task via TaskFactory.
|
|
632
|
+
(taskManager as any).setupTaskListeners(webrtcTask);
|
|
633
|
+
|
|
634
|
+
const task = taskManager.getTask(taskId)!;
|
|
635
|
+
// This test doesn't validate UI controls; avoid requiring full interaction.media
|
|
636
|
+
// shape for WebRTC UI controls computation.
|
|
637
|
+
jest.spyOn(task as any, 'updateUiControls').mockImplementation(() => undefined);
|
|
638
|
+
const originalEmit = task.emit;
|
|
639
|
+
jest.spyOn(task, 'emit').mockImplementation((event, arg) => {
|
|
640
|
+
if (event === CC_EVENTS.CONTACT_ENDED) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
return originalEmit.call(task, event, arg);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
const webCallListenerSpy = jest.spyOn(task, 'unregisterWebCallListeners');
|
|
437
647
|
const callOffSpy = jest.spyOn(mockCall, 'off');
|
|
648
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
438
649
|
const payload = {
|
|
439
650
|
data: {
|
|
440
651
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
@@ -452,10 +663,25 @@ describe('TaskManager', () => {
|
|
|
452
663
|
},
|
|
453
664
|
};
|
|
454
665
|
|
|
666
|
+
// Ensure the state machine is hydrated into a connected state before CONTACT_ENDED
|
|
667
|
+
const hydratePayload = {
|
|
668
|
+
data: {
|
|
669
|
+
...payload.data,
|
|
670
|
+
type: CC_EVENTS.AGENT_CONTACT,
|
|
671
|
+
interaction: {state: 'connected', mediaType: 'telephony'},
|
|
672
|
+
},
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
taskManager.getTask(taskId).data = hydratePayload.data;
|
|
676
|
+
webSocketManagerMock.emit('message', JSON.stringify(hydratePayload));
|
|
677
|
+
|
|
455
678
|
taskManager.getTask(taskId).data = payload.data;
|
|
456
|
-
const task = taskManager.getTask(taskId);
|
|
457
679
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
458
|
-
|
|
680
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
681
|
+
sendStateMachineEventSpy,
|
|
682
|
+
TaskEvent.CONTACT_ENDED
|
|
683
|
+
);
|
|
684
|
+
expect(stateMachineEvent?.taskData.wrapUpRequired).toBe(false);
|
|
459
685
|
expect(webCallListenerSpy).toHaveBeenCalledWith();
|
|
460
686
|
expect(callOffSpy).toHaveBeenCalledWith(
|
|
461
687
|
CALL_EVENT_KEYS.REMOTE_MEDIA,
|
|
@@ -466,12 +692,13 @@ describe('TaskManager', () => {
|
|
|
466
692
|
expect(offSpy.mock.calls.length).toBe(2); // 1 for incoming call and 1 for remote media
|
|
467
693
|
expect(offSpy).toHaveBeenCalledWith(CALL_EVENT_KEYS.REMOTE_MEDIA, offSpy.mock.calls[0][1]);
|
|
468
694
|
expect(offSpy).toHaveBeenCalledWith(LINE_EVENTS.INCOMING_CALL, offSpy.mock.calls[1][1]);
|
|
695
|
+
sendStateMachineEventSpy.mockRestore();
|
|
469
696
|
});
|
|
470
697
|
|
|
471
698
|
it('should emit TASK_END event with wrapupRequired on regular call end', () => {
|
|
472
699
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
473
|
-
|
|
474
|
-
const
|
|
700
|
+
const task = taskManager.getTask(taskId);
|
|
701
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
475
702
|
const payload = {
|
|
476
703
|
data: {
|
|
477
704
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
@@ -489,17 +716,21 @@ describe('TaskManager', () => {
|
|
|
489
716
|
},
|
|
490
717
|
};
|
|
491
718
|
|
|
492
|
-
|
|
719
|
+
task.updateTaskData(payload.data);
|
|
493
720
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
494
|
-
|
|
495
|
-
|
|
721
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
722
|
+
sendStateMachineEventSpy,
|
|
723
|
+
TaskEvent.CONTACT_ENDED
|
|
724
|
+
);
|
|
725
|
+
expect(stateMachineEvent?.taskData.wrapUpRequired).toBe(true);
|
|
726
|
+
sendStateMachineEventSpy.mockRestore();
|
|
496
727
|
});
|
|
497
728
|
|
|
498
729
|
it('should emit TASK_REJECT event on AGENT_INVITE_FAILED event', () => {
|
|
499
730
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
500
731
|
|
|
501
|
-
const
|
|
502
|
-
const
|
|
732
|
+
const task = taskManager.getTask(taskId);
|
|
733
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
503
734
|
const payload = {
|
|
504
735
|
data: {
|
|
505
736
|
type: CC_EVENTS.AGENT_INVITE_FAILED,
|
|
@@ -518,35 +749,40 @@ describe('TaskManager', () => {
|
|
|
518
749
|
},
|
|
519
750
|
};
|
|
520
751
|
|
|
521
|
-
|
|
752
|
+
task.updateTaskData(payload.data);
|
|
522
753
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
expect(
|
|
754
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
755
|
+
sendStateMachineEventSpy,
|
|
756
|
+
TaskEvent.INVITE_FAILED
|
|
757
|
+
);
|
|
758
|
+
expect(stateMachineEvent?.reason).toBe(payload.data.reason);
|
|
759
|
+
sendStateMachineEventSpy.mockRestore();
|
|
528
760
|
});
|
|
529
761
|
|
|
530
|
-
it('should
|
|
762
|
+
it('should emit TASK_HYDRATE even if task is already present in taskManager', () => {
|
|
531
763
|
const payload = {
|
|
532
764
|
data: {
|
|
533
765
|
...initalPayload.data,
|
|
534
766
|
type: CC_EVENTS.AGENT_CONTACT,
|
|
535
767
|
},
|
|
536
768
|
};
|
|
537
|
-
const
|
|
769
|
+
const existingTask = taskManager.getTask(taskId);
|
|
770
|
+
const sendStateMachineEventSpy = jest.spyOn(existingTask, 'sendStateMachineEvent');
|
|
538
771
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
539
772
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
773
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
774
|
+
sendStateMachineEventSpy,
|
|
775
|
+
TaskEvent.HYDRATE
|
|
543
776
|
);
|
|
777
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
778
|
+
expect(existingTask).toBe(taskManager.getTask(taskId));
|
|
544
779
|
expect(taskManager.taskCollection[payload.data.interactionId]).toBe(
|
|
545
780
|
taskManager.getTask(taskId)
|
|
546
781
|
);
|
|
782
|
+
sendStateMachineEventSpy.mockRestore();
|
|
547
783
|
});
|
|
548
784
|
|
|
549
|
-
it('should emit
|
|
785
|
+
it('should emit TASK_HYDRATE event on AGENT_CONTACT when task is created from payload', () => {
|
|
550
786
|
taskManager.taskCollection = [];
|
|
551
787
|
const payload = {
|
|
552
788
|
data: {
|
|
@@ -556,13 +792,15 @@ describe('TaskManager', () => {
|
|
|
556
792
|
},
|
|
557
793
|
};
|
|
558
794
|
|
|
559
|
-
const taskEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
560
795
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
561
796
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
797
|
+
const createdTask = taskManager.getTask(taskId);
|
|
798
|
+
const sendStateMachineEventSpy = createdTask.sendStateMachineEvent as jest.Mock;
|
|
799
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
800
|
+
sendStateMachineEventSpy,
|
|
801
|
+
TaskEvent.HYDRATE
|
|
565
802
|
);
|
|
803
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
566
804
|
expect(taskManager.taskCollection[payload.data.interactionId]).toBe(
|
|
567
805
|
taskManager.getTask(taskId)
|
|
568
806
|
);
|
|
@@ -577,10 +815,15 @@ describe('TaskManager', () => {
|
|
|
577
815
|
},
|
|
578
816
|
};
|
|
579
817
|
|
|
580
|
-
const taskEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
581
818
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
582
819
|
|
|
583
|
-
|
|
820
|
+
const createdTask = taskManager.getTask(taskId);
|
|
821
|
+
const sendStateMachineEventSpy = createdTask.sendStateMachineEvent as jest.Mock;
|
|
822
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
823
|
+
sendStateMachineEventSpy,
|
|
824
|
+
TaskEvent.HYDRATE
|
|
825
|
+
);
|
|
826
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
584
827
|
expect(taskManager.taskCollection[payload.data.interactionId]).toBe(
|
|
585
828
|
taskManager.getTask(taskId)
|
|
586
829
|
);
|
|
@@ -653,7 +896,7 @@ describe('TaskManager', () => {
|
|
|
653
896
|
expect(createdTask.data.isConferenceInProgress).toBe(false);
|
|
654
897
|
});
|
|
655
898
|
|
|
656
|
-
it('should emit
|
|
899
|
+
it('should emit TASK_WRAPUP event on AGENT_WRAPUP event', () => {
|
|
657
900
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
658
901
|
|
|
659
902
|
const wrapupPayload = {
|
|
@@ -675,13 +918,15 @@ describe('TaskManager', () => {
|
|
|
675
918
|
};
|
|
676
919
|
|
|
677
920
|
const task = taskManager.getTask(taskId);
|
|
678
|
-
const updateTaskDataSpy = jest.
|
|
679
|
-
|
|
921
|
+
const updateTaskDataSpy = task.updateTaskData as jest.Mock;
|
|
922
|
+
updateTaskDataSpy.mockClear();
|
|
923
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
680
924
|
|
|
681
925
|
webSocketManagerMock.emit('message', JSON.stringify(wrapupPayload));
|
|
682
926
|
|
|
683
927
|
expect(updateTaskDataSpy).toHaveBeenCalledWith(wrapupPayload.data);
|
|
684
|
-
|
|
928
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.TASK_WRAPUP);
|
|
929
|
+
sendStateMachineEventSpy.mockRestore();
|
|
685
930
|
});
|
|
686
931
|
|
|
687
932
|
it('should emit TASK_HOLD event on AGENT_CONTACT_HELD event', () => {
|
|
@@ -704,13 +949,20 @@ describe('TaskManager', () => {
|
|
|
704
949
|
},
|
|
705
950
|
};
|
|
706
951
|
|
|
707
|
-
const
|
|
708
|
-
const taskUpdateTaskDataSpy = jest.
|
|
952
|
+
const task = taskManager.getTask(taskId);
|
|
953
|
+
const taskUpdateTaskDataSpy = task.updateTaskData as jest.Mock;
|
|
954
|
+
taskUpdateTaskDataSpy.mockClear();
|
|
955
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
709
956
|
|
|
710
957
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
711
958
|
|
|
712
959
|
expect(taskUpdateTaskDataSpy).toHaveBeenCalledWith(payload.data);
|
|
713
|
-
|
|
960
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
961
|
+
sendStateMachineEventSpy,
|
|
962
|
+
TaskEvent.HOLD_SUCCESS
|
|
963
|
+
);
|
|
964
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
965
|
+
sendStateMachineEventSpy.mockRestore();
|
|
714
966
|
});
|
|
715
967
|
|
|
716
968
|
it('should emit TASK_RESUME event on AGENT_CONTACT_UNHELD event', () => {
|
|
@@ -733,11 +985,18 @@ describe('TaskManager', () => {
|
|
|
733
985
|
},
|
|
734
986
|
};
|
|
735
987
|
|
|
736
|
-
const
|
|
737
|
-
const taskUpdateTaskDataSpy = jest.
|
|
988
|
+
const task = taskManager.getTask(taskId);
|
|
989
|
+
const taskUpdateTaskDataSpy = task.updateTaskData as jest.Mock;
|
|
990
|
+
taskUpdateTaskDataSpy.mockClear();
|
|
991
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
738
992
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
739
993
|
expect(taskUpdateTaskDataSpy).toHaveBeenCalledWith(payload.data);
|
|
740
|
-
|
|
994
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
995
|
+
sendStateMachineEventSpy,
|
|
996
|
+
TaskEvent.UNHOLD_SUCCESS
|
|
997
|
+
);
|
|
998
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
999
|
+
sendStateMachineEventSpy.mockRestore();
|
|
741
1000
|
});
|
|
742
1001
|
|
|
743
1002
|
it('handle AGENT_CONSULT_CREATED event', () => {
|
|
@@ -750,17 +1009,16 @@ describe('TaskManager', () => {
|
|
|
750
1009
|
|
|
751
1010
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
752
1011
|
const task = taskManager.getTask(taskId);
|
|
753
|
-
const
|
|
754
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
1012
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
755
1013
|
|
|
756
1014
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
757
1015
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
expect(
|
|
763
|
-
|
|
1016
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1017
|
+
sendStateMachineEventSpy,
|
|
1018
|
+
TaskEvent.CONSULT_CREATED
|
|
1019
|
+
);
|
|
1020
|
+
expect(stateMachineEvent?.taskData).toEqual({...payload.data, isConsulted: false});
|
|
1021
|
+
sendStateMachineEventSpy.mockRestore();
|
|
764
1022
|
});
|
|
765
1023
|
|
|
766
1024
|
it('handle AGENT_OFFER_CONTACT event', () => {
|
|
@@ -773,11 +1031,17 @@ describe('TaskManager', () => {
|
|
|
773
1031
|
|
|
774
1032
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
775
1033
|
|
|
776
|
-
const
|
|
1034
|
+
const task = taskManager.getTask(taskId);
|
|
1035
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
777
1036
|
|
|
778
1037
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
779
1038
|
|
|
780
|
-
|
|
1039
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1040
|
+
sendStateMachineEventSpy,
|
|
1041
|
+
TaskEvent.TASK_OFFERED
|
|
1042
|
+
);
|
|
1043
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1044
|
+
sendStateMachineEventSpy.mockRestore();
|
|
781
1045
|
});
|
|
782
1046
|
|
|
783
1047
|
describe('Auto-Answer Functionality', () => {
|
|
@@ -787,8 +1051,8 @@ describe('TaskManager', () => {
|
|
|
787
1051
|
|
|
788
1052
|
const task = taskManager.getTask(taskId);
|
|
789
1053
|
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
790
|
-
const taskManagerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
791
1054
|
const taskAcceptSpy = jest.spyOn(task, 'accept').mockResolvedValue(undefined);
|
|
1055
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
792
1056
|
|
|
793
1057
|
// Step 2: Trigger AGENT_OFFER_CONTACT with auto-answer
|
|
794
1058
|
const autoAnswerPayload = {
|
|
@@ -812,9 +1076,14 @@ describe('TaskManager', () => {
|
|
|
812
1076
|
// Verify accept was called
|
|
813
1077
|
expect(taskAcceptSpy).toHaveBeenCalledTimes(1);
|
|
814
1078
|
|
|
815
|
-
|
|
816
|
-
|
|
1079
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1080
|
+
sendStateMachineEventSpy,
|
|
1081
|
+
TaskEvent.TASK_OFFERED
|
|
1082
|
+
);
|
|
1083
|
+
expect(stateMachineEvent?.taskData).toEqual(autoAnswerPayload.data);
|
|
1084
|
+
// Verify task auto-answer event was emitted
|
|
817
1085
|
expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_AUTO_ANSWERED, task);
|
|
1086
|
+
sendStateMachineEventSpy.mockRestore();
|
|
818
1087
|
});
|
|
819
1088
|
|
|
820
1089
|
it('should NOT emit TASK_AUTO_ANSWERED event when auto-answer fails', async () => {
|
|
@@ -860,6 +1129,7 @@ describe('TaskManager', () => {
|
|
|
860
1129
|
const task = taskManager.getTask(taskId);
|
|
861
1130
|
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
862
1131
|
const taskAcceptSpy = jest.spyOn(task, 'accept').mockResolvedValue(undefined);
|
|
1132
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
863
1133
|
|
|
864
1134
|
// Step 2: Trigger AGENT_OFFER_CONSULT with auto-answer
|
|
865
1135
|
const consultAutoAnswerPayload = {
|
|
@@ -884,12 +1154,20 @@ describe('TaskManager', () => {
|
|
|
884
1154
|
// Verify accept was called
|
|
885
1155
|
expect(taskAcceptSpy).toHaveBeenCalledTimes(1);
|
|
886
1156
|
|
|
887
|
-
|
|
888
|
-
|
|
1157
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1158
|
+
sendStateMachineEventSpy,
|
|
1159
|
+
TaskEvent.OFFER_CONSULT
|
|
1160
|
+
);
|
|
1161
|
+
expect(stateMachineEvent?.taskData).toEqual({
|
|
1162
|
+
...consultAutoAnswerPayload.data,
|
|
1163
|
+
isConsulted: true,
|
|
1164
|
+
});
|
|
1165
|
+
// Verify task auto-answer event was emitted
|
|
889
1166
|
expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_AUTO_ANSWERED, task);
|
|
890
1167
|
|
|
891
1168
|
// Verify isConsulted flag is set correctly
|
|
892
1169
|
expect(task.data.isConsulted).toBe(true);
|
|
1170
|
+
sendStateMachineEventSpy.mockRestore();
|
|
893
1171
|
});
|
|
894
1172
|
|
|
895
1173
|
it('should NOT emit TASK_AUTO_ANSWERED when isAutoAnswering is false', async () => {
|
|
@@ -930,24 +1208,20 @@ describe('TaskManager', () => {
|
|
|
930
1208
|
});
|
|
931
1209
|
});
|
|
932
1210
|
|
|
933
|
-
it('should
|
|
1211
|
+
it('should remove OUTDIAL task from taskCollection on AGENT_OUTBOUND_FAILED when terminated', () => {
|
|
934
1212
|
const task = taskManager.getTask(taskId);
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
...task.data,
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
state: 'new',
|
|
944
|
-
isTerminated: true,
|
|
945
|
-
},
|
|
946
|
-
};
|
|
947
|
-
return task;
|
|
1213
|
+
Object.assign(task.data, {
|
|
1214
|
+
interaction: {
|
|
1215
|
+
...task.data.interaction,
|
|
1216
|
+
outboundType: 'OUTDIAL',
|
|
1217
|
+
state: 'new',
|
|
1218
|
+
isTerminated: true,
|
|
1219
|
+
},
|
|
1220
|
+
agentsPendingWrapUp: ['agent-123'],
|
|
948
1221
|
});
|
|
949
1222
|
task.unregisterWebCallListeners = jest.fn();
|
|
950
1223
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
1224
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
951
1225
|
|
|
952
1226
|
const payload = {
|
|
953
1227
|
data: {
|
|
@@ -960,6 +1234,7 @@ describe('TaskManager', () => {
|
|
|
960
1234
|
state: 'new',
|
|
961
1235
|
isTerminated: true,
|
|
962
1236
|
},
|
|
1237
|
+
agentsPendingWrapUp: ['agent-123'],
|
|
963
1238
|
interactionId: taskId,
|
|
964
1239
|
orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a',
|
|
965
1240
|
trackingId: '575c0ec2-618c-42af-a61c-53aeb0a221ee',
|
|
@@ -974,14 +1249,21 @@ describe('TaskManager', () => {
|
|
|
974
1249
|
|
|
975
1250
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
976
1251
|
|
|
977
|
-
|
|
978
|
-
|
|
1252
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1253
|
+
|
|
1254
|
+
expect(taskManager.getTask(taskId)).toBeUndefined();
|
|
1255
|
+
expect(removeTaskSpy).toHaveBeenCalled();
|
|
1256
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1257
|
+
sendStateMachineEventSpy,
|
|
1258
|
+
TaskEvent.OUTBOUND_FAILED
|
|
1259
|
+
);
|
|
1260
|
+
expect(stateMachineEvent?.reason).toBe('CUSTOMER_BUSY');
|
|
1261
|
+
sendStateMachineEventSpy.mockRestore();
|
|
979
1262
|
});
|
|
980
1263
|
|
|
981
1264
|
it('should emit TASK_OUTDIAL_FAILED event on AGENT_OUTBOUND_FAILED', () => {
|
|
982
1265
|
const task = taskManager.getTask(taskId);
|
|
983
|
-
|
|
984
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
1266
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
985
1267
|
const payload = {
|
|
986
1268
|
data: {
|
|
987
1269
|
type: CC_EVENTS.AGENT_OUTBOUND_FAILED,
|
|
@@ -990,7 +1272,12 @@ describe('TaskManager', () => {
|
|
|
990
1272
|
},
|
|
991
1273
|
};
|
|
992
1274
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
993
|
-
|
|
1275
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1276
|
+
sendStateMachineEventSpy,
|
|
1277
|
+
TaskEvent.OUTBOUND_FAILED
|
|
1278
|
+
);
|
|
1279
|
+
expect(stateMachineEvent?.reason).toBe('CUSTOMER_BUSY');
|
|
1280
|
+
sendStateMachineEventSpy.mockRestore();
|
|
994
1281
|
});
|
|
995
1282
|
|
|
996
1283
|
it('should handle AGENT_OUTBOUND_FAILED gracefully when task is undefined', () => {
|
|
@@ -1008,23 +1295,15 @@ describe('TaskManager', () => {
|
|
|
1008
1295
|
});
|
|
1009
1296
|
|
|
1010
1297
|
it('should NOT remove OUTDIAL task on CONTACT_ENDED when agentsPendingWrapUp exists', () => {
|
|
1011
|
-
const agentId = '723a8ffb-a26e-496d-b14a-ff44fb83b64f';
|
|
1012
|
-
taskManager.setAgentId(agentId);
|
|
1013
|
-
|
|
1014
1298
|
const task = taskManager.getTask(taskId);
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
...task.data,
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
mediaType: 'telephony',
|
|
1024
|
-
},
|
|
1025
|
-
agentsPendingWrapUp: [agentId],
|
|
1026
|
-
};
|
|
1027
|
-
return task;
|
|
1299
|
+
Object.assign(task.data, {
|
|
1300
|
+
interaction: {
|
|
1301
|
+
...task.data.interaction,
|
|
1302
|
+
outboundType: 'OUTDIAL',
|
|
1303
|
+
state: 'new',
|
|
1304
|
+
mediaType: 'telephony',
|
|
1305
|
+
},
|
|
1306
|
+
agentsPendingWrapUp: ['agent-123'],
|
|
1028
1307
|
});
|
|
1029
1308
|
task.unregisterWebCallListeners = jest.fn();
|
|
1030
1309
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
@@ -1038,7 +1317,7 @@ describe('TaskManager', () => {
|
|
|
1038
1317
|
state: 'new',
|
|
1039
1318
|
mediaType: 'telephony',
|
|
1040
1319
|
},
|
|
1041
|
-
agentsPendingWrapUp: [
|
|
1320
|
+
agentsPendingWrapUp: ['agent-123'],
|
|
1042
1321
|
},
|
|
1043
1322
|
};
|
|
1044
1323
|
|
|
@@ -1050,19 +1329,14 @@ describe('TaskManager', () => {
|
|
|
1050
1329
|
|
|
1051
1330
|
it('should remove OUTDIAL task on CONTACT_ENDED when agentsPendingWrapUp is empty', () => {
|
|
1052
1331
|
const task = taskManager.getTask(taskId);
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
...task.data,
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
mediaType: 'telephony',
|
|
1062
|
-
},
|
|
1063
|
-
agentsPendingWrapUp: [],
|
|
1064
|
-
};
|
|
1065
|
-
return task;
|
|
1332
|
+
Object.assign(task.data, {
|
|
1333
|
+
interaction: {
|
|
1334
|
+
...task.data.interaction,
|
|
1335
|
+
outboundType: 'OUTDIAL',
|
|
1336
|
+
state: 'new',
|
|
1337
|
+
mediaType: 'telephony',
|
|
1338
|
+
},
|
|
1339
|
+
agentsPendingWrapUp: [],
|
|
1066
1340
|
});
|
|
1067
1341
|
task.unregisterWebCallListeners = jest.fn();
|
|
1068
1342
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
@@ -1087,19 +1361,13 @@ describe('TaskManager', () => {
|
|
|
1087
1361
|
|
|
1088
1362
|
it('should remove OUTDIAL task on CONTACT_ENDED when agentsPendingWrapUp is undefined', () => {
|
|
1089
1363
|
const task = taskManager.getTask(taskId);
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
...task.data,
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
state: 'new',
|
|
1098
|
-
mediaType: 'telephony',
|
|
1099
|
-
},
|
|
1100
|
-
// agentsPendingWrapUp is undefined
|
|
1101
|
-
};
|
|
1102
|
-
return task;
|
|
1364
|
+
Object.assign(task.data, {
|
|
1365
|
+
interaction: {
|
|
1366
|
+
...task.data.interaction,
|
|
1367
|
+
outboundType: 'OUTDIAL',
|
|
1368
|
+
state: 'new',
|
|
1369
|
+
mediaType: 'telephony',
|
|
1370
|
+
},
|
|
1103
1371
|
});
|
|
1104
1372
|
task.unregisterWebCallListeners = jest.fn();
|
|
1105
1373
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
@@ -1138,350 +1406,77 @@ describe('TaskManager', () => {
|
|
|
1138
1406
|
}).not.toThrow();
|
|
1139
1407
|
});
|
|
1140
1408
|
|
|
1141
|
-
|
|
1142
|
-
const
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1409
|
+
it('should remove OUTDIAL task from taskCollection on AGENT_CONTACT_ASSIGN_FAILED when NOT terminated (user-declined)', () => {
|
|
1410
|
+
const task = taskManager.getTask(taskId);
|
|
1411
|
+
Object.assign(task.data, {
|
|
1412
|
+
interaction: {
|
|
1413
|
+
...task.data.interaction,
|
|
1414
|
+
outboundType: 'OUTDIAL',
|
|
1415
|
+
state: 'new',
|
|
1416
|
+
isTerminated: false,
|
|
1417
|
+
},
|
|
1147
1418
|
});
|
|
1419
|
+
task.unregisterWebCallListeners = jest.fn();
|
|
1420
|
+
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
1148
1421
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
const payload = {
|
|
1161
|
-
data: {
|
|
1162
|
-
type: CC_EVENTS.CONTACT_ENDED,
|
|
1163
|
-
interactionId: taskId,
|
|
1164
|
-
interaction: {
|
|
1165
|
-
state: 'connected',
|
|
1166
|
-
mediaType: 'telephony',
|
|
1167
|
-
},
|
|
1168
|
-
agentsPendingWrapUp: [agentId, 'other-agent-id'],
|
|
1422
|
+
const payload = {
|
|
1423
|
+
data: {
|
|
1424
|
+
type: CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED,
|
|
1425
|
+
agentId: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
1426
|
+
eventTime: 1733211616959,
|
|
1427
|
+
eventType: 'RoutingMessage',
|
|
1428
|
+
interaction: {
|
|
1429
|
+
outboundType: 'OUTDIAL',
|
|
1430
|
+
state: 'new',
|
|
1431
|
+
isTerminated: false,
|
|
1169
1432
|
},
|
|
1170
|
-
|
|
1433
|
+
interactionId: taskId,
|
|
1434
|
+
orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a',
|
|
1435
|
+
trackingId: '575c0ec2-618c-42af-a61c-53aeb0a221ee',
|
|
1436
|
+
mediaResourceId: '0ae913a4-c857-4705-8d49-76dd3dde75e4',
|
|
1437
|
+
destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
|
|
1438
|
+
owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
1439
|
+
queueMgr: 'aqm',
|
|
1440
|
+
reason: 'USER_DECLINED',
|
|
1441
|
+
reasonCode: 156,
|
|
1442
|
+
},
|
|
1443
|
+
};
|
|
1171
1444
|
|
|
1172
|
-
|
|
1445
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1173
1446
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
})
|
|
1178
|
-
);
|
|
1179
|
-
});
|
|
1447
|
+
expect(taskManager.getTask(taskId)).toBeUndefined();
|
|
1448
|
+
expect(removeTaskSpy).toHaveBeenCalled();
|
|
1449
|
+
});
|
|
1180
1450
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
return task;
|
|
1189
|
-
});
|
|
1190
|
-
task.unregisterWebCallListeners = jest.fn();
|
|
1451
|
+
it('handle AGENT_OFFER_CONSULT event', () => {
|
|
1452
|
+
const payload = {
|
|
1453
|
+
data: {
|
|
1454
|
+
...initalPayload.data,
|
|
1455
|
+
type: CC_EVENTS.AGENT_OFFER_CONSULT,
|
|
1456
|
+
},
|
|
1457
|
+
};
|
|
1191
1458
|
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
interactionId: taskId,
|
|
1196
|
-
interaction: {
|
|
1197
|
-
state: 'connected',
|
|
1198
|
-
mediaType: 'telephony',
|
|
1199
|
-
},
|
|
1200
|
-
agentsPendingWrapUp: ['other-agent-id', 'another-agent-id'],
|
|
1201
|
-
},
|
|
1202
|
-
};
|
|
1459
|
+
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1460
|
+
const task = taskManager.getTask(taskId);
|
|
1461
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1203
1462
|
|
|
1204
|
-
|
|
1463
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1205
1464
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1465
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1466
|
+
sendStateMachineEventSpy,
|
|
1467
|
+
TaskEvent.OFFER_CONSULT
|
|
1468
|
+
);
|
|
1469
|
+
expect(stateMachineEvent?.taskData).toEqual({...payload.data, isConsulted: true});
|
|
1470
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1471
|
+
});
|
|
1212
1472
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
return task;
|
|
1221
|
-
});
|
|
1222
|
-
task.unregisterWebCallListeners = jest.fn();
|
|
1223
|
-
|
|
1224
|
-
const payload = {
|
|
1225
|
-
data: {
|
|
1226
|
-
type: CC_EVENTS.CONTACT_ENDED,
|
|
1227
|
-
interactionId: taskId,
|
|
1228
|
-
interaction: {
|
|
1229
|
-
state: 'connected',
|
|
1230
|
-
mediaType: 'telephony',
|
|
1231
|
-
},
|
|
1232
|
-
agentsPendingWrapUp: [],
|
|
1233
|
-
},
|
|
1234
|
-
};
|
|
1235
|
-
|
|
1236
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1237
|
-
|
|
1238
|
-
expect(task.updateTaskData).toHaveBeenCalledWith(
|
|
1239
|
-
expect.objectContaining({
|
|
1240
|
-
wrapUpRequired: false,
|
|
1241
|
-
})
|
|
1242
|
-
);
|
|
1243
|
-
});
|
|
1244
|
-
|
|
1245
|
-
it('should set wrapUpRequired to false when agentsPendingWrapUp is undefined', () => {
|
|
1246
|
-
const task = taskManager.getTask(taskId);
|
|
1247
|
-
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1248
|
-
task.data = {
|
|
1249
|
-
...task.data,
|
|
1250
|
-
...newData,
|
|
1251
|
-
};
|
|
1252
|
-
return task;
|
|
1253
|
-
});
|
|
1254
|
-
task.unregisterWebCallListeners = jest.fn();
|
|
1255
|
-
|
|
1256
|
-
const payload = {
|
|
1257
|
-
data: {
|
|
1258
|
-
type: CC_EVENTS.CONTACT_ENDED,
|
|
1259
|
-
interactionId: taskId,
|
|
1260
|
-
interaction: {
|
|
1261
|
-
state: 'connected',
|
|
1262
|
-
mediaType: 'telephony',
|
|
1263
|
-
},
|
|
1264
|
-
// agentsPendingWrapUp is not defined
|
|
1265
|
-
},
|
|
1266
|
-
};
|
|
1267
|
-
|
|
1268
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1269
|
-
|
|
1270
|
-
expect(task.updateTaskData).toHaveBeenCalledWith(
|
|
1271
|
-
expect.objectContaining({
|
|
1272
|
-
wrapUpRequired: false,
|
|
1273
|
-
})
|
|
1274
|
-
);
|
|
1275
|
-
});
|
|
1276
|
-
|
|
1277
|
-
it('should set wrapUpRequired to false when agentsPendingWrapUp is null', () => {
|
|
1278
|
-
const task = taskManager.getTask(taskId);
|
|
1279
|
-
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1280
|
-
task.data = {
|
|
1281
|
-
...task.data,
|
|
1282
|
-
...newData,
|
|
1283
|
-
};
|
|
1284
|
-
return task;
|
|
1285
|
-
});
|
|
1286
|
-
task.unregisterWebCallListeners = jest.fn();
|
|
1287
|
-
|
|
1288
|
-
const payload = {
|
|
1289
|
-
data: {
|
|
1290
|
-
type: CC_EVENTS.CONTACT_ENDED,
|
|
1291
|
-
interactionId: taskId,
|
|
1292
|
-
interaction: {
|
|
1293
|
-
state: 'connected',
|
|
1294
|
-
mediaType: 'telephony',
|
|
1295
|
-
},
|
|
1296
|
-
agentsPendingWrapUp: null,
|
|
1297
|
-
},
|
|
1298
|
-
};
|
|
1299
|
-
|
|
1300
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1301
|
-
|
|
1302
|
-
expect(task.updateTaskData).toHaveBeenCalledWith(
|
|
1303
|
-
expect.objectContaining({
|
|
1304
|
-
wrapUpRequired: false,
|
|
1305
|
-
})
|
|
1306
|
-
);
|
|
1307
|
-
});
|
|
1308
|
-
|
|
1309
|
-
it('should set wrapUpRequired correctly when agent is the only one in agentsPendingWrapUp', () => {
|
|
1310
|
-
const task = taskManager.getTask(taskId);
|
|
1311
|
-
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1312
|
-
task.data = {
|
|
1313
|
-
...task.data,
|
|
1314
|
-
...newData,
|
|
1315
|
-
};
|
|
1316
|
-
return task;
|
|
1317
|
-
});
|
|
1318
|
-
task.unregisterWebCallListeners = jest.fn();
|
|
1319
|
-
|
|
1320
|
-
const payload = {
|
|
1321
|
-
data: {
|
|
1322
|
-
type: CC_EVENTS.CONTACT_ENDED,
|
|
1323
|
-
interactionId: taskId,
|
|
1324
|
-
interaction: {
|
|
1325
|
-
state: 'connected',
|
|
1326
|
-
mediaType: 'telephony',
|
|
1327
|
-
},
|
|
1328
|
-
agentsPendingWrapUp: [agentId],
|
|
1329
|
-
},
|
|
1330
|
-
};
|
|
1331
|
-
|
|
1332
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1333
|
-
|
|
1334
|
-
expect(task.updateTaskData).toHaveBeenCalledWith(
|
|
1335
|
-
expect.objectContaining({
|
|
1336
|
-
wrapUpRequired: true,
|
|
1337
|
-
})
|
|
1338
|
-
);
|
|
1339
|
-
});
|
|
1340
|
-
|
|
1341
|
-
it('should work correctly for different interaction states when agent is in agentsPendingWrapUp', () => {
|
|
1342
|
-
const task = taskManager.getTask(taskId);
|
|
1343
|
-
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1344
|
-
task.data = {
|
|
1345
|
-
...task.data,
|
|
1346
|
-
...newData,
|
|
1347
|
-
interaction: {
|
|
1348
|
-
...task.data.interaction,
|
|
1349
|
-
...newData.interaction,
|
|
1350
|
-
},
|
|
1351
|
-
};
|
|
1352
|
-
return task;
|
|
1353
|
-
});
|
|
1354
|
-
task.unregisterWebCallListeners = jest.fn();
|
|
1355
|
-
|
|
1356
|
-
// Test with 'connected' state
|
|
1357
|
-
const payloadConnected = {
|
|
1358
|
-
data: {
|
|
1359
|
-
type: CC_EVENTS.CONTACT_ENDED,
|
|
1360
|
-
interactionId: taskId,
|
|
1361
|
-
interaction: {
|
|
1362
|
-
state: 'connected',
|
|
1363
|
-
mediaType: 'telephony',
|
|
1364
|
-
},
|
|
1365
|
-
agentsPendingWrapUp: [agentId],
|
|
1366
|
-
},
|
|
1367
|
-
};
|
|
1368
|
-
|
|
1369
|
-
webSocketManagerMock.emit('message', JSON.stringify(payloadConnected));
|
|
1370
|
-
|
|
1371
|
-
// First call should set wrapUpRequired to true
|
|
1372
|
-
expect(task.updateTaskData).toHaveBeenNthCalledWith(
|
|
1373
|
-
1,
|
|
1374
|
-
expect.objectContaining({
|
|
1375
|
-
wrapUpRequired: true,
|
|
1376
|
-
})
|
|
1377
|
-
);
|
|
1378
|
-
|
|
1379
|
-
// Test with 'held' state to verify it still works regardless of state
|
|
1380
|
-
const payloadHeld = {
|
|
1381
|
-
data: {
|
|
1382
|
-
type: CC_EVENTS.CONTACT_ENDED,
|
|
1383
|
-
interactionId: taskId,
|
|
1384
|
-
interaction: {
|
|
1385
|
-
state: 'held',
|
|
1386
|
-
mediaType: 'telephony',
|
|
1387
|
-
},
|
|
1388
|
-
agentsPendingWrapUp: [agentId],
|
|
1389
|
-
},
|
|
1390
|
-
};
|
|
1391
|
-
|
|
1392
|
-
webSocketManagerMock.emit('message', JSON.stringify(payloadHeld));
|
|
1393
|
-
|
|
1394
|
-
// Second call should also set wrapUpRequired to true
|
|
1395
|
-
expect(task.updateTaskData).toHaveBeenNthCalledWith(
|
|
1396
|
-
2,
|
|
1397
|
-
expect.objectContaining({
|
|
1398
|
-
wrapUpRequired: true,
|
|
1399
|
-
})
|
|
1400
|
-
);
|
|
1401
|
-
});
|
|
1402
|
-
});
|
|
1403
|
-
|
|
1404
|
-
it('should remove OUTDIAL task from taskCollection on AGENT_CONTACT_ASSIGN_FAILED when NOT terminated (user-declined)', () => {
|
|
1405
|
-
const task = taskManager.getTask(taskId);
|
|
1406
|
-
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1407
|
-
task.data = {
|
|
1408
|
-
...task.data,
|
|
1409
|
-
...newData,
|
|
1410
|
-
interaction: {
|
|
1411
|
-
...task.data.interaction,
|
|
1412
|
-
...newData.interaction,
|
|
1413
|
-
outboundType: 'OUTDIAL',
|
|
1414
|
-
state: 'new',
|
|
1415
|
-
isTerminated: false,
|
|
1416
|
-
},
|
|
1417
|
-
};
|
|
1418
|
-
return task;
|
|
1419
|
-
});
|
|
1420
|
-
task.unregisterWebCallListeners = jest.fn();
|
|
1421
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
1422
|
-
|
|
1423
|
-
const payload = {
|
|
1424
|
-
data: {
|
|
1425
|
-
type: CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED,
|
|
1426
|
-
agentId: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
1427
|
-
eventTime: 1733211616959,
|
|
1428
|
-
eventType: 'RoutingMessage',
|
|
1429
|
-
interaction: {
|
|
1430
|
-
outboundType: 'OUTDIAL',
|
|
1431
|
-
state: 'new',
|
|
1432
|
-
isTerminated: false,
|
|
1433
|
-
},
|
|
1434
|
-
interactionId: taskId,
|
|
1435
|
-
orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a',
|
|
1436
|
-
trackingId: '575c0ec2-618c-42af-a61c-53aeb0a221ee',
|
|
1437
|
-
mediaResourceId: '0ae913a4-c857-4705-8d49-76dd3dde75e4',
|
|
1438
|
-
destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
|
|
1439
|
-
owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
1440
|
-
queueMgr: 'aqm',
|
|
1441
|
-
reason: 'USER_DECLINED',
|
|
1442
|
-
reasonCode: 156,
|
|
1443
|
-
},
|
|
1444
|
-
};
|
|
1445
|
-
|
|
1446
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1447
|
-
|
|
1448
|
-
expect(taskManager.getTask(taskId)).toBeUndefined();
|
|
1449
|
-
expect(removeTaskSpy).toHaveBeenCalled();
|
|
1450
|
-
});
|
|
1451
|
-
|
|
1452
|
-
it('handle AGENT_OFFER_CONSULT event', () => {
|
|
1453
|
-
const payload = {
|
|
1454
|
-
data: {
|
|
1455
|
-
...initalPayload.data,
|
|
1456
|
-
type: CC_EVENTS.AGENT_OFFER_CONSULT,
|
|
1457
|
-
},
|
|
1458
|
-
};
|
|
1459
|
-
|
|
1460
|
-
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1461
|
-
const task = taskManager.getTask(taskId);
|
|
1462
|
-
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1463
|
-
task.data = {...newData, isConsulted: true};
|
|
1464
|
-
return task;
|
|
1465
|
-
});
|
|
1466
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
1467
|
-
|
|
1468
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1469
|
-
|
|
1470
|
-
expect(task.updateTaskData).toHaveBeenCalledWith({
|
|
1471
|
-
...payload.data,
|
|
1472
|
-
isConsulted: true,
|
|
1473
|
-
});
|
|
1474
|
-
expect(task.data.isConsulted).toBe(true);
|
|
1475
|
-
expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_OFFER_CONSULT, task);
|
|
1476
|
-
});
|
|
1477
|
-
|
|
1478
|
-
it('should emit TASK_CONSULT_ACCEPTED event on AGENT_CONSULTING event', () => {
|
|
1479
|
-
const initialConsultingPayload = {
|
|
1480
|
-
data: {
|
|
1481
|
-
...initalPayload.data,
|
|
1482
|
-
type: CC_EVENTS.AGENT_OFFER_CONSULT,
|
|
1483
|
-
},
|
|
1484
|
-
};
|
|
1473
|
+
it('should emit TASK_CONSULT_ACCEPTED event on AGENT_CONSULTING event', () => {
|
|
1474
|
+
const initialConsultingPayload = {
|
|
1475
|
+
data: {
|
|
1476
|
+
...initalPayload.data,
|
|
1477
|
+
type: CC_EVENTS.AGENT_OFFER_CONSULT,
|
|
1478
|
+
},
|
|
1479
|
+
};
|
|
1485
1480
|
|
|
1486
1481
|
const consultingPayload = {
|
|
1487
1482
|
data: {
|
|
@@ -1491,19 +1486,18 @@ describe('TaskManager', () => {
|
|
|
1491
1486
|
};
|
|
1492
1487
|
|
|
1493
1488
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1494
|
-
taskManager.getTask(taskId).updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1495
|
-
taskManager.getTask(taskId).data = {...newData, isConsulted: true};
|
|
1496
|
-
return taskManager.getTask(taskId);
|
|
1497
|
-
});
|
|
1498
1489
|
|
|
1499
|
-
const
|
|
1490
|
+
const task = taskManager.getTask(taskId);
|
|
1491
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1492
|
+
|
|
1500
1493
|
webSocketManagerMock.emit('message', JSON.stringify(initialConsultingPayload));
|
|
1501
1494
|
webSocketManagerMock.emit('message', JSON.stringify(consultingPayload));
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
taskManager.getTask(taskId)
|
|
1495
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1496
|
+
sendStateMachineEventSpy,
|
|
1497
|
+
TaskEvent.CONSULTING_ACTIVE
|
|
1506
1498
|
);
|
|
1499
|
+
expect(stateMachineEvent?.taskData).toEqual(consultingPayload.data);
|
|
1500
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1507
1501
|
});
|
|
1508
1502
|
|
|
1509
1503
|
it('should emit TASK_CONSULT_ENDED event on AGENT_CONSULT_ENDED event', () => {
|
|
@@ -1515,14 +1509,16 @@ describe('TaskManager', () => {
|
|
|
1515
1509
|
};
|
|
1516
1510
|
|
|
1517
1511
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1518
|
-
|
|
1519
|
-
const
|
|
1512
|
+
taskManager.getTask(taskId).data.isConsulted = true;
|
|
1513
|
+
const task = taskManager.getTask(taskId);
|
|
1514
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1520
1515
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
taskManager.getTask(taskId)
|
|
1516
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1517
|
+
sendStateMachineEventSpy,
|
|
1518
|
+
TaskEvent.CONSULT_END
|
|
1525
1519
|
);
|
|
1520
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1521
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1526
1522
|
});
|
|
1527
1523
|
|
|
1528
1524
|
it('should emit TASK_CONSULT_ENDED event and remove currentTask when on AGENT_CONSULT_ENDED event when requested for a consult', () => {
|
|
@@ -1535,19 +1531,14 @@ describe('TaskManager', () => {
|
|
|
1535
1531
|
|
|
1536
1532
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1537
1533
|
|
|
1538
|
-
taskManager.getTask(taskId).
|
|
1539
|
-
taskManager.getTask(taskId).data = {...newData, isConsulted: true};
|
|
1540
|
-
return taskManager.getTask(taskId);
|
|
1541
|
-
});
|
|
1534
|
+
taskManager.getTask(taskId).data.isConsulted = true;
|
|
1542
1535
|
const task = taskManager.getTask(taskId);
|
|
1543
1536
|
|
|
1544
|
-
const
|
|
1545
|
-
const taskUpdateTaskDataSpy = jest.spyOn(taskManager.getTask(taskId), 'updateTaskData');
|
|
1537
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1546
1538
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1547
|
-
|
|
1548
|
-
expect(taskEmitSpy).toHaveBeenCalledWith(CC_EVENTS.AGENT_CONSULT_ENDED, payload.data);
|
|
1549
|
-
expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULT_END, task);
|
|
1539
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.CONSULT_END);
|
|
1550
1540
|
expect(taskManager.getTask(taskId)).toBeUndefined(); // Ensure task is removed from the task collection after the consult ends
|
|
1541
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1551
1542
|
});
|
|
1552
1543
|
|
|
1553
1544
|
it('should emit TASK_CANCELLED event on AGENT_CTQ_CANCELLED event', () => {
|
|
@@ -1559,14 +1550,15 @@ describe('TaskManager', () => {
|
|
|
1559
1550
|
};
|
|
1560
1551
|
|
|
1561
1552
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1562
|
-
const
|
|
1563
|
-
const
|
|
1553
|
+
const task = taskManager.getTask(taskId);
|
|
1554
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1564
1555
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
taskManager.getTask(taskId)
|
|
1556
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1557
|
+
sendStateMachineEventSpy,
|
|
1558
|
+
TaskEvent.CTQ_CANCEL
|
|
1569
1559
|
);
|
|
1560
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1561
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1570
1562
|
});
|
|
1571
1563
|
|
|
1572
1564
|
it('should handle AGENT_CONSULT_FAILED event', () => {
|
|
@@ -1580,9 +1572,15 @@ describe('TaskManager', () => {
|
|
|
1580
1572
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1581
1573
|
|
|
1582
1574
|
// Always spy on the updated task object after CONTACT_RESERVED is emitted
|
|
1583
|
-
const
|
|
1575
|
+
const task = taskManager.getTask(taskId);
|
|
1576
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1584
1577
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1585
|
-
|
|
1578
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1579
|
+
sendStateMachineEventSpy,
|
|
1580
|
+
TaskEvent.CONSULT_FAILED
|
|
1581
|
+
);
|
|
1582
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1583
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1586
1584
|
});
|
|
1587
1585
|
|
|
1588
1586
|
it('should emit TASK_CONSULT_QUEUE_FAILED on AGENT_CTQ_CANCEL_FAILED event', () => {
|
|
@@ -1594,14 +1592,15 @@ describe('TaskManager', () => {
|
|
|
1594
1592
|
};
|
|
1595
1593
|
|
|
1596
1594
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1597
|
-
const
|
|
1598
|
-
const
|
|
1595
|
+
const task = taskManager.getTask(taskId);
|
|
1596
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1599
1597
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
taskManager.getTask(taskId)
|
|
1598
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1599
|
+
sendStateMachineEventSpy,
|
|
1600
|
+
TaskEvent.CTQ_CANCEL_FAILED
|
|
1604
1601
|
);
|
|
1602
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1603
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1605
1604
|
});
|
|
1606
1605
|
|
|
1607
1606
|
it('should emit TASK_REJECT event on AGENT_CONTACT_OFFER_RONA event', () => {
|
|
@@ -1644,15 +1643,14 @@ describe('TaskManager', () => {
|
|
|
1644
1643
|
};
|
|
1645
1644
|
|
|
1646
1645
|
taskManager.taskCollection[taskId] = taskManager.getTask(taskId);
|
|
1647
|
-
const
|
|
1648
|
-
const
|
|
1646
|
+
const task = taskManager.getTask(taskId);
|
|
1647
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1649
1648
|
|
|
1650
1649
|
webSocketManagerMock.emit('message', JSON.stringify(ronaPayload));
|
|
1651
1650
|
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
expect(metricsTrackSpy.mock.calls[0][0]).toBe('Agent RONA');
|
|
1651
|
+
const stateMachineEvent = expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.RONA);
|
|
1652
|
+
expect(stateMachineEvent?.reason).toBe(ronaPayload.data.reason);
|
|
1653
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1656
1654
|
});
|
|
1657
1655
|
|
|
1658
1656
|
it('should emit TASK_REJECT event on AGENT_CONTACT_ASSIGN_FAILED event', () => {
|
|
@@ -1696,20 +1694,16 @@ describe('TaskManager', () => {
|
|
|
1696
1694
|
|
|
1697
1695
|
taskManager.taskCollection[taskId] = taskManager.getTask(taskId);
|
|
1698
1696
|
const task = taskManager.getTask(taskId);
|
|
1699
|
-
const
|
|
1700
|
-
const taskUpdateDataSpy = jest.spyOn(task, 'updateTaskData');
|
|
1701
|
-
const metricsTrackSpy = jest.spyOn(taskManager.metricsManager, 'trackEvent');
|
|
1697
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1702
1698
|
|
|
1703
1699
|
webSocketManagerMock.emit('message', JSON.stringify(assignFailedPayload));
|
|
1704
1700
|
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
assignFailedPayload.data.reason
|
|
1701
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1702
|
+
sendStateMachineEventSpy,
|
|
1703
|
+
TaskEvent.ASSIGN_FAILED
|
|
1709
1704
|
);
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
expect(metricsTrackSpy.mock.calls[0][0]).toBe('Agent Contact Assign Failed');
|
|
1705
|
+
expect(stateMachineEvent?.reason).toBe(assignFailedPayload.data.reason);
|
|
1706
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1713
1707
|
});
|
|
1714
1708
|
|
|
1715
1709
|
it('should remove currentTask from taskCollection on AGENT_WRAPPEDUP event', () => {
|
|
@@ -1732,11 +1726,12 @@ describe('TaskManager', () => {
|
|
|
1732
1726
|
|
|
1733
1727
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1734
1728
|
const task = taskManager.getTask(taskId);
|
|
1735
|
-
const
|
|
1729
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1736
1730
|
|
|
1737
1731
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1738
1732
|
|
|
1739
|
-
|
|
1733
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.WRAPUP_COMPLETE);
|
|
1734
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1740
1735
|
expect(taskManager.getTask(taskId)).toBeUndefined();
|
|
1741
1736
|
});
|
|
1742
1737
|
|
|
@@ -1762,7 +1757,8 @@ describe('TaskManager', () => {
|
|
|
1762
1757
|
};
|
|
1763
1758
|
|
|
1764
1759
|
const taskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
|
|
1765
|
-
const taskUpdateTaskDataSpy =
|
|
1760
|
+
const taskUpdateTaskDataSpy = taskManager.getTask(taskId).updateTaskData as jest.Mock;
|
|
1761
|
+
taskUpdateTaskDataSpy.mockClear();
|
|
1766
1762
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1767
1763
|
expect(taskEmitSpy).not.toHaveBeenCalled();
|
|
1768
1764
|
expect(taskUpdateTaskDataSpy).not.toHaveBeenCalled();
|
|
@@ -1771,7 +1767,8 @@ describe('TaskManager', () => {
|
|
|
1771
1767
|
it('should emit TASK_CONSULTING event when agent is consulting', () => {
|
|
1772
1768
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1773
1769
|
taskManager.getTask(taskId).data.isConsulted = false;
|
|
1774
|
-
const
|
|
1770
|
+
const task = taskManager.getTask(taskId);
|
|
1771
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1775
1772
|
const consultingPayload = {
|
|
1776
1773
|
data: {
|
|
1777
1774
|
...initalPayload.data,
|
|
@@ -1780,16 +1777,17 @@ describe('TaskManager', () => {
|
|
|
1780
1777
|
},
|
|
1781
1778
|
};
|
|
1782
1779
|
webSocketManagerMock.emit('message', JSON.stringify(consultingPayload));
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
TASK_EVENTS.TASK_CONSULTING,
|
|
1786
|
-
taskManager.getTask(taskId)
|
|
1787
|
-
);
|
|
1780
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.CONSULTING_ACTIVE);
|
|
1781
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1788
1782
|
});
|
|
1789
1783
|
|
|
1790
|
-
it('should
|
|
1784
|
+
it('should update task data on AGENT_CONTACT_UNASSIGNED', () => {
|
|
1791
1785
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1792
|
-
const
|
|
1786
|
+
const task = taskManager.getTask(taskId);
|
|
1787
|
+
const sendStateMachineEventSpy = task.sendStateMachineEvent as jest.Mock;
|
|
1788
|
+
sendStateMachineEventSpy.mockClear();
|
|
1789
|
+
const updateTaskDataSpy = task.updateTaskData as jest.Mock;
|
|
1790
|
+
updateTaskDataSpy.mockClear();
|
|
1793
1791
|
const unassignedPayload = {
|
|
1794
1792
|
data: {
|
|
1795
1793
|
type: CC_EVENTS.AGENT_CONTACT_UNASSIGNED,
|
|
@@ -1807,11 +1805,76 @@ describe('TaskManager', () => {
|
|
|
1807
1805
|
},
|
|
1808
1806
|
};
|
|
1809
1807
|
webSocketManagerMock.emit('message', JSON.stringify(unassignedPayload));
|
|
1810
|
-
expect(
|
|
1811
|
-
|
|
1812
|
-
|
|
1808
|
+
expect(sendStateMachineEventSpy).not.toHaveBeenCalled();
|
|
1809
|
+
expect(updateTaskDataSpy).toHaveBeenCalledWith(unassignedPayload.data);
|
|
1810
|
+
});
|
|
1811
|
+
|
|
1812
|
+
it('preserves consult fields from state context during consulting payload refresh', () => {
|
|
1813
|
+
const task = createMockTask({
|
|
1814
|
+
...taskDataMock,
|
|
1815
|
+
interaction: {state: 'consulting'} as any,
|
|
1816
|
+
interactionId: taskId,
|
|
1817
|
+
});
|
|
1818
|
+
task.stateMachineService = {
|
|
1819
|
+
getSnapshot: () => ({
|
|
1820
|
+
value: 'CONSULTING',
|
|
1821
|
+
context: {
|
|
1822
|
+
consultDestinationAgentId: 'agent-preserved',
|
|
1823
|
+
consultDestinationType: 'agent',
|
|
1824
|
+
},
|
|
1825
|
+
}),
|
|
1826
|
+
};
|
|
1827
|
+
|
|
1828
|
+
const incomingTaskData = {
|
|
1829
|
+
...taskDataMock,
|
|
1830
|
+
interaction: {state: 'consulting'} as any,
|
|
1831
|
+
interactionId: taskId,
|
|
1832
|
+
destAgentId: null,
|
|
1833
|
+
destinationType: null,
|
|
1834
|
+
};
|
|
1835
|
+
|
|
1836
|
+
(taskManager as any).updateTaskData(task, incomingTaskData);
|
|
1837
|
+
|
|
1838
|
+
expect(task.updateTaskData).toHaveBeenCalledWith(
|
|
1839
|
+
expect.objectContaining({
|
|
1840
|
+
destAgentId: 'agent-preserved',
|
|
1841
|
+
destinationType: 'agent',
|
|
1842
|
+
})
|
|
1843
|
+
);
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1846
|
+
it('does not preserve stale consult fields once consult is no longer active', () => {
|
|
1847
|
+
const task = createMockTask({
|
|
1848
|
+
...taskDataMock,
|
|
1849
|
+
interaction: {state: 'connected'} as any,
|
|
1850
|
+
interactionId: taskId,
|
|
1851
|
+
});
|
|
1852
|
+
task.stateMachineService = {
|
|
1853
|
+
getSnapshot: () => ({
|
|
1854
|
+
value: 'CONNECTED',
|
|
1855
|
+
context: {
|
|
1856
|
+
consultDestinationAgentId: 'agent-stale',
|
|
1857
|
+
consultDestinationType: 'agent',
|
|
1858
|
+
},
|
|
1859
|
+
}),
|
|
1860
|
+
};
|
|
1861
|
+
|
|
1862
|
+
const incomingTaskData = {
|
|
1863
|
+
...taskDataMock,
|
|
1864
|
+
interaction: {state: 'connected'} as any,
|
|
1865
|
+
interactionId: taskId,
|
|
1866
|
+
destAgentId: null,
|
|
1867
|
+
destinationType: null,
|
|
1868
|
+
};
|
|
1869
|
+
|
|
1870
|
+
(taskManager as any).updateTaskData(task, incomingTaskData);
|
|
1871
|
+
|
|
1872
|
+
expect(task.updateTaskData).toHaveBeenCalledWith(
|
|
1873
|
+
expect.objectContaining({
|
|
1874
|
+
destAgentId: null,
|
|
1875
|
+
destinationType: null,
|
|
1876
|
+
})
|
|
1813
1877
|
);
|
|
1814
|
-
expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_END, taskManager.getTask(taskId));
|
|
1815
1878
|
});
|
|
1816
1879
|
|
|
1817
1880
|
it('should handle chat interaction and emit TASK_INCOMING immediately', () => {
|
|
@@ -1823,16 +1886,16 @@ describe('TaskManager', () => {
|
|
|
1823
1886
|
},
|
|
1824
1887
|
};
|
|
1825
1888
|
|
|
1826
|
-
const taskIncomingSpy = jest.spyOn(taskManager, 'emit');
|
|
1827
|
-
|
|
1828
1889
|
// Simulate receiving a chat task
|
|
1829
1890
|
webSocketManagerMock.emit('message', JSON.stringify(chatPayload));
|
|
1830
1891
|
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1892
|
+
const chatTask = taskManager.getTask(chatPayload.data.interactionId);
|
|
1893
|
+
const sendStateMachineEventSpy = chatTask.sendStateMachineEvent as jest.Mock;
|
|
1894
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1895
|
+
sendStateMachineEventSpy,
|
|
1896
|
+
TaskEvent.TASK_INCOMING
|
|
1835
1897
|
);
|
|
1898
|
+
expect(stateMachineEvent?.taskData).toEqual(chatPayload.data);
|
|
1836
1899
|
expect(taskManager.getAllTasks()).toHaveProperty(chatPayload.data.interactionId);
|
|
1837
1900
|
});
|
|
1838
1901
|
|
|
@@ -1845,16 +1908,16 @@ describe('TaskManager', () => {
|
|
|
1845
1908
|
},
|
|
1846
1909
|
};
|
|
1847
1910
|
|
|
1848
|
-
const taskIncomingSpy = jest.spyOn(taskManager, 'emit');
|
|
1849
|
-
|
|
1850
1911
|
// Simulate receiving an email task
|
|
1851
1912
|
webSocketManagerMock.emit('message', JSON.stringify(emailPayload));
|
|
1852
1913
|
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1914
|
+
const emailTask = taskManager.getTask(emailPayload.data.interactionId);
|
|
1915
|
+
const sendStateMachineEventSpy = emailTask.sendStateMachineEvent as jest.Mock;
|
|
1916
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1917
|
+
sendStateMachineEventSpy,
|
|
1918
|
+
TaskEvent.TASK_INCOMING
|
|
1857
1919
|
);
|
|
1920
|
+
expect(stateMachineEvent?.taskData).toEqual(emailPayload.data);
|
|
1858
1921
|
expect(taskManager.getAllTasks()).toHaveProperty(emailPayload.data.interactionId);
|
|
1859
1922
|
});
|
|
1860
1923
|
|
|
@@ -1868,13 +1931,11 @@ describe('TaskManager', () => {
|
|
|
1868
1931
|
},
|
|
1869
1932
|
};
|
|
1870
1933
|
|
|
1871
|
-
const taskIncomingSpy = jest.spyOn(taskManager, 'emit');
|
|
1872
1934
|
webSocketManagerMock.emit('message', JSON.stringify(chatReservedPayload));
|
|
1935
|
+
const task = taskManager.getTask(chatReservedPayload.data.interactionId);
|
|
1936
|
+
const sendStateMachineEventSpy = task.sendStateMachineEvent as jest.Mock;
|
|
1873
1937
|
|
|
1874
|
-
|
|
1875
|
-
TASK_EVENTS.TASK_INCOMING,
|
|
1876
|
-
taskManager.getTask(chatReservedPayload.data.interactionId)
|
|
1877
|
-
);
|
|
1938
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.TASK_INCOMING);
|
|
1878
1939
|
|
|
1879
1940
|
// 2. Chat task is assigned
|
|
1880
1941
|
const chatAssignedPayload = {
|
|
@@ -1884,12 +1945,9 @@ describe('TaskManager', () => {
|
|
|
1884
1945
|
},
|
|
1885
1946
|
};
|
|
1886
1947
|
|
|
1887
|
-
const task = taskManager.getTask(chatReservedPayload.data.interactionId);
|
|
1888
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
1889
|
-
|
|
1890
1948
|
webSocketManagerMock.emit('message', JSON.stringify(chatAssignedPayload));
|
|
1891
1949
|
|
|
1892
|
-
|
|
1950
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.ASSIGN);
|
|
1893
1951
|
|
|
1894
1952
|
// 3. Chat task is ended with state 'new' to trigger cleanup
|
|
1895
1953
|
const chatEndedPayload = {
|
|
@@ -1897,12 +1955,15 @@ describe('TaskManager', () => {
|
|
|
1897
1955
|
...chatReservedPayload.data,
|
|
1898
1956
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
1899
1957
|
interaction: {mediaType: 'chat', state: 'new'}, // Change to 'new' state
|
|
1958
|
+
wrapUpRequired: false,
|
|
1900
1959
|
},
|
|
1901
1960
|
};
|
|
1902
1961
|
|
|
1962
|
+
// Simulate state on the task to allow cleanup logic
|
|
1963
|
+
task.data.interaction.state = 'new';
|
|
1903
1964
|
webSocketManagerMock.emit('message', JSON.stringify(chatEndedPayload));
|
|
1904
1965
|
|
|
1905
|
-
|
|
1966
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.CONTACT_ENDED);
|
|
1906
1967
|
// Verify task is removed since it was in a 'new' state
|
|
1907
1968
|
expect(taskManager.getTask(chatReservedPayload.data.interactionId)).toBeUndefined();
|
|
1908
1969
|
});
|
|
@@ -1991,13 +2052,8 @@ describe('TaskManager', () => {
|
|
|
1991
2052
|
expect(taskManager.getAllTasks()).toHaveProperty(task2Payload.data.interactionId);
|
|
1992
2053
|
expect(taskManager.getAllTasks()).toHaveProperty(task3Payload.data.interactionId);
|
|
1993
2054
|
|
|
1994
|
-
// Create spies for all tasks
|
|
1995
|
-
const task1EmitSpy = jest.spyOn(taskManager.getTask(task1Payload.data.interactionId), 'emit');
|
|
1996
|
-
const task2EmitSpy = jest.spyOn(taskManager.getTask(task2Payload.data.interactionId), 'emit');
|
|
1997
|
-
const task3EmitSpy = jest.spyOn(taskManager.getTask(task3Payload.data.interactionId), 'emit');
|
|
1998
|
-
|
|
1999
|
-
// Store reference to task2 before it gets removed
|
|
2000
2055
|
const task2 = taskManager.getTask(task2Payload.data.interactionId);
|
|
2056
|
+
const task2SendStateMachineEventSpy = task2.sendStateMachineEvent as jest.Mock;
|
|
2001
2057
|
|
|
2002
2058
|
// End only the second task (chat task)
|
|
2003
2059
|
const chatEndedPayload = {
|
|
@@ -2005,15 +2061,18 @@ describe('TaskManager', () => {
|
|
|
2005
2061
|
...task2Payload.data,
|
|
2006
2062
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
2007
2063
|
interaction: {mediaType: 'chat', state: 'new'}, // Using 'new' to trigger cleanup
|
|
2064
|
+
wrapUpRequired: false,
|
|
2008
2065
|
},
|
|
2009
2066
|
};
|
|
2010
2067
|
|
|
2068
|
+
task2.data.interaction.state = 'new';
|
|
2011
2069
|
webSocketManagerMock.emit('message', JSON.stringify(chatEndedPayload));
|
|
2012
2070
|
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2071
|
+
const firstEndEvent = expectLastStateMachineEvent(
|
|
2072
|
+
task2SendStateMachineEventSpy,
|
|
2073
|
+
TaskEvent.CONTACT_ENDED
|
|
2074
|
+
);
|
|
2075
|
+
expect(firstEndEvent?.taskData).toEqual(chatEndedPayload.data);
|
|
2017
2076
|
|
|
2018
2077
|
// Verify task2 was removed from collection (since state was 'new')
|
|
2019
2078
|
expect(taskManager.getTask(task2Payload.data.interactionId)).toBeUndefined();
|
|
@@ -2024,6 +2083,7 @@ describe('TaskManager', () => {
|
|
|
2024
2083
|
|
|
2025
2084
|
// Store reference to task3 before we end it
|
|
2026
2085
|
const task3 = taskManager.getTask(task3Payload.data.interactionId);
|
|
2086
|
+
const task3SendStateMachineEventSpy = task3.sendStateMachineEvent as jest.Mock;
|
|
2027
2087
|
|
|
2028
2088
|
// Now end task3 with a state that doesn't trigger cleanup
|
|
2029
2089
|
const emailEndedPayload = {
|
|
@@ -2031,31 +2091,31 @@ describe('TaskManager', () => {
|
|
|
2031
2091
|
...task3Payload.data,
|
|
2032
2092
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
2033
2093
|
interaction: {mediaType: 'email', state: 'connected'}, // Using 'connected' to NOT trigger cleanup
|
|
2094
|
+
wrapUpRequired: true,
|
|
2034
2095
|
},
|
|
2035
2096
|
};
|
|
2036
2097
|
|
|
2098
|
+
task3.data.interaction.state = 'connected';
|
|
2037
2099
|
webSocketManagerMock.emit('message', JSON.stringify(emailEndedPayload));
|
|
2038
2100
|
|
|
2039
|
-
|
|
2040
|
-
|
|
2101
|
+
const secondEndEvent = expectLastStateMachineEvent(
|
|
2102
|
+
task3SendStateMachineEventSpy,
|
|
2103
|
+
TaskEvent.CONTACT_ENDED
|
|
2104
|
+
);
|
|
2105
|
+
expect(secondEndEvent?.taskData).toEqual(emailEndedPayload.data);
|
|
2041
2106
|
|
|
2042
2107
|
// Verify task3 is still in collection (since state was 'connected')
|
|
2043
2108
|
expect(taskManager.getTask(task3Payload.data.interactionId)).toBeDefined();
|
|
2044
2109
|
|
|
2045
2110
|
// Verify task1 remains unaffected
|
|
2046
|
-
expect(task1EmitSpy).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_END);
|
|
2047
2111
|
expect(taskManager.getTask(task1Payload.data.interactionId)).toBeDefined();
|
|
2048
2112
|
});
|
|
2049
2113
|
|
|
2050
|
-
it('should emit
|
|
2114
|
+
it('should emit TRANSFER_SUCCESS event on AGENT_VTEAM_TRANSFERRED event', () => {
|
|
2051
2115
|
// First create a task by emitting the initial payload
|
|
2052
2116
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
2053
|
-
|
|
2054
|
-
// Get a reference to the task from taskCollection
|
|
2055
2117
|
const task = taskManager.getTask(taskId);
|
|
2056
|
-
|
|
2057
|
-
// Now spy on the task's emit method
|
|
2058
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
2118
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
2059
2119
|
|
|
2060
2120
|
const vteamTransferredPayload = {
|
|
2061
2121
|
data: {
|
|
@@ -2079,8 +2139,9 @@ describe('TaskManager', () => {
|
|
|
2079
2139
|
|
|
2080
2140
|
webSocketManagerMock.emit('message', JSON.stringify(vteamTransferredPayload));
|
|
2081
2141
|
|
|
2082
|
-
// Check that
|
|
2083
|
-
|
|
2142
|
+
// Check that the state machine received the END event
|
|
2143
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.TRANSFER_SUCCESS);
|
|
2144
|
+
sendStateMachineEventSpy.mockRestore();
|
|
2084
2145
|
|
|
2085
2146
|
// The task should still exist in the collection based on current implementation
|
|
2086
2147
|
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
@@ -2095,12 +2156,16 @@ describe('TaskManager', () => {
|
|
|
2095
2156
|
},
|
|
2096
2157
|
};
|
|
2097
2158
|
const task = taskManager.getTask(taskId);
|
|
2098
|
-
const
|
|
2099
|
-
task.data = {...(task.data || {}), ...(data || {})};
|
|
2100
|
-
return task;
|
|
2101
|
-
});
|
|
2159
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
2102
2160
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2103
|
-
expect(
|
|
2161
|
+
expect(sendStateMachineEventSpy).toHaveBeenCalled();
|
|
2162
|
+
const stateMachineEvent = sendStateMachineEventSpy.mock.calls.at(-1)?.[0];
|
|
2163
|
+
expect(stateMachineEvent?.type).toBe(TaskEvent.TASK_WRAPUP);
|
|
2164
|
+
expect(stateMachineEvent?.taskData).toEqual({
|
|
2165
|
+
...payload.data,
|
|
2166
|
+
wrapUpRequired: true,
|
|
2167
|
+
});
|
|
2168
|
+
sendStateMachineEventSpy.mockRestore();
|
|
2104
2169
|
});
|
|
2105
2170
|
|
|
2106
2171
|
it('should not attempt cleanup twice when AGENT_CONTACT_UNASSIGNED is followed by AGENT_WRAPUP', () => {
|
|
@@ -2176,1176 +2241,157 @@ describe('TaskManager', () => {
|
|
|
2176
2241
|
});
|
|
2177
2242
|
|
|
2178
2243
|
describe('should emit appropriate task events for recording events', () => {
|
|
2179
|
-
|
|
2244
|
+
const eventMap: Record<string, TaskEvent | null> = {
|
|
2245
|
+
STARTED: TaskEvent.RECORDING_STARTED,
|
|
2246
|
+
PAUSED: TaskEvent.PAUSE_RECORDING,
|
|
2247
|
+
PAUSE_FAILED: null,
|
|
2248
|
+
RESUMED: TaskEvent.RESUME_RECORDING,
|
|
2249
|
+
RESUME_FAILED: null,
|
|
2250
|
+
};
|
|
2251
|
+
|
|
2252
|
+
['STARTED', 'PAUSED', 'PAUSE_FAILED', 'RESUMED', 'RESUME_FAILED'].forEach((suffix) => {
|
|
2180
2253
|
const ccEvent = CC_EVENTS[`CONTACT_RECORDING_${suffix}`];
|
|
2181
|
-
const
|
|
2182
|
-
it(`should
|
|
2254
|
+
const expectedTaskEvent = eventMap[suffix];
|
|
2255
|
+
it(`should ${expectedTaskEvent ? 'send' : 'not send'} ${
|
|
2256
|
+
expectedTaskEvent ?? 'a'
|
|
2257
|
+
} state machine event on ${ccEvent} event`, () => {
|
|
2183
2258
|
const payload = {data: {...initalPayload.data, type: ccEvent}};
|
|
2184
2259
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
2185
2260
|
const task = taskManager.getTask(taskId);
|
|
2186
|
-
const
|
|
2261
|
+
const sendStateMachineEventSpy = task.sendStateMachineEvent as jest.Mock;
|
|
2262
|
+
sendStateMachineEventSpy.mockClear();
|
|
2187
2263
|
|
|
2188
2264
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2189
|
-
|
|
2265
|
+
if (expectedTaskEvent) {
|
|
2266
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
2267
|
+
sendStateMachineEventSpy,
|
|
2268
|
+
expectedTaskEvent
|
|
2269
|
+
);
|
|
2270
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
2271
|
+
} else {
|
|
2272
|
+
expect(sendStateMachineEventSpy).not.toHaveBeenCalled();
|
|
2273
|
+
}
|
|
2190
2274
|
});
|
|
2191
2275
|
});
|
|
2192
2276
|
});
|
|
2193
2277
|
|
|
2194
2278
|
describe('Conference event handling', () => {
|
|
2195
2279
|
let task;
|
|
2196
|
-
const agentId = '723a8ffb-a26e-496d-b14a-ff44fb83b64f';
|
|
2197
2280
|
|
|
2198
2281
|
beforeEach(() => {
|
|
2199
|
-
// Set the agentId on taskManager before tests run
|
|
2200
|
-
taskManager.setAgentId(agentId);
|
|
2201
|
-
|
|
2202
2282
|
task = {
|
|
2203
2283
|
data: {interactionId: taskId},
|
|
2204
2284
|
emit: jest.fn(),
|
|
2205
|
-
updateTaskData: jest.fn()
|
|
2206
|
-
|
|
2207
|
-
task.data = {...task.data, ...updatedData};
|
|
2208
|
-
return task;
|
|
2209
|
-
}),
|
|
2285
|
+
updateTaskData: jest.fn(),
|
|
2286
|
+
sendStateMachineEvent: jest.fn(),
|
|
2210
2287
|
};
|
|
2211
|
-
taskManager.taskCollection[taskId] = task;
|
|
2288
|
+
taskManager.taskCollection[taskId] = task as any;
|
|
2212
2289
|
});
|
|
2213
2290
|
|
|
2214
|
-
it('
|
|
2291
|
+
it('sends AGENT_CONSULT_CONFERENCED to state machine as CONFERENCE_START', () => {
|
|
2215
2292
|
const payload = {
|
|
2216
|
-
data: {
|
|
2217
|
-
type: CC_EVENTS.AGENT_CONSULT_CONFERENCED,
|
|
2218
|
-
interactionId: taskId,
|
|
2219
|
-
isConferencing: true,
|
|
2220
|
-
},
|
|
2293
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCED, interactionId: taskId},
|
|
2221
2294
|
};
|
|
2222
|
-
|
|
2223
2295
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
expect(
|
|
2296
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
2297
|
+
const call = task.sendStateMachineEvent.mock.calls[0][0];
|
|
2298
|
+
expect(call.type).toBe(TaskEvent.CONFERENCE_START);
|
|
2227
2299
|
});
|
|
2228
2300
|
|
|
2229
|
-
it('
|
|
2301
|
+
it('sends PARTICIPANT_JOINED_CONFERENCE to state machine as CONFERENCE_START', () => {
|
|
2230
2302
|
const payload = {
|
|
2231
|
-
data: {
|
|
2232
|
-
type: CC_EVENTS.AGENT_CONSULT_CONFERENCING,
|
|
2233
|
-
interactionId: taskId,
|
|
2234
|
-
isConferencing: true,
|
|
2235
|
-
},
|
|
2303
|
+
data: {type: CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE, interactionId: taskId},
|
|
2236
2304
|
};
|
|
2237
|
-
|
|
2238
2305
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
expect(task.emit).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_CONFERENCE_STARTED, task);
|
|
2306
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
2307
|
+
const call = task.sendStateMachineEvent.mock.calls[0][0];
|
|
2308
|
+
expect(call.type).toBe(TaskEvent.CONFERENCE_START);
|
|
2243
2309
|
});
|
|
2244
2310
|
|
|
2245
|
-
it('
|
|
2311
|
+
it('sends AGENT_CONSULT_CONFERENCE_FAILED to state machine as CONFERENCE_FAILED', () => {
|
|
2246
2312
|
const payload = {
|
|
2247
|
-
data: {
|
|
2248
|
-
type: CC_EVENTS.AGENT_CONSULT_CONFERENCE_FAILED,
|
|
2249
|
-
interactionId: taskId,
|
|
2250
|
-
reason: 'Network error',
|
|
2251
|
-
},
|
|
2313
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCE_FAILED, interactionId: taskId},
|
|
2252
2314
|
};
|
|
2253
|
-
|
|
2254
2315
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2316
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
2317
|
+
const call = task.sendStateMachineEvent.mock.calls[0][0];
|
|
2318
|
+
expect(call.type).toBe(TaskEvent.CONFERENCE_FAILED);
|
|
2258
2319
|
});
|
|
2259
2320
|
|
|
2260
|
-
it('
|
|
2321
|
+
it('sends PARTICIPANT_LEFT_CONFERENCE to state machine as PARTICIPANT_LEAVE', () => {
|
|
2261
2322
|
const payload = {
|
|
2262
|
-
data: {
|
|
2263
|
-
type: CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE,
|
|
2264
|
-
interactionId: taskId,
|
|
2265
|
-
participantId: 'new-participant-123',
|
|
2266
|
-
participantType: 'agent',
|
|
2267
|
-
interaction: {
|
|
2268
|
-
participants: {
|
|
2269
|
-
[agentId]: {pType: 'Agent', hasLeft: false},
|
|
2270
|
-
'new-participant-123': {pType: 'Agent', hasLeft: false},
|
|
2271
|
-
},
|
|
2272
|
-
media: {
|
|
2273
|
-
[taskId]: {
|
|
2274
|
-
mType: 'mainCall',
|
|
2275
|
-
participants: [agentId, 'new-participant-123'],
|
|
2276
|
-
},
|
|
2277
|
-
},
|
|
2278
|
-
},
|
|
2279
|
-
},
|
|
2323
|
+
data: {type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, interactionId: taskId},
|
|
2280
2324
|
};
|
|
2281
|
-
|
|
2282
2325
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
expect(
|
|
2286
|
-
// No specific task event emission for participant joined - just data update
|
|
2326
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
2327
|
+
const call = task.sendStateMachineEvent.mock.calls[0][0];
|
|
2328
|
+
expect(call.type).toBe(TaskEvent.PARTICIPANT_LEAVE);
|
|
2287
2329
|
});
|
|
2288
2330
|
|
|
2289
|
-
it('
|
|
2331
|
+
it('handles AGENT_CONSULT_CONFERENCING event without errors', () => {
|
|
2290
2332
|
const payload = {
|
|
2291
|
-
data: {
|
|
2292
|
-
type: CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE,
|
|
2293
|
-
interactionId: taskId,
|
|
2294
|
-
participantId: 'new-agent-789',
|
|
2295
|
-
interaction: {
|
|
2296
|
-
participants: {
|
|
2297
|
-
[agentId]: {pType: 'Agent', hasLeft: false},
|
|
2298
|
-
'agent-2': {pType: 'Agent', hasLeft: false},
|
|
2299
|
-
'new-agent-789': {pType: 'Agent', hasLeft: false},
|
|
2300
|
-
'customer-1': {pType: 'Customer', hasLeft: false},
|
|
2301
|
-
},
|
|
2302
|
-
media: {
|
|
2303
|
-
[taskId]: {
|
|
2304
|
-
mType: 'mainCall',
|
|
2305
|
-
participants: [agentId, 'agent-2', 'new-agent-789', 'customer-1'],
|
|
2306
|
-
},
|
|
2307
|
-
},
|
|
2308
|
-
},
|
|
2309
|
-
},
|
|
2333
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCING, interactionId: taskId},
|
|
2310
2334
|
};
|
|
2311
|
-
|
|
2312
|
-
const updateTaskDataSpy = jest.spyOn(task, 'updateTaskData');
|
|
2313
|
-
|
|
2314
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2315
|
-
|
|
2316
|
-
// Verify updateTaskData was called exactly once
|
|
2317
|
-
expect(updateTaskDataSpy).toHaveBeenCalledTimes(1);
|
|
2318
|
-
|
|
2319
|
-
// Verify it was called with isConferenceInProgress already calculated
|
|
2320
|
-
expect(updateTaskDataSpy).toHaveBeenCalledWith(
|
|
2321
|
-
expect.objectContaining({
|
|
2322
|
-
participantId: 'new-agent-789',
|
|
2323
|
-
isConferenceInProgress: true, // 3 active agents
|
|
2324
|
-
})
|
|
2325
|
-
);
|
|
2326
|
-
|
|
2327
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_JOINED, task);
|
|
2328
|
-
});
|
|
2329
|
-
|
|
2330
|
-
describe('PARTICIPANT_LEFT_CONFERENCE event handling', () => {
|
|
2331
|
-
it('should call updateTaskData only once for PARTICIPANT_LEFT_CONFERENCE with pre-calculated isConferenceInProgress', () => {
|
|
2332
|
-
const payload = {
|
|
2333
|
-
data: {
|
|
2334
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2335
|
-
interactionId: taskId,
|
|
2336
|
-
interaction: {
|
|
2337
|
-
participants: {
|
|
2338
|
-
[agentId]: {pType: 'Agent', hasLeft: false},
|
|
2339
|
-
'agent-2': {pType: 'Agent', hasLeft: true}, // This agent left
|
|
2340
|
-
'customer-1': {pType: 'Customer', hasLeft: false},
|
|
2341
|
-
},
|
|
2342
|
-
media: {
|
|
2343
|
-
[taskId]: {
|
|
2344
|
-
mType: 'mainCall',
|
|
2345
|
-
participants: [agentId, 'customer-1'], // agent-2 removed from participants
|
|
2346
|
-
},
|
|
2347
|
-
},
|
|
2348
|
-
},
|
|
2349
|
-
},
|
|
2350
|
-
};
|
|
2351
|
-
|
|
2352
|
-
const updateTaskDataSpy = jest.spyOn(task, 'updateTaskData');
|
|
2353
|
-
|
|
2354
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2355
|
-
|
|
2356
|
-
// Verify updateTaskData was called exactly once
|
|
2357
|
-
expect(updateTaskDataSpy).toHaveBeenCalledTimes(1);
|
|
2358
|
-
|
|
2359
|
-
// Verify it was called with isConferenceInProgress already calculated
|
|
2360
|
-
expect(updateTaskDataSpy).toHaveBeenCalledWith(
|
|
2361
|
-
expect.objectContaining({
|
|
2362
|
-
isConferenceInProgress: false, // Only 1 active agent remains
|
|
2363
|
-
})
|
|
2364
|
-
);
|
|
2365
|
-
|
|
2366
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2367
|
-
});
|
|
2368
|
-
|
|
2369
|
-
it('should emit TASK_PARTICIPANT_LEFT event when participant leaves conference', () => {
|
|
2370
|
-
const payload = {
|
|
2371
|
-
data: {
|
|
2372
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2373
|
-
interactionId: taskId,
|
|
2374
|
-
interaction: {
|
|
2375
|
-
participants: {
|
|
2376
|
-
[agentId]: {
|
|
2377
|
-
hasLeft: false,
|
|
2378
|
-
},
|
|
2379
|
-
},
|
|
2380
|
-
},
|
|
2381
|
-
},
|
|
2382
|
-
};
|
|
2383
|
-
|
|
2335
|
+
expect(() => {
|
|
2384
2336
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2385
|
-
|
|
2386
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2387
|
-
});
|
|
2388
|
-
|
|
2389
|
-
it('should NOT remove task when agent is still in interaction', () => {
|
|
2390
|
-
const payload = {
|
|
2391
|
-
data: {
|
|
2392
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2393
|
-
interactionId: taskId,
|
|
2394
|
-
interaction: {
|
|
2395
|
-
participants: {
|
|
2396
|
-
[agentId]: {
|
|
2397
|
-
hasLeft: false,
|
|
2398
|
-
},
|
|
2399
|
-
},
|
|
2400
|
-
},
|
|
2401
|
-
},
|
|
2402
|
-
};
|
|
2403
|
-
|
|
2404
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2405
|
-
|
|
2406
|
-
// Task should still exist in collection
|
|
2407
|
-
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
2408
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2409
|
-
});
|
|
2410
|
-
|
|
2411
|
-
it('should NOT remove task when agent left but is in main interaction', () => {
|
|
2412
|
-
const payload = {
|
|
2413
|
-
data: {
|
|
2414
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2415
|
-
interactionId: taskId,
|
|
2416
|
-
interaction: {
|
|
2417
|
-
participants: {
|
|
2418
|
-
[agentId]: {
|
|
2419
|
-
hasLeft: true,
|
|
2420
|
-
},
|
|
2421
|
-
},
|
|
2422
|
-
media: {
|
|
2423
|
-
[taskId]: {
|
|
2424
|
-
mType: 'mainCall',
|
|
2425
|
-
participants: [agentId],
|
|
2426
|
-
},
|
|
2427
|
-
},
|
|
2428
|
-
},
|
|
2429
|
-
},
|
|
2430
|
-
};
|
|
2431
|
-
|
|
2432
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2433
|
-
|
|
2434
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2435
|
-
|
|
2436
|
-
// Task should still exist - not removed
|
|
2437
|
-
expect(removeTaskSpy).not.toHaveBeenCalled();
|
|
2438
|
-
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
2439
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2440
|
-
});
|
|
2441
|
-
|
|
2442
|
-
it('should NOT remove task when agent left but is primary (owner)', () => {
|
|
2443
|
-
const payload = {
|
|
2444
|
-
data: {
|
|
2445
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2446
|
-
interactionId: taskId,
|
|
2447
|
-
interaction: {
|
|
2448
|
-
participants: {
|
|
2449
|
-
[agentId]: {
|
|
2450
|
-
hasLeft: true,
|
|
2451
|
-
},
|
|
2452
|
-
},
|
|
2453
|
-
owner: agentId,
|
|
2454
|
-
media: {
|
|
2455
|
-
[taskId]: {
|
|
2456
|
-
mType: 'consultCall',
|
|
2457
|
-
participants: ['other-agent'],
|
|
2458
|
-
},
|
|
2459
|
-
},
|
|
2460
|
-
},
|
|
2461
|
-
},
|
|
2462
|
-
};
|
|
2463
|
-
|
|
2464
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2465
|
-
|
|
2466
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2467
|
-
|
|
2468
|
-
// Task should still exist - not removed because agent is primary
|
|
2469
|
-
expect(removeTaskSpy).not.toHaveBeenCalled();
|
|
2470
|
-
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
2471
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2472
|
-
});
|
|
2473
|
-
|
|
2474
|
-
it('should remove task when agent left and is NOT in main interaction and is NOT primary', () => {
|
|
2475
|
-
const payload = {
|
|
2476
|
-
data: {
|
|
2477
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2478
|
-
interactionId: taskId,
|
|
2479
|
-
interaction: {
|
|
2480
|
-
participants: {
|
|
2481
|
-
[agentId]: {
|
|
2482
|
-
hasLeft: true,
|
|
2483
|
-
},
|
|
2484
|
-
},
|
|
2485
|
-
owner: 'another-agent-id',
|
|
2486
|
-
media: {
|
|
2487
|
-
[taskId]: {
|
|
2488
|
-
mType: 'mainCall',
|
|
2489
|
-
participants: ['another-agent-id'],
|
|
2490
|
-
},
|
|
2491
|
-
},
|
|
2492
|
-
},
|
|
2493
|
-
},
|
|
2494
|
-
};
|
|
2495
|
-
|
|
2496
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2497
|
-
|
|
2498
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2499
|
-
|
|
2500
|
-
// Task should be removed
|
|
2501
|
-
expect(removeTaskSpy).toHaveBeenCalled();
|
|
2502
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2503
|
-
});
|
|
2504
|
-
|
|
2505
|
-
it('should remove task when agent is not in participants list', () => {
|
|
2506
|
-
const payload = {
|
|
2507
|
-
data: {
|
|
2508
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2509
|
-
interactionId: taskId,
|
|
2510
|
-
interaction: {
|
|
2511
|
-
participants: {
|
|
2512
|
-
'other-agent-id': {
|
|
2513
|
-
hasLeft: false,
|
|
2514
|
-
},
|
|
2515
|
-
},
|
|
2516
|
-
owner: 'another-agent-id',
|
|
2517
|
-
},
|
|
2518
|
-
},
|
|
2519
|
-
};
|
|
2520
|
-
|
|
2521
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2522
|
-
|
|
2523
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2524
|
-
|
|
2525
|
-
// Task should be removed because agent is not in participants
|
|
2526
|
-
expect(removeTaskSpy).toHaveBeenCalled();
|
|
2527
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2528
|
-
});
|
|
2529
|
-
|
|
2530
|
-
it('should update isConferenceInProgress based on remaining active agents', () => {
|
|
2531
|
-
const payload = {
|
|
2532
|
-
data: {
|
|
2533
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2534
|
-
interactionId: taskId,
|
|
2535
|
-
interaction: {
|
|
2536
|
-
participants: {
|
|
2537
|
-
[agentId]: {
|
|
2538
|
-
hasLeft: false,
|
|
2539
|
-
pType: 'Agent',
|
|
2540
|
-
},
|
|
2541
|
-
'agent-2': {
|
|
2542
|
-
hasLeft: false,
|
|
2543
|
-
pType: 'Agent',
|
|
2544
|
-
},
|
|
2545
|
-
'customer-1': {
|
|
2546
|
-
hasLeft: false,
|
|
2547
|
-
pType: 'Customer',
|
|
2548
|
-
},
|
|
2549
|
-
},
|
|
2550
|
-
media: {
|
|
2551
|
-
[taskId]: {
|
|
2552
|
-
mType: 'mainCall',
|
|
2553
|
-
participants: [agentId, 'agent-2', 'customer-1'],
|
|
2554
|
-
},
|
|
2555
|
-
},
|
|
2556
|
-
},
|
|
2557
|
-
},
|
|
2558
|
-
};
|
|
2559
|
-
|
|
2560
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2561
|
-
|
|
2562
|
-
// isConferenceInProgress should be true (2 active agents)
|
|
2563
|
-
expect(task.data.isConferenceInProgress).toBe(true);
|
|
2564
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2565
|
-
});
|
|
2566
|
-
|
|
2567
|
-
it('should set isConferenceInProgress to false when only one agent remains', () => {
|
|
2568
|
-
const payload = {
|
|
2569
|
-
data: {
|
|
2570
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2571
|
-
interactionId: taskId,
|
|
2572
|
-
interaction: {
|
|
2573
|
-
participants: {
|
|
2574
|
-
[agentId]: {
|
|
2575
|
-
hasLeft: false,
|
|
2576
|
-
pType: 'Agent',
|
|
2577
|
-
},
|
|
2578
|
-
'agent-2': {
|
|
2579
|
-
hasLeft: true,
|
|
2580
|
-
pType: 'Agent',
|
|
2581
|
-
},
|
|
2582
|
-
'customer-1': {
|
|
2583
|
-
hasLeft: false,
|
|
2584
|
-
pType: 'Customer',
|
|
2585
|
-
},
|
|
2586
|
-
},
|
|
2587
|
-
media: {
|
|
2588
|
-
[taskId]: {
|
|
2589
|
-
mType: 'mainCall',
|
|
2590
|
-
participants: [agentId, 'customer-1'],
|
|
2591
|
-
},
|
|
2592
|
-
},
|
|
2593
|
-
},
|
|
2594
|
-
},
|
|
2595
|
-
};
|
|
2596
|
-
|
|
2597
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2598
|
-
|
|
2599
|
-
// isConferenceInProgress should be false (only 1 active agent)
|
|
2600
|
-
expect(task.data.isConferenceInProgress).toBe(false);
|
|
2601
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2602
|
-
});
|
|
2603
|
-
|
|
2604
|
-
it('should handle participant left when no participants data exists', () => {
|
|
2605
|
-
const payload = {
|
|
2606
|
-
data: {
|
|
2607
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2608
|
-
interactionId: taskId,
|
|
2609
|
-
interaction: {},
|
|
2610
|
-
},
|
|
2611
|
-
};
|
|
2612
|
-
|
|
2613
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2614
|
-
|
|
2615
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2616
|
-
|
|
2617
|
-
// When no participants data exists, checkParticipantNotInInteraction returns true
|
|
2618
|
-
// Since agent won't be in main interaction either, task should be removed
|
|
2619
|
-
expect(removeTaskSpy).toHaveBeenCalled();
|
|
2620
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2621
|
-
});
|
|
2337
|
+
}).not.toThrow();
|
|
2622
2338
|
});
|
|
2623
2339
|
|
|
2624
|
-
it('
|
|
2340
|
+
it('handles PARTICIPANT_LEFT_CONFERENCE_FAILED event without errors', () => {
|
|
2625
2341
|
const payload = {
|
|
2626
|
-
data: {
|
|
2627
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE_FAILED,
|
|
2628
|
-
interactionId: taskId,
|
|
2629
|
-
reason: 'Exit failed',
|
|
2630
|
-
},
|
|
2342
|
+
data: {type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE_FAILED, interactionId: taskId},
|
|
2631
2343
|
};
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
expect(task.data.reason).toBe('Exit failed');
|
|
2636
|
-
// No event emission expected for failure - handled by contact method promise rejection
|
|
2344
|
+
expect(() => {
|
|
2345
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2346
|
+
}).not.toThrow();
|
|
2637
2347
|
});
|
|
2638
2348
|
|
|
2639
|
-
it('
|
|
2349
|
+
it('only routes conference events to matching tasks', () => {
|
|
2640
2350
|
const otherTaskId = 'other-task-id';
|
|
2641
|
-
const otherTask = {
|
|
2351
|
+
const otherTask: any = {
|
|
2642
2352
|
data: {interactionId: otherTaskId},
|
|
2643
2353
|
emit: jest.fn(),
|
|
2354
|
+
updateTaskData: jest.fn(),
|
|
2355
|
+
sendStateMachineEvent: jest.fn(),
|
|
2644
2356
|
};
|
|
2645
2357
|
taskManager.taskCollection[otherTaskId] = otherTask;
|
|
2646
2358
|
|
|
2647
2359
|
const payload = {
|
|
2648
|
-
data: {
|
|
2649
|
-
type: CC_EVENTS.AGENT_CONSULT_CONFERENCED,
|
|
2650
|
-
interactionId: taskId,
|
|
2651
|
-
isConferencing: true,
|
|
2652
|
-
},
|
|
2360
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCED, interactionId: taskId},
|
|
2653
2361
|
};
|
|
2654
|
-
|
|
2655
2362
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2656
2363
|
|
|
2657
|
-
|
|
2658
|
-
expect(
|
|
2659
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONFERENCE_STARTED, task);
|
|
2660
|
-
|
|
2661
|
-
// Other task should not be affected
|
|
2662
|
-
expect(otherTask.data.isConferencing).toBeUndefined();
|
|
2663
|
-
expect(otherTask.emit).not.toHaveBeenCalled();
|
|
2364
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
2365
|
+
expect(otherTask.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
2664
2366
|
});
|
|
2665
2367
|
});
|
|
2666
2368
|
|
|
2667
|
-
describe('
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
it('should remove OUTDIAL task on CONTACT_ENDED when current agent is NOT in agentsPendingWrapUp', () => {
|
|
2675
|
-
const task = taskManager.getTask(taskId);
|
|
2676
|
-
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
2677
|
-
task.data = {
|
|
2678
|
-
...task.data,
|
|
2679
|
-
...newData,
|
|
2680
|
-
interaction: {
|
|
2681
|
-
...task.data.interaction,
|
|
2682
|
-
outboundType: 'OUTDIAL',
|
|
2683
|
-
state: 'new',
|
|
2684
|
-
mediaType: 'telephony',
|
|
2685
|
-
},
|
|
2686
|
-
agentsPendingWrapUp: ['different-agent-123'], // Current agent not in the list
|
|
2687
|
-
};
|
|
2688
|
-
return task;
|
|
2689
|
-
});
|
|
2690
|
-
task.unregisterWebCallListeners = jest.fn();
|
|
2691
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2692
|
-
|
|
2693
|
-
const payload = {
|
|
2694
|
-
data: {
|
|
2695
|
-
type: CC_EVENTS.CONTACT_ENDED,
|
|
2696
|
-
interactionId: taskId,
|
|
2697
|
-
interaction: {
|
|
2698
|
-
outboundType: 'OUTDIAL',
|
|
2699
|
-
state: 'new',
|
|
2700
|
-
mediaType: 'telephony',
|
|
2701
|
-
},
|
|
2702
|
-
agentsPendingWrapUp: ['different-agent-123'], // Current agent not in the list
|
|
2703
|
-
},
|
|
2704
|
-
};
|
|
2705
|
-
|
|
2706
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2707
|
-
|
|
2708
|
-
expect(removeTaskSpy).toHaveBeenCalled();
|
|
2709
|
-
expect(taskManager.getTask(taskId)).toBeUndefined();
|
|
2710
|
-
});
|
|
2711
|
-
|
|
2712
|
-
it('should NOT remove OUTDIAL task on CONTACT_ENDED when current agent IS in agentsPendingWrapUp', () => {
|
|
2713
|
-
const task = taskManager.getTask(taskId);
|
|
2714
|
-
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
2715
|
-
task.data = {
|
|
2716
|
-
...task.data,
|
|
2717
|
-
...newData,
|
|
2718
|
-
interaction: {
|
|
2719
|
-
...task.data.interaction,
|
|
2720
|
-
outboundType: 'OUTDIAL',
|
|
2721
|
-
state: 'new',
|
|
2722
|
-
mediaType: 'telephony',
|
|
2723
|
-
},
|
|
2724
|
-
agentsPendingWrapUp: [agentId, 'other-agent-456'], // Current agent IS in the list
|
|
2725
|
-
};
|
|
2726
|
-
return task;
|
|
2727
|
-
});
|
|
2728
|
-
task.unregisterWebCallListeners = jest.fn();
|
|
2729
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2730
|
-
|
|
2731
|
-
const payload = {
|
|
2732
|
-
data: {
|
|
2733
|
-
type: CC_EVENTS.CONTACT_ENDED,
|
|
2734
|
-
interactionId: taskId,
|
|
2735
|
-
interaction: {
|
|
2736
|
-
outboundType: 'OUTDIAL',
|
|
2737
|
-
state: 'new',
|
|
2738
|
-
mediaType: 'telephony',
|
|
2739
|
-
},
|
|
2740
|
-
agentsPendingWrapUp: [agentId, 'other-agent-456'], // Current agent IS in the list
|
|
2741
|
-
},
|
|
2742
|
-
};
|
|
2743
|
-
|
|
2744
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2745
|
-
|
|
2746
|
-
expect(removeTaskSpy).not.toHaveBeenCalled();
|
|
2747
|
-
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
2748
|
-
});
|
|
2749
|
-
|
|
2750
|
-
it('should remove OUTDIAL task when needsWrapUp is false and task is outdial', () => {
|
|
2751
|
-
const task = taskManager.getTask(taskId);
|
|
2752
|
-
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
2753
|
-
task.data = {
|
|
2754
|
-
...task.data,
|
|
2755
|
-
...newData,
|
|
2756
|
-
interaction: {
|
|
2757
|
-
...task.data.interaction,
|
|
2758
|
-
outboundType: 'OUTDIAL',
|
|
2759
|
-
state: 'WRAPUP', // Not 'new' state
|
|
2760
|
-
mediaType: 'telephony',
|
|
2761
|
-
},
|
|
2762
|
-
agentsPendingWrapUp: [], // No agents pending wrap-up
|
|
2763
|
-
};
|
|
2764
|
-
return task;
|
|
2765
|
-
});
|
|
2766
|
-
task.unregisterWebCallListeners = jest.fn();
|
|
2767
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2768
|
-
|
|
2769
|
-
const payload = {
|
|
2770
|
-
data: {
|
|
2771
|
-
type: CC_EVENTS.CONTACT_ENDED,
|
|
2772
|
-
interactionId: taskId,
|
|
2773
|
-
interaction: {
|
|
2774
|
-
outboundType: 'OUTDIAL',
|
|
2775
|
-
state: 'WRAPUP', // Not 'new' state
|
|
2776
|
-
mediaType: 'telephony',
|
|
2777
|
-
},
|
|
2778
|
-
agentsPendingWrapUp: [], // No agents pending wrap-up
|
|
2779
|
-
},
|
|
2780
|
-
};
|
|
2781
|
-
|
|
2782
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2783
|
-
|
|
2784
|
-
expect(removeTaskSpy).toHaveBeenCalled();
|
|
2785
|
-
expect(taskManager.getTask(taskId)).toBeUndefined();
|
|
2786
|
-
});
|
|
2787
|
-
|
|
2788
|
-
it('should remove OUTDIAL task when needsWrapUp is false (agentsPendingWrapUp is undefined)', () => {
|
|
2789
|
-
const task = taskManager.getTask(taskId);
|
|
2790
|
-
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
2791
|
-
task.data = {
|
|
2792
|
-
...task.data,
|
|
2793
|
-
...newData,
|
|
2794
|
-
interaction: {
|
|
2795
|
-
...task.data.interaction,
|
|
2796
|
-
outboundType: 'OUTDIAL',
|
|
2797
|
-
state: 'WRAPUP',
|
|
2798
|
-
mediaType: 'telephony',
|
|
2799
|
-
},
|
|
2800
|
-
agentsPendingWrapUp: undefined, // No agentsPendingWrapUp field
|
|
2801
|
-
};
|
|
2802
|
-
return task;
|
|
2803
|
-
});
|
|
2804
|
-
task.unregisterWebCallListeners = jest.fn();
|
|
2805
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2806
|
-
|
|
2807
|
-
const payload = {
|
|
2808
|
-
data: {
|
|
2809
|
-
type: CC_EVENTS.CONTACT_ENDED,
|
|
2810
|
-
interactionId: taskId,
|
|
2811
|
-
interaction: {
|
|
2812
|
-
outboundType: 'OUTDIAL',
|
|
2813
|
-
state: 'WRAPUP',
|
|
2814
|
-
mediaType: 'telephony',
|
|
2815
|
-
},
|
|
2816
|
-
// agentsPendingWrapUp not included
|
|
2817
|
-
},
|
|
2818
|
-
};
|
|
2819
|
-
|
|
2820
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2821
|
-
|
|
2822
|
-
expect(removeTaskSpy).toHaveBeenCalled();
|
|
2823
|
-
expect(taskManager.getTask(taskId)).toBeUndefined();
|
|
2824
|
-
});
|
|
2825
|
-
|
|
2826
|
-
it('should NOT remove OUTDIAL task when needsWrapUp is true (current agent in agentsPendingWrapUp) even if state is WRAPUP', () => {
|
|
2827
|
-
const task = taskManager.getTask(taskId);
|
|
2828
|
-
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
2829
|
-
task.data = {
|
|
2830
|
-
...task.data,
|
|
2831
|
-
...newData,
|
|
2832
|
-
interaction: {
|
|
2833
|
-
...task.data.interaction,
|
|
2834
|
-
outboundType: 'OUTDIAL',
|
|
2835
|
-
state: 'WRAPUP',
|
|
2836
|
-
mediaType: 'telephony',
|
|
2837
|
-
},
|
|
2838
|
-
agentsPendingWrapUp: [agentId], // Current agent needs wrap-up
|
|
2839
|
-
};
|
|
2840
|
-
return task;
|
|
2841
|
-
});
|
|
2842
|
-
task.unregisterWebCallListeners = jest.fn();
|
|
2843
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2844
|
-
|
|
2845
|
-
const payload = {
|
|
2846
|
-
data: {
|
|
2847
|
-
type: CC_EVENTS.CONTACT_ENDED,
|
|
2848
|
-
interactionId: taskId,
|
|
2849
|
-
interaction: {
|
|
2850
|
-
outboundType: 'OUTDIAL',
|
|
2851
|
-
state: 'WRAPUP',
|
|
2852
|
-
mediaType: 'telephony',
|
|
2853
|
-
},
|
|
2854
|
-
agentsPendingWrapUp: [agentId], // Current agent needs wrap-up
|
|
2855
|
-
},
|
|
2856
|
-
};
|
|
2857
|
-
|
|
2858
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2859
|
-
|
|
2860
|
-
expect(removeTaskSpy).not.toHaveBeenCalled();
|
|
2861
|
-
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
2862
|
-
});
|
|
2369
|
+
describe('state machine integration', () => {
|
|
2370
|
+
it('maps CC events to task state machine events using normalized payload', () => {
|
|
2371
|
+
const mapped = (TaskManager as any).mapEventToTaskStateMachineEvent(
|
|
2372
|
+
CC_EVENTS.AGENT_CONTACT_ASSIGNED,
|
|
2373
|
+
{...taskDataMock, type: CC_EVENTS.AGENT_CONTACT_ASSIGNED}
|
|
2374
|
+
);
|
|
2863
2375
|
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
task.data = {
|
|
2868
|
-
...task.data,
|
|
2869
|
-
...newData,
|
|
2870
|
-
interaction: {
|
|
2871
|
-
...task.data.interaction,
|
|
2872
|
-
outboundType: 'PREVIEW', // Not OUTDIAL
|
|
2873
|
-
state: 'new',
|
|
2874
|
-
mediaType: 'telephony',
|
|
2875
|
-
},
|
|
2876
|
-
agentsPendingWrapUp: [agentId],
|
|
2877
|
-
};
|
|
2878
|
-
return task;
|
|
2376
|
+
expect(mapped).toEqual({
|
|
2377
|
+
type: TaskEvent.ASSIGN,
|
|
2378
|
+
taskData: {...taskDataMock, type: CC_EVENTS.AGENT_CONTACT_ASSIGNED},
|
|
2879
2379
|
});
|
|
2880
|
-
task.unregisterWebCallListeners = jest.fn();
|
|
2881
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2882
|
-
|
|
2883
|
-
const payload = {
|
|
2884
|
-
data: {
|
|
2885
|
-
type: CC_EVENTS.CONTACT_ENDED,
|
|
2886
|
-
interactionId: taskId,
|
|
2887
|
-
interaction: {
|
|
2888
|
-
outboundType: 'PREVIEW',
|
|
2889
|
-
state: 'new',
|
|
2890
|
-
mediaType: 'telephony',
|
|
2891
|
-
},
|
|
2892
|
-
agentsPendingWrapUp: [agentId],
|
|
2893
|
-
},
|
|
2894
|
-
};
|
|
2895
|
-
|
|
2896
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2897
|
-
|
|
2898
|
-
expect(removeTaskSpy).toHaveBeenCalled();
|
|
2899
|
-
expect(taskManager.getTask(taskId)).toBeUndefined();
|
|
2900
2380
|
});
|
|
2901
2381
|
|
|
2902
|
-
it('
|
|
2382
|
+
it('sends mapped events to the task state machine service', () => {
|
|
2383
|
+
const payload = {...taskDataMock, type: CC_EVENTS.AGENT_CONTACT_ASSIGNED};
|
|
2903
2384
|
const task = taskManager.getTask(taskId);
|
|
2904
|
-
|
|
2905
|
-
task.data = {
|
|
2906
|
-
...task.data,
|
|
2907
|
-
...newData,
|
|
2908
|
-
interaction: {
|
|
2909
|
-
...task.data.interaction,
|
|
2910
|
-
outboundType: 'OUTDIAL',
|
|
2911
|
-
state: 'new',
|
|
2912
|
-
mediaType: 'telephony',
|
|
2913
|
-
},
|
|
2914
|
-
agentsPendingWrapUp: ['agent-1', 'agent-2', 'agent-3'], // Current agent not in the list
|
|
2915
|
-
};
|
|
2916
|
-
return task;
|
|
2917
|
-
});
|
|
2918
|
-
task.unregisterWebCallListeners = jest.fn();
|
|
2919
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2920
|
-
|
|
2921
|
-
const payload = {
|
|
2922
|
-
data: {
|
|
2923
|
-
type: CC_EVENTS.CONTACT_ENDED,
|
|
2924
|
-
interactionId: taskId,
|
|
2925
|
-
interaction: {
|
|
2926
|
-
outboundType: 'OUTDIAL',
|
|
2927
|
-
state: 'new',
|
|
2928
|
-
mediaType: 'telephony',
|
|
2929
|
-
},
|
|
2930
|
-
agentsPendingWrapUp: ['agent-1', 'agent-2', 'agent-3'],
|
|
2931
|
-
},
|
|
2932
|
-
};
|
|
2933
|
-
|
|
2934
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2385
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
2935
2386
|
|
|
2936
|
-
|
|
2937
|
-
expect(taskManager.getTask(taskId)).toBeUndefined();
|
|
2938
|
-
});
|
|
2387
|
+
webSocketManagerMock.emit('message', JSON.stringify({data: payload}));
|
|
2939
2388
|
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
task.data = {
|
|
2944
|
-
...task.data,
|
|
2945
|
-
...newData,
|
|
2946
|
-
interaction: {
|
|
2947
|
-
...task.data.interaction,
|
|
2948
|
-
outboundType: 'OUTDIAL',
|
|
2949
|
-
state: 'new',
|
|
2950
|
-
mediaType: 'telephony',
|
|
2951
|
-
},
|
|
2952
|
-
agentsPendingWrapUp: ['agent-1', agentId, 'agent-3'], // Current agent IS in the list
|
|
2953
|
-
};
|
|
2954
|
-
return task;
|
|
2389
|
+
expect(sendStateMachineEventSpy).toHaveBeenCalledWith({
|
|
2390
|
+
type: TaskEvent.ASSIGN,
|
|
2391
|
+
taskData: payload,
|
|
2955
2392
|
});
|
|
2956
|
-
task.unregisterWebCallListeners = jest.fn();
|
|
2957
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2958
|
-
|
|
2959
|
-
const payload = {
|
|
2960
|
-
data: {
|
|
2961
|
-
type: CC_EVENTS.CONTACT_ENDED,
|
|
2962
|
-
interactionId: taskId,
|
|
2963
|
-
interaction: {
|
|
2964
|
-
outboundType: 'OUTDIAL',
|
|
2965
|
-
state: 'new',
|
|
2966
|
-
mediaType: 'telephony',
|
|
2967
|
-
},
|
|
2968
|
-
agentsPendingWrapUp: ['agent-1', agentId, 'agent-3'],
|
|
2969
|
-
},
|
|
2970
|
-
};
|
|
2971
|
-
|
|
2972
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2973
|
-
|
|
2974
|
-
expect(removeTaskSpy).not.toHaveBeenCalled();
|
|
2975
|
-
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
2976
|
-
});
|
|
2977
|
-
});
|
|
2978
|
-
|
|
2979
|
-
describe('CONTACT_MERGED event handling', () => {
|
|
2980
|
-
let task;
|
|
2981
|
-
let taskEmitSpy;
|
|
2982
|
-
let managerEmitSpy;
|
|
2983
|
-
|
|
2984
|
-
beforeEach(() => {
|
|
2985
|
-
// Create initial task
|
|
2986
|
-
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
2987
|
-
task = taskManager.getTask(taskId);
|
|
2988
|
-
taskEmitSpy = jest.spyOn(task, 'emit');
|
|
2989
|
-
managerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
2990
|
-
});
|
|
2991
|
-
|
|
2992
|
-
it('should update existing task data and emit TASK_MERGED event when CONTACT_MERGED is received', () => {
|
|
2993
|
-
const mergedPayload = {
|
|
2994
|
-
data: {
|
|
2995
|
-
type: CC_EVENTS.CONTACT_MERGED,
|
|
2996
|
-
interactionId: taskId,
|
|
2997
|
-
agentId: taskDataMock.agentId,
|
|
2998
|
-
interaction: {
|
|
2999
|
-
...taskDataMock.interaction,
|
|
3000
|
-
state: 'merged',
|
|
3001
|
-
customField: 'updated-value',
|
|
3002
|
-
},
|
|
3003
|
-
},
|
|
3004
|
-
};
|
|
3005
|
-
|
|
3006
|
-
webSocketManagerMock.emit('message', JSON.stringify(mergedPayload));
|
|
3007
|
-
|
|
3008
|
-
const updatedTask = taskManager.getTask(taskId);
|
|
3009
|
-
expect(updatedTask).toBeDefined();
|
|
3010
|
-
expect(updatedTask.data.interaction.customField).toBe('updated-value');
|
|
3011
|
-
expect(updatedTask.data.interaction.state).toBe('merged');
|
|
3012
|
-
expect(managerEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_MERGED, updatedTask);
|
|
3013
|
-
});
|
|
3014
|
-
|
|
3015
|
-
it('should create new task when CONTACT_MERGED is received for non-existing task', () => {
|
|
3016
|
-
const newMergedTaskId = 'new-merged-task-id';
|
|
3017
|
-
const mergedPayload = {
|
|
3018
|
-
data: {
|
|
3019
|
-
type: CC_EVENTS.CONTACT_MERGED,
|
|
3020
|
-
interactionId: newMergedTaskId,
|
|
3021
|
-
agentId: taskDataMock.agentId,
|
|
3022
|
-
interaction: {
|
|
3023
|
-
mediaType: 'telephony',
|
|
3024
|
-
state: 'merged',
|
|
3025
|
-
participants: {
|
|
3026
|
-
[taskDataMock.agentId]: {
|
|
3027
|
-
isWrapUp: false,
|
|
3028
|
-
hasJoined: true,
|
|
3029
|
-
},
|
|
3030
|
-
},
|
|
3031
|
-
},
|
|
3032
|
-
},
|
|
3033
|
-
};
|
|
3034
|
-
|
|
3035
|
-
// Verify task doesn't exist before
|
|
3036
|
-
expect(taskManager.getTask(newMergedTaskId)).toBeUndefined();
|
|
3037
|
-
|
|
3038
|
-
webSocketManagerMock.emit('message', JSON.stringify(mergedPayload));
|
|
3039
|
-
|
|
3040
|
-
// Verify task was created
|
|
3041
|
-
const newTask = taskManager.getTask(newMergedTaskId);
|
|
3042
|
-
expect(newTask).toBeDefined();
|
|
3043
|
-
expect(newTask.data.interactionId).toBe(newMergedTaskId);
|
|
3044
|
-
expect(managerEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_MERGED, newTask);
|
|
3045
|
-
});
|
|
3046
|
-
|
|
3047
|
-
it('should remove child task when childInteractionId is present in CONTACT_MERGED', () => {
|
|
3048
|
-
const childTaskId = 'child-task-id';
|
|
3049
|
-
const parentTaskId = 'parent-task-id';
|
|
3050
|
-
|
|
3051
|
-
// Create child task
|
|
3052
|
-
const childPayload = {
|
|
3053
|
-
data: {
|
|
3054
|
-
type: CC_EVENTS.AGENT_CONTACT_RESERVED,
|
|
3055
|
-
interactionId: childTaskId,
|
|
3056
|
-
agentId: taskDataMock.agentId,
|
|
3057
|
-
interaction: {mediaType: 'telephony'},
|
|
3058
|
-
},
|
|
3059
|
-
};
|
|
3060
|
-
webSocketManagerMock.emit('message', JSON.stringify(childPayload));
|
|
3061
|
-
|
|
3062
|
-
// Verify child task exists
|
|
3063
|
-
expect(taskManager.getTask(childTaskId)).toBeDefined();
|
|
3064
|
-
|
|
3065
|
-
// Create parent task
|
|
3066
|
-
const parentPayload = {
|
|
3067
|
-
data: {
|
|
3068
|
-
type: CC_EVENTS.AGENT_CONTACT_RESERVED,
|
|
3069
|
-
interactionId: parentTaskId,
|
|
3070
|
-
agentId: taskDataMock.agentId,
|
|
3071
|
-
interaction: {mediaType: 'telephony'},
|
|
3072
|
-
},
|
|
3073
|
-
};
|
|
3074
|
-
webSocketManagerMock.emit('message', JSON.stringify(parentPayload));
|
|
3075
|
-
|
|
3076
|
-
// Send CONTACT_MERGED with childInteractionId
|
|
3077
|
-
const mergedPayload = {
|
|
3078
|
-
data: {
|
|
3079
|
-
type: CC_EVENTS.CONTACT_MERGED,
|
|
3080
|
-
interactionId: parentTaskId,
|
|
3081
|
-
childInteractionId: childTaskId,
|
|
3082
|
-
agentId: taskDataMock.agentId,
|
|
3083
|
-
interaction: {
|
|
3084
|
-
mediaType: 'telephony',
|
|
3085
|
-
state: 'merged',
|
|
3086
|
-
},
|
|
3087
|
-
},
|
|
3088
|
-
};
|
|
3089
|
-
|
|
3090
|
-
webSocketManagerMock.emit('message', JSON.stringify(mergedPayload));
|
|
3091
|
-
|
|
3092
|
-
// Verify child task was removed
|
|
3093
|
-
expect(taskManager.getTask(childTaskId)).toBeUndefined();
|
|
3094
|
-
|
|
3095
|
-
// Verify parent task still exists
|
|
3096
|
-
expect(taskManager.getTask(parentTaskId)).toBeDefined();
|
|
3097
|
-
|
|
3098
|
-
// Verify TASK_MERGED event was emitted
|
|
3099
|
-
expect(managerEmitSpy).toHaveBeenCalledWith(
|
|
3100
|
-
TASK_EVENTS.TASK_MERGED,
|
|
3101
|
-
expect.objectContaining({
|
|
3102
|
-
data: expect.objectContaining({
|
|
3103
|
-
interactionId: parentTaskId,
|
|
3104
|
-
}),
|
|
3105
|
-
})
|
|
3106
|
-
);
|
|
3107
|
-
});
|
|
3108
|
-
|
|
3109
|
-
it('should handle CONTACT_MERGED with EP-DN participant correctly', () => {
|
|
3110
|
-
const epdnTaskId = 'epdn-merged-task';
|
|
3111
|
-
const mergedPayload = {
|
|
3112
|
-
data: {
|
|
3113
|
-
type: CC_EVENTS.CONTACT_MERGED,
|
|
3114
|
-
interactionId: epdnTaskId,
|
|
3115
|
-
agentId: taskDataMock.agentId,
|
|
3116
|
-
interaction: {
|
|
3117
|
-
mediaType: 'telephony',
|
|
3118
|
-
state: 'merged',
|
|
3119
|
-
participants: {
|
|
3120
|
-
[taskDataMock.agentId]: {
|
|
3121
|
-
type: 'Agent',
|
|
3122
|
-
isWrapUp: false,
|
|
3123
|
-
hasJoined: true,
|
|
3124
|
-
},
|
|
3125
|
-
'epdn-participant': {
|
|
3126
|
-
type: 'EpDn',
|
|
3127
|
-
epId: 'entry-point-123',
|
|
3128
|
-
isWrapUp: false,
|
|
3129
|
-
},
|
|
3130
|
-
},
|
|
3131
|
-
},
|
|
3132
|
-
},
|
|
3133
|
-
};
|
|
3134
|
-
|
|
3135
|
-
webSocketManagerMock.emit('message', JSON.stringify(mergedPayload));
|
|
3136
|
-
|
|
3137
|
-
const mergedTask = taskManager.getTask(epdnTaskId);
|
|
3138
|
-
expect(mergedTask).toBeDefined();
|
|
3139
|
-
expect(mergedTask.data.interaction.participants['epdn-participant']).toBeDefined();
|
|
3140
|
-
expect(mergedTask.data.interaction.participants['epdn-participant'].type).toBe('EpDn');
|
|
3141
|
-
expect(managerEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_MERGED, mergedTask);
|
|
3142
|
-
});
|
|
3143
|
-
|
|
3144
|
-
it('should not affect other tasks when CONTACT_MERGED is received', () => {
|
|
3145
|
-
const otherTaskId = 'other-unrelated-task';
|
|
3146
|
-
const otherPayload = {
|
|
3147
|
-
data: {
|
|
3148
|
-
type: CC_EVENTS.AGENT_CONTACT_RESERVED,
|
|
3149
|
-
interactionId: otherTaskId,
|
|
3150
|
-
agentId: taskDataMock.agentId,
|
|
3151
|
-
interaction: {mediaType: 'chat'},
|
|
3152
|
-
},
|
|
3153
|
-
};
|
|
3154
|
-
webSocketManagerMock.emit('message', JSON.stringify(otherPayload));
|
|
3155
|
-
|
|
3156
|
-
const otherTask = taskManager.getTask(otherTaskId);
|
|
3157
|
-
const otherTaskEmitSpy = jest.spyOn(otherTask, 'emit');
|
|
3158
|
-
|
|
3159
|
-
// Send CONTACT_MERGED for the original task
|
|
3160
|
-
const mergedPayload = {
|
|
3161
|
-
data: {
|
|
3162
|
-
type: CC_EVENTS.CONTACT_MERGED,
|
|
3163
|
-
interactionId: taskId,
|
|
3164
|
-
agentId: taskDataMock.agentId,
|
|
3165
|
-
interaction: {
|
|
3166
|
-
mediaType: 'telephony',
|
|
3167
|
-
state: 'merged',
|
|
3168
|
-
},
|
|
3169
|
-
},
|
|
3170
|
-
};
|
|
3171
|
-
|
|
3172
|
-
webSocketManagerMock.emit('message', JSON.stringify(mergedPayload));
|
|
3173
|
-
|
|
3174
|
-
// Verify other task was not affected
|
|
3175
|
-
expect(otherTaskEmitSpy).not.toHaveBeenCalled();
|
|
3176
|
-
expect(otherTask.data.interaction.mediaType).toBe('chat');
|
|
3177
|
-
|
|
3178
|
-
// Verify original task was updated
|
|
3179
|
-
expect(managerEmitSpy).toHaveBeenCalledWith(
|
|
3180
|
-
TASK_EVENTS.TASK_MERGED,
|
|
3181
|
-
expect.objectContaining({
|
|
3182
|
-
data: expect.objectContaining({
|
|
3183
|
-
interactionId: taskId,
|
|
3184
|
-
}),
|
|
3185
|
-
})
|
|
3186
|
-
);
|
|
3187
|
-
});
|
|
3188
|
-
});
|
|
3189
|
-
|
|
3190
|
-
describe('Campaign Preview Reservation', () => {
|
|
3191
|
-
it('should create a task and emit TASK_CAMPAIGN_PREVIEW_RESERVATION when AgentOfferCampaignReservation is received', () => {
|
|
3192
|
-
const campaignPayload = {
|
|
3193
|
-
data: {
|
|
3194
|
-
type: CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION,
|
|
3195
|
-
interactionId: 'campaign-interaction-123',
|
|
3196
|
-
agentId: taskDataMock.agentId,
|
|
3197
|
-
orgId: taskDataMock.orgId,
|
|
3198
|
-
trackingId: 'campaign-tracking-456',
|
|
3199
|
-
interaction: {
|
|
3200
|
-
mediaType: 'telephony',
|
|
3201
|
-
callProcessingDetails: {
|
|
3202
|
-
campaignId: 'campaign-789',
|
|
3203
|
-
},
|
|
3204
|
-
},
|
|
3205
|
-
},
|
|
3206
|
-
};
|
|
3207
|
-
|
|
3208
|
-
const managerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
3209
|
-
|
|
3210
|
-
webSocketManagerMock.emit('message', JSON.stringify(campaignPayload));
|
|
3211
|
-
|
|
3212
|
-
// Should emit with a task object (not raw data)
|
|
3213
|
-
expect(managerEmitSpy).toHaveBeenCalledWith(
|
|
3214
|
-
TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
|
|
3215
|
-
expect.objectContaining({
|
|
3216
|
-
data: expect.objectContaining({
|
|
3217
|
-
interactionId: 'campaign-interaction-123',
|
|
3218
|
-
type: CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION,
|
|
3219
|
-
wrapUpRequired: false,
|
|
3220
|
-
isAutoAnswering: false,
|
|
3221
|
-
}),
|
|
3222
|
-
})
|
|
3223
|
-
);
|
|
3224
|
-
|
|
3225
|
-
// Task should be in the collection so subsequent events (e.g. AGENT_CONTACT_ASSIGNED) can find it
|
|
3226
|
-
expect(taskManager['taskCollection']['campaign-interaction-123']).toBeDefined();
|
|
3227
|
-
});
|
|
3228
|
-
|
|
3229
|
-
it('should not emit TASK_INCOMING for campaign preview reservation when incoming WebRTC call arrives', () => {
|
|
3230
|
-
const campaignPayload = {
|
|
3231
|
-
data: {
|
|
3232
|
-
type: CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION,
|
|
3233
|
-
interactionId: 'campaign-interaction-123',
|
|
3234
|
-
agentId: taskDataMock.agentId,
|
|
3235
|
-
orgId: taskDataMock.orgId,
|
|
3236
|
-
trackingId: 'campaign-tracking-456',
|
|
3237
|
-
interaction: {
|
|
3238
|
-
mediaType: 'telephony',
|
|
3239
|
-
callProcessingDetails: {
|
|
3240
|
-
campaignId: 'campaign-789',
|
|
3241
|
-
},
|
|
3242
|
-
},
|
|
3243
|
-
},
|
|
3244
|
-
};
|
|
3245
|
-
|
|
3246
|
-
// Remove the default task so only the campaign preview task is in the collection
|
|
3247
|
-
delete taskManager['taskCollection'][taskId];
|
|
3248
|
-
|
|
3249
|
-
// Create campaign preview task via the reservation event
|
|
3250
|
-
webSocketManagerMock.emit('message', JSON.stringify(campaignPayload));
|
|
3251
|
-
|
|
3252
|
-
const managerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
3253
|
-
|
|
3254
|
-
// Simulate an incoming WebRTC call
|
|
3255
|
-
const incomingCallCb = onSpy.mock.calls[0][1];
|
|
3256
|
-
incomingCallCb(mockCall);
|
|
3257
|
-
|
|
3258
|
-
// TASK_INCOMING should NOT be emitted because the only telephony task is a campaign preview
|
|
3259
|
-
expect(managerEmitSpy).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_INCOMING, expect.anything());
|
|
3260
|
-
});
|
|
3261
|
-
|
|
3262
|
-
it('should update existing task when AgentOfferCampaignReservation is received for known interactionId', () => {
|
|
3263
|
-
const campaignPayload = {
|
|
3264
|
-
data: {
|
|
3265
|
-
type: CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION,
|
|
3266
|
-
interactionId: 'campaign-interaction-123',
|
|
3267
|
-
agentId: taskDataMock.agentId,
|
|
3268
|
-
orgId: taskDataMock.orgId,
|
|
3269
|
-
trackingId: 'campaign-tracking-456',
|
|
3270
|
-
interaction: {
|
|
3271
|
-
mediaType: 'telephony',
|
|
3272
|
-
callProcessingDetails: {
|
|
3273
|
-
campaignId: 'campaign-789',
|
|
3274
|
-
},
|
|
3275
|
-
},
|
|
3276
|
-
},
|
|
3277
|
-
};
|
|
3278
|
-
|
|
3279
|
-
// Send the first reservation to create the task
|
|
3280
|
-
webSocketManagerMock.emit('message', JSON.stringify(campaignPayload));
|
|
3281
|
-
|
|
3282
|
-
const managerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
3283
|
-
|
|
3284
|
-
// Send a second reservation for the same interactionId
|
|
3285
|
-
webSocketManagerMock.emit('message', JSON.stringify(campaignPayload));
|
|
3286
|
-
|
|
3287
|
-
expect(managerEmitSpy).toHaveBeenCalledWith(
|
|
3288
|
-
TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
|
|
3289
|
-
expect.objectContaining({
|
|
3290
|
-
data: expect.objectContaining({
|
|
3291
|
-
interactionId: 'campaign-interaction-123',
|
|
3292
|
-
}),
|
|
3293
|
-
})
|
|
3294
|
-
);
|
|
3295
|
-
});
|
|
3296
|
-
|
|
3297
|
-
it('should update task data but NOT remove task when CampaignContactUpdated is received', () => {
|
|
3298
|
-
const campaignInteractionId = 'campaign-interaction-123';
|
|
3299
|
-
|
|
3300
|
-
// First create a campaign preview task
|
|
3301
|
-
const reservationPayload = {
|
|
3302
|
-
data: {
|
|
3303
|
-
type: CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION,
|
|
3304
|
-
interactionId: campaignInteractionId,
|
|
3305
|
-
agentId: taskDataMock.agentId,
|
|
3306
|
-
orgId: taskDataMock.orgId,
|
|
3307
|
-
trackingId: 'campaign-tracking-456',
|
|
3308
|
-
interaction: {
|
|
3309
|
-
mediaType: 'telephony',
|
|
3310
|
-
callProcessingDetails: {
|
|
3311
|
-
campaignId: 'campaign-789',
|
|
3312
|
-
},
|
|
3313
|
-
},
|
|
3314
|
-
},
|
|
3315
|
-
};
|
|
3316
|
-
|
|
3317
|
-
webSocketManagerMock.emit('message', JSON.stringify(reservationPayload));
|
|
3318
|
-
|
|
3319
|
-
// Verify task exists in collection
|
|
3320
|
-
const task = taskManager['taskCollection'][campaignInteractionId];
|
|
3321
|
-
expect(task).toBeDefined();
|
|
3322
|
-
|
|
3323
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
3324
|
-
|
|
3325
|
-
// Now send CampaignContactUpdated
|
|
3326
|
-
const campaignContactUpdatedPayload = {
|
|
3327
|
-
data: {
|
|
3328
|
-
type: CC_EVENTS.CAMPAIGN_CONTACT_UPDATED,
|
|
3329
|
-
interactionId: campaignInteractionId,
|
|
3330
|
-
agentId: taskDataMock.agentId,
|
|
3331
|
-
orgId: taskDataMock.orgId,
|
|
3332
|
-
interaction: {
|
|
3333
|
-
mediaType: 'telephony',
|
|
3334
|
-
state: 'new',
|
|
3335
|
-
callProcessingDetails: {
|
|
3336
|
-
campaignId: 'campaign-789',
|
|
3337
|
-
},
|
|
3338
|
-
},
|
|
3339
|
-
},
|
|
3340
|
-
};
|
|
3341
|
-
|
|
3342
|
-
webSocketManagerMock.emit('message', JSON.stringify(campaignContactUpdatedPayload));
|
|
3343
|
-
|
|
3344
|
-
// Task should still exist in collection (not removed — non-terminal event)
|
|
3345
|
-
expect(taskManager['taskCollection'][campaignInteractionId]).toBeDefined();
|
|
3346
2393
|
|
|
3347
|
-
|
|
3348
|
-
expect(taskEmitSpy).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_END, expect.anything());
|
|
2394
|
+
sendStateMachineEventSpy.mockRestore();
|
|
3349
2395
|
});
|
|
3350
2396
|
});
|
|
3351
2397
|
});
|