@webex/contact-center 3.12.0-next.8 → 3.12.0-next.81
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 +265 -29
- package/dist/cc.js.map +1 -1
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +16 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.js +20 -5
- package/dist/index.js.map +1 -1
- package/dist/metrics/behavioral-events.js +101 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +23 -4
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/ApiAiAssistant.js +74 -3
- package/dist/services/ApiAiAssistant.js.map +1 -1
- package/dist/services/UserPreference.js +427 -0
- package/dist/services/UserPreference.js.map +1 -0
- package/dist/services/config/Util.js +3 -3
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/constants.js +23 -2
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/types.js +49 -9
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/core/Utils.js +107 -32
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/core/websocket/WebSocketManager.js +2 -1
- 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 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/task/Task.js +688 -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 +725 -526
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +162 -26
- package/dist/services/task/TaskUtils.js.map +1 -1
- package/dist/services/task/constants.js +9 -2
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/dialer.js +78 -0
- package/dist/services/task/dialer.js.map +1 -1
- package/dist/services/task/digital/Digital.js +77 -0
- package/dist/services/task/digital/Digital.js.map +1 -0
- package/dist/services/task/state-machine/TaskStateMachine.js +837 -0
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
- package/dist/services/task/state-machine/actions.js +543 -0
- package/dist/services/task/state-machine/actions.js.map +1 -0
- package/dist/services/task/state-machine/constants.js +161 -0
- package/dist/services/task/state-machine/constants.js.map +1 -0
- package/dist/services/task/state-machine/guards.js +340 -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 +553 -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 +212 -4
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +1042 -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 +94 -1
- package/dist/types/config.d.ts +6 -0
- package/dist/types/constants.d.ts +16 -1
- package/dist/types/index.d.ts +21 -6
- package/dist/types/metrics/constants.d.ts +19 -1
- package/dist/types/services/ApiAiAssistant.d.ts +11 -3
- package/dist/types/services/UserPreference.d.ts +118 -0
- package/dist/types/services/config/constants.d.ts +21 -0
- package/dist/types/services/config/types.d.ts +171 -10
- package/dist/types/services/core/Err.d.ts +4 -0
- package/dist/types/services/core/Utils.d.ts +33 -13
- package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
- 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 +157 -0
- package/dist/types/services/task/TaskFactory.d.ts +12 -0
- package/dist/types/services/task/TaskUtils.d.ts +46 -2
- package/dist/types/services/task/constants.d.ts +7 -0
- package/dist/types/services/task/dialer.d.ts +30 -0
- package/dist/types/services/task/digital/Digital.d.ts +22 -0
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1144 -0
- package/dist/types/services/task/state-machine/actions.d.ts +10 -0
- package/dist/types/services/task/state-machine/constants.d.ts +107 -0
- package/dist/types/services/task/state-machine/guards.d.ts +90 -0
- package/dist/types/services/task/state-machine/index.d.ts +13 -0
- package/dist/types/services/task/state-machine/types.d.ts +267 -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 +603 -66
- package/dist/types/services/task/voice/Voice.d.ts +184 -0
- package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
- package/dist/types/types.d.ts +94 -0
- package/dist/types/webex.d.ts +1 -0
- package/dist/types.js +85 -0
- package/dist/types.js.map +1 -1
- package/dist/webex.js +14 -2
- package/dist/webex.js.map +1 -1
- package/package.json +15 -12
- package/src/cc.ts +329 -30
- package/src/config.ts +6 -0
- package/src/constants.ts +16 -1
- package/src/index.ts +23 -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 +106 -0
- package/src/metrics/constants.ts +23 -4
- package/src/services/ApiAiAssistant.ts +104 -3
- package/src/services/UserPreference.ts +509 -0
- 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 +3 -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/constants.ts +25 -1
- package/src/services/config/types.ts +174 -11
- package/src/services/core/Err.ts +2 -0
- package/src/services/core/Utils.ts +123 -37
- package/src/services/core/ai-docs/AGENTS.md +379 -0
- package/src/services/core/ai-docs/ARCHITECTURE.md +696 -0
- package/src/services/core/websocket/WebSocketManager.ts +2 -0
- package/src/services/core/websocket/types.ts +1 -1
- package/src/services/index.ts +1 -1
- package/src/services/task/Task.ts +837 -0
- package/src/services/task/TaskFactory.ts +55 -0
- package/src/services/task/TaskManager.ts +738 -613
- package/src/services/task/TaskUtils.ts +205 -25
- package/src/services/task/ai-docs/AGENTS.md +455 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +585 -0
- package/src/services/task/constants.ts +7 -0
- package/src/services/task/dialer.ts +80 -0
- package/src/services/task/digital/Digital.ts +95 -0
- package/src/services/task/state-machine/TaskStateMachine.ts +1077 -0
- package/src/services/task/state-machine/actions.ts +685 -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 +172 -0
- package/src/services/task/state-machine/guards.ts +406 -0
- package/src/services/task/state-machine/index.ts +28 -0
- package/src/services/task/state-machine/types.ts +241 -0
- package/src/services/task/state-machine/uiControlsComputer.ts +867 -0
- package/src/services/task/taskDataNormalizer.ts +137 -0
- package/src/services/task/types.ts +710 -71
- package/src/services/task/voice/Voice.ts +1267 -0
- package/src/services/task/voice/WebRTC.ts +187 -0
- package/src/types.ts +122 -2
- package/src/utils/AGENTS.md +276 -0
- package/src/webex.js +2 -0
- package/test/unit/spec/cc.ts +343 -23
- package/test/unit/spec/logger-proxy.ts +70 -0
- package/test/unit/spec/services/ApiAiAssistant.ts +122 -17
- package/test/unit/spec/services/UserPreference.ts +401 -0
- package/test/unit/spec/services/WebCallingService.ts +7 -1
- package/test/unit/spec/services/config/index.ts +30 -30
- package/test/unit/spec/services/core/Utils.ts +425 -8
- package/test/unit/spec/services/core/websocket/WebSocketManager.ts +66 -40
- package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
- package/test/unit/spec/services/task/Task.ts +477 -0
- package/test/unit/spec/services/task/TaskFactory.ts +62 -0
- package/test/unit/spec/services/task/TaskManager.ts +832 -1702
- package/test/unit/spec/services/task/TaskUtils.ts +206 -0
- package/test/unit/spec/services/task/dialer.ts +190 -0
- package/test/unit/spec/services/task/digital/Digital.ts +105 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +1825 -0
- package/test/unit/spec/services/task/state-machine/guards.ts +479 -0
- package/test/unit/spec/services/task/state-machine/types.ts +18 -0
- package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +2020 -0
- package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
- package/test/unit/spec/services/task/voice/Voice.ts +631 -0
- package/test/unit/spec/services/task/voice/WebRTC.ts +235 -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,108 @@ 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
|
+
if ([TaskEvent.ASSIGN, TaskEvent.CONSULTING_ACTIVE].includes(event.type as TaskEvent)) {
|
|
96
|
+
task.emit(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, task);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Auto-answer is now handled at the Task layer (triggered by state machine actions)
|
|
100
|
+
if (
|
|
101
|
+
[TaskEvent.TASK_OFFERED, TaskEvent.OFFER_CONSULT].includes(event.type as TaskEvent) &&
|
|
102
|
+
(event.taskData?.isAutoAnswering === true || event.taskData?.isAutoAnswering === 'true')
|
|
103
|
+
) {
|
|
104
|
+
Promise.resolve(task.accept())
|
|
105
|
+
.then(() => {
|
|
106
|
+
task.emit(TASK_EVENTS.TASK_AUTO_ANSWERED, task);
|
|
107
|
+
})
|
|
108
|
+
.catch(() => undefined);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Cleanup is now emitted by state machine actions (Task layer).
|
|
112
|
+
// Simulate the TASK_CLEANUP emission for unit tests using mock tasks.
|
|
113
|
+
const eventType = event.type as TaskEvent;
|
|
114
|
+
const shouldCleanup =
|
|
115
|
+
eventType === TaskEvent.CONTACT_ENDED ||
|
|
116
|
+
eventType === TaskEvent.END ||
|
|
117
|
+
eventType === TaskEvent.TASK_WRAPUP ||
|
|
118
|
+
eventType === TaskEvent.WRAPUP_COMPLETE ||
|
|
119
|
+
eventType === TaskEvent.ASSIGN_FAILED ||
|
|
120
|
+
eventType === TaskEvent.INVITE_FAILED ||
|
|
121
|
+
eventType === TaskEvent.RONA ||
|
|
122
|
+
eventType === TaskEvent.OUTBOUND_FAILED ||
|
|
123
|
+
(eventType === TaskEvent.CONSULT_END && task.data?.isConsulted === true);
|
|
124
|
+
|
|
125
|
+
if (shouldCleanup) {
|
|
126
|
+
const removeFromCollection = eventType !== TaskEvent.CONTACT_ENDED;
|
|
127
|
+
task.emit(TASK_EVENTS.TASK_CLEANUP, task, {removeFromCollection});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return task;
|
|
132
|
+
};
|
|
133
|
+
|
|
27
134
|
taskDataMock = {
|
|
28
135
|
type: CC_EVENTS.AGENT_CONTACT_RESERVED,
|
|
29
136
|
agentId: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
@@ -43,18 +150,21 @@ describe('TaskManager', () => {
|
|
|
43
150
|
data: taskDataMock,
|
|
44
151
|
};
|
|
45
152
|
|
|
153
|
+
const expectLastStateMachineEvent = (
|
|
154
|
+
spy: jest.SpyInstance | jest.Mock,
|
|
155
|
+
expectedType: TaskEvent
|
|
156
|
+
) => {
|
|
157
|
+
expect(spy).toHaveBeenCalled();
|
|
158
|
+
const lastCall = spy.mock.calls[spy.mock.calls.length - 1] || [];
|
|
159
|
+
const event = lastCall[3]?.type ? lastCall[3] : lastCall[0];
|
|
160
|
+
expect(event?.type).toBe(expectedType);
|
|
161
|
+
return event;
|
|
162
|
+
};
|
|
163
|
+
|
|
46
164
|
beforeEach(() => {
|
|
47
165
|
contactMock = contact;
|
|
48
166
|
webSocketManagerMock = new EventEmitter();
|
|
49
|
-
|
|
50
|
-
sendEvent: jest.fn().mockResolvedValue({}),
|
|
51
|
-
setAIFeatureFlags: jest.fn(),
|
|
52
|
-
aiFeature: {
|
|
53
|
-
realtimeTranscripts: {
|
|
54
|
-
enable: true,
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
};
|
|
167
|
+
rtdWebSocketManagerMock = new EventEmitter();
|
|
58
168
|
|
|
59
169
|
webex = {
|
|
60
170
|
logger: {
|
|
@@ -84,20 +194,25 @@ describe('TaskManager', () => {
|
|
|
84
194
|
onSpy = jest.spyOn(webCallingService, 'on');
|
|
85
195
|
offSpy = jest.spyOn(webCallingService, 'off');
|
|
86
196
|
|
|
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,
|
|
197
|
+
mockApiAIAssistant = {
|
|
198
|
+
sendEvent: jest.fn().mockResolvedValue({}),
|
|
97
199
|
};
|
|
98
|
-
|
|
99
|
-
taskManager
|
|
200
|
+
|
|
201
|
+
taskManager = new TaskManager(
|
|
202
|
+
mockApiAIAssistant as any,
|
|
203
|
+
contactMock,
|
|
204
|
+
webCallingService,
|
|
205
|
+
webSocketManagerMock as any,
|
|
206
|
+
rtdWebSocketManagerMock as any
|
|
207
|
+
);
|
|
208
|
+
taskManager.taskCollection[taskId] = createMockTask(taskDataMock);
|
|
209
|
+
(taskManager as any).setupTaskListeners?.(taskManager.taskCollection[taskId]);
|
|
100
210
|
taskManager.call = mockCall;
|
|
211
|
+
taskManager.setAgentId('test-agent-id');
|
|
212
|
+
|
|
213
|
+
jest
|
|
214
|
+
.spyOn(TaskFactory, 'createTask')
|
|
215
|
+
.mockImplementation((contact, webCallingService, data, configFlags) => createMockTask(data));
|
|
101
216
|
});
|
|
102
217
|
|
|
103
218
|
afterEach(() => {
|
|
@@ -108,7 +223,8 @@ describe('TaskManager', () => {
|
|
|
108
223
|
it('should initialize TaskManager and register listeners', () => {
|
|
109
224
|
webSocketManagerMock.emit('message', JSON.stringify({data: taskDataMock}));
|
|
110
225
|
const incomingCallCb = onSpy.mock.calls[0][1];
|
|
111
|
-
const
|
|
226
|
+
const incomingHandler = jest.fn();
|
|
227
|
+
taskManager.on(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
112
228
|
|
|
113
229
|
expect(taskManager).toBeInstanceOf(TaskManager);
|
|
114
230
|
expect(webCallingService.listenerCount(LINE_EVENTS.INCOMING_CALL)).toBe(1);
|
|
@@ -117,10 +233,8 @@ describe('TaskManager', () => {
|
|
|
117
233
|
|
|
118
234
|
incomingCallCb(mockCall);
|
|
119
235
|
|
|
120
|
-
expect(
|
|
121
|
-
|
|
122
|
-
taskManager.getTask(taskId)
|
|
123
|
-
);
|
|
236
|
+
expect(incomingHandler).toHaveBeenCalledWith(taskManager.getTask(taskId));
|
|
237
|
+
taskManager.off(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
124
238
|
});
|
|
125
239
|
|
|
126
240
|
it('should re-emit task related events', () => {
|
|
@@ -136,15 +250,32 @@ describe('TaskManager', () => {
|
|
|
136
250
|
|
|
137
251
|
webSocketManagerMock.emit('message', JSON.stringify(dummyPayload));
|
|
138
252
|
|
|
139
|
-
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
253
|
+
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
254
|
+
TASK_EVENTS.TASK_CONSULT_ACCEPTED,
|
|
255
|
+
taskManager.getTask(taskId)
|
|
256
|
+
);
|
|
140
257
|
});
|
|
141
258
|
|
|
142
259
|
it('should invoke sendEvent for configured start/stop backend events', () => {
|
|
260
|
+
taskManager.setConfigFlags({
|
|
261
|
+
isEndTaskEnabled: true,
|
|
262
|
+
isEndConsultEnabled: true,
|
|
263
|
+
webRtcEnabled: true,
|
|
264
|
+
autoWrapup: false,
|
|
265
|
+
aiFeature: {
|
|
266
|
+
id: 'ai-feature-1',
|
|
267
|
+
realtimeTranscripts: {
|
|
268
|
+
enable: true,
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const interactionId = taskId;
|
|
143
274
|
const message = (type: CC_EVENTS) =>
|
|
144
275
|
JSON.stringify({
|
|
145
276
|
data: {
|
|
146
277
|
...taskDataMock,
|
|
147
|
-
|
|
278
|
+
interactionId,
|
|
148
279
|
type,
|
|
149
280
|
},
|
|
150
281
|
});
|
|
@@ -153,67 +284,114 @@ describe('TaskManager', () => {
|
|
|
153
284
|
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULTING));
|
|
154
285
|
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULT_CONFERENCED));
|
|
155
286
|
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULT_ENDED));
|
|
156
|
-
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_WRAPUP));
|
|
157
287
|
webSocketManagerMock.emit('message', message(CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE));
|
|
288
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_WRAPUP));
|
|
158
289
|
|
|
159
290
|
expect(mockApiAIAssistant.sendEvent).toHaveBeenCalledTimes(6);
|
|
160
291
|
expect(mockApiAIAssistant.sendEvent).toHaveBeenCalledWith(
|
|
161
292
|
'test-agent-id',
|
|
162
|
-
|
|
293
|
+
interactionId,
|
|
163
294
|
'CUSTOM_EVENT',
|
|
164
295
|
'GET_TRANSCRIPTS',
|
|
165
296
|
'START'
|
|
166
297
|
);
|
|
167
298
|
expect(mockApiAIAssistant.sendEvent).toHaveBeenCalledWith(
|
|
168
299
|
'test-agent-id',
|
|
169
|
-
|
|
300
|
+
interactionId,
|
|
170
301
|
'CUSTOM_EVENT',
|
|
171
302
|
'GET_TRANSCRIPTS',
|
|
172
303
|
'STOP'
|
|
173
304
|
);
|
|
174
305
|
});
|
|
175
306
|
|
|
176
|
-
it('should not invoke sendEvent
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
307
|
+
it('should not invoke sendEvent when realtime transcripts are disabled in aiFeature', () => {
|
|
308
|
+
taskManager.setConfigFlags({
|
|
309
|
+
isEndTaskEnabled: true,
|
|
310
|
+
isEndConsultEnabled: true,
|
|
311
|
+
webRtcEnabled: true,
|
|
312
|
+
autoWrapup: false,
|
|
313
|
+
aiFeature: {
|
|
314
|
+
id: 'ai-feature-1',
|
|
315
|
+
realtimeTranscripts: {
|
|
316
|
+
enable: false,
|
|
317
|
+
},
|
|
180
318
|
},
|
|
181
|
-
};
|
|
182
|
-
mockApiAIAssistant.setAIFeatureFlags(mockApiAIAssistant.aiFeature);
|
|
319
|
+
});
|
|
183
320
|
|
|
184
321
|
const message = (type: CC_EVENTS) =>
|
|
185
322
|
JSON.stringify({
|
|
186
323
|
data: {
|
|
187
324
|
...taskDataMock,
|
|
188
|
-
taskId,
|
|
325
|
+
interactionId: taskId,
|
|
326
|
+
type,
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONTACT_ASSIGNED));
|
|
331
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULTING));
|
|
332
|
+
|
|
333
|
+
expect(mockApiAIAssistant.sendEvent).not.toHaveBeenCalled();
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('should not invoke sendEvent when realtime transcripts config is missing', () => {
|
|
337
|
+
taskManager.setConfigFlags({
|
|
338
|
+
isEndTaskEnabled: true,
|
|
339
|
+
isEndConsultEnabled: true,
|
|
340
|
+
webRtcEnabled: true,
|
|
341
|
+
autoWrapup: false,
|
|
342
|
+
aiFeature: {
|
|
343
|
+
id: 'ai-feature-1',
|
|
344
|
+
suggestedResponses: {
|
|
345
|
+
enable: true,
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const message = (type: CC_EVENTS) =>
|
|
351
|
+
JSON.stringify({
|
|
352
|
+
data: {
|
|
353
|
+
...taskDataMock,
|
|
354
|
+
interactionId: taskId,
|
|
189
355
|
type,
|
|
190
356
|
},
|
|
191
357
|
});
|
|
192
358
|
|
|
193
359
|
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONTACT_ASSIGNED));
|
|
194
360
|
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
361
|
|
|
200
362
|
expect(mockApiAIAssistant.sendEvent).not.toHaveBeenCalled();
|
|
201
363
|
});
|
|
202
364
|
|
|
203
|
-
it('should emit REAL_TIME_TRANSCRIPTION from
|
|
365
|
+
it('should emit REAL_TIME_TRANSCRIPTION from RTD websocket payload', () => {
|
|
204
366
|
const task = taskManager.getTask(taskId);
|
|
205
367
|
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
206
368
|
const realtimePayload = {
|
|
207
369
|
data: {
|
|
208
|
-
|
|
209
|
-
type: CC_EVENTS.REAL_TIME_TRANSCRIPTION,
|
|
370
|
+
agentId: 'test-agent-id',
|
|
210
371
|
data: {
|
|
211
|
-
content: '
|
|
372
|
+
content: 'Thank you. Okay.',
|
|
373
|
+
conversationId: taskId,
|
|
374
|
+
isFinal: true,
|
|
375
|
+
languageCode: 'en-US',
|
|
376
|
+
messageId: '1',
|
|
377
|
+
orgId: 'org-id',
|
|
378
|
+
publishTimestamp: 1773807297475,
|
|
379
|
+
role: 'AGENT',
|
|
380
|
+
trackingId: 'tracking-id',
|
|
381
|
+
utteranceId: 'utterance-id',
|
|
212
382
|
},
|
|
383
|
+
notifDetails: {
|
|
384
|
+
actionEvent: 'REAL_TIME_TRANSCRIPTION',
|
|
385
|
+
},
|
|
386
|
+
notifType: 'REAL_TIME_TRANSCRIPTION',
|
|
387
|
+
orgId: 'org-id',
|
|
213
388
|
},
|
|
389
|
+
orgId: 'org-id',
|
|
390
|
+
trackingId: 'notifs_tracking-id',
|
|
391
|
+
type: 'REAL_TIME_TRANSCRIPTION',
|
|
214
392
|
};
|
|
215
393
|
|
|
216
|
-
|
|
394
|
+
taskManager.handleRealtimeWebsocketEvent(JSON.stringify(realtimePayload));
|
|
217
395
|
|
|
218
396
|
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
219
397
|
CC_EVENTS.REAL_TIME_TRANSCRIPTION,
|
|
@@ -221,22 +399,37 @@ describe('TaskManager', () => {
|
|
|
221
399
|
);
|
|
222
400
|
});
|
|
223
401
|
|
|
224
|
-
it('should
|
|
225
|
-
const task = taskManager.getTask(taskId);
|
|
226
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
402
|
+
it('should ignore RTD transcript events when task is not found', () => {
|
|
227
403
|
const realtimePayload = {
|
|
228
404
|
data: {
|
|
229
|
-
notifType: CC_EVENTS.REAL_TIME_TRANSCRIPTION,
|
|
230
405
|
data: {
|
|
231
|
-
|
|
232
|
-
|
|
406
|
+
content: 'Thank you. Okay.',
|
|
407
|
+
conversationId: 'missing-task-id',
|
|
408
|
+
isFinal: true,
|
|
409
|
+
languageCode: 'en-US',
|
|
410
|
+
messageId: '1',
|
|
411
|
+
orgId: 'org-id',
|
|
412
|
+
publishTimestamp: 1773807297475,
|
|
413
|
+
role: 'AGENT',
|
|
414
|
+
trackingId: 'tracking-id',
|
|
415
|
+
utteranceId: 'utterance-id',
|
|
416
|
+
},
|
|
417
|
+
notifDetails: {
|
|
418
|
+
actionEvent: 'REAL_TIME_TRANSCRIPTION',
|
|
233
419
|
},
|
|
420
|
+
notifType: 'REAL_TIME_TRANSCRIPTION',
|
|
421
|
+
orgId: 'org-id',
|
|
234
422
|
},
|
|
423
|
+
orgId: 'org-id',
|
|
424
|
+
trackingId: 'notifs_tracking-id',
|
|
425
|
+
type: 'REAL_TIME_TRANSCRIPTION',
|
|
235
426
|
};
|
|
236
427
|
|
|
428
|
+
const existingTaskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
|
|
429
|
+
|
|
237
430
|
taskManager.handleRealtimeWebsocketEvent(JSON.stringify(realtimePayload));
|
|
238
431
|
|
|
239
|
-
expect(
|
|
432
|
+
expect(existingTaskEmitSpy).not.toHaveBeenCalled();
|
|
240
433
|
});
|
|
241
434
|
|
|
242
435
|
it('should not re-emit agent related events', () => {
|
|
@@ -254,7 +447,7 @@ describe('TaskManager', () => {
|
|
|
254
447
|
|
|
255
448
|
webSocketManagerMock.emit('message', JSON.stringify(dummyPayload));
|
|
256
449
|
|
|
257
|
-
expect(taskEmitSpy).not.
|
|
450
|
+
expect(taskEmitSpy).not.toHaveBeenCalled();
|
|
258
451
|
});
|
|
259
452
|
|
|
260
453
|
it('should handle WebSocket message for AGENT_CONTACT_RESERVED and emit task:incoming for browser case', () => {
|
|
@@ -275,14 +468,12 @@ describe('TaskManager', () => {
|
|
|
275
468
|
},
|
|
276
469
|
};
|
|
277
470
|
|
|
278
|
-
const
|
|
471
|
+
const incomingHandler = jest.fn();
|
|
472
|
+
taskManager.on(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
279
473
|
|
|
280
474
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
281
475
|
|
|
282
|
-
expect(
|
|
283
|
-
TASK_EVENTS.TASK_INCOMING,
|
|
284
|
-
taskManager.getTask(payload.data.interactionId)
|
|
285
|
-
);
|
|
476
|
+
expect(incomingHandler).toHaveBeenCalledWith(taskManager.getTask(payload.data.interactionId));
|
|
286
477
|
expect(taskManager.getTask(payload.data.interactionId)).toBe(taskManager.getTask(taskId));
|
|
287
478
|
expect(taskManager.getAllTasks()).toHaveProperty(payload.data.interactionId);
|
|
288
479
|
|
|
@@ -307,6 +498,7 @@ describe('TaskManager', () => {
|
|
|
307
498
|
taskManager.getTask(payload.data.interactionId),
|
|
308
499
|
'emit'
|
|
309
500
|
);
|
|
501
|
+
const taskManagerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
310
502
|
|
|
311
503
|
webSocketManagerMock.emit('message', JSON.stringify(assignedPayload));
|
|
312
504
|
|
|
@@ -314,6 +506,10 @@ describe('TaskManager', () => {
|
|
|
314
506
|
TASK_EVENTS.TASK_ASSIGNED,
|
|
315
507
|
taskManager.getTask(taskId)
|
|
316
508
|
);
|
|
509
|
+
expect(taskManagerEmitSpy).toHaveBeenCalledWith(
|
|
510
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
511
|
+
taskManager.getTask(taskId)
|
|
512
|
+
);
|
|
317
513
|
});
|
|
318
514
|
|
|
319
515
|
it('should handle WebSocket message for AGENT_CONTACT_RESERVED and emit task:incoming for extension case', () => {
|
|
@@ -335,16 +531,64 @@ describe('TaskManager', () => {
|
|
|
335
531
|
},
|
|
336
532
|
};
|
|
337
533
|
|
|
338
|
-
const
|
|
534
|
+
const incomingHandler = jest.fn();
|
|
535
|
+
taskManager.on(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
339
536
|
|
|
340
537
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
341
538
|
|
|
342
|
-
expect(
|
|
343
|
-
TASK_EVENTS.TASK_INCOMING,
|
|
344
|
-
taskManager.getTask(taskId)
|
|
345
|
-
);
|
|
539
|
+
expect(incomingHandler).toHaveBeenCalledWith(taskManager.getTask(taskId));
|
|
346
540
|
expect(taskManager.getTask(payload.data.interactionId)).toBe(taskManager.getTask(taskId));
|
|
347
541
|
expect(taskManager.getAllTasks()).toHaveProperty(payload.data.interactionId);
|
|
542
|
+
taskManager.off(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it('should send mapped events through the state machine without duplicate updates', () => {
|
|
546
|
+
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
547
|
+
const task = taskManager.getTask(taskId);
|
|
548
|
+
const updateSpy = task.updateTaskData as jest.Mock;
|
|
549
|
+
updateSpy.mockClear();
|
|
550
|
+
const sendSpy = task.sendStateMachineEvent as jest.Mock;
|
|
551
|
+
sendSpy.mockClear();
|
|
552
|
+
const cleanupSpy = jest.spyOn(taskManager as any, 'handleTaskCleanup');
|
|
553
|
+
|
|
554
|
+
const assignFailedPayload = {
|
|
555
|
+
data: {
|
|
556
|
+
...initalPayload.data,
|
|
557
|
+
type: CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED,
|
|
558
|
+
reason: 'ASSIGN_FAILED',
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
webSocketManagerMock.emit('message', JSON.stringify(assignFailedPayload));
|
|
563
|
+
|
|
564
|
+
const stateMachineEvent = expectLastStateMachineEvent(sendSpy, TaskEvent.ASSIGN_FAILED);
|
|
565
|
+
expect(stateMachineEvent).toEqual({
|
|
566
|
+
type: TaskEvent.ASSIGN_FAILED,
|
|
567
|
+
reason: assignFailedPayload.data.reason,
|
|
568
|
+
});
|
|
569
|
+
expect(updateSpy).toHaveBeenCalledWith(assignFailedPayload.data);
|
|
570
|
+
expect(cleanupSpy).toHaveBeenCalledWith(task);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it('should update task data directly when no state machine mapping exists', () => {
|
|
574
|
+
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
575
|
+
const task = taskManager.getTask(taskId);
|
|
576
|
+
const updateSpy = task.updateTaskData as jest.Mock;
|
|
577
|
+
updateSpy.mockClear();
|
|
578
|
+
const sendSpy = task.sendStateMachineEvent as jest.Mock;
|
|
579
|
+
sendSpy.mockClear();
|
|
580
|
+
|
|
581
|
+
const participantMovedPayload = {
|
|
582
|
+
data: {
|
|
583
|
+
...initalPayload.data,
|
|
584
|
+
type: CC_EVENTS.CONSULTED_PARTICIPANT_MOVING,
|
|
585
|
+
},
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
webSocketManagerMock.emit('message', JSON.stringify(participantMovedPayload));
|
|
589
|
+
|
|
590
|
+
expect(sendSpy).not.toHaveBeenCalled();
|
|
591
|
+
expect(updateSpy).toHaveBeenCalledWith(participantMovedPayload.data);
|
|
348
592
|
});
|
|
349
593
|
|
|
350
594
|
it('should return task by ID', () => {
|
|
@@ -429,12 +673,30 @@ describe('TaskManager', () => {
|
|
|
429
673
|
it('test call listeners being switched off on call end', () => {
|
|
430
674
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
431
675
|
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
);
|
|
676
|
+
const webrtcTask = new WebRTC(contactMock, webCallingService, taskDataMock, {
|
|
677
|
+
isEndTaskEnabled: true,
|
|
678
|
+
isEndConsultEnabled: true,
|
|
679
|
+
});
|
|
680
|
+
(taskManager as any).taskCollection[taskId] = webrtcTask;
|
|
681
|
+
// TaskManager must listen to task-level cleanup events emitted by the state machine.
|
|
682
|
+
// This is normally wired when TaskManager creates the task via TaskFactory.
|
|
683
|
+
(taskManager as any).setupTaskListeners(webrtcTask);
|
|
684
|
+
|
|
685
|
+
const task = taskManager.getTask(taskId)!;
|
|
686
|
+
// This test doesn't validate UI controls; avoid requiring full interaction.media
|
|
687
|
+
// shape for WebRTC UI controls computation.
|
|
688
|
+
jest.spyOn(task as any, 'updateUiControls').mockImplementation(() => undefined);
|
|
689
|
+
const originalEmit = task.emit;
|
|
690
|
+
jest.spyOn(task, 'emit').mockImplementation((event, arg) => {
|
|
691
|
+
if (event === CC_EVENTS.CONTACT_ENDED) {
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
return originalEmit.call(task, event, arg);
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
const webCallListenerSpy = jest.spyOn(task, 'unregisterWebCallListeners');
|
|
437
698
|
const callOffSpy = jest.spyOn(mockCall, 'off');
|
|
699
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
438
700
|
const payload = {
|
|
439
701
|
data: {
|
|
440
702
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
@@ -452,10 +714,25 @@ describe('TaskManager', () => {
|
|
|
452
714
|
},
|
|
453
715
|
};
|
|
454
716
|
|
|
717
|
+
// Ensure the state machine is hydrated into a connected state before CONTACT_ENDED
|
|
718
|
+
const hydratePayload = {
|
|
719
|
+
data: {
|
|
720
|
+
...payload.data,
|
|
721
|
+
type: CC_EVENTS.AGENT_CONTACT,
|
|
722
|
+
interaction: {state: 'connected', mediaType: 'telephony'},
|
|
723
|
+
},
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
taskManager.getTask(taskId).data = hydratePayload.data;
|
|
727
|
+
webSocketManagerMock.emit('message', JSON.stringify(hydratePayload));
|
|
728
|
+
|
|
455
729
|
taskManager.getTask(taskId).data = payload.data;
|
|
456
|
-
const task = taskManager.getTask(taskId);
|
|
457
730
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
458
|
-
|
|
731
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
732
|
+
sendStateMachineEventSpy,
|
|
733
|
+
TaskEvent.CONTACT_ENDED
|
|
734
|
+
);
|
|
735
|
+
expect(stateMachineEvent?.taskData.wrapUpRequired).toBe(false);
|
|
459
736
|
expect(webCallListenerSpy).toHaveBeenCalledWith();
|
|
460
737
|
expect(callOffSpy).toHaveBeenCalledWith(
|
|
461
738
|
CALL_EVENT_KEYS.REMOTE_MEDIA,
|
|
@@ -466,12 +743,13 @@ describe('TaskManager', () => {
|
|
|
466
743
|
expect(offSpy.mock.calls.length).toBe(2); // 1 for incoming call and 1 for remote media
|
|
467
744
|
expect(offSpy).toHaveBeenCalledWith(CALL_EVENT_KEYS.REMOTE_MEDIA, offSpy.mock.calls[0][1]);
|
|
468
745
|
expect(offSpy).toHaveBeenCalledWith(LINE_EVENTS.INCOMING_CALL, offSpy.mock.calls[1][1]);
|
|
746
|
+
sendStateMachineEventSpy.mockRestore();
|
|
469
747
|
});
|
|
470
748
|
|
|
471
749
|
it('should emit TASK_END event with wrapupRequired on regular call end', () => {
|
|
472
750
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
473
|
-
|
|
474
|
-
const
|
|
751
|
+
const task = taskManager.getTask(taskId);
|
|
752
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
475
753
|
const payload = {
|
|
476
754
|
data: {
|
|
477
755
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
@@ -486,20 +764,25 @@ describe('TaskManager', () => {
|
|
|
486
764
|
destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
|
|
487
765
|
owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
488
766
|
queueMgr: 'aqm',
|
|
767
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
489
768
|
},
|
|
490
769
|
};
|
|
491
770
|
|
|
492
|
-
|
|
771
|
+
task.updateTaskData(payload.data);
|
|
493
772
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
494
|
-
|
|
495
|
-
|
|
773
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
774
|
+
sendStateMachineEventSpy,
|
|
775
|
+
TaskEvent.CONTACT_ENDED
|
|
776
|
+
);
|
|
777
|
+
expect(stateMachineEvent?.taskData.wrapUpRequired).toBe(true);
|
|
778
|
+
sendStateMachineEventSpy.mockRestore();
|
|
496
779
|
});
|
|
497
780
|
|
|
498
781
|
it('should emit TASK_REJECT event on AGENT_INVITE_FAILED event', () => {
|
|
499
782
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
500
783
|
|
|
501
|
-
const
|
|
502
|
-
const
|
|
784
|
+
const task = taskManager.getTask(taskId);
|
|
785
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
503
786
|
const payload = {
|
|
504
787
|
data: {
|
|
505
788
|
type: CC_EVENTS.AGENT_INVITE_FAILED,
|
|
@@ -518,35 +801,40 @@ describe('TaskManager', () => {
|
|
|
518
801
|
},
|
|
519
802
|
};
|
|
520
803
|
|
|
521
|
-
|
|
804
|
+
task.updateTaskData(payload.data);
|
|
522
805
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
expect(
|
|
806
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
807
|
+
sendStateMachineEventSpy,
|
|
808
|
+
TaskEvent.INVITE_FAILED
|
|
809
|
+
);
|
|
810
|
+
expect(stateMachineEvent?.reason).toBe(payload.data.reason);
|
|
811
|
+
sendStateMachineEventSpy.mockRestore();
|
|
528
812
|
});
|
|
529
813
|
|
|
530
|
-
it('should
|
|
814
|
+
it('should emit TASK_HYDRATE even if task is already present in taskManager', () => {
|
|
531
815
|
const payload = {
|
|
532
816
|
data: {
|
|
533
817
|
...initalPayload.data,
|
|
534
818
|
type: CC_EVENTS.AGENT_CONTACT,
|
|
535
819
|
},
|
|
536
820
|
};
|
|
537
|
-
const
|
|
821
|
+
const existingTask = taskManager.getTask(taskId);
|
|
822
|
+
const sendStateMachineEventSpy = jest.spyOn(existingTask, 'sendStateMachineEvent');
|
|
538
823
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
539
824
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
825
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
826
|
+
sendStateMachineEventSpy,
|
|
827
|
+
TaskEvent.HYDRATE
|
|
543
828
|
);
|
|
829
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
830
|
+
expect(existingTask).toBe(taskManager.getTask(taskId));
|
|
544
831
|
expect(taskManager.taskCollection[payload.data.interactionId]).toBe(
|
|
545
832
|
taskManager.getTask(taskId)
|
|
546
833
|
);
|
|
834
|
+
sendStateMachineEventSpy.mockRestore();
|
|
547
835
|
});
|
|
548
836
|
|
|
549
|
-
it('should emit
|
|
837
|
+
it('should emit TASK_HYDRATE event on AGENT_CONTACT when task is created from payload', () => {
|
|
550
838
|
taskManager.taskCollection = [];
|
|
551
839
|
const payload = {
|
|
552
840
|
data: {
|
|
@@ -556,13 +844,15 @@ describe('TaskManager', () => {
|
|
|
556
844
|
},
|
|
557
845
|
};
|
|
558
846
|
|
|
559
|
-
const taskEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
560
847
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
561
848
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
849
|
+
const createdTask = taskManager.getTask(taskId);
|
|
850
|
+
const sendStateMachineEventSpy = createdTask.sendStateMachineEvent as jest.Mock;
|
|
851
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
852
|
+
sendStateMachineEventSpy,
|
|
853
|
+
TaskEvent.HYDRATE
|
|
565
854
|
);
|
|
855
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
566
856
|
expect(taskManager.taskCollection[payload.data.interactionId]).toBe(
|
|
567
857
|
taskManager.getTask(taskId)
|
|
568
858
|
);
|
|
@@ -577,10 +867,15 @@ describe('TaskManager', () => {
|
|
|
577
867
|
},
|
|
578
868
|
};
|
|
579
869
|
|
|
580
|
-
const taskEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
581
870
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
582
871
|
|
|
583
|
-
|
|
872
|
+
const createdTask = taskManager.getTask(taskId);
|
|
873
|
+
const sendStateMachineEventSpy = createdTask.sendStateMachineEvent as jest.Mock;
|
|
874
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
875
|
+
sendStateMachineEventSpy,
|
|
876
|
+
TaskEvent.HYDRATE
|
|
877
|
+
);
|
|
878
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
584
879
|
expect(taskManager.taskCollection[payload.data.interactionId]).toBe(
|
|
585
880
|
taskManager.getTask(taskId)
|
|
586
881
|
);
|
|
@@ -653,7 +948,7 @@ describe('TaskManager', () => {
|
|
|
653
948
|
expect(createdTask.data.isConferenceInProgress).toBe(false);
|
|
654
949
|
});
|
|
655
950
|
|
|
656
|
-
it('should emit
|
|
951
|
+
it('should emit TASK_WRAPUP event on AGENT_WRAPUP event', () => {
|
|
657
952
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
658
953
|
|
|
659
954
|
const wrapupPayload = {
|
|
@@ -675,13 +970,15 @@ describe('TaskManager', () => {
|
|
|
675
970
|
};
|
|
676
971
|
|
|
677
972
|
const task = taskManager.getTask(taskId);
|
|
678
|
-
const updateTaskDataSpy = jest.
|
|
679
|
-
|
|
973
|
+
const updateTaskDataSpy = task.updateTaskData as jest.Mock;
|
|
974
|
+
updateTaskDataSpy.mockClear();
|
|
975
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
680
976
|
|
|
681
977
|
webSocketManagerMock.emit('message', JSON.stringify(wrapupPayload));
|
|
682
978
|
|
|
683
979
|
expect(updateTaskDataSpy).toHaveBeenCalledWith(wrapupPayload.data);
|
|
684
|
-
|
|
980
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.TASK_WRAPUP);
|
|
981
|
+
sendStateMachineEventSpy.mockRestore();
|
|
685
982
|
});
|
|
686
983
|
|
|
687
984
|
it('should emit TASK_HOLD event on AGENT_CONTACT_HELD event', () => {
|
|
@@ -704,13 +1001,20 @@ describe('TaskManager', () => {
|
|
|
704
1001
|
},
|
|
705
1002
|
};
|
|
706
1003
|
|
|
707
|
-
const
|
|
708
|
-
const taskUpdateTaskDataSpy = jest.
|
|
1004
|
+
const task = taskManager.getTask(taskId);
|
|
1005
|
+
const taskUpdateTaskDataSpy = task.updateTaskData as jest.Mock;
|
|
1006
|
+
taskUpdateTaskDataSpy.mockClear();
|
|
1007
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
709
1008
|
|
|
710
1009
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
711
1010
|
|
|
712
1011
|
expect(taskUpdateTaskDataSpy).toHaveBeenCalledWith(payload.data);
|
|
713
|
-
|
|
1012
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1013
|
+
sendStateMachineEventSpy,
|
|
1014
|
+
TaskEvent.HOLD_SUCCESS
|
|
1015
|
+
);
|
|
1016
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1017
|
+
sendStateMachineEventSpy.mockRestore();
|
|
714
1018
|
});
|
|
715
1019
|
|
|
716
1020
|
it('should emit TASK_RESUME event on AGENT_CONTACT_UNHELD event', () => {
|
|
@@ -733,11 +1037,18 @@ describe('TaskManager', () => {
|
|
|
733
1037
|
},
|
|
734
1038
|
};
|
|
735
1039
|
|
|
736
|
-
const
|
|
737
|
-
const taskUpdateTaskDataSpy = jest.
|
|
1040
|
+
const task = taskManager.getTask(taskId);
|
|
1041
|
+
const taskUpdateTaskDataSpy = task.updateTaskData as jest.Mock;
|
|
1042
|
+
taskUpdateTaskDataSpy.mockClear();
|
|
1043
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
738
1044
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
739
1045
|
expect(taskUpdateTaskDataSpy).toHaveBeenCalledWith(payload.data);
|
|
740
|
-
|
|
1046
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1047
|
+
sendStateMachineEventSpy,
|
|
1048
|
+
TaskEvent.UNHOLD_SUCCESS
|
|
1049
|
+
);
|
|
1050
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1051
|
+
sendStateMachineEventSpy.mockRestore();
|
|
741
1052
|
});
|
|
742
1053
|
|
|
743
1054
|
it('handle AGENT_CONSULT_CREATED event', () => {
|
|
@@ -750,17 +1061,16 @@ describe('TaskManager', () => {
|
|
|
750
1061
|
|
|
751
1062
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
752
1063
|
const task = taskManager.getTask(taskId);
|
|
753
|
-
const
|
|
754
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
1064
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
755
1065
|
|
|
756
1066
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
757
1067
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
expect(
|
|
763
|
-
|
|
1068
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1069
|
+
sendStateMachineEventSpy,
|
|
1070
|
+
TaskEvent.CONSULT_CREATED
|
|
1071
|
+
);
|
|
1072
|
+
expect(stateMachineEvent?.taskData).toEqual({...payload.data, isConsulted: false});
|
|
1073
|
+
sendStateMachineEventSpy.mockRestore();
|
|
764
1074
|
});
|
|
765
1075
|
|
|
766
1076
|
it('handle AGENT_OFFER_CONTACT event', () => {
|
|
@@ -773,11 +1083,17 @@ describe('TaskManager', () => {
|
|
|
773
1083
|
|
|
774
1084
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
775
1085
|
|
|
776
|
-
const
|
|
1086
|
+
const task = taskManager.getTask(taskId);
|
|
1087
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
777
1088
|
|
|
778
1089
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
779
1090
|
|
|
780
|
-
|
|
1091
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1092
|
+
sendStateMachineEventSpy,
|
|
1093
|
+
TaskEvent.TASK_OFFERED
|
|
1094
|
+
);
|
|
1095
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1096
|
+
sendStateMachineEventSpy.mockRestore();
|
|
781
1097
|
});
|
|
782
1098
|
|
|
783
1099
|
describe('Auto-Answer Functionality', () => {
|
|
@@ -787,8 +1103,8 @@ describe('TaskManager', () => {
|
|
|
787
1103
|
|
|
788
1104
|
const task = taskManager.getTask(taskId);
|
|
789
1105
|
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
790
|
-
const taskManagerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
791
1106
|
const taskAcceptSpy = jest.spyOn(task, 'accept').mockResolvedValue(undefined);
|
|
1107
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
792
1108
|
|
|
793
1109
|
// Step 2: Trigger AGENT_OFFER_CONTACT with auto-answer
|
|
794
1110
|
const autoAnswerPayload = {
|
|
@@ -812,9 +1128,14 @@ describe('TaskManager', () => {
|
|
|
812
1128
|
// Verify accept was called
|
|
813
1129
|
expect(taskAcceptSpy).toHaveBeenCalledTimes(1);
|
|
814
1130
|
|
|
815
|
-
|
|
816
|
-
|
|
1131
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1132
|
+
sendStateMachineEventSpy,
|
|
1133
|
+
TaskEvent.TASK_OFFERED
|
|
1134
|
+
);
|
|
1135
|
+
expect(stateMachineEvent?.taskData).toEqual(autoAnswerPayload.data);
|
|
1136
|
+
// Verify task auto-answer event was emitted
|
|
817
1137
|
expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_AUTO_ANSWERED, task);
|
|
1138
|
+
sendStateMachineEventSpy.mockRestore();
|
|
818
1139
|
});
|
|
819
1140
|
|
|
820
1141
|
it('should NOT emit TASK_AUTO_ANSWERED event when auto-answer fails', async () => {
|
|
@@ -860,6 +1181,7 @@ describe('TaskManager', () => {
|
|
|
860
1181
|
const task = taskManager.getTask(taskId);
|
|
861
1182
|
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
862
1183
|
const taskAcceptSpy = jest.spyOn(task, 'accept').mockResolvedValue(undefined);
|
|
1184
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
863
1185
|
|
|
864
1186
|
// Step 2: Trigger AGENT_OFFER_CONSULT with auto-answer
|
|
865
1187
|
const consultAutoAnswerPayload = {
|
|
@@ -884,12 +1206,20 @@ describe('TaskManager', () => {
|
|
|
884
1206
|
// Verify accept was called
|
|
885
1207
|
expect(taskAcceptSpy).toHaveBeenCalledTimes(1);
|
|
886
1208
|
|
|
887
|
-
|
|
888
|
-
|
|
1209
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1210
|
+
sendStateMachineEventSpy,
|
|
1211
|
+
TaskEvent.OFFER_CONSULT
|
|
1212
|
+
);
|
|
1213
|
+
expect(stateMachineEvent?.taskData).toEqual({
|
|
1214
|
+
...consultAutoAnswerPayload.data,
|
|
1215
|
+
isConsulted: true,
|
|
1216
|
+
});
|
|
1217
|
+
// Verify task auto-answer event was emitted
|
|
889
1218
|
expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_AUTO_ANSWERED, task);
|
|
890
1219
|
|
|
891
1220
|
// Verify isConsulted flag is set correctly
|
|
892
1221
|
expect(task.data.isConsulted).toBe(true);
|
|
1222
|
+
sendStateMachineEventSpy.mockRestore();
|
|
893
1223
|
});
|
|
894
1224
|
|
|
895
1225
|
it('should NOT emit TASK_AUTO_ANSWERED when isAutoAnswering is false', async () => {
|
|
@@ -930,24 +1260,20 @@ describe('TaskManager', () => {
|
|
|
930
1260
|
});
|
|
931
1261
|
});
|
|
932
1262
|
|
|
933
|
-
it('should
|
|
1263
|
+
it('should remove OUTDIAL task from taskCollection on AGENT_OUTBOUND_FAILED when terminated', () => {
|
|
934
1264
|
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;
|
|
1265
|
+
Object.assign(task.data, {
|
|
1266
|
+
interaction: {
|
|
1267
|
+
...task.data.interaction,
|
|
1268
|
+
outboundType: 'OUTDIAL',
|
|
1269
|
+
state: 'new',
|
|
1270
|
+
isTerminated: true,
|
|
1271
|
+
},
|
|
1272
|
+
agentsPendingWrapUp: ['agent-123'],
|
|
948
1273
|
});
|
|
949
1274
|
task.unregisterWebCallListeners = jest.fn();
|
|
950
1275
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
1276
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
951
1277
|
|
|
952
1278
|
const payload = {
|
|
953
1279
|
data: {
|
|
@@ -960,6 +1286,7 @@ describe('TaskManager', () => {
|
|
|
960
1286
|
state: 'new',
|
|
961
1287
|
isTerminated: true,
|
|
962
1288
|
},
|
|
1289
|
+
agentsPendingWrapUp: ['agent-123'],
|
|
963
1290
|
interactionId: taskId,
|
|
964
1291
|
orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a',
|
|
965
1292
|
trackingId: '575c0ec2-618c-42af-a61c-53aeb0a221ee',
|
|
@@ -974,23 +1301,61 @@ describe('TaskManager', () => {
|
|
|
974
1301
|
|
|
975
1302
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
976
1303
|
|
|
977
|
-
|
|
978
|
-
|
|
1304
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1305
|
+
|
|
1306
|
+
expect(taskManager.getTask(taskId)).toBeUndefined();
|
|
1307
|
+
expect(removeTaskSpy).toHaveBeenCalled();
|
|
1308
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1309
|
+
sendStateMachineEventSpy,
|
|
1310
|
+
TaskEvent.OUTBOUND_FAILED
|
|
1311
|
+
);
|
|
1312
|
+
expect(stateMachineEvent?.reason).toBe('CUSTOMER_BUSY');
|
|
1313
|
+
sendStateMachineEventSpy.mockRestore();
|
|
979
1314
|
});
|
|
980
1315
|
|
|
981
1316
|
it('should emit TASK_OUTDIAL_FAILED event on AGENT_OUTBOUND_FAILED', () => {
|
|
982
1317
|
const task = taskManager.getTask(taskId);
|
|
983
|
-
|
|
984
|
-
const
|
|
1318
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1319
|
+
const payload = {
|
|
1320
|
+
data: {
|
|
1321
|
+
type: CC_EVENTS.AGENT_OUTBOUND_FAILED,
|
|
1322
|
+
interactionId: taskId,
|
|
1323
|
+
reason: 'CUSTOMER_BUSY',
|
|
1324
|
+
},
|
|
1325
|
+
};
|
|
1326
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1327
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1328
|
+
sendStateMachineEventSpy,
|
|
1329
|
+
TaskEvent.OUTBOUND_FAILED
|
|
1330
|
+
);
|
|
1331
|
+
expect(stateMachineEvent?.reason).toBe('CUSTOMER_BUSY');
|
|
1332
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1333
|
+
});
|
|
1334
|
+
|
|
1335
|
+
it('should pass taskData in OUTBOUND_FAILED event for shouldWrapUp guard evaluation', () => {
|
|
1336
|
+
const task = taskManager.getTask(taskId);
|
|
1337
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
985
1338
|
const payload = {
|
|
986
1339
|
data: {
|
|
987
1340
|
type: CC_EVENTS.AGENT_OUTBOUND_FAILED,
|
|
988
1341
|
interactionId: taskId,
|
|
989
1342
|
reason: 'CUSTOMER_BUSY',
|
|
1343
|
+
agentsPendingWrapUp: ['agent-123'],
|
|
1344
|
+
interaction: {
|
|
1345
|
+
outboundType: 'OUTDIAL',
|
|
1346
|
+
isTerminated: true,
|
|
1347
|
+
},
|
|
990
1348
|
},
|
|
991
1349
|
};
|
|
992
1350
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
993
|
-
|
|
1351
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1352
|
+
sendStateMachineEventSpy,
|
|
1353
|
+
TaskEvent.OUTBOUND_FAILED
|
|
1354
|
+
);
|
|
1355
|
+
expect(stateMachineEvent?.taskData).toBeDefined();
|
|
1356
|
+
expect(stateMachineEvent?.taskData?.agentsPendingWrapUp).toEqual(['agent-123']);
|
|
1357
|
+
expect(stateMachineEvent?.taskData?.interaction?.outboundType).toBe('OUTDIAL');
|
|
1358
|
+
sendStateMachineEventSpy.mockRestore();
|
|
994
1359
|
});
|
|
995
1360
|
|
|
996
1361
|
it('should handle AGENT_OUTBOUND_FAILED gracefully when task is undefined', () => {
|
|
@@ -1008,23 +1373,15 @@ describe('TaskManager', () => {
|
|
|
1008
1373
|
});
|
|
1009
1374
|
|
|
1010
1375
|
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
1376
|
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;
|
|
1377
|
+
Object.assign(task.data, {
|
|
1378
|
+
interaction: {
|
|
1379
|
+
...task.data.interaction,
|
|
1380
|
+
outboundType: 'OUTDIAL',
|
|
1381
|
+
state: 'new',
|
|
1382
|
+
mediaType: 'telephony',
|
|
1383
|
+
},
|
|
1384
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
1028
1385
|
});
|
|
1029
1386
|
task.unregisterWebCallListeners = jest.fn();
|
|
1030
1387
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
@@ -1038,7 +1395,7 @@ describe('TaskManager', () => {
|
|
|
1038
1395
|
state: 'new',
|
|
1039
1396
|
mediaType: 'telephony',
|
|
1040
1397
|
},
|
|
1041
|
-
agentsPendingWrapUp: [
|
|
1398
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
1042
1399
|
},
|
|
1043
1400
|
};
|
|
1044
1401
|
|
|
@@ -1050,19 +1407,14 @@ describe('TaskManager', () => {
|
|
|
1050
1407
|
|
|
1051
1408
|
it('should remove OUTDIAL task on CONTACT_ENDED when agentsPendingWrapUp is empty', () => {
|
|
1052
1409
|
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;
|
|
1410
|
+
Object.assign(task.data, {
|
|
1411
|
+
interaction: {
|
|
1412
|
+
...task.data.interaction,
|
|
1413
|
+
outboundType: 'OUTDIAL',
|
|
1414
|
+
state: 'new',
|
|
1415
|
+
mediaType: 'telephony',
|
|
1416
|
+
},
|
|
1417
|
+
agentsPendingWrapUp: [],
|
|
1066
1418
|
});
|
|
1067
1419
|
task.unregisterWebCallListeners = jest.fn();
|
|
1068
1420
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
@@ -1087,19 +1439,13 @@ describe('TaskManager', () => {
|
|
|
1087
1439
|
|
|
1088
1440
|
it('should remove OUTDIAL task on CONTACT_ENDED when agentsPendingWrapUp is undefined', () => {
|
|
1089
1441
|
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;
|
|
1442
|
+
Object.assign(task.data, {
|
|
1443
|
+
interaction: {
|
|
1444
|
+
...task.data.interaction,
|
|
1445
|
+
outboundType: 'OUTDIAL',
|
|
1446
|
+
state: 'new',
|
|
1447
|
+
mediaType: 'telephony',
|
|
1448
|
+
},
|
|
1103
1449
|
});
|
|
1104
1450
|
task.unregisterWebCallListeners = jest.fn();
|
|
1105
1451
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
@@ -1138,298 +1484,29 @@ describe('TaskManager', () => {
|
|
|
1138
1484
|
}).not.toThrow();
|
|
1139
1485
|
});
|
|
1140
1486
|
|
|
1141
|
-
|
|
1142
|
-
const
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1487
|
+
it('should remove OUTDIAL task from taskCollection on AGENT_CONTACT_ASSIGN_FAILED when NOT terminated (user-declined)', () => {
|
|
1488
|
+
const task = taskManager.getTask(taskId);
|
|
1489
|
+
Object.assign(task.data, {
|
|
1490
|
+
interaction: {
|
|
1491
|
+
...task.data.interaction,
|
|
1492
|
+
outboundType: 'OUTDIAL',
|
|
1493
|
+
state: 'new',
|
|
1494
|
+
isTerminated: false,
|
|
1495
|
+
},
|
|
1147
1496
|
});
|
|
1497
|
+
task.unregisterWebCallListeners = jest.fn();
|
|
1498
|
+
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
1148
1499
|
|
|
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'],
|
|
1169
|
-
},
|
|
1170
|
-
};
|
|
1171
|
-
|
|
1172
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1173
|
-
|
|
1174
|
-
expect(task.updateTaskData).toHaveBeenCalledWith(
|
|
1175
|
-
expect.objectContaining({
|
|
1176
|
-
wrapUpRequired: true,
|
|
1177
|
-
})
|
|
1178
|
-
);
|
|
1179
|
-
});
|
|
1180
|
-
|
|
1181
|
-
it('should set wrapUpRequired to false when agent is not in agentsPendingWrapUp array', () => {
|
|
1182
|
-
const task = taskManager.getTask(taskId);
|
|
1183
|
-
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1184
|
-
task.data = {
|
|
1185
|
-
...task.data,
|
|
1186
|
-
...newData,
|
|
1187
|
-
};
|
|
1188
|
-
return task;
|
|
1189
|
-
});
|
|
1190
|
-
task.unregisterWebCallListeners = jest.fn();
|
|
1191
|
-
|
|
1192
|
-
const payload = {
|
|
1193
|
-
data: {
|
|
1194
|
-
type: CC_EVENTS.CONTACT_ENDED,
|
|
1195
|
-
interactionId: taskId,
|
|
1196
|
-
interaction: {
|
|
1197
|
-
state: 'connected',
|
|
1198
|
-
mediaType: 'telephony',
|
|
1199
|
-
},
|
|
1200
|
-
agentsPendingWrapUp: ['other-agent-id', 'another-agent-id'],
|
|
1201
|
-
},
|
|
1202
|
-
};
|
|
1203
|
-
|
|
1204
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1205
|
-
|
|
1206
|
-
expect(task.updateTaskData).toHaveBeenCalledWith(
|
|
1207
|
-
expect.objectContaining({
|
|
1208
|
-
wrapUpRequired: false,
|
|
1209
|
-
})
|
|
1210
|
-
);
|
|
1211
|
-
});
|
|
1212
|
-
|
|
1213
|
-
it('should set wrapUpRequired to false when agentsPendingWrapUp is an empty array', () => {
|
|
1214
|
-
const task = taskManager.getTask(taskId);
|
|
1215
|
-
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1216
|
-
task.data = {
|
|
1217
|
-
...task.data,
|
|
1218
|
-
...newData,
|
|
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,
|
|
1500
|
+
const payload = {
|
|
1501
|
+
data: {
|
|
1502
|
+
type: CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED,
|
|
1503
|
+
agentId: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
1504
|
+
eventTime: 1733211616959,
|
|
1505
|
+
eventType: 'RoutingMessage',
|
|
1506
|
+
interaction: {
|
|
1507
|
+
outboundType: 'OUTDIAL',
|
|
1508
|
+
state: 'new',
|
|
1509
|
+
isTerminated: false,
|
|
1433
1510
|
},
|
|
1434
1511
|
interactionId: taskId,
|
|
1435
1512
|
orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a',
|
|
@@ -1459,20 +1536,16 @@ describe('TaskManager', () => {
|
|
|
1459
1536
|
|
|
1460
1537
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1461
1538
|
const task = taskManager.getTask(taskId);
|
|
1462
|
-
|
|
1463
|
-
task.data = {...newData, isConsulted: true};
|
|
1464
|
-
return task;
|
|
1465
|
-
});
|
|
1466
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
1539
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1467
1540
|
|
|
1468
1541
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1469
1542
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
expect(
|
|
1475
|
-
|
|
1543
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1544
|
+
sendStateMachineEventSpy,
|
|
1545
|
+
TaskEvent.OFFER_CONSULT
|
|
1546
|
+
);
|
|
1547
|
+
expect(stateMachineEvent?.taskData).toEqual({...payload.data, isConsulted: true});
|
|
1548
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1476
1549
|
});
|
|
1477
1550
|
|
|
1478
1551
|
it('should emit TASK_CONSULT_ACCEPTED event on AGENT_CONSULTING event', () => {
|
|
@@ -1491,19 +1564,18 @@ describe('TaskManager', () => {
|
|
|
1491
1564
|
};
|
|
1492
1565
|
|
|
1493
1566
|
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
1567
|
|
|
1499
|
-
const
|
|
1568
|
+
const task = taskManager.getTask(taskId);
|
|
1569
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1570
|
+
|
|
1500
1571
|
webSocketManagerMock.emit('message', JSON.stringify(initialConsultingPayload));
|
|
1501
1572
|
webSocketManagerMock.emit('message', JSON.stringify(consultingPayload));
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
taskManager.getTask(taskId)
|
|
1573
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1574
|
+
sendStateMachineEventSpy,
|
|
1575
|
+
TaskEvent.CONSULTING_ACTIVE
|
|
1506
1576
|
);
|
|
1577
|
+
expect(stateMachineEvent?.taskData).toEqual(consultingPayload.data);
|
|
1578
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1507
1579
|
});
|
|
1508
1580
|
|
|
1509
1581
|
it('should emit TASK_CONSULT_ENDED event on AGENT_CONSULT_ENDED event', () => {
|
|
@@ -1515,14 +1587,16 @@ describe('TaskManager', () => {
|
|
|
1515
1587
|
};
|
|
1516
1588
|
|
|
1517
1589
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1518
|
-
|
|
1519
|
-
const
|
|
1590
|
+
taskManager.getTask(taskId).data.isConsulted = true;
|
|
1591
|
+
const task = taskManager.getTask(taskId);
|
|
1592
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1520
1593
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
taskManager.getTask(taskId)
|
|
1594
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1595
|
+
sendStateMachineEventSpy,
|
|
1596
|
+
TaskEvent.CONSULT_END
|
|
1525
1597
|
);
|
|
1598
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1599
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1526
1600
|
});
|
|
1527
1601
|
|
|
1528
1602
|
it('should emit TASK_CONSULT_ENDED event and remove currentTask when on AGENT_CONSULT_ENDED event when requested for a consult', () => {
|
|
@@ -1535,19 +1609,14 @@ describe('TaskManager', () => {
|
|
|
1535
1609
|
|
|
1536
1610
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1537
1611
|
|
|
1538
|
-
taskManager.getTask(taskId).
|
|
1539
|
-
taskManager.getTask(taskId).data = {...newData, isConsulted: true};
|
|
1540
|
-
return taskManager.getTask(taskId);
|
|
1541
|
-
});
|
|
1612
|
+
taskManager.getTask(taskId).data.isConsulted = true;
|
|
1542
1613
|
const task = taskManager.getTask(taskId);
|
|
1543
1614
|
|
|
1544
|
-
const
|
|
1545
|
-
const taskUpdateTaskDataSpy = jest.spyOn(taskManager.getTask(taskId), 'updateTaskData');
|
|
1615
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1546
1616
|
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);
|
|
1617
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.CONSULT_END);
|
|
1550
1618
|
expect(taskManager.getTask(taskId)).toBeUndefined(); // Ensure task is removed from the task collection after the consult ends
|
|
1619
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1551
1620
|
});
|
|
1552
1621
|
|
|
1553
1622
|
it('should emit TASK_CANCELLED event on AGENT_CTQ_CANCELLED event', () => {
|
|
@@ -1559,14 +1628,15 @@ describe('TaskManager', () => {
|
|
|
1559
1628
|
};
|
|
1560
1629
|
|
|
1561
1630
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1562
|
-
const
|
|
1563
|
-
const
|
|
1631
|
+
const task = taskManager.getTask(taskId);
|
|
1632
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1564
1633
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
taskManager.getTask(taskId)
|
|
1634
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1635
|
+
sendStateMachineEventSpy,
|
|
1636
|
+
TaskEvent.CTQ_CANCEL
|
|
1569
1637
|
);
|
|
1638
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1639
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1570
1640
|
});
|
|
1571
1641
|
|
|
1572
1642
|
it('should handle AGENT_CONSULT_FAILED event', () => {
|
|
@@ -1580,9 +1650,15 @@ describe('TaskManager', () => {
|
|
|
1580
1650
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1581
1651
|
|
|
1582
1652
|
// Always spy on the updated task object after CONTACT_RESERVED is emitted
|
|
1583
|
-
const
|
|
1653
|
+
const task = taskManager.getTask(taskId);
|
|
1654
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1584
1655
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1585
|
-
|
|
1656
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1657
|
+
sendStateMachineEventSpy,
|
|
1658
|
+
TaskEvent.CONSULT_FAILED
|
|
1659
|
+
);
|
|
1660
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1661
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1586
1662
|
});
|
|
1587
1663
|
|
|
1588
1664
|
it('should emit TASK_CONSULT_QUEUE_FAILED on AGENT_CTQ_CANCEL_FAILED event', () => {
|
|
@@ -1594,14 +1670,15 @@ describe('TaskManager', () => {
|
|
|
1594
1670
|
};
|
|
1595
1671
|
|
|
1596
1672
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1597
|
-
const
|
|
1598
|
-
const
|
|
1673
|
+
const task = taskManager.getTask(taskId);
|
|
1674
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1599
1675
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
taskManager.getTask(taskId)
|
|
1676
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1677
|
+
sendStateMachineEventSpy,
|
|
1678
|
+
TaskEvent.CTQ_CANCEL_FAILED
|
|
1604
1679
|
);
|
|
1680
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1681
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1605
1682
|
});
|
|
1606
1683
|
|
|
1607
1684
|
it('should emit TASK_REJECT event on AGENT_CONTACT_OFFER_RONA event', () => {
|
|
@@ -1644,15 +1721,14 @@ describe('TaskManager', () => {
|
|
|
1644
1721
|
};
|
|
1645
1722
|
|
|
1646
1723
|
taskManager.taskCollection[taskId] = taskManager.getTask(taskId);
|
|
1647
|
-
const
|
|
1648
|
-
const
|
|
1724
|
+
const task = taskManager.getTask(taskId);
|
|
1725
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1649
1726
|
|
|
1650
1727
|
webSocketManagerMock.emit('message', JSON.stringify(ronaPayload));
|
|
1651
1728
|
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
expect(metricsTrackSpy.mock.calls[0][0]).toBe('Agent RONA');
|
|
1729
|
+
const stateMachineEvent = expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.RONA);
|
|
1730
|
+
expect(stateMachineEvent?.reason).toBe(ronaPayload.data.reason);
|
|
1731
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1656
1732
|
});
|
|
1657
1733
|
|
|
1658
1734
|
it('should emit TASK_REJECT event on AGENT_CONTACT_ASSIGN_FAILED event', () => {
|
|
@@ -1696,20 +1772,16 @@ describe('TaskManager', () => {
|
|
|
1696
1772
|
|
|
1697
1773
|
taskManager.taskCollection[taskId] = taskManager.getTask(taskId);
|
|
1698
1774
|
const task = taskManager.getTask(taskId);
|
|
1699
|
-
const
|
|
1700
|
-
const taskUpdateDataSpy = jest.spyOn(task, 'updateTaskData');
|
|
1701
|
-
const metricsTrackSpy = jest.spyOn(taskManager.metricsManager, 'trackEvent');
|
|
1775
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1702
1776
|
|
|
1703
1777
|
webSocketManagerMock.emit('message', JSON.stringify(assignFailedPayload));
|
|
1704
1778
|
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
assignFailedPayload.data.reason
|
|
1779
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1780
|
+
sendStateMachineEventSpy,
|
|
1781
|
+
TaskEvent.ASSIGN_FAILED
|
|
1709
1782
|
);
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
expect(metricsTrackSpy.mock.calls[0][0]).toBe('Agent Contact Assign Failed');
|
|
1783
|
+
expect(stateMachineEvent?.reason).toBe(assignFailedPayload.data.reason);
|
|
1784
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1713
1785
|
});
|
|
1714
1786
|
|
|
1715
1787
|
it('should remove currentTask from taskCollection on AGENT_WRAPPEDUP event', () => {
|
|
@@ -1732,11 +1804,12 @@ describe('TaskManager', () => {
|
|
|
1732
1804
|
|
|
1733
1805
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1734
1806
|
const task = taskManager.getTask(taskId);
|
|
1735
|
-
const
|
|
1807
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1736
1808
|
|
|
1737
1809
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1738
1810
|
|
|
1739
|
-
|
|
1811
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.WRAPUP_COMPLETE);
|
|
1812
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1740
1813
|
expect(taskManager.getTask(taskId)).toBeUndefined();
|
|
1741
1814
|
});
|
|
1742
1815
|
|
|
@@ -1762,7 +1835,8 @@ describe('TaskManager', () => {
|
|
|
1762
1835
|
};
|
|
1763
1836
|
|
|
1764
1837
|
const taskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
|
|
1765
|
-
const taskUpdateTaskDataSpy =
|
|
1838
|
+
const taskUpdateTaskDataSpy = taskManager.getTask(taskId).updateTaskData as jest.Mock;
|
|
1839
|
+
taskUpdateTaskDataSpy.mockClear();
|
|
1766
1840
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1767
1841
|
expect(taskEmitSpy).not.toHaveBeenCalled();
|
|
1768
1842
|
expect(taskUpdateTaskDataSpy).not.toHaveBeenCalled();
|
|
@@ -1771,7 +1845,9 @@ describe('TaskManager', () => {
|
|
|
1771
1845
|
it('should emit TASK_CONSULTING event when agent is consulting', () => {
|
|
1772
1846
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1773
1847
|
taskManager.getTask(taskId).data.isConsulted = false;
|
|
1774
|
-
const
|
|
1848
|
+
const task = taskManager.getTask(taskId);
|
|
1849
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1850
|
+
const taskManagerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
1775
1851
|
const consultingPayload = {
|
|
1776
1852
|
data: {
|
|
1777
1853
|
...initalPayload.data,
|
|
@@ -1780,16 +1856,21 @@ describe('TaskManager', () => {
|
|
|
1780
1856
|
},
|
|
1781
1857
|
};
|
|
1782
1858
|
webSocketManagerMock.emit('message', JSON.stringify(consultingPayload));
|
|
1783
|
-
|
|
1784
|
-
expect(
|
|
1785
|
-
TASK_EVENTS.
|
|
1859
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.CONSULTING_ACTIVE);
|
|
1860
|
+
expect(taskManagerEmitSpy).toHaveBeenCalledWith(
|
|
1861
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
1786
1862
|
taskManager.getTask(taskId)
|
|
1787
1863
|
);
|
|
1864
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1788
1865
|
});
|
|
1789
1866
|
|
|
1790
|
-
it('should
|
|
1867
|
+
it('should update task data on AGENT_CONTACT_UNASSIGNED', () => {
|
|
1791
1868
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1792
|
-
const
|
|
1869
|
+
const task = taskManager.getTask(taskId);
|
|
1870
|
+
const sendStateMachineEventSpy = task.sendStateMachineEvent as jest.Mock;
|
|
1871
|
+
sendStateMachineEventSpy.mockClear();
|
|
1872
|
+
const updateTaskDataSpy = task.updateTaskData as jest.Mock;
|
|
1873
|
+
updateTaskDataSpy.mockClear();
|
|
1793
1874
|
const unassignedPayload = {
|
|
1794
1875
|
data: {
|
|
1795
1876
|
type: CC_EVENTS.AGENT_CONTACT_UNASSIGNED,
|
|
@@ -1807,11 +1888,76 @@ describe('TaskManager', () => {
|
|
|
1807
1888
|
},
|
|
1808
1889
|
};
|
|
1809
1890
|
webSocketManagerMock.emit('message', JSON.stringify(unassignedPayload));
|
|
1810
|
-
expect(
|
|
1811
|
-
|
|
1812
|
-
|
|
1891
|
+
expect(sendStateMachineEventSpy).not.toHaveBeenCalled();
|
|
1892
|
+
expect(updateTaskDataSpy).toHaveBeenCalledWith(unassignedPayload.data);
|
|
1893
|
+
});
|
|
1894
|
+
|
|
1895
|
+
it('preserves consult fields from state context during consulting payload refresh', () => {
|
|
1896
|
+
const task = createMockTask({
|
|
1897
|
+
...taskDataMock,
|
|
1898
|
+
interaction: {state: 'consulting'} as any,
|
|
1899
|
+
interactionId: taskId,
|
|
1900
|
+
});
|
|
1901
|
+
task.stateMachineService = {
|
|
1902
|
+
getSnapshot: () => ({
|
|
1903
|
+
value: 'CONSULTING',
|
|
1904
|
+
context: {
|
|
1905
|
+
consultDestinationAgentId: 'agent-preserved',
|
|
1906
|
+
consultDestinationType: 'agent',
|
|
1907
|
+
},
|
|
1908
|
+
}),
|
|
1909
|
+
};
|
|
1910
|
+
|
|
1911
|
+
const incomingTaskData = {
|
|
1912
|
+
...taskDataMock,
|
|
1913
|
+
interaction: {state: 'consulting'} as any,
|
|
1914
|
+
interactionId: taskId,
|
|
1915
|
+
destAgentId: null,
|
|
1916
|
+
destinationType: null,
|
|
1917
|
+
};
|
|
1918
|
+
|
|
1919
|
+
(taskManager as any).updateTaskData(task, incomingTaskData);
|
|
1920
|
+
|
|
1921
|
+
expect(task.updateTaskData).toHaveBeenCalledWith(
|
|
1922
|
+
expect.objectContaining({
|
|
1923
|
+
destAgentId: 'agent-preserved',
|
|
1924
|
+
destinationType: 'agent',
|
|
1925
|
+
})
|
|
1926
|
+
);
|
|
1927
|
+
});
|
|
1928
|
+
|
|
1929
|
+
it('does not preserve stale consult fields once consult is no longer active', () => {
|
|
1930
|
+
const task = createMockTask({
|
|
1931
|
+
...taskDataMock,
|
|
1932
|
+
interaction: {state: 'connected'} as any,
|
|
1933
|
+
interactionId: taskId,
|
|
1934
|
+
});
|
|
1935
|
+
task.stateMachineService = {
|
|
1936
|
+
getSnapshot: () => ({
|
|
1937
|
+
value: 'CONNECTED',
|
|
1938
|
+
context: {
|
|
1939
|
+
consultDestinationAgentId: 'agent-stale',
|
|
1940
|
+
consultDestinationType: 'agent',
|
|
1941
|
+
},
|
|
1942
|
+
}),
|
|
1943
|
+
};
|
|
1944
|
+
|
|
1945
|
+
const incomingTaskData = {
|
|
1946
|
+
...taskDataMock,
|
|
1947
|
+
interaction: {state: 'connected'} as any,
|
|
1948
|
+
interactionId: taskId,
|
|
1949
|
+
destAgentId: null,
|
|
1950
|
+
destinationType: null,
|
|
1951
|
+
};
|
|
1952
|
+
|
|
1953
|
+
(taskManager as any).updateTaskData(task, incomingTaskData);
|
|
1954
|
+
|
|
1955
|
+
expect(task.updateTaskData).toHaveBeenCalledWith(
|
|
1956
|
+
expect.objectContaining({
|
|
1957
|
+
destAgentId: null,
|
|
1958
|
+
destinationType: null,
|
|
1959
|
+
})
|
|
1813
1960
|
);
|
|
1814
|
-
expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_END, taskManager.getTask(taskId));
|
|
1815
1961
|
});
|
|
1816
1962
|
|
|
1817
1963
|
it('should handle chat interaction and emit TASK_INCOMING immediately', () => {
|
|
@@ -1823,16 +1969,16 @@ describe('TaskManager', () => {
|
|
|
1823
1969
|
},
|
|
1824
1970
|
};
|
|
1825
1971
|
|
|
1826
|
-
const taskIncomingSpy = jest.spyOn(taskManager, 'emit');
|
|
1827
|
-
|
|
1828
1972
|
// Simulate receiving a chat task
|
|
1829
1973
|
webSocketManagerMock.emit('message', JSON.stringify(chatPayload));
|
|
1830
1974
|
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1975
|
+
const chatTask = taskManager.getTask(chatPayload.data.interactionId);
|
|
1976
|
+
const sendStateMachineEventSpy = chatTask.sendStateMachineEvent as jest.Mock;
|
|
1977
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1978
|
+
sendStateMachineEventSpy,
|
|
1979
|
+
TaskEvent.TASK_INCOMING
|
|
1835
1980
|
);
|
|
1981
|
+
expect(stateMachineEvent?.taskData).toEqual(chatPayload.data);
|
|
1836
1982
|
expect(taskManager.getAllTasks()).toHaveProperty(chatPayload.data.interactionId);
|
|
1837
1983
|
});
|
|
1838
1984
|
|
|
@@ -1845,16 +1991,16 @@ describe('TaskManager', () => {
|
|
|
1845
1991
|
},
|
|
1846
1992
|
};
|
|
1847
1993
|
|
|
1848
|
-
const taskIncomingSpy = jest.spyOn(taskManager, 'emit');
|
|
1849
|
-
|
|
1850
1994
|
// Simulate receiving an email task
|
|
1851
1995
|
webSocketManagerMock.emit('message', JSON.stringify(emailPayload));
|
|
1852
1996
|
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1997
|
+
const emailTask = taskManager.getTask(emailPayload.data.interactionId);
|
|
1998
|
+
const sendStateMachineEventSpy = emailTask.sendStateMachineEvent as jest.Mock;
|
|
1999
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
2000
|
+
sendStateMachineEventSpy,
|
|
2001
|
+
TaskEvent.TASK_INCOMING
|
|
1857
2002
|
);
|
|
2003
|
+
expect(stateMachineEvent?.taskData).toEqual(emailPayload.data);
|
|
1858
2004
|
expect(taskManager.getAllTasks()).toHaveProperty(emailPayload.data.interactionId);
|
|
1859
2005
|
});
|
|
1860
2006
|
|
|
@@ -1868,13 +2014,11 @@ describe('TaskManager', () => {
|
|
|
1868
2014
|
},
|
|
1869
2015
|
};
|
|
1870
2016
|
|
|
1871
|
-
const taskIncomingSpy = jest.spyOn(taskManager, 'emit');
|
|
1872
2017
|
webSocketManagerMock.emit('message', JSON.stringify(chatReservedPayload));
|
|
2018
|
+
const task = taskManager.getTask(chatReservedPayload.data.interactionId);
|
|
2019
|
+
const sendStateMachineEventSpy = task.sendStateMachineEvent as jest.Mock;
|
|
1873
2020
|
|
|
1874
|
-
|
|
1875
|
-
TASK_EVENTS.TASK_INCOMING,
|
|
1876
|
-
taskManager.getTask(chatReservedPayload.data.interactionId)
|
|
1877
|
-
);
|
|
2021
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.TASK_INCOMING);
|
|
1878
2022
|
|
|
1879
2023
|
// 2. Chat task is assigned
|
|
1880
2024
|
const chatAssignedPayload = {
|
|
@@ -1884,12 +2028,9 @@ describe('TaskManager', () => {
|
|
|
1884
2028
|
},
|
|
1885
2029
|
};
|
|
1886
2030
|
|
|
1887
|
-
const task = taskManager.getTask(chatReservedPayload.data.interactionId);
|
|
1888
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
1889
|
-
|
|
1890
2031
|
webSocketManagerMock.emit('message', JSON.stringify(chatAssignedPayload));
|
|
1891
2032
|
|
|
1892
|
-
|
|
2033
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.ASSIGN);
|
|
1893
2034
|
|
|
1894
2035
|
// 3. Chat task is ended with state 'new' to trigger cleanup
|
|
1895
2036
|
const chatEndedPayload = {
|
|
@@ -1897,12 +2038,15 @@ describe('TaskManager', () => {
|
|
|
1897
2038
|
...chatReservedPayload.data,
|
|
1898
2039
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
1899
2040
|
interaction: {mediaType: 'chat', state: 'new'}, // Change to 'new' state
|
|
2041
|
+
wrapUpRequired: false,
|
|
1900
2042
|
},
|
|
1901
2043
|
};
|
|
1902
2044
|
|
|
2045
|
+
// Simulate state on the task to allow cleanup logic
|
|
2046
|
+
task.data.interaction.state = 'new';
|
|
1903
2047
|
webSocketManagerMock.emit('message', JSON.stringify(chatEndedPayload));
|
|
1904
2048
|
|
|
1905
|
-
|
|
2049
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.CONTACT_ENDED);
|
|
1906
2050
|
// Verify task is removed since it was in a 'new' state
|
|
1907
2051
|
expect(taskManager.getTask(chatReservedPayload.data.interactionId)).toBeUndefined();
|
|
1908
2052
|
});
|
|
@@ -1991,13 +2135,8 @@ describe('TaskManager', () => {
|
|
|
1991
2135
|
expect(taskManager.getAllTasks()).toHaveProperty(task2Payload.data.interactionId);
|
|
1992
2136
|
expect(taskManager.getAllTasks()).toHaveProperty(task3Payload.data.interactionId);
|
|
1993
2137
|
|
|
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
2138
|
const task2 = taskManager.getTask(task2Payload.data.interactionId);
|
|
2139
|
+
const task2SendStateMachineEventSpy = task2.sendStateMachineEvent as jest.Mock;
|
|
2001
2140
|
|
|
2002
2141
|
// End only the second task (chat task)
|
|
2003
2142
|
const chatEndedPayload = {
|
|
@@ -2005,15 +2144,18 @@ describe('TaskManager', () => {
|
|
|
2005
2144
|
...task2Payload.data,
|
|
2006
2145
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
2007
2146
|
interaction: {mediaType: 'chat', state: 'new'}, // Using 'new' to trigger cleanup
|
|
2147
|
+
wrapUpRequired: false,
|
|
2008
2148
|
},
|
|
2009
2149
|
};
|
|
2010
2150
|
|
|
2151
|
+
task2.data.interaction.state = 'new';
|
|
2011
2152
|
webSocketManagerMock.emit('message', JSON.stringify(chatEndedPayload));
|
|
2012
2153
|
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2154
|
+
const firstEndEvent = expectLastStateMachineEvent(
|
|
2155
|
+
task2SendStateMachineEventSpy,
|
|
2156
|
+
TaskEvent.CONTACT_ENDED
|
|
2157
|
+
);
|
|
2158
|
+
expect(firstEndEvent?.taskData).toEqual(chatEndedPayload.data);
|
|
2017
2159
|
|
|
2018
2160
|
// Verify task2 was removed from collection (since state was 'new')
|
|
2019
2161
|
expect(taskManager.getTask(task2Payload.data.interactionId)).toBeUndefined();
|
|
@@ -2024,6 +2166,7 @@ describe('TaskManager', () => {
|
|
|
2024
2166
|
|
|
2025
2167
|
// Store reference to task3 before we end it
|
|
2026
2168
|
const task3 = taskManager.getTask(task3Payload.data.interactionId);
|
|
2169
|
+
const task3SendStateMachineEventSpy = task3.sendStateMachineEvent as jest.Mock;
|
|
2027
2170
|
|
|
2028
2171
|
// Now end task3 with a state that doesn't trigger cleanup
|
|
2029
2172
|
const emailEndedPayload = {
|
|
@@ -2031,31 +2174,32 @@ describe('TaskManager', () => {
|
|
|
2031
2174
|
...task3Payload.data,
|
|
2032
2175
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
2033
2176
|
interaction: {mediaType: 'email', state: 'connected'}, // Using 'connected' to NOT trigger cleanup
|
|
2177
|
+
wrapUpRequired: true,
|
|
2178
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
2034
2179
|
},
|
|
2035
2180
|
};
|
|
2036
2181
|
|
|
2182
|
+
task3.data.interaction.state = 'connected';
|
|
2037
2183
|
webSocketManagerMock.emit('message', JSON.stringify(emailEndedPayload));
|
|
2038
2184
|
|
|
2039
|
-
|
|
2040
|
-
|
|
2185
|
+
const secondEndEvent = expectLastStateMachineEvent(
|
|
2186
|
+
task3SendStateMachineEventSpy,
|
|
2187
|
+
TaskEvent.CONTACT_ENDED
|
|
2188
|
+
);
|
|
2189
|
+
expect(secondEndEvent?.taskData).toEqual(emailEndedPayload.data);
|
|
2041
2190
|
|
|
2042
2191
|
// Verify task3 is still in collection (since state was 'connected')
|
|
2043
2192
|
expect(taskManager.getTask(task3Payload.data.interactionId)).toBeDefined();
|
|
2044
2193
|
|
|
2045
2194
|
// Verify task1 remains unaffected
|
|
2046
|
-
expect(task1EmitSpy).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_END);
|
|
2047
2195
|
expect(taskManager.getTask(task1Payload.data.interactionId)).toBeDefined();
|
|
2048
2196
|
});
|
|
2049
2197
|
|
|
2050
|
-
it('should emit
|
|
2198
|
+
it('should emit TRANSFER_SUCCESS event on AGENT_VTEAM_TRANSFERRED event', () => {
|
|
2051
2199
|
// First create a task by emitting the initial payload
|
|
2052
2200
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
2053
|
-
|
|
2054
|
-
// Get a reference to the task from taskCollection
|
|
2055
2201
|
const task = taskManager.getTask(taskId);
|
|
2056
|
-
|
|
2057
|
-
// Now spy on the task's emit method
|
|
2058
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
2202
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
2059
2203
|
|
|
2060
2204
|
const vteamTransferredPayload = {
|
|
2061
2205
|
data: {
|
|
@@ -2079,8 +2223,9 @@ describe('TaskManager', () => {
|
|
|
2079
2223
|
|
|
2080
2224
|
webSocketManagerMock.emit('message', JSON.stringify(vteamTransferredPayload));
|
|
2081
2225
|
|
|
2082
|
-
// Check that
|
|
2083
|
-
|
|
2226
|
+
// Check that the state machine received the END event
|
|
2227
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.TRANSFER_SUCCESS);
|
|
2228
|
+
sendStateMachineEventSpy.mockRestore();
|
|
2084
2229
|
|
|
2085
2230
|
// The task should still exist in the collection based on current implementation
|
|
2086
2231
|
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
@@ -2095,12 +2240,16 @@ describe('TaskManager', () => {
|
|
|
2095
2240
|
},
|
|
2096
2241
|
};
|
|
2097
2242
|
const task = taskManager.getTask(taskId);
|
|
2098
|
-
const
|
|
2099
|
-
task.data = {...(task.data || {}), ...(data || {})};
|
|
2100
|
-
return task;
|
|
2101
|
-
});
|
|
2243
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
2102
2244
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2103
|
-
expect(
|
|
2245
|
+
expect(sendStateMachineEventSpy).toHaveBeenCalled();
|
|
2246
|
+
const stateMachineEvent = sendStateMachineEventSpy.mock.calls.at(-1)?.[0];
|
|
2247
|
+
expect(stateMachineEvent?.type).toBe(TaskEvent.TASK_WRAPUP);
|
|
2248
|
+
expect(stateMachineEvent?.taskData).toEqual({
|
|
2249
|
+
...payload.data,
|
|
2250
|
+
wrapUpRequired: true,
|
|
2251
|
+
});
|
|
2252
|
+
sendStateMachineEventSpy.mockRestore();
|
|
2104
2253
|
});
|
|
2105
2254
|
|
|
2106
2255
|
it('should not attempt cleanup twice when AGENT_CONTACT_UNASSIGNED is followed by AGENT_WRAPUP', () => {
|
|
@@ -2176,1176 +2325,157 @@ describe('TaskManager', () => {
|
|
|
2176
2325
|
});
|
|
2177
2326
|
|
|
2178
2327
|
describe('should emit appropriate task events for recording events', () => {
|
|
2179
|
-
|
|
2328
|
+
const eventMap: Record<string, TaskEvent | null> = {
|
|
2329
|
+
STARTED: TaskEvent.RECORDING_STARTED,
|
|
2330
|
+
PAUSED: TaskEvent.PAUSE_RECORDING,
|
|
2331
|
+
PAUSE_FAILED: null,
|
|
2332
|
+
RESUMED: TaskEvent.RESUME_RECORDING,
|
|
2333
|
+
RESUME_FAILED: null,
|
|
2334
|
+
};
|
|
2335
|
+
|
|
2336
|
+
['STARTED', 'PAUSED', 'PAUSE_FAILED', 'RESUMED', 'RESUME_FAILED'].forEach((suffix) => {
|
|
2180
2337
|
const ccEvent = CC_EVENTS[`CONTACT_RECORDING_${suffix}`];
|
|
2181
|
-
const
|
|
2182
|
-
it(`should
|
|
2338
|
+
const expectedTaskEvent = eventMap[suffix];
|
|
2339
|
+
it(`should ${expectedTaskEvent ? 'send' : 'not send'} ${
|
|
2340
|
+
expectedTaskEvent ?? 'a'
|
|
2341
|
+
} state machine event on ${ccEvent} event`, () => {
|
|
2183
2342
|
const payload = {data: {...initalPayload.data, type: ccEvent}};
|
|
2184
2343
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
2185
2344
|
const task = taskManager.getTask(taskId);
|
|
2186
|
-
const
|
|
2345
|
+
const sendStateMachineEventSpy = task.sendStateMachineEvent as jest.Mock;
|
|
2346
|
+
sendStateMachineEventSpy.mockClear();
|
|
2187
2347
|
|
|
2188
2348
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2189
|
-
|
|
2349
|
+
if (expectedTaskEvent) {
|
|
2350
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
2351
|
+
sendStateMachineEventSpy,
|
|
2352
|
+
expectedTaskEvent
|
|
2353
|
+
);
|
|
2354
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
2355
|
+
} else {
|
|
2356
|
+
expect(sendStateMachineEventSpy).not.toHaveBeenCalled();
|
|
2357
|
+
}
|
|
2190
2358
|
});
|
|
2191
2359
|
});
|
|
2192
2360
|
});
|
|
2193
2361
|
|
|
2194
2362
|
describe('Conference event handling', () => {
|
|
2195
2363
|
let task;
|
|
2196
|
-
const agentId = '723a8ffb-a26e-496d-b14a-ff44fb83b64f';
|
|
2197
2364
|
|
|
2198
2365
|
beforeEach(() => {
|
|
2199
|
-
// Set the agentId on taskManager before tests run
|
|
2200
|
-
taskManager.setAgentId(agentId);
|
|
2201
|
-
|
|
2202
2366
|
task = {
|
|
2203
2367
|
data: {interactionId: taskId},
|
|
2204
2368
|
emit: jest.fn(),
|
|
2205
|
-
updateTaskData: jest.fn()
|
|
2206
|
-
|
|
2207
|
-
task.data = {...task.data, ...updatedData};
|
|
2208
|
-
return task;
|
|
2209
|
-
}),
|
|
2369
|
+
updateTaskData: jest.fn(),
|
|
2370
|
+
sendStateMachineEvent: jest.fn(),
|
|
2210
2371
|
};
|
|
2211
|
-
taskManager.taskCollection[taskId] = task;
|
|
2372
|
+
taskManager.taskCollection[taskId] = task as any;
|
|
2212
2373
|
});
|
|
2213
2374
|
|
|
2214
|
-
it('
|
|
2375
|
+
it('sends AGENT_CONSULT_CONFERENCED to state machine as CONFERENCE_START', () => {
|
|
2215
2376
|
const payload = {
|
|
2216
|
-
data: {
|
|
2217
|
-
type: CC_EVENTS.AGENT_CONSULT_CONFERENCED,
|
|
2218
|
-
interactionId: taskId,
|
|
2219
|
-
isConferencing: true,
|
|
2220
|
-
},
|
|
2377
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCED, interactionId: taskId},
|
|
2221
2378
|
};
|
|
2222
|
-
|
|
2223
2379
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
expect(
|
|
2380
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
2381
|
+
const call = task.sendStateMachineEvent.mock.calls[0][0];
|
|
2382
|
+
expect(call.type).toBe(TaskEvent.CONFERENCE_START);
|
|
2227
2383
|
});
|
|
2228
2384
|
|
|
2229
|
-
it('
|
|
2385
|
+
it('sends PARTICIPANT_JOINED_CONFERENCE to state machine as CONFERENCE_START', () => {
|
|
2230
2386
|
const payload = {
|
|
2231
|
-
data: {
|
|
2232
|
-
type: CC_EVENTS.AGENT_CONSULT_CONFERENCING,
|
|
2233
|
-
interactionId: taskId,
|
|
2234
|
-
isConferencing: true,
|
|
2235
|
-
},
|
|
2387
|
+
data: {type: CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE, interactionId: taskId},
|
|
2236
2388
|
};
|
|
2237
|
-
|
|
2238
2389
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
expect(task.emit).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_CONFERENCE_STARTED, task);
|
|
2390
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
2391
|
+
const call = task.sendStateMachineEvent.mock.calls[0][0];
|
|
2392
|
+
expect(call.type).toBe(TaskEvent.CONFERENCE_START);
|
|
2243
2393
|
});
|
|
2244
2394
|
|
|
2245
|
-
it('
|
|
2395
|
+
it('sends AGENT_CONSULT_CONFERENCE_FAILED to state machine as CONFERENCE_FAILED', () => {
|
|
2246
2396
|
const payload = {
|
|
2247
|
-
data: {
|
|
2248
|
-
type: CC_EVENTS.AGENT_CONSULT_CONFERENCE_FAILED,
|
|
2249
|
-
interactionId: taskId,
|
|
2250
|
-
reason: 'Network error',
|
|
2251
|
-
},
|
|
2397
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCE_FAILED, interactionId: taskId},
|
|
2252
2398
|
};
|
|
2253
|
-
|
|
2254
2399
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2400
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
2401
|
+
const call = task.sendStateMachineEvent.mock.calls[0][0];
|
|
2402
|
+
expect(call.type).toBe(TaskEvent.CONFERENCE_FAILED);
|
|
2258
2403
|
});
|
|
2259
2404
|
|
|
2260
|
-
it('
|
|
2405
|
+
it('sends PARTICIPANT_LEFT_CONFERENCE to state machine as PARTICIPANT_LEAVE', () => {
|
|
2261
2406
|
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
|
-
},
|
|
2407
|
+
data: {type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, interactionId: taskId},
|
|
2280
2408
|
};
|
|
2281
|
-
|
|
2282
2409
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
expect(
|
|
2286
|
-
// No specific task event emission for participant joined - just data update
|
|
2410
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
2411
|
+
const call = task.sendStateMachineEvent.mock.calls[0][0];
|
|
2412
|
+
expect(call.type).toBe(TaskEvent.PARTICIPANT_LEAVE);
|
|
2287
2413
|
});
|
|
2288
2414
|
|
|
2289
|
-
it('
|
|
2415
|
+
it('handles AGENT_CONSULT_CONFERENCING event without errors', () => {
|
|
2290
2416
|
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
|
-
},
|
|
2417
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCING, interactionId: taskId},
|
|
2310
2418
|
};
|
|
2419
|
+
expect(() => {
|
|
2420
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2421
|
+
}).not.toThrow();
|
|
2422
|
+
});
|
|
2311
2423
|
|
|
2312
|
-
|
|
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
|
-
|
|
2384
|
-
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
|
-
});
|
|
2622
|
-
});
|
|
2623
|
-
|
|
2624
|
-
it('should handle PARTICIPANT_LEFT_CONFERENCE_FAILED event', () => {
|
|
2424
|
+
it('handles PARTICIPANT_LEFT_CONFERENCE_FAILED event without errors', () => {
|
|
2625
2425
|
const payload = {
|
|
2626
|
-
data: {
|
|
2627
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE_FAILED,
|
|
2628
|
-
interactionId: taskId,
|
|
2629
|
-
reason: 'Exit failed',
|
|
2630
|
-
},
|
|
2426
|
+
data: {type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE_FAILED, interactionId: taskId},
|
|
2631
2427
|
};
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
expect(task.data.reason).toBe('Exit failed');
|
|
2636
|
-
// No event emission expected for failure - handled by contact method promise rejection
|
|
2428
|
+
expect(() => {
|
|
2429
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2430
|
+
}).not.toThrow();
|
|
2637
2431
|
});
|
|
2638
2432
|
|
|
2639
|
-
it('
|
|
2433
|
+
it('only routes conference events to matching tasks', () => {
|
|
2640
2434
|
const otherTaskId = 'other-task-id';
|
|
2641
|
-
const otherTask = {
|
|
2435
|
+
const otherTask: any = {
|
|
2642
2436
|
data: {interactionId: otherTaskId},
|
|
2643
2437
|
emit: jest.fn(),
|
|
2438
|
+
updateTaskData: jest.fn(),
|
|
2439
|
+
sendStateMachineEvent: jest.fn(),
|
|
2644
2440
|
};
|
|
2645
2441
|
taskManager.taskCollection[otherTaskId] = otherTask;
|
|
2646
2442
|
|
|
2647
2443
|
const payload = {
|
|
2648
|
-
data: {
|
|
2649
|
-
type: CC_EVENTS.AGENT_CONSULT_CONFERENCED,
|
|
2650
|
-
interactionId: taskId,
|
|
2651
|
-
isConferencing: true,
|
|
2652
|
-
},
|
|
2444
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCED, interactionId: taskId},
|
|
2653
2445
|
};
|
|
2654
|
-
|
|
2655
2446
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2656
2447
|
|
|
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();
|
|
2448
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
2449
|
+
expect(otherTask.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
2664
2450
|
});
|
|
2665
2451
|
});
|
|
2666
2452
|
|
|
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
|
-
});
|
|
2453
|
+
describe('state machine integration', () => {
|
|
2454
|
+
it('maps CC events to task state machine events using normalized payload', () => {
|
|
2455
|
+
const mapped = (TaskManager as any).mapEventToTaskStateMachineEvent(
|
|
2456
|
+
CC_EVENTS.AGENT_CONTACT_ASSIGNED,
|
|
2457
|
+
{...taskDataMock, type: CC_EVENTS.AGENT_CONTACT_ASSIGNED}
|
|
2458
|
+
);
|
|
2787
2459
|
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
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;
|
|
2460
|
+
expect(mapped).toEqual({
|
|
2461
|
+
type: TaskEvent.ASSIGN,
|
|
2462
|
+
taskData: {...taskDataMock, type: CC_EVENTS.AGENT_CONTACT_ASSIGNED},
|
|
2803
2463
|
});
|
|
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
2464
|
});
|
|
2825
2465
|
|
|
2826
|
-
it('
|
|
2466
|
+
it('sends mapped events to the task state machine service', () => {
|
|
2467
|
+
const payload = {...taskDataMock, type: CC_EVENTS.AGENT_CONTACT_ASSIGNED};
|
|
2827
2468
|
const task = taskManager.getTask(taskId);
|
|
2828
|
-
|
|
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');
|
|
2469
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
2844
2470
|
|
|
2845
|
-
|
|
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));
|
|
2471
|
+
webSocketManagerMock.emit('message', JSON.stringify({data: payload}));
|
|
2859
2472
|
|
|
2860
|
-
expect(
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
it('should remove non-OUTDIAL task when state is new regardless of agentsPendingWrapUp', () => {
|
|
2865
|
-
const task = taskManager.getTask(taskId);
|
|
2866
|
-
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
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;
|
|
2473
|
+
expect(sendStateMachineEventSpy).toHaveBeenCalledWith({
|
|
2474
|
+
type: TaskEvent.ASSIGN,
|
|
2475
|
+
taskData: payload,
|
|
2879
2476
|
});
|
|
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
|
-
});
|
|
2901
|
-
|
|
2902
|
-
it('should handle agentsPendingWrapUp with multiple agents correctly - remove if current agent not in list', () => {
|
|
2903
|
-
const task = taskManager.getTask(taskId);
|
|
2904
|
-
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
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));
|
|
2935
|
-
|
|
2936
|
-
expect(removeTaskSpy).toHaveBeenCalled();
|
|
2937
|
-
expect(taskManager.getTask(taskId)).toBeUndefined();
|
|
2938
|
-
});
|
|
2939
|
-
|
|
2940
|
-
it('should handle agentsPendingWrapUp with multiple agents correctly - keep if current agent is in list', () => {
|
|
2941
|
-
const task = taskManager.getTask(taskId);
|
|
2942
|
-
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
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;
|
|
2955
|
-
});
|
|
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
2477
|
|
|
3347
|
-
|
|
3348
|
-
expect(taskEmitSpy).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_END, expect.anything());
|
|
2478
|
+
sendStateMachineEventSpy.mockRestore();
|
|
3349
2479
|
});
|
|
3350
2480
|
});
|
|
3351
2481
|
});
|