@webex/contact-center 3.12.0-llmrefactor.1 → 3.12.0-llmrefactor.11
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/.sdd/manifest.json +7 -5
- package/ai-docs/ARCHITECTURE.md +1 -1
- package/ai-docs/CONTRACTS.md +8 -3
- package/ai-docs/SECURITY.md +1 -1
- package/ai-docs/contact-center-spec.md +22 -4
- package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -0
- package/dist/cc.js +544 -12
- package/dist/cc.js.map +1 -1
- package/dist/config.js +7 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +10 -2
- package/dist/constants.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics/MetricsManager.js +16 -0
- package/dist/metrics/MetricsManager.js.map +1 -1
- package/dist/metrics/behavioral-events.js +104 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +18 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/AddressBook.js +18 -15
- package/dist/services/AddressBook.js.map +1 -1
- package/dist/services/AnswerCallOnWebexService.js +202 -0
- package/dist/services/AnswerCallOnWebexService.js.map +1 -0
- package/dist/services/EntryPoint.js +46 -68
- package/dist/services/EntryPoint.js.map +1 -1
- package/dist/services/Queue.js +27 -22
- package/dist/services/Queue.js.map +1 -1
- package/dist/services/WebexCrossClientService.js +222 -0
- package/dist/services/WebexCrossClientService.js.map +1 -0
- package/dist/services/WxAppTelephonyMercurySync.js +108 -0
- package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
- package/dist/services/config/Util.js +3 -0
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/constants.js +10 -6
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/types.js +4 -0
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/core/WebexRequest.js +6 -2
- package/dist/services/core/WebexRequest.js.map +1 -1
- package/dist/services/core/aqm-reqs.js +28 -14
- package/dist/services/core/aqm-reqs.js.map +1 -1
- package/dist/services/core/types.js.map +1 -1
- package/dist/services/task/Task.js +73 -7
- package/dist/services/task/Task.js.map +1 -1
- package/dist/services/task/TaskFactory.js +8 -4
- package/dist/services/task/TaskFactory.js.map +1 -1
- package/dist/services/task/TaskManager.js +490 -21
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/WebexCallingUtils.js +70 -0
- package/dist/services/task/WebexCallingUtils.js.map +1 -0
- package/dist/services/task/constants.js +4 -1
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/contact.js +29 -0
- package/dist/services/task/contact.js.map +1 -1
- package/dist/services/task/digital/Digital.js +3 -2
- package/dist/services/task/digital/Digital.js.map +1 -1
- package/dist/services/task/state-machine/TaskStateMachine.js +162 -53
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
- package/dist/services/task/state-machine/actions.js +6 -1
- package/dist/services/task/state-machine/actions.js.map +1 -1
- package/dist/services/task/state-machine/guards.js +7 -10
- package/dist/services/task/state-machine/guards.js.map +1 -1
- package/dist/services/task/state-machine/types.js.map +1 -1
- package/dist/services/task/state-machine/uiControlsComputer.js +118 -18
- package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -1
- package/dist/services/task/types.js +16 -1
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +456 -15
- package/dist/services/task/voice/Voice.js.map +1 -1
- package/dist/services/task/voice/WebRTC.js +4 -1
- package/dist/services/task/voice/WebRTC.js.map +1 -1
- package/dist/services/task/voice/wxAppVoiceMethods.js +354 -0
- package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
- package/dist/services/wxAppDiagnosticLogging.js +122 -0
- package/dist/services/wxAppDiagnosticLogging.js.map +1 -0
- package/dist/services/wxAppTelephonyUtils.js +19 -0
- package/dist/services/wxAppTelephonyUtils.js.map +1 -0
- package/dist/types/cc.d.ts +78 -4
- package/dist/types/config.d.ts +7 -0
- package/dist/types/constants.d.ts +8 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/metrics/MetricsManager.d.ts +6 -0
- package/dist/types/metrics/constants.d.ts +17 -0
- package/dist/types/services/AddressBook.d.ts +2 -1
- package/dist/types/services/AnswerCallOnWebexService.d.ts +38 -0
- package/dist/types/services/EntryPoint.d.ts +7 -5
- package/dist/types/services/Queue.d.ts +5 -3
- package/dist/types/services/WebexCrossClientService.d.ts +32 -0
- package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
- package/dist/types/services/config/constants.d.ts +8 -5
- package/dist/types/services/config/types.d.ts +17 -4
- package/dist/types/services/core/Err.d.ts +2 -0
- package/dist/types/services/core/WebexRequest.d.ts +1 -0
- package/dist/types/services/core/types.d.ts +2 -0
- package/dist/types/services/task/Task.d.ts +20 -2
- package/dist/types/services/task/TaskFactory.d.ts +2 -1
- package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
- package/dist/types/services/task/constants.d.ts +3 -0
- package/dist/types/services/task/contact.d.ts +4 -0
- package/dist/types/services/task/digital/Digital.d.ts +2 -2
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +304 -56
- package/dist/types/services/task/state-machine/guards.d.ts +6 -5
- package/dist/types/services/task/state-machine/types.d.ts +9 -1
- package/dist/types/services/task/types.d.ts +81 -4
- package/dist/types/services/task/voice/Voice.d.ts +52 -9
- package/dist/types/services/task/voice/WebRTC.d.ts +2 -2
- package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +55 -0
- package/dist/types/services/wxAppDiagnosticLogging.d.ts +51 -0
- package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
- package/dist/types/types.d.ts +14 -8
- package/dist/types/utils/PageCache.d.ts +20 -3
- package/dist/types.js +7 -2
- package/dist/types.js.map +1 -1
- package/dist/utils/PageCache.js +19 -5
- package/dist/utils/PageCache.js.map +1 -1
- package/dist/webex.js +1 -1
- package/package.json +8 -8
- package/src/cc.ts +657 -12
- package/src/config.ts +7 -0
- package/src/constants.ts +8 -0
- package/src/index.ts +2 -0
- package/src/metrics/MetricsManager.ts +16 -0
- package/src/metrics/ai-docs/metrics-spec.md +78 -10
- package/src/metrics/behavioral-events.ts +106 -0
- package/src/metrics/constants.ts +20 -0
- package/src/services/AddressBook.ts +17 -6
- package/src/services/AnswerCallOnWebexService.ts +239 -0
- package/src/services/EntryPoint.ts +59 -60
- package/src/services/Queue.ts +29 -12
- package/src/services/WebexCrossClientService.ts +293 -0
- package/src/services/WxAppTelephonyMercurySync.ts +142 -0
- package/src/services/agent/ai-docs/agent-spec.md +1 -0
- package/src/services/ai-docs/AGENTS.md +10 -10
- package/src/services/ai-docs/services-spec.md +6 -1
- package/src/services/config/Util.ts +3 -0
- package/src/services/config/ai-docs/AGENTS.md +1 -1
- package/src/services/config/ai-docs/ARCHITECTURE.md +2 -2
- package/src/services/config/ai-docs/config-spec.md +6 -0
- package/src/services/config/constants.ts +10 -6
- package/src/services/config/types.ts +16 -4
- package/src/services/core/Err.ts +1 -0
- package/src/services/core/WebexRequest.ts +3 -1
- package/src/services/core/ai-docs/core-spec.md +5 -1
- package/src/services/core/aqm-reqs.ts +30 -15
- package/src/services/core/types.ts +2 -0
- package/src/services/task/Task.ts +79 -8
- package/src/services/task/TaskFactory.ts +8 -3
- package/src/services/task/TaskManager.ts +716 -17
- package/src/services/task/WebexCallingUtils.ts +136 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +1 -0
- package/src/services/task/ai-docs/task-spec.md +156 -3
- package/src/services/task/constants.ts +3 -0
- package/src/services/task/contact.ts +30 -0
- package/src/services/task/digital/Digital.ts +4 -2
- package/src/services/task/state-machine/TaskStateMachine.ts +235 -81
- package/src/services/task/state-machine/actions.ts +5 -2
- package/src/services/task/state-machine/ai-docs/AGENTS.md +6 -4
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +35 -16
- package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +103 -33
- package/src/services/task/state-machine/guards.ts +7 -10
- package/src/services/task/state-machine/types.ts +16 -1
- package/src/services/task/state-machine/uiControlsComputer.ts +206 -32
- package/src/services/task/types.ts +114 -4
- package/src/services/task/voice/Voice.ts +595 -14
- package/src/services/task/voice/WebRTC.ts +5 -1
- package/src/services/task/voice/wxAppVoiceMethods.ts +516 -0
- package/src/services/wxAppDiagnosticLogging.ts +174 -0
- package/src/services/wxAppTelephonyUtils.ts +14 -0
- package/src/types.ts +33 -9
- package/src/utils/AGENTS.md +21 -10
- package/src/utils/PageCache.ts +38 -5
- package/src/utils/ai-docs/utils-spec.md +21 -11
- package/test/unit/spec/cc.ts +1522 -19
- package/test/unit/spec/metrics/MetricsManager.ts +25 -0
- package/test/unit/spec/metrics/behavioral-events.ts +60 -0
- package/test/unit/spec/services/AddressBook.ts +37 -6
- package/test/unit/spec/services/AnswerCallOnWebexService.ts +264 -0
- package/test/unit/spec/services/EntryPoint.ts +87 -40
- package/test/unit/spec/services/Queue.ts +123 -12
- package/test/unit/spec/services/WebexCrossClientService.ts +467 -0
- package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +136 -0
- package/test/unit/spec/services/config/Util.ts +85 -0
- package/test/unit/spec/services/core/WebexRequest.ts +3 -1
- package/test/unit/spec/services/core/aqm-reqs.ts +113 -1
- package/test/unit/spec/services/task/Task.ts +274 -6
- package/test/unit/spec/services/task/TaskFactory.ts +39 -2
- package/test/unit/spec/services/task/TaskManager.ts +1717 -9
- package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
- package/test/unit/spec/services/task/contact.ts +33 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +1485 -109
- package/test/unit/spec/services/task/state-machine/guards.ts +210 -8
- package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +445 -7
- package/test/unit/spec/services/task/voice/Voice.ts +1412 -0
- package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +603 -0
- package/test/unit/spec/services/wxAppDiagnosticLogging.ts +143 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
TaskState,
|
|
6
6
|
} from '../../../../../../src/services/task/state-machine';
|
|
7
7
|
import {createTaskData} from '../taskTestUtils';
|
|
8
|
+
import {guards} from '../../../../../../src/services/task/state-machine/guards';
|
|
8
9
|
|
|
9
10
|
const createConfig = () => ({
|
|
10
11
|
channelType: 'voice' as const,
|
|
@@ -14,6 +15,18 @@ const createConfig = () => ({
|
|
|
14
15
|
isRecordingEnabled: true,
|
|
15
16
|
});
|
|
16
17
|
|
|
18
|
+
/** Mirrors Task.emitTaskWrapup: publishWrapup spy only runs when context would emit task:wrapup. */
|
|
19
|
+
function createPublishAwareEmitTaskWrapupMock() {
|
|
20
|
+
const publishWrapup = jest.fn();
|
|
21
|
+
const emitTaskWrapup = ({context}: {context: {taskData?: {wrapUpRequired?: boolean}}}) => {
|
|
22
|
+
if (context.taskData?.wrapUpRequired) {
|
|
23
|
+
publishWrapup();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return {emitTaskWrapup, publishWrapup};
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
describe('Task state machine', () => {
|
|
18
31
|
const startMachine = () => {
|
|
19
32
|
const actor = createActor(createTaskStateMachine(createConfig()));
|
|
@@ -106,6 +119,234 @@ describe('Task state machine', () => {
|
|
|
106
119
|
});
|
|
107
120
|
});
|
|
108
121
|
|
|
122
|
+
describe('partial lifecycle payloads', () => {
|
|
123
|
+
const createActiveMainCallTaskData = (isHold = false) =>
|
|
124
|
+
createTaskData({
|
|
125
|
+
agentId: 'agent-1',
|
|
126
|
+
interactionId: 'interaction-1',
|
|
127
|
+
mediaResourceId: 'main-media',
|
|
128
|
+
interaction: {
|
|
129
|
+
state: 'connected',
|
|
130
|
+
participants: {
|
|
131
|
+
'agent-1': {id: 'agent-1', pType: 'Agent', hasLeft: false},
|
|
132
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false},
|
|
133
|
+
},
|
|
134
|
+
media: {
|
|
135
|
+
'main-media': {
|
|
136
|
+
mediaResourceId: 'main-media',
|
|
137
|
+
mType: 'mainCall',
|
|
138
|
+
participants: ['agent-1', 'customer-1'],
|
|
139
|
+
isHold,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
} as any,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const createPartialMainCallTaskData = (isHold = false, omitSelf = false) =>
|
|
146
|
+
createTaskData({
|
|
147
|
+
agentId: 'agent-1',
|
|
148
|
+
interactionId: 'interaction-1',
|
|
149
|
+
mediaResourceId: 'main-media',
|
|
150
|
+
interaction: {
|
|
151
|
+
state: 'connected',
|
|
152
|
+
participants: omitSelf
|
|
153
|
+
? {'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false}}
|
|
154
|
+
: {
|
|
155
|
+
'agent-1': {id: 'agent-1', pType: 'Agent', hasLeft: false},
|
|
156
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false},
|
|
157
|
+
},
|
|
158
|
+
media: {
|
|
159
|
+
'main-media': {
|
|
160
|
+
mediaResourceId: 'main-media',
|
|
161
|
+
mType: 'mainCall',
|
|
162
|
+
participants: ['customer-1'],
|
|
163
|
+
isHold,
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
} as any,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const startMachineWithEndSpy = () => {
|
|
170
|
+
const emitTaskEnd = jest.fn();
|
|
171
|
+
const service = createActor(
|
|
172
|
+
createTaskStateMachine({...createConfig(), agentId: 'agent-1'}, {actions: {emitTaskEnd}})
|
|
173
|
+
);
|
|
174
|
+
service.start();
|
|
175
|
+
|
|
176
|
+
return {service, emitTaskEnd};
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const enterTransientHoldState = (
|
|
180
|
+
service: ReturnType<typeof startMachine>,
|
|
181
|
+
state: TaskState.HOLD_INITIATING | TaskState.RESUME_INITIATING,
|
|
182
|
+
taskData: ReturnType<typeof createActiveMainCallTaskData>
|
|
183
|
+
) => {
|
|
184
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
185
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
186
|
+
service.send({type: TaskEvent.HOLD_INITIATED, mediaResourceId: 'main-media'});
|
|
187
|
+
|
|
188
|
+
if (state === TaskState.RESUME_INITIATING) {
|
|
189
|
+
service.send({type: TaskEvent.HOLD_SUCCESS, mediaResourceId: 'main-media'});
|
|
190
|
+
service.send({type: TaskEvent.UNHOLD_INITIATED, mediaResourceId: 'main-media'});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
expect(service.getSnapshot().value).toBe(state);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
it('keeps CONNECTED when another participant leave event has a partial mainCall', () => {
|
|
197
|
+
const {service, emitTaskEnd} = startMachineWithEndSpy();
|
|
198
|
+
const taskData = createActiveMainCallTaskData();
|
|
199
|
+
|
|
200
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
201
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
202
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONNECTED);
|
|
203
|
+
|
|
204
|
+
service.send({
|
|
205
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
206
|
+
participantId: 'agent-2',
|
|
207
|
+
taskData: createPartialMainCallTaskData(false, true),
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONNECTED);
|
|
211
|
+
expect(emitTaskEnd).not.toHaveBeenCalled();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('ignores PARTICIPANT_LEAVE in CONNECTED even when it identifies the current agent', () => {
|
|
215
|
+
const {service, emitTaskEnd} = startMachineWithEndSpy();
|
|
216
|
+
const taskData = createActiveMainCallTaskData();
|
|
217
|
+
|
|
218
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
219
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
220
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONNECTED);
|
|
221
|
+
|
|
222
|
+
service.send({
|
|
223
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
224
|
+
participantId: 'agent-1',
|
|
225
|
+
taskData: createPartialMainCallTaskData(false, true),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONNECTED);
|
|
229
|
+
expect(emitTaskEnd).not.toHaveBeenCalled();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('enters WRAPPING_UP on TASK_WRAPUP while HOLD_INITIATING and emits wrap-up once', () => {
|
|
233
|
+
const emitTaskWrapup = jest.fn();
|
|
234
|
+
const service = createActor(
|
|
235
|
+
createTaskStateMachine({...createConfig(), agentId: 'agent-1'}, {actions: {emitTaskWrapup}})
|
|
236
|
+
);
|
|
237
|
+
service.start();
|
|
238
|
+
const taskData = createActiveMainCallTaskData();
|
|
239
|
+
|
|
240
|
+
enterTransientHoldState(service, TaskState.HOLD_INITIATING, taskData);
|
|
241
|
+
service.send({
|
|
242
|
+
type: TaskEvent.TASK_WRAPUP,
|
|
243
|
+
taskData: {...taskData, wrapUpRequired: true},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
247
|
+
expect(emitTaskWrapup).toHaveBeenCalledTimes(1);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('ignores PARTICIPANT_LEAVE in HOLD_INITIATING when a remote Drop identifies the current agent', () => {
|
|
251
|
+
const {service, emitTaskEnd} = startMachineWithEndSpy();
|
|
252
|
+
const taskData = createActiveMainCallTaskData();
|
|
253
|
+
|
|
254
|
+
enterTransientHoldState(service, TaskState.HOLD_INITIATING, taskData);
|
|
255
|
+
service.send({
|
|
256
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
257
|
+
participantId: 'agent-1',
|
|
258
|
+
taskData: createPartialMainCallTaskData(false, true),
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
expect(service.getSnapshot().value).toBe(TaskState.HOLD_INITIATING);
|
|
262
|
+
expect(emitTaskEnd).not.toHaveBeenCalled();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('terminates RESUME_INITIATING when a remote Drop identifies the current agent', () => {
|
|
266
|
+
const {service, emitTaskEnd} = startMachineWithEndSpy();
|
|
267
|
+
const taskData = createActiveMainCallTaskData(true);
|
|
268
|
+
|
|
269
|
+
enterTransientHoldState(service, TaskState.RESUME_INITIATING, taskData);
|
|
270
|
+
service.send({
|
|
271
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
272
|
+
participantId: 'agent-1',
|
|
273
|
+
taskData: createPartialMainCallTaskData(true, true),
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
277
|
+
expect(emitTaskEnd).toHaveBeenCalledTimes(1);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('keeps HOLD_INITIATING when ParticipantLeftConference identifies another participant', () => {
|
|
281
|
+
const {service, emitTaskEnd} = startMachineWithEndSpy();
|
|
282
|
+
const taskData = createActiveMainCallTaskData();
|
|
283
|
+
|
|
284
|
+
enterTransientHoldState(service, TaskState.HOLD_INITIATING, taskData);
|
|
285
|
+
service.send({
|
|
286
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
287
|
+
participantId: 'agent-2',
|
|
288
|
+
taskData: createPartialMainCallTaskData(false, true),
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
expect(service.getSnapshot().value).toBe(TaskState.HOLD_INITIATING);
|
|
292
|
+
expect(emitTaskEnd).not.toHaveBeenCalled();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('terminates RESUME_INITIATING when another participant leave omits self from the roster', () => {
|
|
296
|
+
const {service, emitTaskEnd} = startMachineWithEndSpy();
|
|
297
|
+
const taskData = createActiveMainCallTaskData(true);
|
|
298
|
+
|
|
299
|
+
enterTransientHoldState(service, TaskState.RESUME_INITIATING, taskData);
|
|
300
|
+
service.send({
|
|
301
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
302
|
+
participantId: 'agent-2',
|
|
303
|
+
taskData: createPartialMainCallTaskData(true, true),
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
307
|
+
expect(emitTaskEnd).toHaveBeenCalledTimes(1);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('terminates HELD when another participant leave event omits self from its roster', () => {
|
|
311
|
+
const {service, emitTaskEnd} = startMachineWithEndSpy();
|
|
312
|
+
const taskData = createActiveMainCallTaskData(true);
|
|
313
|
+
|
|
314
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
315
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
316
|
+
service.send({type: TaskEvent.HOLD_INITIATED, mediaResourceId: 'main-media'});
|
|
317
|
+
service.send({type: TaskEvent.HOLD_SUCCESS, mediaResourceId: 'main-media'});
|
|
318
|
+
expect(service.getSnapshot().value).toBe(TaskState.HELD);
|
|
319
|
+
|
|
320
|
+
service.send({
|
|
321
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
322
|
+
participantId: 'agent-2',
|
|
323
|
+
taskData: createPartialMainCallTaskData(true, true),
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
327
|
+
expect(emitTaskEnd).toHaveBeenCalledTimes(1);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('keeps HELD when CONSULT_END has a partial mainCall', () => {
|
|
331
|
+
const {service, emitTaskEnd} = startMachineWithEndSpy();
|
|
332
|
+
const taskData = createActiveMainCallTaskData(true);
|
|
333
|
+
|
|
334
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
335
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
336
|
+
service.send({type: TaskEvent.HOLD_INITIATED, mediaResourceId: 'main-media'});
|
|
337
|
+
service.send({type: TaskEvent.HOLD_SUCCESS, mediaResourceId: 'main-media'});
|
|
338
|
+
expect(service.getSnapshot().value).toBe(TaskState.HELD);
|
|
339
|
+
|
|
340
|
+
service.send({
|
|
341
|
+
type: TaskEvent.CONSULT_END,
|
|
342
|
+
taskData: createPartialMainCallTaskData(true),
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
expect(service.getSnapshot().value).toBe(TaskState.HELD);
|
|
346
|
+
expect(emitTaskEnd).not.toHaveBeenCalled();
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
109
350
|
describe('recording pause/resume events', () => {
|
|
110
351
|
it('toggles recordingPaused flag based on events', () => {
|
|
111
352
|
const service = startMachine();
|
|
@@ -180,6 +421,32 @@ describe('Task state machine', () => {
|
|
|
180
421
|
service.send({type: TaskEvent.WRAPUP_COMPLETE});
|
|
181
422
|
expect(service.getSnapshot().value).toBe(TaskState.COMPLETED);
|
|
182
423
|
});
|
|
424
|
+
|
|
425
|
+
it('emits task wrappedup before cleanup on COMPLETED entry', () => {
|
|
426
|
+
const actionOrder: string[] = [];
|
|
427
|
+
const actor = createActor(
|
|
428
|
+
createTaskStateMachine(createConfig(), {
|
|
429
|
+
actions: {
|
|
430
|
+
emitTaskWrappedup: () => {
|
|
431
|
+
actionOrder.push('emitTaskWrappedup');
|
|
432
|
+
},
|
|
433
|
+
cleanupResources: () => {
|
|
434
|
+
actionOrder.push('cleanupResources');
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
})
|
|
438
|
+
);
|
|
439
|
+
actor.start();
|
|
440
|
+
const taskData = createTaskData();
|
|
441
|
+
|
|
442
|
+
actor.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
443
|
+
actor.send({type: TaskEvent.ASSIGN, taskData});
|
|
444
|
+
actor.send({type: TaskEvent.TASK_WRAPUP});
|
|
445
|
+
actor.send({type: TaskEvent.WRAPUP_COMPLETE});
|
|
446
|
+
|
|
447
|
+
expect(actor.getSnapshot().value).toBe(TaskState.COMPLETED);
|
|
448
|
+
expect(actionOrder).toEqual(['emitTaskWrappedup', 'cleanupResources']);
|
|
449
|
+
});
|
|
183
450
|
});
|
|
184
451
|
|
|
185
452
|
describe('consult and conference flows', () => {
|
|
@@ -343,53 +610,53 @@ describe('Task state machine', () => {
|
|
|
343
610
|
expect(snapshot.context.consultFromConference).toBe(true);
|
|
344
611
|
});
|
|
345
612
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
},
|
|
372
|
-
media: {
|
|
373
|
-
'interaction-1': {
|
|
374
|
-
mediaResourceId: 'interaction-1',
|
|
375
|
-
mType: 'mainCall',
|
|
376
|
-
participants: ['agent-1', 'customer-1'],
|
|
377
|
-
isHold: true,
|
|
378
|
-
},
|
|
379
|
-
'consult-media-1': {
|
|
380
|
-
mediaResourceId: 'consult-media-1',
|
|
381
|
-
mType: 'consult',
|
|
382
|
-
participants: ['agent-1', 'agent-2'],
|
|
383
|
-
isHold: false,
|
|
613
|
+
it('hydrates consulted agent to CONSULTING when self consultState is consulting and main leg is held', () => {
|
|
614
|
+
const service = startMachine();
|
|
615
|
+
const taskData = createTaskData({
|
|
616
|
+
agentId: 'agent-2',
|
|
617
|
+
isConsulted: true,
|
|
618
|
+
interaction: {
|
|
619
|
+
state: 'conference',
|
|
620
|
+
mainInteractionId: 'interaction-1',
|
|
621
|
+
interactionId: 'interaction-1',
|
|
622
|
+
participants: {
|
|
623
|
+
'agent-1': {
|
|
624
|
+
id: 'agent-1',
|
|
625
|
+
pType: 'Agent',
|
|
626
|
+
hasLeft: false,
|
|
627
|
+
consultState: 'consulting',
|
|
628
|
+
isConsulted: false,
|
|
629
|
+
},
|
|
630
|
+
'agent-2': {
|
|
631
|
+
id: 'agent-2',
|
|
632
|
+
pType: 'Agent',
|
|
633
|
+
hasLeft: false,
|
|
634
|
+
consultState: 'consulting',
|
|
635
|
+
isConsulted: true,
|
|
636
|
+
},
|
|
637
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false},
|
|
384
638
|
},
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
639
|
+
media: {
|
|
640
|
+
'interaction-1': {
|
|
641
|
+
mediaResourceId: 'interaction-1',
|
|
642
|
+
mType: 'mainCall',
|
|
643
|
+
participants: ['agent-1', 'customer-1'],
|
|
644
|
+
isHold: true,
|
|
645
|
+
},
|
|
646
|
+
'consult-media-1': {
|
|
647
|
+
mediaResourceId: 'consult-media-1',
|
|
648
|
+
mType: 'consult',
|
|
649
|
+
participants: ['agent-1', 'agent-2'],
|
|
650
|
+
isHold: false,
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
} as any,
|
|
654
|
+
});
|
|
388
655
|
|
|
389
|
-
|
|
656
|
+
service.send({type: TaskEvent.HYDRATE, taskData});
|
|
390
657
|
|
|
391
|
-
|
|
392
|
-
|
|
658
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONSULTING);
|
|
659
|
+
});
|
|
393
660
|
|
|
394
661
|
it('hydrates to CONSULTING when consult is pending (self consultState is consultInitiated)', () => {
|
|
395
662
|
const service = startMachine();
|
|
@@ -1587,13 +1854,25 @@ describe('Task state machine', () => {
|
|
|
1587
1854
|
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
1588
1855
|
});
|
|
1589
1856
|
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1857
|
+
const primeConferencing = (service: ReturnType<typeof startMachine>, taskData: ReturnType<typeof createTaskData>) => {
|
|
1858
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
1859
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
1860
|
+
service.send({
|
|
1861
|
+
type: TaskEvent.CONSULT,
|
|
1862
|
+
destination: 'agent-42',
|
|
1863
|
+
destinationType: 'agent',
|
|
1864
|
+
});
|
|
1865
|
+
service.send({type: TaskEvent.CONSULT_SUCCESS, taskData});
|
|
1866
|
+
service.send({type: TaskEvent.MERGE_TO_CONFERENCE});
|
|
1867
|
+
service.send({type: TaskEvent.CONFERENCE_START, taskData});
|
|
1868
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONFERENCING);
|
|
1869
|
+
};
|
|
1870
|
+
|
|
1871
|
+
const conferenceInProgressTaskData = (overrides: Parameters<typeof createTaskData>[0] = {}) =>
|
|
1872
|
+
createTaskData({
|
|
1873
|
+
wrapUpRequired: true,
|
|
1595
1874
|
interaction: {
|
|
1596
|
-
owner: '
|
|
1875
|
+
owner: 'agent-1',
|
|
1597
1876
|
state: 'conference',
|
|
1598
1877
|
mainInteractionId: 'interaction-1',
|
|
1599
1878
|
interactionId: 'interaction-1',
|
|
@@ -1604,54 +1883,485 @@ describe('Task state machine', () => {
|
|
|
1604
1883
|
type: 'Agent',
|
|
1605
1884
|
hasJoined: true,
|
|
1606
1885
|
hasLeft: false,
|
|
1607
|
-
isInPredial: false,
|
|
1608
|
-
isWrapUp: false,
|
|
1609
1886
|
},
|
|
1610
|
-
|
|
1611
|
-
id: '
|
|
1612
|
-
pType: '
|
|
1613
|
-
type: '
|
|
1887
|
+
'agent-2': {
|
|
1888
|
+
id: 'agent-2',
|
|
1889
|
+
pType: 'Agent',
|
|
1890
|
+
type: 'Agent',
|
|
1614
1891
|
hasJoined: true,
|
|
1615
1892
|
hasLeft: false,
|
|
1616
|
-
isInPredial: false,
|
|
1617
1893
|
},
|
|
1618
1894
|
},
|
|
1619
1895
|
media: {
|
|
1620
1896
|
'interaction-1': {
|
|
1621
1897
|
mediaResourceId: 'interaction-1',
|
|
1622
1898
|
mediaType: 'telephony',
|
|
1623
|
-
|
|
1624
|
-
participants: ['agent-1', 'c1'],
|
|
1899
|
+
participants: ['agent-1', 'agent-2'],
|
|
1625
1900
|
mType: 'mainCall',
|
|
1626
1901
|
isHold: false,
|
|
1627
|
-
holdTimestamp: null,
|
|
1628
1902
|
},
|
|
1629
1903
|
},
|
|
1630
1904
|
} as any,
|
|
1905
|
+
...overrides,
|
|
1631
1906
|
});
|
|
1632
1907
|
|
|
1908
|
+
const primeConnectedWithConferenceTaskData = (
|
|
1909
|
+
service: ReturnType<typeof startMachine>,
|
|
1910
|
+
taskData: ReturnType<typeof createTaskData>
|
|
1911
|
+
) => {
|
|
1633
1912
|
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
1634
1913
|
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
1635
|
-
service.
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
service.send({type: TaskEvent.MERGE_TO_CONFERENCE});
|
|
1642
|
-
service.send({type: TaskEvent.CONFERENCE_START, taskData});
|
|
1643
|
-
expect(service.getSnapshot().value).toBe(TaskState.CONFERENCING);
|
|
1914
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONNECTED);
|
|
1915
|
+
};
|
|
1916
|
+
|
|
1917
|
+
it('enters WRAPPING_UP on EXIT_CONFERENCE_SUCCESS while CONNECTED with conference in task data', () => {
|
|
1918
|
+
const service = startMachine();
|
|
1919
|
+
const taskData = conferenceInProgressTaskData();
|
|
1644
1920
|
|
|
1921
|
+
primeConnectedWithConferenceTaskData(service, taskData);
|
|
1645
1922
|
service.send({type: TaskEvent.EXIT_CONFERENCE_SUCCESS, taskData});
|
|
1646
|
-
|
|
1923
|
+
|
|
1924
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
1925
|
+
expect(service.getSnapshot().context.taskData?.wrapUpRequired).toBe(true);
|
|
1647
1926
|
});
|
|
1648
1927
|
|
|
1649
|
-
it('
|
|
1650
|
-
const
|
|
1651
|
-
const
|
|
1652
|
-
|
|
1928
|
+
it('emits wrap-up once on EXIT_CONFERENCE_SUCCESS while CONNECTED', () => {
|
|
1929
|
+
const emitTaskWrapup = jest.fn();
|
|
1930
|
+
const service = createActor(
|
|
1931
|
+
createTaskStateMachine(createConfig(), {actions: {emitTaskWrapup}})
|
|
1932
|
+
);
|
|
1933
|
+
service.start();
|
|
1934
|
+
const taskData = conferenceInProgressTaskData();
|
|
1653
1935
|
|
|
1654
|
-
service
|
|
1936
|
+
primeConnectedWithConferenceTaskData(service, taskData);
|
|
1937
|
+
service.send({type: TaskEvent.EXIT_CONFERENCE_SUCCESS, taskData});
|
|
1938
|
+
|
|
1939
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
1940
|
+
expect(emitTaskWrapup).toHaveBeenCalledTimes(1);
|
|
1941
|
+
});
|
|
1942
|
+
|
|
1943
|
+
it('terminates on EXIT_CONFERENCE_SUCCESS while CONNECTED when wrap-up is not required', () => {
|
|
1944
|
+
const service = startMachine();
|
|
1945
|
+
const taskData = conferenceInProgressTaskData({
|
|
1946
|
+
wrapUpRequired: false,
|
|
1947
|
+
interaction: {
|
|
1948
|
+
owner: 'other-agent',
|
|
1949
|
+
state: 'conference',
|
|
1950
|
+
mainInteractionId: 'interaction-1',
|
|
1951
|
+
interactionId: 'interaction-1',
|
|
1952
|
+
participants: {
|
|
1953
|
+
'agent-1': {
|
|
1954
|
+
id: 'agent-1',
|
|
1955
|
+
pType: 'Agent',
|
|
1956
|
+
type: 'Agent',
|
|
1957
|
+
hasJoined: true,
|
|
1958
|
+
hasLeft: false,
|
|
1959
|
+
isWrapUp: false,
|
|
1960
|
+
},
|
|
1961
|
+
'agent-2': {
|
|
1962
|
+
id: 'agent-2',
|
|
1963
|
+
pType: 'Agent',
|
|
1964
|
+
type: 'Agent',
|
|
1965
|
+
hasJoined: true,
|
|
1966
|
+
hasLeft: false,
|
|
1967
|
+
},
|
|
1968
|
+
},
|
|
1969
|
+
media: {
|
|
1970
|
+
'interaction-1': {
|
|
1971
|
+
mediaResourceId: 'interaction-1',
|
|
1972
|
+
mediaType: 'telephony',
|
|
1973
|
+
participants: ['agent-1', 'agent-2'],
|
|
1974
|
+
mType: 'mainCall',
|
|
1975
|
+
isHold: false,
|
|
1976
|
+
},
|
|
1977
|
+
},
|
|
1978
|
+
} as any,
|
|
1979
|
+
});
|
|
1980
|
+
|
|
1981
|
+
primeConnectedWithConferenceTaskData(service, taskData);
|
|
1982
|
+
service.send({type: TaskEvent.EXIT_CONFERENCE_SUCCESS, taskData});
|
|
1983
|
+
|
|
1984
|
+
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
1985
|
+
});
|
|
1986
|
+
|
|
1987
|
+
it('does not re-emit wrap-up on late TASK_WRAPUP when wrap-up was already published', () => {
|
|
1988
|
+
const {emitTaskWrapup, publishWrapup} = createPublishAwareEmitTaskWrapupMock();
|
|
1989
|
+
const service = createActor(
|
|
1990
|
+
createTaskStateMachine(createConfig(), {actions: {emitTaskWrapup}})
|
|
1991
|
+
);
|
|
1992
|
+
service.start();
|
|
1993
|
+
const taskData = conferenceInProgressTaskData();
|
|
1994
|
+
|
|
1995
|
+
primeConferencing(service, taskData);
|
|
1996
|
+
service.send({
|
|
1997
|
+
type: TaskEvent.EXIT_CONFERENCE_SUCCESS,
|
|
1998
|
+
taskData: {...taskData, wrapUpRequired: true},
|
|
1999
|
+
});
|
|
2000
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
2001
|
+
expect(service.getSnapshot().context.taskData?.wrapUpRequired).toBe(true);
|
|
2002
|
+
|
|
2003
|
+
const emitCountAfterExit = publishWrapup.mock.calls.length;
|
|
2004
|
+
service.send({
|
|
2005
|
+
type: TaskEvent.TASK_WRAPUP,
|
|
2006
|
+
taskData: {...taskData, wrapUpRequired: true},
|
|
2007
|
+
});
|
|
2008
|
+
|
|
2009
|
+
expect(publishWrapup.mock.calls.length).toBe(emitCountAfterExit);
|
|
2010
|
+
});
|
|
2011
|
+
|
|
2012
|
+
it('emits wrap-up on late TASK_WRAPUP when wrap-up was not yet published', () => {
|
|
2013
|
+
const {emitTaskWrapup, publishWrapup} = createPublishAwareEmitTaskWrapupMock();
|
|
2014
|
+
const service = createActor(
|
|
2015
|
+
createTaskStateMachine(createConfig(), {actions: {emitTaskWrapup}})
|
|
2016
|
+
);
|
|
2017
|
+
service.start();
|
|
2018
|
+
const taskData = createTaskData({
|
|
2019
|
+
wrapUpRequired: false,
|
|
2020
|
+
interaction: {
|
|
2021
|
+
owner: 'agent-1',
|
|
2022
|
+
state: 'connected',
|
|
2023
|
+
mainInteractionId: 'interaction-1',
|
|
2024
|
+
interactionId: 'interaction-1',
|
|
2025
|
+
participants: {
|
|
2026
|
+
'agent-1': {
|
|
2027
|
+
id: 'agent-1',
|
|
2028
|
+
pType: 'Agent',
|
|
2029
|
+
type: 'Agent',
|
|
2030
|
+
hasJoined: true,
|
|
2031
|
+
hasLeft: false,
|
|
2032
|
+
},
|
|
2033
|
+
},
|
|
2034
|
+
} as any,
|
|
2035
|
+
});
|
|
2036
|
+
|
|
2037
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
2038
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
2039
|
+
service.send({type: TaskEvent.TASK_WRAPUP, taskData});
|
|
2040
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
2041
|
+
expect(service.getSnapshot().context.taskData?.wrapUpRequired).not.toBe(true);
|
|
2042
|
+
expect(publishWrapup).not.toHaveBeenCalled();
|
|
2043
|
+
|
|
2044
|
+
service.send({
|
|
2045
|
+
type: TaskEvent.TASK_WRAPUP,
|
|
2046
|
+
taskData: {...taskData, wrapUpRequired: true},
|
|
2047
|
+
});
|
|
2048
|
+
|
|
2049
|
+
expect(publishWrapup).toHaveBeenCalledTimes(1);
|
|
2050
|
+
});
|
|
2051
|
+
|
|
2052
|
+
it('terminates when EXIT_CONFERENCE_SUCCESS is received in conferencing and wrapup is not required', () => {
|
|
2053
|
+
const service = startMachine();
|
|
2054
|
+
const taskData = createTaskData({
|
|
2055
|
+
isConsulted: true,
|
|
2056
|
+
wrapUpRequired: false,
|
|
2057
|
+
interaction: {
|
|
2058
|
+
owner: 'other-agent',
|
|
2059
|
+
state: 'conference',
|
|
2060
|
+
mainInteractionId: 'interaction-1',
|
|
2061
|
+
interactionId: 'interaction-1',
|
|
2062
|
+
participants: {
|
|
2063
|
+
'agent-1': {
|
|
2064
|
+
id: 'agent-1',
|
|
2065
|
+
pType: 'Agent',
|
|
2066
|
+
type: 'Agent',
|
|
2067
|
+
hasJoined: true,
|
|
2068
|
+
hasLeft: false,
|
|
2069
|
+
isInPredial: false,
|
|
2070
|
+
isWrapUp: false,
|
|
2071
|
+
},
|
|
2072
|
+
c1: {
|
|
2073
|
+
id: 'c1',
|
|
2074
|
+
pType: 'Customer',
|
|
2075
|
+
type: 'Customer',
|
|
2076
|
+
hasJoined: true,
|
|
2077
|
+
hasLeft: false,
|
|
2078
|
+
isInPredial: false,
|
|
2079
|
+
},
|
|
2080
|
+
},
|
|
2081
|
+
media: {
|
|
2082
|
+
'interaction-1': {
|
|
2083
|
+
mediaResourceId: 'interaction-1',
|
|
2084
|
+
mediaType: 'telephony',
|
|
2085
|
+
mediaMgr: 'mm',
|
|
2086
|
+
participants: ['agent-1', 'c1'],
|
|
2087
|
+
mType: 'mainCall',
|
|
2088
|
+
isHold: false,
|
|
2089
|
+
holdTimestamp: null,
|
|
2090
|
+
},
|
|
2091
|
+
},
|
|
2092
|
+
} as any,
|
|
2093
|
+
});
|
|
2094
|
+
|
|
2095
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
2096
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
2097
|
+
service.send({
|
|
2098
|
+
type: TaskEvent.CONSULT,
|
|
2099
|
+
destination: 'agent-42',
|
|
2100
|
+
destinationType: 'agent',
|
|
2101
|
+
});
|
|
2102
|
+
service.send({type: TaskEvent.CONSULT_SUCCESS, taskData});
|
|
2103
|
+
service.send({type: TaskEvent.MERGE_TO_CONFERENCE});
|
|
2104
|
+
service.send({type: TaskEvent.CONFERENCE_START, taskData});
|
|
2105
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONFERENCING);
|
|
2106
|
+
|
|
2107
|
+
service.send({type: TaskEvent.EXIT_CONFERENCE_SUCCESS, taskData});
|
|
2108
|
+
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
2109
|
+
});
|
|
2110
|
+
|
|
2111
|
+
it('enters WRAPPING_UP on CONFERENCE_END when wrapUpRequired is stamped', () => {
|
|
2112
|
+
const service = startMachine();
|
|
2113
|
+
const taskData = createTaskData({
|
|
2114
|
+
wrapUpRequired: true,
|
|
2115
|
+
interaction: {
|
|
2116
|
+
owner: 'agent-1',
|
|
2117
|
+
state: 'conference',
|
|
2118
|
+
mainInteractionId: 'interaction-1',
|
|
2119
|
+
interactionId: 'interaction-1',
|
|
2120
|
+
participants: {
|
|
2121
|
+
'agent-1': {
|
|
2122
|
+
id: 'agent-1',
|
|
2123
|
+
pType: 'Agent',
|
|
2124
|
+
type: 'Agent',
|
|
2125
|
+
isWrapUp: true,
|
|
2126
|
+
hasJoined: true,
|
|
2127
|
+
hasLeft: true,
|
|
2128
|
+
},
|
|
2129
|
+
},
|
|
2130
|
+
} as any,
|
|
2131
|
+
});
|
|
2132
|
+
|
|
2133
|
+
primeConferencing(service, taskData);
|
|
2134
|
+
service.send({type: TaskEvent.CONFERENCE_END, taskData});
|
|
2135
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
2136
|
+
});
|
|
2137
|
+
|
|
2138
|
+
it('emits wrap-up once on CONFERENCE_END when wrap-up is required', () => {
|
|
2139
|
+
const {emitTaskWrapup, publishWrapup} = createPublishAwareEmitTaskWrapupMock();
|
|
2140
|
+
const service = createActor(
|
|
2141
|
+
createTaskStateMachine(createConfig(), {actions: {emitTaskWrapup}})
|
|
2142
|
+
);
|
|
2143
|
+
service.start();
|
|
2144
|
+
const taskData = createTaskData({
|
|
2145
|
+
wrapUpRequired: true,
|
|
2146
|
+
interaction: {
|
|
2147
|
+
owner: 'agent-1',
|
|
2148
|
+
state: 'conference',
|
|
2149
|
+
mainInteractionId: 'interaction-1',
|
|
2150
|
+
interactionId: 'interaction-1',
|
|
2151
|
+
participants: {
|
|
2152
|
+
'agent-1': {
|
|
2153
|
+
id: 'agent-1',
|
|
2154
|
+
pType: 'Agent',
|
|
2155
|
+
type: 'Agent',
|
|
2156
|
+
isWrapUp: true,
|
|
2157
|
+
hasJoined: true,
|
|
2158
|
+
hasLeft: true,
|
|
2159
|
+
},
|
|
2160
|
+
},
|
|
2161
|
+
} as any,
|
|
2162
|
+
});
|
|
2163
|
+
|
|
2164
|
+
primeConferencing(service, taskData);
|
|
2165
|
+
service.send({type: TaskEvent.CONFERENCE_END, taskData});
|
|
2166
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
2167
|
+
expect(publishWrapup).toHaveBeenCalledTimes(1);
|
|
2168
|
+
});
|
|
2169
|
+
|
|
2170
|
+
it('emits wrap-up once on TASK_WRAPUP from CONSULTING when wrap-up is required', () => {
|
|
2171
|
+
const {emitTaskWrapup, publishWrapup} = createPublishAwareEmitTaskWrapupMock();
|
|
2172
|
+
const service = createActor(
|
|
2173
|
+
createTaskStateMachine(createConfig(), {actions: {emitTaskWrapup}})
|
|
2174
|
+
);
|
|
2175
|
+
service.start();
|
|
2176
|
+
const taskData = createTaskData({
|
|
2177
|
+
interaction: {owner: 'agent-1', state: 'connected'} as any,
|
|
2178
|
+
});
|
|
2179
|
+
|
|
2180
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
2181
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
2182
|
+
service.send({type: TaskEvent.CONSULT, destination: 'agent-2', destinationType: 'agent'});
|
|
2183
|
+
service.send({type: TaskEvent.CONSULT_SUCCESS, taskData});
|
|
2184
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONSULTING);
|
|
2185
|
+
|
|
2186
|
+
service.send({
|
|
2187
|
+
type: TaskEvent.TASK_WRAPUP,
|
|
2188
|
+
taskData: {...taskData, wrapUpRequired: true},
|
|
2189
|
+
});
|
|
2190
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
2191
|
+
expect(publishWrapup).toHaveBeenCalledTimes(1);
|
|
2192
|
+
});
|
|
2193
|
+
|
|
2194
|
+
it('emits wrap-up once on TASK_WRAPUP from CONFERENCING when wrap-up is required', () => {
|
|
2195
|
+
const {emitTaskWrapup, publishWrapup} = createPublishAwareEmitTaskWrapupMock();
|
|
2196
|
+
const service = createActor(
|
|
2197
|
+
createTaskStateMachine(createConfig(), {actions: {emitTaskWrapup}})
|
|
2198
|
+
);
|
|
2199
|
+
service.start();
|
|
2200
|
+
const taskData = createTaskData({
|
|
2201
|
+
interaction: {
|
|
2202
|
+
owner: 'agent-1',
|
|
2203
|
+
state: 'conference',
|
|
2204
|
+
mainInteractionId: 'interaction-1',
|
|
2205
|
+
interactionId: 'interaction-1',
|
|
2206
|
+
} as any,
|
|
2207
|
+
});
|
|
2208
|
+
|
|
2209
|
+
primeConferencing(service, taskData);
|
|
2210
|
+
service.send({
|
|
2211
|
+
type: TaskEvent.TASK_WRAPUP,
|
|
2212
|
+
taskData: {...taskData, wrapUpRequired: true},
|
|
2213
|
+
});
|
|
2214
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
2215
|
+
expect(publishWrapup).toHaveBeenCalledTimes(1);
|
|
2216
|
+
});
|
|
2217
|
+
|
|
2218
|
+
it('enters WRAPPING_UP on PARTICIPANT_LEAVE when this agent left and wrap-up is required', () => {
|
|
2219
|
+
const service = startMachine();
|
|
2220
|
+
const conferenceData = createTaskData({
|
|
2221
|
+
interaction: {
|
|
2222
|
+
owner: 'agent-1',
|
|
2223
|
+
state: 'conference',
|
|
2224
|
+
mainInteractionId: 'interaction-1',
|
|
2225
|
+
interactionId: 'interaction-1',
|
|
2226
|
+
participants: {
|
|
2227
|
+
'agent-1': {id: 'agent-1', pType: 'Agent', type: 'Agent', hasJoined: true, hasLeft: false},
|
|
2228
|
+
'agent-2': {id: 'agent-2', pType: 'Agent', type: 'Agent', hasJoined: true, hasLeft: false},
|
|
2229
|
+
},
|
|
2230
|
+
} as any,
|
|
2231
|
+
});
|
|
2232
|
+
const leaveData = createTaskData({
|
|
2233
|
+
wrapUpRequired: true,
|
|
2234
|
+
participantId: 'agent-1',
|
|
2235
|
+
interaction: {
|
|
2236
|
+
owner: 'agent-1',
|
|
2237
|
+
state: 'conference',
|
|
2238
|
+
mainInteractionId: 'interaction-1',
|
|
2239
|
+
interactionId: 'interaction-1',
|
|
2240
|
+
participants: {
|
|
2241
|
+
'agent-2': {id: 'agent-2', pType: 'Agent', type: 'Agent', hasJoined: true, hasLeft: false},
|
|
2242
|
+
},
|
|
2243
|
+
} as any,
|
|
2244
|
+
});
|
|
2245
|
+
|
|
2246
|
+
primeConferencing(service, conferenceData);
|
|
2247
|
+
service.send({
|
|
2248
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
2249
|
+
participantId: 'agent-1',
|
|
2250
|
+
taskData: leaveData,
|
|
2251
|
+
});
|
|
2252
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
2253
|
+
});
|
|
2254
|
+
|
|
2255
|
+
it('emits wrap-up once on PARTICIPANT_LEAVE when wrap-up is required', () => {
|
|
2256
|
+
const emitTaskWrapup = jest.fn();
|
|
2257
|
+
const service = createActor(
|
|
2258
|
+
createTaskStateMachine(createConfig(), {actions: {emitTaskWrapup}})
|
|
2259
|
+
);
|
|
2260
|
+
service.start();
|
|
2261
|
+
const conferenceData = createTaskData({
|
|
2262
|
+
interaction: {
|
|
2263
|
+
owner: 'agent-1',
|
|
2264
|
+
state: 'conference',
|
|
2265
|
+
mainInteractionId: 'interaction-1',
|
|
2266
|
+
interactionId: 'interaction-1',
|
|
2267
|
+
participants: {
|
|
2268
|
+
'agent-1': {id: 'agent-1', pType: 'Agent', type: 'Agent', hasJoined: true, hasLeft: false},
|
|
2269
|
+
'agent-2': {id: 'agent-2', pType: 'Agent', type: 'Agent', hasJoined: true, hasLeft: false},
|
|
2270
|
+
},
|
|
2271
|
+
} as any,
|
|
2272
|
+
});
|
|
2273
|
+
const leaveData = createTaskData({
|
|
2274
|
+
wrapUpRequired: true,
|
|
2275
|
+
participantId: 'agent-1',
|
|
2276
|
+
interaction: {
|
|
2277
|
+
owner: 'agent-1',
|
|
2278
|
+
state: 'conference',
|
|
2279
|
+
mainInteractionId: 'interaction-1',
|
|
2280
|
+
interactionId: 'interaction-1',
|
|
2281
|
+
participants: {
|
|
2282
|
+
'agent-2': {id: 'agent-2', pType: 'Agent', type: 'Agent', hasJoined: true, hasLeft: false},
|
|
2283
|
+
},
|
|
2284
|
+
} as any,
|
|
2285
|
+
});
|
|
2286
|
+
|
|
2287
|
+
primeConferencing(service, conferenceData);
|
|
2288
|
+
service.send({
|
|
2289
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
2290
|
+
participantId: 'agent-1',
|
|
2291
|
+
taskData: leaveData,
|
|
2292
|
+
});
|
|
2293
|
+
|
|
2294
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
2295
|
+
expect(emitTaskWrapup).toHaveBeenCalledTimes(1);
|
|
2296
|
+
});
|
|
2297
|
+
|
|
2298
|
+
it('does not wrap remaining agent on PARTICIPANT_LEAVE', () => {
|
|
2299
|
+
const service = startMachine();
|
|
2300
|
+
const conferenceData = createTaskData({
|
|
2301
|
+
interaction: {
|
|
2302
|
+
owner: 'agent-1',
|
|
2303
|
+
state: 'conference',
|
|
2304
|
+
mainInteractionId: 'interaction-1',
|
|
2305
|
+
interactionId: 'interaction-1',
|
|
2306
|
+
participants: {
|
|
2307
|
+
'agent-1': {id: 'agent-1', pType: 'Agent', type: 'Agent', hasJoined: true, hasLeft: false},
|
|
2308
|
+
'agent-2': {id: 'agent-2', pType: 'Agent', type: 'Agent', hasJoined: true, hasLeft: false},
|
|
2309
|
+
c1: {id: 'c1', pType: 'Customer', type: 'Customer', hasJoined: true, hasLeft: false},
|
|
2310
|
+
},
|
|
2311
|
+
media: {
|
|
2312
|
+
'interaction-1': {
|
|
2313
|
+
mediaResourceId: 'interaction-1',
|
|
2314
|
+
mediaType: 'telephony',
|
|
2315
|
+
mediaMgr: 'mm',
|
|
2316
|
+
participants: ['agent-1', 'agent-2', 'c1'],
|
|
2317
|
+
mType: 'mainCall',
|
|
2318
|
+
isHold: false,
|
|
2319
|
+
holdTimestamp: null,
|
|
2320
|
+
},
|
|
2321
|
+
},
|
|
2322
|
+
} as any,
|
|
2323
|
+
});
|
|
2324
|
+
const otherLeft = createTaskData({
|
|
2325
|
+
wrapUpRequired: false,
|
|
2326
|
+
participantId: 'agent-2',
|
|
2327
|
+
interaction: {
|
|
2328
|
+
owner: 'agent-1',
|
|
2329
|
+
state: 'conference',
|
|
2330
|
+
mainInteractionId: 'interaction-1',
|
|
2331
|
+
interactionId: 'interaction-1',
|
|
2332
|
+
participants: {
|
|
2333
|
+
'agent-1': {id: 'agent-1', pType: 'Agent', type: 'Agent', hasJoined: true, hasLeft: false},
|
|
2334
|
+
c1: {id: 'c1', pType: 'Customer', type: 'Customer', hasJoined: true, hasLeft: false},
|
|
2335
|
+
},
|
|
2336
|
+
media: {
|
|
2337
|
+
'interaction-1': {
|
|
2338
|
+
mediaResourceId: 'interaction-1',
|
|
2339
|
+
mediaType: 'telephony',
|
|
2340
|
+
mediaMgr: 'mm',
|
|
2341
|
+
participants: ['agent-1', 'c1'],
|
|
2342
|
+
mType: 'mainCall',
|
|
2343
|
+
isHold: false,
|
|
2344
|
+
holdTimestamp: null,
|
|
2345
|
+
},
|
|
2346
|
+
},
|
|
2347
|
+
} as any,
|
|
2348
|
+
});
|
|
2349
|
+
|
|
2350
|
+
primeConferencing(service, conferenceData);
|
|
2351
|
+
service.send({
|
|
2352
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
2353
|
+
participantId: 'agent-2',
|
|
2354
|
+
taskData: otherLeft,
|
|
2355
|
+
});
|
|
2356
|
+
expect(service.getSnapshot().value).not.toBe(TaskState.WRAPPING_UP);
|
|
2357
|
+
});
|
|
2358
|
+
|
|
2359
|
+
it('downgrades to HELD on HOLD_SUCCESS when no other agents remain on task', () => {
|
|
2360
|
+
const service = startMachine();
|
|
2361
|
+
const conferenceTaskData = createSingleAgentConferenceTaskData('conference');
|
|
2362
|
+
const heldTaskData = createSingleAgentConferenceTaskData('hold', true);
|
|
2363
|
+
|
|
2364
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData: conferenceTaskData});
|
|
1655
2365
|
service.send({type: TaskEvent.ASSIGN, taskData: conferenceTaskData});
|
|
1656
2366
|
service.send({type: TaskEvent.CONFERENCE_START, taskData: conferenceTaskData});
|
|
1657
2367
|
expect(service.getSnapshot().value).toBe(TaskState.CONFERENCING);
|
|
@@ -1684,6 +2394,47 @@ describe('Task state machine', () => {
|
|
|
1684
2394
|
expect(service.getSnapshot().value).toBe(TaskState.CONNECTED);
|
|
1685
2395
|
});
|
|
1686
2396
|
|
|
2397
|
+
it('downgrades to CONNECTED on PARTICIPANT_LEAVE when the current agent remains', () => {
|
|
2398
|
+
const service = startMachine();
|
|
2399
|
+
const conferenceTaskData = createSingleAgentConferenceTaskData('conference');
|
|
2400
|
+
const connectedTaskData = createSingleAgentConferenceTaskData('connected');
|
|
2401
|
+
|
|
2402
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData: conferenceTaskData});
|
|
2403
|
+
service.send({type: TaskEvent.ASSIGN, taskData: conferenceTaskData});
|
|
2404
|
+
service.send({type: TaskEvent.CONFERENCE_START, taskData: conferenceTaskData});
|
|
2405
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONFERENCING);
|
|
2406
|
+
|
|
2407
|
+
service.send({
|
|
2408
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
2409
|
+
participantId: 'agent-2',
|
|
2410
|
+
taskData: connectedTaskData,
|
|
2411
|
+
});
|
|
2412
|
+
|
|
2413
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONNECTED);
|
|
2414
|
+
});
|
|
2415
|
+
|
|
2416
|
+
it('terminates instead of downgrading when PARTICIPANT_LEAVE identifies the current agent', () => {
|
|
2417
|
+
const service = startMachine();
|
|
2418
|
+
const conferenceTaskData = createSingleAgentConferenceTaskData('conference');
|
|
2419
|
+
const connectedTaskData = {
|
|
2420
|
+
...createSingleAgentConferenceTaskData('connected'),
|
|
2421
|
+
wrapUpRequired: false,
|
|
2422
|
+
};
|
|
2423
|
+
|
|
2424
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData: conferenceTaskData});
|
|
2425
|
+
service.send({type: TaskEvent.ASSIGN, taskData: conferenceTaskData});
|
|
2426
|
+
service.send({type: TaskEvent.CONFERENCE_START, taskData: conferenceTaskData});
|
|
2427
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONFERENCING);
|
|
2428
|
+
|
|
2429
|
+
service.send({
|
|
2430
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
2431
|
+
participantId: 'agent-1',
|
|
2432
|
+
taskData: connectedTaskData,
|
|
2433
|
+
});
|
|
2434
|
+
|
|
2435
|
+
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
2436
|
+
});
|
|
2437
|
+
|
|
1687
2438
|
it('returns to CONNECTED when CTQ cancel arrives before queue connects', () => {
|
|
1688
2439
|
const service = startMachine();
|
|
1689
2440
|
const taskData = createTaskData();
|
|
@@ -1798,13 +2549,21 @@ describe('Task state machine', () => {
|
|
|
1798
2549
|
owner: 'agent-1',
|
|
1799
2550
|
} as any,
|
|
1800
2551
|
});
|
|
1801
|
-
service.send({type: TaskEvent.CONSULT_END, taskData: terminatedTaskData});
|
|
1802
|
-
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
1803
|
-
});
|
|
1804
|
-
|
|
1805
|
-
it('transitions to CONNECTED on CONSULT_END without isTerminated during CONF_INITIATING', () => {
|
|
1806
|
-
const service = startMachine();
|
|
1807
|
-
const taskData = createTaskData({
|
|
2552
|
+
service.send({type: TaskEvent.CONSULT_END, taskData: terminatedTaskData});
|
|
2553
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
2554
|
+
});
|
|
2555
|
+
|
|
2556
|
+
it('transitions to CONNECTED on CONSULT_END without isTerminated during CONF_INITIATING', () => {
|
|
2557
|
+
const service = startMachine();
|
|
2558
|
+
const taskData = createTaskData({
|
|
2559
|
+
consultingAgentId: 'agent-1',
|
|
2560
|
+
interaction: {
|
|
2561
|
+
participants: {
|
|
2562
|
+
'agent-1': {id: 'agent-1', pType: 'Agent', hasLeft: false},
|
|
2563
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false},
|
|
2564
|
+
},
|
|
2565
|
+
} as any,
|
|
2566
|
+
});
|
|
1808
2567
|
|
|
1809
2568
|
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
1810
2569
|
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
@@ -1820,10 +2579,8 @@ describe('Task state machine', () => {
|
|
|
1820
2579
|
service.send({type: TaskEvent.CONSULT_END, taskData});
|
|
1821
2580
|
expect(service.getSnapshot().value).toBe(TaskState.CONNECTED);
|
|
1822
2581
|
});
|
|
1823
|
-
|
|
1824
2582
|
});
|
|
1825
2583
|
|
|
1826
|
-
|
|
1827
2584
|
describe('CONFERENCING state CONSULT_FAILED ANOTHER_CONSULT_IN_PROGRESS (CAI-8329)', () => {
|
|
1828
2585
|
it('keeps hideBlindTransfer flag and hides transfer after secondary consult rejected from Agent Desktop', () => {
|
|
1829
2586
|
const service = startMachine();
|
|
@@ -1848,7 +2605,12 @@ describe('Task state machine', () => {
|
|
|
1848
2605
|
consultState: 'conferencing',
|
|
1849
2606
|
},
|
|
1850
2607
|
'+13159998059': {id: '+13159998059', pType: 'EP-DN', hasLeft: false},
|
|
1851
|
-
'+14696762938': {
|
|
2608
|
+
'+14696762938': {
|
|
2609
|
+
id: '+14696762938',
|
|
2610
|
+
pType: 'Customer',
|
|
2611
|
+
hasJoined: true,
|
|
2612
|
+
hasLeft: false,
|
|
2613
|
+
},
|
|
1852
2614
|
},
|
|
1853
2615
|
media: {
|
|
1854
2616
|
'interaction-1': {
|
|
@@ -1902,7 +2664,12 @@ describe('Task state machine', () => {
|
|
|
1902
2664
|
consultState: 'conferencing',
|
|
1903
2665
|
},
|
|
1904
2666
|
'+13159998059': {id: '+13159998059', pType: 'EP-DN', hasLeft: false},
|
|
1905
|
-
'+14696762938': {
|
|
2667
|
+
'+14696762938': {
|
|
2668
|
+
id: '+14696762938',
|
|
2669
|
+
pType: 'Customer',
|
|
2670
|
+
hasJoined: true,
|
|
2671
|
+
hasLeft: false,
|
|
2672
|
+
},
|
|
1906
2673
|
},
|
|
1907
2674
|
media: {
|
|
1908
2675
|
'interaction-1': {
|
|
@@ -1920,12 +2687,19 @@ describe('Task state machine', () => {
|
|
|
1920
2687
|
},
|
|
1921
2688
|
} as any,
|
|
1922
2689
|
});
|
|
1923
|
-
service.send({
|
|
2690
|
+
service.send({
|
|
2691
|
+
type: TaskEvent.CONSULT_FAILED,
|
|
2692
|
+
taskData: consultFailedTaskData,
|
|
2693
|
+
reason: 'ANOTHER_CONSULT_IN_PROGRESS',
|
|
2694
|
+
});
|
|
1924
2695
|
|
|
1925
2696
|
const snapshot = service.getSnapshot();
|
|
1926
2697
|
expect(snapshot.value).toBe(TaskState.CONFERENCING);
|
|
1927
2698
|
expect(snapshot.context.hideBlindTransferForEpDnPendingMerge).toBe(true);
|
|
1928
|
-
expect(snapshot.context.uiControls.main.transfer).toEqual({
|
|
2699
|
+
expect(snapshot.context.uiControls.main.transfer).toEqual({
|
|
2700
|
+
isVisible: false,
|
|
2701
|
+
isEnabled: false,
|
|
2702
|
+
});
|
|
1929
2703
|
expect(snapshot.context.uiControls.main.consult).toEqual({isVisible: true, isEnabled: false});
|
|
1930
2704
|
});
|
|
1931
2705
|
});
|
|
@@ -2126,6 +2900,72 @@ describe('Task state machine', () => {
|
|
|
2126
2900
|
service.send({type: TaskEvent.CONSULT_FAILED, taskData});
|
|
2127
2901
|
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
2128
2902
|
});
|
|
2903
|
+
|
|
2904
|
+
it('emits only consult-end when an unaccepted consulted offer is ended', () => {
|
|
2905
|
+
const emitTaskConsultEnd = jest.fn();
|
|
2906
|
+
const emitTaskEnd = jest.fn();
|
|
2907
|
+
const service = createActor(
|
|
2908
|
+
createTaskStateMachine(createConfig(), {
|
|
2909
|
+
actions: {emitTaskConsultEnd, emitTaskEnd},
|
|
2910
|
+
})
|
|
2911
|
+
);
|
|
2912
|
+
service.start();
|
|
2913
|
+
const taskData = createTaskData({isConsulted: true, consultingAgentId: 'agent-1'});
|
|
2914
|
+
|
|
2915
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
2916
|
+
service.send({type: TaskEvent.CONSULT_END, taskData});
|
|
2917
|
+
|
|
2918
|
+
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
2919
|
+
expect(emitTaskConsultEnd).toHaveBeenCalledTimes(1);
|
|
2920
|
+
expect(emitTaskEnd).not.toHaveBeenCalled();
|
|
2921
|
+
});
|
|
2922
|
+
|
|
2923
|
+
it('transitions to WRAPPING_UP on TASK_WRAPUP when wrapup is required', () => {
|
|
2924
|
+
const service = startMachine();
|
|
2925
|
+
const offerTaskData = createTaskData({
|
|
2926
|
+
interaction: {
|
|
2927
|
+
outboundType: 'OUTDIAL',
|
|
2928
|
+
} as any,
|
|
2929
|
+
});
|
|
2930
|
+
|
|
2931
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData: offerTaskData});
|
|
2932
|
+
expect(service.getSnapshot().value).toBe(TaskState.OFFERED);
|
|
2933
|
+
|
|
2934
|
+
const wrapupTaskData = createTaskData({
|
|
2935
|
+
agentId: 'agent-1',
|
|
2936
|
+
agentsPendingWrapUp: ['agent-1'],
|
|
2937
|
+
wrapUpRequired: true,
|
|
2938
|
+
interaction: {
|
|
2939
|
+
outboundType: 'OUTDIAL',
|
|
2940
|
+
state: 'wrapUp',
|
|
2941
|
+
isTerminated: true,
|
|
2942
|
+
} as any,
|
|
2943
|
+
});
|
|
2944
|
+
service.send({type: TaskEvent.TASK_WRAPUP, taskData: wrapupTaskData});
|
|
2945
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
2946
|
+
});
|
|
2947
|
+
|
|
2948
|
+
it('emits wrap-up once on TASK_WRAPUP from OFFERED when wrapup is required', () => {
|
|
2949
|
+
const {emitTaskWrapup, publishWrapup} = createPublishAwareEmitTaskWrapupMock();
|
|
2950
|
+
const service = createActor(
|
|
2951
|
+
createTaskStateMachine(createConfig(), {actions: {emitTaskWrapup}})
|
|
2952
|
+
);
|
|
2953
|
+
service.start();
|
|
2954
|
+
const offerTaskData = createTaskData({
|
|
2955
|
+
interaction: {outboundType: 'OUTDIAL'} as any,
|
|
2956
|
+
});
|
|
2957
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData: offerTaskData});
|
|
2958
|
+
expect(service.getSnapshot().value).toBe(TaskState.OFFERED);
|
|
2959
|
+
|
|
2960
|
+
const wrapupTaskData = createTaskData({
|
|
2961
|
+
agentId: 'agent-1',
|
|
2962
|
+
wrapUpRequired: true,
|
|
2963
|
+
interaction: {outboundType: 'OUTDIAL', state: 'wrapUp', isTerminated: true} as any,
|
|
2964
|
+
});
|
|
2965
|
+
service.send({type: TaskEvent.TASK_WRAPUP, taskData: wrapupTaskData});
|
|
2966
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
2967
|
+
expect(publishWrapup).toHaveBeenCalledTimes(1);
|
|
2968
|
+
});
|
|
2129
2969
|
});
|
|
2130
2970
|
|
|
2131
2971
|
describe('OUTBOUND_FAILED handling', () => {
|
|
@@ -2152,29 +2992,247 @@ describe('Task state machine', () => {
|
|
|
2152
2992
|
} as any,
|
|
2153
2993
|
});
|
|
2154
2994
|
|
|
2155
|
-
service.send({type: TaskEvent.TASK_INCOMING, taskData: offerTaskData});
|
|
2156
|
-
expect(service.getSnapshot().value).toBe(TaskState.OFFERED);
|
|
2995
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData: offerTaskData});
|
|
2996
|
+
expect(service.getSnapshot().value).toBe(TaskState.OFFERED);
|
|
2997
|
+
|
|
2998
|
+
const failedTaskData = createTaskData({
|
|
2999
|
+
interaction: {
|
|
3000
|
+
outboundType: 'OUTDIAL',
|
|
3001
|
+
isTerminated: true,
|
|
3002
|
+
} as any,
|
|
3003
|
+
});
|
|
3004
|
+
service.send({
|
|
3005
|
+
type: TaskEvent.OUTBOUND_FAILED,
|
|
3006
|
+
taskData: failedTaskData,
|
|
3007
|
+
reason: 'CUSTOMER_BUSY',
|
|
3008
|
+
});
|
|
3009
|
+
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
3010
|
+
});
|
|
3011
|
+
|
|
3012
|
+
it('transitions from OFFERED to WRAPPING_UP on OUTBOUND_FAILED when wrapup is required', () => {
|
|
3013
|
+
const service = startMachine();
|
|
3014
|
+
const offerTaskData = createTaskData({
|
|
3015
|
+
interaction: {
|
|
3016
|
+
outboundType: 'OUTDIAL',
|
|
3017
|
+
} as any,
|
|
3018
|
+
});
|
|
3019
|
+
|
|
3020
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData: offerTaskData});
|
|
3021
|
+
expect(service.getSnapshot().value).toBe(TaskState.OFFERED);
|
|
3022
|
+
|
|
3023
|
+
const failedTaskData = createTaskData({
|
|
3024
|
+
agentId: 'agent-1',
|
|
3025
|
+
agentsPendingWrapUp: ['agent-1'],
|
|
3026
|
+
interaction: {
|
|
3027
|
+
outboundType: 'OUTDIAL',
|
|
3028
|
+
isTerminated: true,
|
|
3029
|
+
} as any,
|
|
3030
|
+
});
|
|
3031
|
+
service.send({
|
|
3032
|
+
type: TaskEvent.OUTBOUND_FAILED,
|
|
3033
|
+
taskData: failedTaskData,
|
|
3034
|
+
reason: 'CUSTOMER_BUSY',
|
|
3035
|
+
});
|
|
3036
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
3037
|
+
});
|
|
3038
|
+
|
|
3039
|
+
it('transitions from CONNECTED to WRAPPING_UP on OUTBOUND_FAILED when wrapup is required', () => {
|
|
3040
|
+
const service = startMachine();
|
|
3041
|
+
const offerTaskData = createTaskData({
|
|
3042
|
+
interaction: {
|
|
3043
|
+
outboundType: 'OUTDIAL',
|
|
3044
|
+
} as any,
|
|
3045
|
+
});
|
|
3046
|
+
|
|
3047
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData: offerTaskData});
|
|
3048
|
+
service.send({type: TaskEvent.ASSIGN, taskData: offerTaskData});
|
|
3049
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONNECTED);
|
|
3050
|
+
|
|
3051
|
+
const failedTaskData = createTaskData({
|
|
3052
|
+
agentId: 'agent-1',
|
|
3053
|
+
agentsPendingWrapUp: ['agent-1'],
|
|
3054
|
+
interaction: {
|
|
3055
|
+
outboundType: 'OUTDIAL',
|
|
3056
|
+
isTerminated: true,
|
|
3057
|
+
} as any,
|
|
3058
|
+
});
|
|
3059
|
+
service.send({type: TaskEvent.OUTBOUND_FAILED, taskData: failedTaskData, reason: 'AGENT_ENDS'});
|
|
3060
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
3061
|
+
});
|
|
3062
|
+
|
|
3063
|
+
it('transitions from HELD to WRAPPING_UP on OUTBOUND_FAILED when wrapup is required', () => {
|
|
3064
|
+
const service = startMachine();
|
|
3065
|
+
const taskData = createTaskData({
|
|
3066
|
+
interaction: {
|
|
3067
|
+
outboundType: 'OUTDIAL',
|
|
3068
|
+
} as any,
|
|
3069
|
+
});
|
|
3070
|
+
|
|
3071
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
3072
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
3073
|
+
service.send({type: TaskEvent.HOLD_INITIATED, mediaResourceId: taskData.mediaResourceId});
|
|
3074
|
+
service.send({type: TaskEvent.HOLD_SUCCESS, mediaResourceId: taskData.mediaResourceId});
|
|
3075
|
+
expect(service.getSnapshot().value).toBe(TaskState.HELD);
|
|
3076
|
+
|
|
3077
|
+
const failedTaskData = createTaskData({
|
|
3078
|
+
agentId: 'agent-1',
|
|
3079
|
+
agentsPendingWrapUp: ['agent-1'],
|
|
3080
|
+
interaction: {
|
|
3081
|
+
outboundType: 'OUTDIAL',
|
|
3082
|
+
isTerminated: true,
|
|
3083
|
+
} as any,
|
|
3084
|
+
});
|
|
3085
|
+
service.send({type: TaskEvent.OUTBOUND_FAILED, taskData: failedTaskData, reason: 'AGENT_ENDS'});
|
|
3086
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
3087
|
+
});
|
|
3088
|
+
|
|
3089
|
+
it('transitions from CONSULTING to WRAPPING_UP on OUTBOUND_FAILED when wrapup is required', () => {
|
|
3090
|
+
const service = startMachine();
|
|
3091
|
+
const taskData = createTaskData({
|
|
3092
|
+
interaction: {
|
|
3093
|
+
outboundType: 'OUTDIAL',
|
|
3094
|
+
} as any,
|
|
3095
|
+
});
|
|
3096
|
+
|
|
3097
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
3098
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
3099
|
+
service.send({type: TaskEvent.CONSULT, destination: 'agent-42', destinationType: 'agent'});
|
|
3100
|
+
service.send({type: TaskEvent.CONSULT_SUCCESS, taskData});
|
|
3101
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONSULTING);
|
|
3102
|
+
|
|
3103
|
+
const failedTaskData = createTaskData({
|
|
3104
|
+
agentId: 'agent-1',
|
|
3105
|
+
agentsPendingWrapUp: ['agent-1'],
|
|
3106
|
+
interaction: {
|
|
3107
|
+
outboundType: 'OUTDIAL',
|
|
3108
|
+
isTerminated: true,
|
|
3109
|
+
} as any,
|
|
3110
|
+
});
|
|
3111
|
+
service.send({type: TaskEvent.OUTBOUND_FAILED, taskData: failedTaskData, reason: 'AGENT_ENDS'});
|
|
3112
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
3113
|
+
});
|
|
3114
|
+
|
|
3115
|
+
it('transitions from CONFERENCING to WRAPPING_UP on OUTBOUND_FAILED when wrapup is required', () => {
|
|
3116
|
+
const service = startMachine();
|
|
3117
|
+
const taskData = createTaskData({
|
|
3118
|
+
consultingAgentId: 'agent-1',
|
|
3119
|
+
interaction: {
|
|
3120
|
+
outboundType: 'OUTDIAL',
|
|
3121
|
+
} as any,
|
|
3122
|
+
});
|
|
3123
|
+
|
|
3124
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
3125
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
3126
|
+
service.send({type: TaskEvent.CONSULT, destination: 'agent-42', destinationType: 'agent'});
|
|
3127
|
+
service.send({type: TaskEvent.CONSULT_SUCCESS, taskData});
|
|
3128
|
+
service.send({type: TaskEvent.MERGE_TO_CONFERENCE});
|
|
3129
|
+
service.send({type: TaskEvent.CONFERENCE_START});
|
|
3130
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONFERENCING);
|
|
3131
|
+
|
|
3132
|
+
const failedTaskData = createTaskData({
|
|
3133
|
+
agentId: 'agent-1',
|
|
3134
|
+
agentsPendingWrapUp: ['agent-1'],
|
|
3135
|
+
interaction: {
|
|
3136
|
+
outboundType: 'OUTDIAL',
|
|
3137
|
+
isTerminated: true,
|
|
3138
|
+
} as any,
|
|
3139
|
+
});
|
|
3140
|
+
service.send({type: TaskEvent.OUTBOUND_FAILED, taskData: failedTaskData, reason: 'AGENT_ENDS'});
|
|
3141
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
3142
|
+
});
|
|
3143
|
+
|
|
3144
|
+
it('transitions from HOLD_INITIATING to WRAPPING_UP on OUTBOUND_FAILED when wrapup is required', () => {
|
|
3145
|
+
const service = startMachine();
|
|
3146
|
+
const taskData = createTaskData({
|
|
3147
|
+
interaction: {
|
|
3148
|
+
outboundType: 'OUTDIAL',
|
|
3149
|
+
} as any,
|
|
3150
|
+
});
|
|
3151
|
+
|
|
3152
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
3153
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
3154
|
+
service.send({type: TaskEvent.HOLD_INITIATED, mediaResourceId: taskData.mediaResourceId});
|
|
3155
|
+
expect(service.getSnapshot().value).toBe(TaskState.HOLD_INITIATING);
|
|
3156
|
+
|
|
3157
|
+
const failedTaskData = createTaskData({
|
|
3158
|
+
agentId: 'agent-1',
|
|
3159
|
+
agentsPendingWrapUp: ['agent-1'],
|
|
3160
|
+
interaction: {
|
|
3161
|
+
outboundType: 'OUTDIAL',
|
|
3162
|
+
isTerminated: true,
|
|
3163
|
+
} as any,
|
|
3164
|
+
});
|
|
3165
|
+
service.send({type: TaskEvent.OUTBOUND_FAILED, taskData: failedTaskData, reason: 'AGENT_ENDS'});
|
|
3166
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
3167
|
+
});
|
|
3168
|
+
|
|
3169
|
+
it('transitions from RESUME_INITIATING to WRAPPING_UP on OUTBOUND_FAILED when wrapup is required', () => {
|
|
3170
|
+
const service = startMachine();
|
|
3171
|
+
const taskData = createTaskData({
|
|
3172
|
+
interaction: {
|
|
3173
|
+
outboundType: 'OUTDIAL',
|
|
3174
|
+
} as any,
|
|
3175
|
+
});
|
|
3176
|
+
|
|
3177
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
3178
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
3179
|
+
service.send({type: TaskEvent.HOLD_INITIATED, mediaResourceId: taskData.mediaResourceId});
|
|
3180
|
+
service.send({type: TaskEvent.HOLD_SUCCESS, mediaResourceId: taskData.mediaResourceId});
|
|
3181
|
+
service.send({type: TaskEvent.UNHOLD_INITIATED, mediaResourceId: taskData.mediaResourceId});
|
|
3182
|
+
expect(service.getSnapshot().value).toBe(TaskState.RESUME_INITIATING);
|
|
3183
|
+
|
|
3184
|
+
const failedTaskData = createTaskData({
|
|
3185
|
+
agentId: 'agent-1',
|
|
3186
|
+
agentsPendingWrapUp: ['agent-1'],
|
|
3187
|
+
interaction: {
|
|
3188
|
+
outboundType: 'OUTDIAL',
|
|
3189
|
+
isTerminated: true,
|
|
3190
|
+
} as any,
|
|
3191
|
+
});
|
|
3192
|
+
service.send({type: TaskEvent.OUTBOUND_FAILED, taskData: failedTaskData, reason: 'AGENT_ENDS'});
|
|
3193
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
3194
|
+
});
|
|
3195
|
+
|
|
3196
|
+
it('transitions from CONSULT_INITIATING to WRAPPING_UP on OUTBOUND_FAILED when wrapup is required', () => {
|
|
3197
|
+
const service = startMachine();
|
|
3198
|
+
const taskData = createTaskData({
|
|
3199
|
+
interaction: {
|
|
3200
|
+
outboundType: 'OUTDIAL',
|
|
3201
|
+
} as any,
|
|
3202
|
+
});
|
|
3203
|
+
|
|
3204
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
3205
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
3206
|
+
service.send({type: TaskEvent.CONSULT, destination: 'agent-42', destinationType: 'agent'});
|
|
3207
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONSULT_INITIATING);
|
|
2157
3208
|
|
|
2158
3209
|
const failedTaskData = createTaskData({
|
|
3210
|
+
agentId: 'agent-1',
|
|
3211
|
+
agentsPendingWrapUp: ['agent-1'],
|
|
2159
3212
|
interaction: {
|
|
2160
3213
|
outboundType: 'OUTDIAL',
|
|
2161
3214
|
isTerminated: true,
|
|
2162
3215
|
} as any,
|
|
2163
3216
|
});
|
|
2164
|
-
service.send({type: TaskEvent.OUTBOUND_FAILED, taskData: failedTaskData, reason: '
|
|
2165
|
-
expect(service.getSnapshot().value).toBe(TaskState.
|
|
3217
|
+
service.send({type: TaskEvent.OUTBOUND_FAILED, taskData: failedTaskData, reason: 'AGENT_ENDS'});
|
|
3218
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
2166
3219
|
});
|
|
2167
3220
|
|
|
2168
|
-
it('transitions from
|
|
3221
|
+
it('transitions from CONF_INITIATING to WRAPPING_UP on OUTBOUND_FAILED when wrapup is required', () => {
|
|
2169
3222
|
const service = startMachine();
|
|
2170
|
-
const
|
|
3223
|
+
const taskData = createTaskData({
|
|
3224
|
+
consultingAgentId: 'agent-1',
|
|
2171
3225
|
interaction: {
|
|
2172
3226
|
outboundType: 'OUTDIAL',
|
|
2173
3227
|
} as any,
|
|
2174
3228
|
});
|
|
2175
3229
|
|
|
2176
|
-
service.send({type: TaskEvent.TASK_INCOMING, taskData
|
|
2177
|
-
|
|
3230
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
3231
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
3232
|
+
service.send({type: TaskEvent.CONSULT, destination: 'agent-42', destinationType: 'agent'});
|
|
3233
|
+
service.send({type: TaskEvent.CONSULT_SUCCESS, taskData});
|
|
3234
|
+
service.send({type: TaskEvent.MERGE_TO_CONFERENCE});
|
|
3235
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONF_INITIATING);
|
|
2178
3236
|
|
|
2179
3237
|
const failedTaskData = createTaskData({
|
|
2180
3238
|
agentId: 'agent-1',
|
|
@@ -2184,16 +3242,14 @@ describe('Task state machine', () => {
|
|
|
2184
3242
|
isTerminated: true,
|
|
2185
3243
|
} as any,
|
|
2186
3244
|
});
|
|
2187
|
-
service.send({type: TaskEvent.OUTBOUND_FAILED, taskData: failedTaskData, reason: '
|
|
3245
|
+
service.send({type: TaskEvent.OUTBOUND_FAILED, taskData: failedTaskData, reason: 'AGENT_ENDS'});
|
|
2188
3246
|
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
2189
3247
|
});
|
|
2190
3248
|
});
|
|
2191
3249
|
|
|
2192
3250
|
describe('CONSULTING state TRANSFER_CONFERENCE_SUCCESS desktop-initiated (CAI-8329)', () => {
|
|
2193
3251
|
const startAgent2Machine = () => {
|
|
2194
|
-
const actor = createActor(
|
|
2195
|
-
createTaskStateMachine({...createConfig(), agentId: 'agent-2'})
|
|
2196
|
-
);
|
|
3252
|
+
const actor = createActor(createTaskStateMachine({...createConfig(), agentId: 'agent-2'}));
|
|
2197
3253
|
actor.start();
|
|
2198
3254
|
|
|
2199
3255
|
return actor;
|
|
@@ -2292,7 +3348,54 @@ describe('Task state machine', () => {
|
|
|
2292
3348
|
} as any,
|
|
2293
3349
|
});
|
|
2294
3350
|
|
|
2295
|
-
const
|
|
3351
|
+
const createSecondaryAgentRemovedTaskData = () =>
|
|
3352
|
+
createTaskData({
|
|
3353
|
+
agentId: 'agent-2',
|
|
3354
|
+
isConsulted: true,
|
|
3355
|
+
interactionId: 'interaction-1',
|
|
3356
|
+
mediaResourceId: 'interaction-1',
|
|
3357
|
+
consultMediaResourceId: 'consult-media-1',
|
|
3358
|
+
interaction: {
|
|
3359
|
+
state: 'conference',
|
|
3360
|
+
owner: 'agent-1',
|
|
3361
|
+
mainInteractionId: 'interaction-1',
|
|
3362
|
+
interactionId: 'interaction-1',
|
|
3363
|
+
participants: {
|
|
3364
|
+
'agent-1': {
|
|
3365
|
+
id: 'agent-1',
|
|
3366
|
+
pType: 'Agent',
|
|
3367
|
+
hasLeft: false,
|
|
3368
|
+
consultState: 'conferencing',
|
|
3369
|
+
},
|
|
3370
|
+
'agent-3': {
|
|
3371
|
+
id: 'agent-3',
|
|
3372
|
+
pType: 'Agent',
|
|
3373
|
+
hasLeft: false,
|
|
3374
|
+
isConsulted: true,
|
|
3375
|
+
consultState: 'consulting',
|
|
3376
|
+
},
|
|
3377
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false},
|
|
3378
|
+
},
|
|
3379
|
+
media: {
|
|
3380
|
+
'arbitrary-main-media-key': {
|
|
3381
|
+
mediaResourceId: 'interaction-1',
|
|
3382
|
+
mType: 'mainCall',
|
|
3383
|
+
participants: ['agent-1', 'customer-1'],
|
|
3384
|
+
isHold: false,
|
|
3385
|
+
},
|
|
3386
|
+
'consult-media-1': {
|
|
3387
|
+
mediaResourceId: 'consult-media-1',
|
|
3388
|
+
mType: 'consult',
|
|
3389
|
+
participants: ['agent-2', 'agent-3'],
|
|
3390
|
+
isHold: false,
|
|
3391
|
+
},
|
|
3392
|
+
},
|
|
3393
|
+
} as any,
|
|
3394
|
+
});
|
|
3395
|
+
|
|
3396
|
+
const setupSecondaryAgentConsultingFromConference = (
|
|
3397
|
+
service: ReturnType<typeof startAgent2Machine>
|
|
3398
|
+
) => {
|
|
2296
3399
|
const conferenceTaskData = createSecondaryAgentConferenceBaseTaskData();
|
|
2297
3400
|
const consultingTaskData = createSecondaryAgentConsultingTaskData();
|
|
2298
3401
|
|
|
@@ -2339,7 +3442,10 @@ describe('Task state machine', () => {
|
|
|
2339
3442
|
setupSecondaryAgentDesktopConsultingFromConference(service);
|
|
2340
3443
|
|
|
2341
3444
|
const transferSuccessTaskData = createSecondaryAgentConsultingTaskData();
|
|
2342
|
-
service.send({
|
|
3445
|
+
service.send({
|
|
3446
|
+
type: TaskEvent.TRANSFER_CONFERENCE_SUCCESS,
|
|
3447
|
+
taskData: transferSuccessTaskData,
|
|
3448
|
+
});
|
|
2343
3449
|
|
|
2344
3450
|
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
2345
3451
|
});
|
|
@@ -2349,7 +3455,10 @@ describe('Task state machine', () => {
|
|
|
2349
3455
|
setupSecondaryAgentConsultingFromConference(service);
|
|
2350
3456
|
|
|
2351
3457
|
const transferSuccessTaskData = createSecondaryAgentConsultingTaskData();
|
|
2352
|
-
service.send({
|
|
3458
|
+
service.send({
|
|
3459
|
+
type: TaskEvent.TRANSFER_CONFERENCE_SUCCESS,
|
|
3460
|
+
taskData: transferSuccessTaskData,
|
|
3461
|
+
});
|
|
2353
3462
|
|
|
2354
3463
|
const snapshot = service.getSnapshot();
|
|
2355
3464
|
expect(snapshot.value).toBe(TaskState.TERMINATED);
|
|
@@ -2359,9 +3468,7 @@ describe('Task state machine', () => {
|
|
|
2359
3468
|
|
|
2360
3469
|
it('moves consultee to CONFERENCING when primary agent conference-transfers', () => {
|
|
2361
3470
|
const startAgent3Machine = () => {
|
|
2362
|
-
const actor = createActor(
|
|
2363
|
-
createTaskStateMachine({...createConfig(), agentId: 'agent-3'})
|
|
2364
|
-
);
|
|
3471
|
+
const actor = createActor(createTaskStateMachine({...createConfig(), agentId: 'agent-3'}));
|
|
2365
3472
|
actor.start();
|
|
2366
3473
|
|
|
2367
3474
|
return actor;
|
|
@@ -2524,7 +3631,10 @@ describe('Task state machine', () => {
|
|
|
2524
3631
|
expect(service.getSnapshot().value).toBe(TaskState.CONSULTING);
|
|
2525
3632
|
expect(service.getSnapshot().context.consultInitiator).toBe(false);
|
|
2526
3633
|
|
|
2527
|
-
service.send({
|
|
3634
|
+
service.send({
|
|
3635
|
+
type: TaskEvent.TRANSFER_CONFERENCE_SUCCESS,
|
|
3636
|
+
taskData: transferSuccessTaskData,
|
|
3637
|
+
});
|
|
2528
3638
|
expect(service.getSnapshot().value).toBe(TaskState.CONFERENCING);
|
|
2529
3639
|
});
|
|
2530
3640
|
|
|
@@ -2582,7 +3692,10 @@ describe('Task state machine', () => {
|
|
|
2582
3692
|
expect(service.getSnapshot().value).toBe(TaskState.CONFERENCING);
|
|
2583
3693
|
expect(service.getSnapshot().context.consultInitiator).toBe(false);
|
|
2584
3694
|
|
|
2585
|
-
service.send({
|
|
3695
|
+
service.send({
|
|
3696
|
+
type: TaskEvent.TRANSFER_CONFERENCE_SUCCESS,
|
|
3697
|
+
taskData: transferSuccessTaskData,
|
|
3698
|
+
});
|
|
2586
3699
|
expect(service.getSnapshot().value).toBe(TaskState.CONFERENCING);
|
|
2587
3700
|
});
|
|
2588
3701
|
|
|
@@ -2592,11 +3705,87 @@ describe('Task state machine', () => {
|
|
|
2592
3705
|
|
|
2593
3706
|
const transferSuccessTaskData = createSecondaryAgentConsultingTaskData();
|
|
2594
3707
|
service.send({type: TaskEvent.TRANSFER_CONFERENCE});
|
|
2595
|
-
service.send({
|
|
3708
|
+
service.send({
|
|
3709
|
+
type: TaskEvent.TRANSFER_CONFERENCE_SUCCESS,
|
|
3710
|
+
taskData: transferSuccessTaskData,
|
|
3711
|
+
});
|
|
2596
3712
|
|
|
2597
3713
|
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
2598
3714
|
});
|
|
2599
3715
|
|
|
3716
|
+
it('emits wrap-up once on TRANSFER_CONFERENCE_SUCCESS from CONSULTING when wrap-up is required', () => {
|
|
3717
|
+
const {emitTaskWrapup, publishWrapup} = createPublishAwareEmitTaskWrapupMock();
|
|
3718
|
+
const service = createActor(
|
|
3719
|
+
createTaskStateMachine({...createConfig(), agentId: 'agent-1'}, {actions: {emitTaskWrapup}})
|
|
3720
|
+
);
|
|
3721
|
+
service.start();
|
|
3722
|
+
const taskData = createTaskData({
|
|
3723
|
+
agentId: 'agent-1',
|
|
3724
|
+
consultingAgentId: 'agent-1',
|
|
3725
|
+
interaction: {
|
|
3726
|
+
owner: 'agent-1',
|
|
3727
|
+
state: 'conference',
|
|
3728
|
+
mainInteractionId: 'interaction-1',
|
|
3729
|
+
interactionId: 'interaction-1',
|
|
3730
|
+
participants: {
|
|
3731
|
+
'agent-1': {id: 'agent-1', pType: 'Agent', type: 'Agent', hasJoined: true, hasLeft: false},
|
|
3732
|
+
'agent-2': {id: 'agent-2', pType: 'Agent', type: 'Agent', hasJoined: true, hasLeft: false},
|
|
3733
|
+
},
|
|
3734
|
+
} as any,
|
|
3735
|
+
});
|
|
3736
|
+
|
|
3737
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
3738
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
3739
|
+
service.send({type: TaskEvent.CONSULT, destination: 'agent-2', destinationType: 'agent'});
|
|
3740
|
+
service.send({type: TaskEvent.CONSULT_SUCCESS, taskData});
|
|
3741
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONSULTING);
|
|
3742
|
+
|
|
3743
|
+
service.send({type: TaskEvent.TRANSFER_CONFERENCE});
|
|
3744
|
+
service.send({
|
|
3745
|
+
type: TaskEvent.TRANSFER_CONFERENCE_SUCCESS,
|
|
3746
|
+
taskData: {...taskData, wrapUpRequired: true},
|
|
3747
|
+
});
|
|
3748
|
+
|
|
3749
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
3750
|
+
expect(publishWrapup).toHaveBeenCalledTimes(1);
|
|
3751
|
+
});
|
|
3752
|
+
|
|
3753
|
+
it('emits wrap-up once on TRANSFER_CONFERENCE_SUCCESS from CONFERENCING when wrap-up is required', () => {
|
|
3754
|
+
const {emitTaskWrapup, publishWrapup} = createPublishAwareEmitTaskWrapupMock();
|
|
3755
|
+
const service = createActor(
|
|
3756
|
+
createTaskStateMachine({...createConfig(), agentId: 'agent-1'}, {actions: {emitTaskWrapup}})
|
|
3757
|
+
);
|
|
3758
|
+
service.start();
|
|
3759
|
+
const taskData = createTaskData({
|
|
3760
|
+
agentId: 'agent-1',
|
|
3761
|
+
interaction: {
|
|
3762
|
+
owner: 'agent-1',
|
|
3763
|
+
state: 'conference',
|
|
3764
|
+
mainInteractionId: 'interaction-1',
|
|
3765
|
+
interactionId: 'interaction-1',
|
|
3766
|
+
participants: {
|
|
3767
|
+
'agent-1': {id: 'agent-1', pType: 'Agent', type: 'Agent', hasJoined: true, hasLeft: false},
|
|
3768
|
+
'agent-2': {id: 'agent-2', pType: 'Agent', type: 'Agent', hasJoined: true, hasLeft: false},
|
|
3769
|
+
},
|
|
3770
|
+
} as any,
|
|
3771
|
+
});
|
|
3772
|
+
|
|
3773
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
3774
|
+
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
3775
|
+
service.send({type: TaskEvent.MERGE_TO_CONFERENCE});
|
|
3776
|
+
service.send({type: TaskEvent.CONFERENCE_START, taskData});
|
|
3777
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONFERENCING);
|
|
3778
|
+
|
|
3779
|
+
service.send({type: TaskEvent.TRANSFER_CONFERENCE});
|
|
3780
|
+
service.send({
|
|
3781
|
+
type: TaskEvent.TRANSFER_CONFERENCE_SUCCESS,
|
|
3782
|
+
taskData: {...taskData, wrapUpRequired: true},
|
|
3783
|
+
});
|
|
3784
|
+
|
|
3785
|
+
expect(service.getSnapshot().value).toBe(TaskState.WRAPPING_UP);
|
|
3786
|
+
expect(publishWrapup).toHaveBeenCalledTimes(1);
|
|
3787
|
+
});
|
|
3788
|
+
|
|
2600
3789
|
it('terminates secondary agent on PARTICIPANT_LEAVE while in CONSULTING during conference', () => {
|
|
2601
3790
|
const service = startAgent2Machine();
|
|
2602
3791
|
setupSecondaryAgentDesktopConsultingFromConference(service);
|
|
@@ -2647,5 +3836,192 @@ describe('Task state machine', () => {
|
|
|
2647
3836
|
|
|
2648
3837
|
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
2649
3838
|
});
|
|
3839
|
+
|
|
3840
|
+
it('terminates secondary agent on PARTICIPANT_LEAVE while consult is initiating', () => {
|
|
3841
|
+
const service = startAgent2Machine();
|
|
3842
|
+
const conferenceTaskData = createSecondaryAgentConferenceBaseTaskData();
|
|
3843
|
+
|
|
3844
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData: conferenceTaskData});
|
|
3845
|
+
service.send({type: TaskEvent.ASSIGN, taskData: conferenceTaskData});
|
|
3846
|
+
service.send({type: TaskEvent.CONFERENCE_START, taskData: conferenceTaskData});
|
|
3847
|
+
service.send({type: TaskEvent.CONSULT, destination: 'agent-3', destinationType: 'agent'});
|
|
3848
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONSULT_INITIATING);
|
|
3849
|
+
expect(service.getSnapshot().context.taskData.interaction).toBe(
|
|
3850
|
+
conferenceTaskData.interaction
|
|
3851
|
+
);
|
|
3852
|
+
expect(service.getSnapshot().context.taskData.destAgentId).toBe('agent-3');
|
|
3853
|
+
|
|
3854
|
+
const participantLeaveEvent = {
|
|
3855
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
3856
|
+
taskData: createSecondaryAgentRemovedTaskData(),
|
|
3857
|
+
} as const;
|
|
3858
|
+
expect(
|
|
3859
|
+
guards.didCurrentAgentLeaveMainInteraction({
|
|
3860
|
+
context: service.getSnapshot().context,
|
|
3861
|
+
event: participantLeaveEvent,
|
|
3862
|
+
})
|
|
3863
|
+
).toBe(true);
|
|
3864
|
+
|
|
3865
|
+
service.send(participantLeaveEvent);
|
|
3866
|
+
|
|
3867
|
+
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
3868
|
+
});
|
|
3869
|
+
|
|
3870
|
+
it('terminates secondary agent on CONSULT_END while consult is initiating', () => {
|
|
3871
|
+
const service = startAgent2Machine();
|
|
3872
|
+
const conferenceTaskData = createSecondaryAgentConferenceBaseTaskData();
|
|
3873
|
+
|
|
3874
|
+
service.send({type: TaskEvent.TASK_INCOMING, taskData: conferenceTaskData});
|
|
3875
|
+
service.send({type: TaskEvent.ASSIGN, taskData: conferenceTaskData});
|
|
3876
|
+
service.send({type: TaskEvent.CONFERENCE_START, taskData: conferenceTaskData});
|
|
3877
|
+
service.send({type: TaskEvent.CONSULT, destination: 'agent-3', destinationType: 'agent'});
|
|
3878
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONSULT_INITIATING);
|
|
3879
|
+
|
|
3880
|
+
const consultEndEvent = {
|
|
3881
|
+
type: TaskEvent.CONSULT_END,
|
|
3882
|
+
taskData: createSecondaryAgentRemovedTaskData(),
|
|
3883
|
+
} as const;
|
|
3884
|
+
expect(
|
|
3885
|
+
guards.didCurrentAgentLeaveMainInteraction({
|
|
3886
|
+
context: service.getSnapshot().context,
|
|
3887
|
+
event: consultEndEvent,
|
|
3888
|
+
})
|
|
3889
|
+
).toBe(true);
|
|
3890
|
+
|
|
3891
|
+
service.send(consultEndEvent);
|
|
3892
|
+
|
|
3893
|
+
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
3894
|
+
});
|
|
3895
|
+
|
|
3896
|
+
it('terminates a consulting secondary initiator when CONSULT_END removes it from mainCall', () => {
|
|
3897
|
+
const service = startAgent2Machine();
|
|
3898
|
+
setupSecondaryAgentConsultingFromConference(service);
|
|
3899
|
+
|
|
3900
|
+
service.send({
|
|
3901
|
+
type: TaskEvent.CONSULT_END,
|
|
3902
|
+
taskData: createSecondaryAgentRemovedTaskData(),
|
|
3903
|
+
});
|
|
3904
|
+
|
|
3905
|
+
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
3906
|
+
expect(service.getSnapshot().context.consultInitiator).toBe(false);
|
|
3907
|
+
expect(service.getSnapshot().context.consultDestinationAgentJoined).toBe(false);
|
|
3908
|
+
});
|
|
3909
|
+
|
|
3910
|
+
it('does not infer departure from a partial CONSULT_END payload', () => {
|
|
3911
|
+
const service = startAgent2Machine();
|
|
3912
|
+
setupSecondaryAgentConsultingFromConference(service);
|
|
3913
|
+
const partialTaskData = {
|
|
3914
|
+
...createTaskData({agentId: 'agent-2', interactionId: 'interaction-1'}),
|
|
3915
|
+
interaction: {
|
|
3916
|
+
state: 'conference',
|
|
3917
|
+
interactionId: 'interaction-1',
|
|
3918
|
+
mainInteractionId: 'interaction-1',
|
|
3919
|
+
},
|
|
3920
|
+
} as any;
|
|
3921
|
+
|
|
3922
|
+
const consultEndEvent = {type: TaskEvent.CONSULT_END, taskData: partialTaskData} as const;
|
|
3923
|
+
expect(service.getSnapshot().context.consultInitiator).toBe(true);
|
|
3924
|
+
expect(
|
|
3925
|
+
guards.didCurrentAgentLeaveMainInteraction({
|
|
3926
|
+
context: service.getSnapshot().context,
|
|
3927
|
+
event: consultEndEvent,
|
|
3928
|
+
})
|
|
3929
|
+
).toBe(false);
|
|
3930
|
+
|
|
3931
|
+
service.send(consultEndEvent);
|
|
3932
|
+
|
|
3933
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONNECTED);
|
|
3934
|
+
});
|
|
3935
|
+
|
|
3936
|
+
it('emits consult-end and task-end when AgentConsultEnded terminates the consulted agent', () => {
|
|
3937
|
+
const emitTaskConsultEnd = jest.fn();
|
|
3938
|
+
const emitTaskEnd = jest.fn();
|
|
3939
|
+
const cleanupResources = jest.fn();
|
|
3940
|
+
const service = createActor(
|
|
3941
|
+
createTaskStateMachine(
|
|
3942
|
+
{...createConfig(), agentId: 'agent-3'},
|
|
3943
|
+
{
|
|
3944
|
+
actions: {emitTaskConsultEnd, emitTaskEnd, cleanupResources},
|
|
3945
|
+
}
|
|
3946
|
+
)
|
|
3947
|
+
);
|
|
3948
|
+
service.start();
|
|
3949
|
+
|
|
3950
|
+
const consultingTaskData = createTaskData({
|
|
3951
|
+
agentId: 'agent-3',
|
|
3952
|
+
isConsulted: true,
|
|
3953
|
+
interactionId: 'consult-child',
|
|
3954
|
+
interaction: {
|
|
3955
|
+
state: 'consulting',
|
|
3956
|
+
owner: 'agent-1',
|
|
3957
|
+
interactionId: 'consult-child',
|
|
3958
|
+
mainInteractionId: 'interaction-1',
|
|
3959
|
+
participants: {
|
|
3960
|
+
'agent-1': {id: 'agent-1', pType: 'Agent', hasLeft: false},
|
|
3961
|
+
'agent-2': {
|
|
3962
|
+
id: 'agent-2',
|
|
3963
|
+
pType: 'Agent',
|
|
3964
|
+
hasLeft: false,
|
|
3965
|
+
consultState: 'consulting',
|
|
3966
|
+
},
|
|
3967
|
+
'agent-3': {
|
|
3968
|
+
id: 'agent-3',
|
|
3969
|
+
pType: 'Agent',
|
|
3970
|
+
hasLeft: false,
|
|
3971
|
+
isConsulted: true,
|
|
3972
|
+
consultState: 'consulting',
|
|
3973
|
+
},
|
|
3974
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false},
|
|
3975
|
+
},
|
|
3976
|
+
media: {
|
|
3977
|
+
'interaction-1': {
|
|
3978
|
+
mediaResourceId: 'interaction-1',
|
|
3979
|
+
mType: 'mainCall',
|
|
3980
|
+
participants: ['agent-1', 'agent-2', 'customer-1'],
|
|
3981
|
+
isHold: true,
|
|
3982
|
+
},
|
|
3983
|
+
'consult-child': {
|
|
3984
|
+
mediaResourceId: 'consult-child',
|
|
3985
|
+
mType: 'consult',
|
|
3986
|
+
participants: ['agent-2', 'agent-3'],
|
|
3987
|
+
isHold: false,
|
|
3988
|
+
},
|
|
3989
|
+
},
|
|
3990
|
+
} as any,
|
|
3991
|
+
});
|
|
3992
|
+
service.send({type: TaskEvent.HYDRATE, taskData: consultingTaskData});
|
|
3993
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONSULTING);
|
|
3994
|
+
|
|
3995
|
+
const consultEndedTaskData = createTaskData({
|
|
3996
|
+
...consultingTaskData,
|
|
3997
|
+
type: 'AgentConsultEnded' as any,
|
|
3998
|
+
interaction: {
|
|
3999
|
+
...consultingTaskData.interaction,
|
|
4000
|
+
state: 'conference',
|
|
4001
|
+
participants: {
|
|
4002
|
+
'agent-1': {id: 'agent-1', pType: 'Agent', hasLeft: false},
|
|
4003
|
+
'agent-2': {id: 'agent-2', pType: 'Agent', hasLeft: false},
|
|
4004
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false},
|
|
4005
|
+
},
|
|
4006
|
+
media: {
|
|
4007
|
+
'interaction-1': {
|
|
4008
|
+
mediaResourceId: 'interaction-1',
|
|
4009
|
+
mType: 'mainCall',
|
|
4010
|
+
participants: ['agent-1', 'agent-2', 'customer-1'],
|
|
4011
|
+
isHold: false,
|
|
4012
|
+
},
|
|
4013
|
+
},
|
|
4014
|
+
} as any,
|
|
4015
|
+
});
|
|
4016
|
+
|
|
4017
|
+
service.send({type: TaskEvent.CONSULT_END, taskData: consultEndedTaskData});
|
|
4018
|
+
|
|
4019
|
+
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
4020
|
+
expect(emitTaskConsultEnd).toHaveBeenCalledTimes(1);
|
|
4021
|
+
expect(emitTaskEnd).toHaveBeenCalledTimes(1);
|
|
4022
|
+
expect(cleanupResources).toHaveBeenCalledTimes(1);
|
|
4023
|
+
expect(service.getSnapshot().context.consultDestinationAgentJoined).toBe(false);
|
|
4024
|
+
expect(service.getSnapshot().context.consultInitiator).toBe(false);
|
|
4025
|
+
});
|
|
2650
4026
|
});
|
|
2651
4027
|
});
|