@webex/contact-center 3.11.0 → 3.12.0-llmrefactor.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.sdd/manifest.json +882 -0
- package/AGENTS.md +94 -0
- package/ai-docs/ARCHITECTURE.md +168 -0
- package/ai-docs/CONTRACTS.md +46 -0
- package/ai-docs/GETTING_STARTED.md +168 -0
- package/ai-docs/GLOSSARY.md +43 -0
- package/ai-docs/README.md +138 -0
- package/ai-docs/REVIEW_CHECKLIST.md +41 -0
- package/ai-docs/RULES.md +444 -0
- package/ai-docs/SECURITY.md +52 -0
- package/ai-docs/SERVICE_STATE.md +48 -0
- package/ai-docs/SPEC_INDEX.md +65 -0
- package/ai-docs/adr/0001-spec-source-policy.md +55 -0
- package/ai-docs/adr/README.md +8 -0
- package/ai-docs/adr/_adr-template.md +31 -0
- package/ai-docs/contact-center-spec.md +341 -0
- package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
- package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
- package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -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 +379 -50
- package/dist/cc.js.map +1 -1
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +22 -2
- package/dist/constants.js.map +1 -1
- package/dist/index.js +27 -5
- package/dist/index.js.map +1 -1
- package/dist/metrics/behavioral-events.js +114 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +32 -3
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/ApiAiAssistant.js +318 -0
- package/dist/services/ApiAiAssistant.js.map +1 -0
- package/dist/services/UserPreference.js +427 -0
- package/dist/services/UserPreference.js.map +1 -0
- package/dist/services/agent/types.js.map +1 -1
- package/dist/services/config/Util.js +8 -4
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/constants.js +35 -2
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/index.js +41 -2
- package/dist/services/config/index.js.map +1 -1
- package/dist/services/config/types.js +66 -8
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/constants.js +27 -1
- package/dist/services/constants.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/core/Utils.js +122 -25
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/core/aqm-reqs.js +92 -17
- package/dist/services/core/aqm-reqs.js.map +1 -1
- package/dist/services/core/websocket/WebSocketManager.js +22 -6
- package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
- package/dist/services/core/websocket/connection-service.js +3 -1
- package/dist/services/core/websocket/connection-service.js.map +1 -1
- package/dist/services/core/websocket/types.js.map +1 -1
- package/dist/services/index.js +6 -0
- 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 +751 -457
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +220 -23
- package/dist/services/task/TaskUtils.js.map +1 -1
- package/dist/services/task/constants.js +23 -2
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/dialer.js +129 -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 +873 -0
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
- package/dist/services/task/state-machine/actions.js +567 -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 +382 -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 +603 -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 +227 -4
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +1044 -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 +894 -0
- package/dist/types/config.d.ts +72 -0
- package/dist/types/constants.d.ts +66 -0
- package/dist/types/index.d.ts +199 -0
- package/dist/types/logger-proxy.d.ts +71 -0
- package/dist/types/metrics/MetricsManager.d.ts +223 -0
- package/dist/types/metrics/behavioral-events.d.ts +29 -0
- package/dist/types/metrics/constants.d.ts +181 -0
- package/dist/types/services/AddressBook.d.ts +74 -0
- package/dist/types/services/ApiAiAssistant.d.ts +49 -0
- package/dist/types/services/EntryPoint.d.ts +67 -0
- package/dist/types/services/Queue.d.ts +76 -0
- package/dist/types/services/UserPreference.d.ts +118 -0
- package/dist/types/services/WebCallingService.d.ts +1 -0
- package/dist/types/services/agent/index.d.ts +46 -0
- package/dist/types/services/agent/types.d.ts +413 -0
- package/dist/types/services/config/Util.d.ts +20 -0
- package/dist/types/services/config/constants.d.ts +270 -0
- package/dist/types/services/config/index.d.ts +177 -0
- package/dist/types/services/config/types.d.ts +1368 -0
- package/dist/types/services/constants.d.ts +110 -0
- package/dist/types/services/core/Err.d.ts +125 -0
- package/dist/types/services/core/GlobalTypes.d.ts +58 -0
- package/dist/types/services/core/Utils.d.ts +121 -0
- package/dist/types/services/core/WebexRequest.d.ts +22 -0
- package/dist/types/services/core/aqm-reqs.d.ts +65 -0
- package/dist/types/services/core/constants.d.ts +99 -0
- package/dist/types/services/core/types.d.ts +47 -0
- package/dist/types/services/core/websocket/WebSocketManager.d.ts +36 -0
- package/dist/types/services/core/websocket/connection-service.d.ts +27 -0
- package/dist/types/services/core/websocket/keepalive.worker.d.ts +2 -0
- package/dist/types/services/core/websocket/types.d.ts +37 -0
- package/dist/types/services/index.d.ts +54 -0
- package/dist/types/services/task/AutoWrapup.d.ts +40 -0
- 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/TaskManager.d.ts +1 -0
- package/dist/types/services/task/TaskUtils.d.ts +138 -0
- package/dist/types/services/task/constants.d.ts +91 -0
- package/dist/types/services/task/contact.d.ts +69 -0
- package/dist/types/services/task/dialer.d.ts +73 -0
- package/dist/types/services/task/digital/Digital.d.ts +22 -0
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1194 -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 +102 -0
- package/dist/types/services/task/state-machine/index.d.ts +13 -0
- package/dist/types/services/task/state-machine/types.d.ts +269 -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 +1856 -0
- 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 +778 -0
- package/dist/types/utils/PageCache.d.ts +173 -0
- package/dist/types/webex-config.d.ts +53 -0
- package/dist/types/webex.d.ts +8 -0
- package/dist/types.js +130 -1
- package/dist/types.js.map +1 -1
- package/dist/webex.js +14 -2
- package/dist/webex.js.map +1 -1
- package/package.json +16 -12
- package/src/cc.ts +477 -51
- package/src/config.ts +6 -0
- package/src/constants.ts +21 -1
- package/src/index.ts +24 -5
- package/src/metrics/ai-docs/AGENTS.md +350 -0
- package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
- package/src/metrics/ai-docs/metrics-spec.md +854 -0
- package/src/metrics/behavioral-events.ts +120 -0
- package/src/metrics/constants.ts +37 -3
- package/src/services/ApiAiAssistant.ts +412 -0
- package/src/services/UserPreference.ts +509 -0
- package/src/services/agent/ai-docs/AGENTS.md +240 -0
- package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
- package/src/services/agent/ai-docs/agent-spec.md +504 -0
- package/src/services/agent/types.ts +1 -1
- package/src/services/ai-docs/AGENTS.md +386 -0
- package/src/services/ai-docs/services-spec.md +492 -0
- package/src/services/config/Util.ts +10 -2
- package/src/services/config/ai-docs/AGENTS.md +255 -0
- package/src/services/config/ai-docs/ARCHITECTURE.md +426 -0
- package/src/services/config/ai-docs/config-spec.md +669 -0
- package/src/services/config/constants.ts +37 -1
- package/src/services/config/index.ts +45 -1
- package/src/services/config/types.ts +241 -11
- package/src/services/constants.ts +29 -0
- package/src/services/core/Err.ts +3 -0
- package/src/services/core/Utils.ts +143 -30
- package/src/services/core/ai-docs/AGENTS.md +381 -0
- package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
- package/src/services/core/ai-docs/core-spec.md +783 -0
- package/src/services/core/aqm-reqs.ts +100 -22
- package/src/services/core/websocket/WebSocketManager.ts +23 -6
- package/src/services/core/websocket/connection-service.ts +5 -1
- package/src/services/core/websocket/types.ts +1 -1
- package/src/services/index.ts +4 -0
- package/src/services/task/Task.ts +837 -0
- package/src/services/task/TaskFactory.ts +55 -0
- package/src/services/task/TaskManager.ts +793 -521
- package/src/services/task/TaskUtils.ts +314 -24
- package/src/services/task/ai-docs/AGENTS.md +457 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +594 -0
- package/src/services/task/ai-docs/task-spec.md +1319 -0
- package/src/services/task/constants.ts +23 -0
- package/src/services/task/dialer.ts +136 -1
- package/src/services/task/digital/Digital.ts +95 -0
- package/src/services/task/state-machine/TaskStateMachine.ts +1166 -0
- package/src/services/task/state-machine/actions.ts +738 -0
- package/src/services/task/state-machine/ai-docs/AGENTS.md +458 -0
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1137 -0
- package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2177 -0
- package/src/services/task/state-machine/constants.ts +172 -0
- package/src/services/task/state-machine/guards.ts +445 -0
- package/src/services/task/state-machine/index.ts +28 -0
- package/src/services/task/state-machine/types.ts +243 -0
- package/src/services/task/state-machine/uiControlsComputer.ts +961 -0
- package/src/services/task/taskDataNormalizer.ts +137 -0
- package/src/services/task/types.ts +734 -71
- package/src/services/task/voice/Voice.ts +1270 -0
- package/src/services/task/voice/WebRTC.ts +187 -0
- package/src/types.ts +205 -2
- package/src/utils/AGENTS.md +278 -0
- package/src/utils/ai-docs/utils-spec.md +381 -0
- package/src/webex.js +2 -0
- package/test/unit/spec/cc.ts +503 -43
- package/test/unit/spec/logger-proxy.ts +70 -0
- package/test/unit/spec/services/ApiAiAssistant.ts +273 -0
- 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 +85 -29
- package/test/unit/spec/services/core/Utils.ts +481 -2
- package/test/unit/spec/services/core/websocket/WebSocketManager.ts +137 -41
- package/test/unit/spec/services/core/websocket/connection-service.ts +3 -1
- 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 +1001 -1003
- package/test/unit/spec/services/task/TaskUtils.ts +235 -0
- package/test/unit/spec/services/task/dialer.ts +372 -96
- package/test/unit/spec/services/task/digital/Digital.ts +105 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +2651 -0
- package/test/unit/spec/services/task/state-machine/guards.ts +637 -0
- package/test/unit/spec/services/task/state-machine/types.ts +18 -0
- package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +2663 -0
- package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
- package/test/unit/spec/services/task/voice/Voice.ts +649 -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/src/services/task/index.ts +0 -1801
- package/test/unit/spec/services/task/index.ts +0 -2184
|
@@ -5,15 +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;
|
|
20
|
+
let mockApiAIAssistant;
|
|
16
21
|
let webSocketManagerMock;
|
|
22
|
+
let rtdWebSocketManagerMock;
|
|
17
23
|
let onSpy;
|
|
18
24
|
let offSpy;
|
|
19
25
|
let taskManager;
|
|
@@ -23,6 +29,108 @@ describe('TaskManager', () => {
|
|
|
23
29
|
let webex: WebexSDK;
|
|
24
30
|
const taskId = '0ae913a4-c857-4705-8d49-76dd3dde75e4';
|
|
25
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
|
+
|
|
26
134
|
taskDataMock = {
|
|
27
135
|
type: CC_EVENTS.AGENT_CONTACT_RESERVED,
|
|
28
136
|
agentId: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
@@ -42,9 +150,21 @@ describe('TaskManager', () => {
|
|
|
42
150
|
data: taskDataMock,
|
|
43
151
|
};
|
|
44
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
|
+
|
|
45
164
|
beforeEach(() => {
|
|
46
165
|
contactMock = contact;
|
|
47
166
|
webSocketManagerMock = new EventEmitter();
|
|
167
|
+
rtdWebSocketManagerMock = new EventEmitter();
|
|
48
168
|
|
|
49
169
|
webex = {
|
|
50
170
|
logger: {
|
|
@@ -74,15 +194,25 @@ describe('TaskManager', () => {
|
|
|
74
194
|
onSpy = jest.spyOn(webCallingService, 'on');
|
|
75
195
|
offSpy = jest.spyOn(webCallingService, 'off');
|
|
76
196
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
emit: jest.fn(),
|
|
80
|
-
accept: jest.fn(),
|
|
81
|
-
decline: jest.fn(),
|
|
82
|
-
updateTaskData: jest.fn(),
|
|
83
|
-
data: taskDataMock,
|
|
197
|
+
mockApiAIAssistant = {
|
|
198
|
+
sendEvent: jest.fn().mockResolvedValue({}),
|
|
84
199
|
};
|
|
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]);
|
|
85
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));
|
|
86
216
|
});
|
|
87
217
|
|
|
88
218
|
afterEach(() => {
|
|
@@ -93,7 +223,8 @@ describe('TaskManager', () => {
|
|
|
93
223
|
it('should initialize TaskManager and register listeners', () => {
|
|
94
224
|
webSocketManagerMock.emit('message', JSON.stringify({data: taskDataMock}));
|
|
95
225
|
const incomingCallCb = onSpy.mock.calls[0][1];
|
|
96
|
-
const
|
|
226
|
+
const incomingHandler = jest.fn();
|
|
227
|
+
taskManager.on(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
97
228
|
|
|
98
229
|
expect(taskManager).toBeInstanceOf(TaskManager);
|
|
99
230
|
expect(webCallingService.listenerCount(LINE_EVENTS.INCOMING_CALL)).toBe(1);
|
|
@@ -102,14 +233,13 @@ describe('TaskManager', () => {
|
|
|
102
233
|
|
|
103
234
|
incomingCallCb(mockCall);
|
|
104
235
|
|
|
105
|
-
expect(
|
|
236
|
+
expect(incomingHandler).toHaveBeenCalledWith(taskManager.getTask(taskId));
|
|
237
|
+
taskManager.off(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
106
238
|
});
|
|
107
239
|
|
|
108
240
|
it('should re-emit task related events', () => {
|
|
109
241
|
const dummyPayload = {
|
|
110
|
-
data: {...taskDataMock,
|
|
111
|
-
type: CC_TASK_EVENTS.AGENT_CONSULTING,
|
|
112
|
-
},
|
|
242
|
+
data: {...taskDataMock, type: CC_TASK_EVENTS.AGENT_CONSULTING},
|
|
113
243
|
};
|
|
114
244
|
webSocketManagerMock.emit('message', JSON.stringify({data: taskDataMock}));
|
|
115
245
|
const taskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
|
|
@@ -120,7 +250,186 @@ describe('TaskManager', () => {
|
|
|
120
250
|
|
|
121
251
|
webSocketManagerMock.emit('message', JSON.stringify(dummyPayload));
|
|
122
252
|
|
|
123
|
-
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
253
|
+
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
254
|
+
TASK_EVENTS.TASK_CONSULT_ACCEPTED,
|
|
255
|
+
taskManager.getTask(taskId)
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
|
|
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;
|
|
274
|
+
const message = (type: CC_EVENTS) =>
|
|
275
|
+
JSON.stringify({
|
|
276
|
+
data: {
|
|
277
|
+
...taskDataMock,
|
|
278
|
+
interactionId,
|
|
279
|
+
type,
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONTACT_ASSIGNED));
|
|
284
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULTING));
|
|
285
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULT_CONFERENCED));
|
|
286
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULT_ENDED));
|
|
287
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE));
|
|
288
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_WRAPUP));
|
|
289
|
+
|
|
290
|
+
expect(mockApiAIAssistant.sendEvent).toHaveBeenCalledTimes(6);
|
|
291
|
+
expect(mockApiAIAssistant.sendEvent).toHaveBeenCalledWith(
|
|
292
|
+
'test-agent-id',
|
|
293
|
+
interactionId,
|
|
294
|
+
'CUSTOM_EVENT',
|
|
295
|
+
'GET_TRANSCRIPTS',
|
|
296
|
+
{action: 'START'}
|
|
297
|
+
);
|
|
298
|
+
expect(mockApiAIAssistant.sendEvent).toHaveBeenCalledWith(
|
|
299
|
+
'test-agent-id',
|
|
300
|
+
interactionId,
|
|
301
|
+
'CUSTOM_EVENT',
|
|
302
|
+
'GET_TRANSCRIPTS',
|
|
303
|
+
{action: 'STOP'}
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
|
|
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
|
+
},
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const message = (type: CC_EVENTS) =>
|
|
322
|
+
JSON.stringify({
|
|
323
|
+
data: {
|
|
324
|
+
...taskDataMock,
|
|
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,
|
|
355
|
+
type,
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONTACT_ASSIGNED));
|
|
360
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULTING));
|
|
361
|
+
|
|
362
|
+
expect(mockApiAIAssistant.sendEvent).not.toHaveBeenCalled();
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('should emit REAL_TIME_TRANSCRIPTION from RTD websocket payload', () => {
|
|
366
|
+
const task = taskManager.getTask(taskId);
|
|
367
|
+
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
368
|
+
const realtimePayload = {
|
|
369
|
+
data: {
|
|
370
|
+
agentId: 'test-agent-id',
|
|
371
|
+
data: {
|
|
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',
|
|
382
|
+
},
|
|
383
|
+
notifDetails: {
|
|
384
|
+
actionEvent: 'REAL_TIME_TRANSCRIPTION',
|
|
385
|
+
},
|
|
386
|
+
notifType: 'REAL_TIME_TRANSCRIPTION',
|
|
387
|
+
orgId: 'org-id',
|
|
388
|
+
},
|
|
389
|
+
orgId: 'org-id',
|
|
390
|
+
trackingId: 'notifs_tracking-id',
|
|
391
|
+
type: 'REAL_TIME_TRANSCRIPTION',
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
taskManager.handleRealtimeWebsocketEvent(JSON.stringify(realtimePayload));
|
|
395
|
+
|
|
396
|
+
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
397
|
+
CC_EVENTS.REAL_TIME_TRANSCRIPTION,
|
|
398
|
+
realtimePayload.data
|
|
399
|
+
);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('should ignore RTD transcript events when task is not found', () => {
|
|
403
|
+
const realtimePayload = {
|
|
404
|
+
data: {
|
|
405
|
+
data: {
|
|
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',
|
|
419
|
+
},
|
|
420
|
+
notifType: 'REAL_TIME_TRANSCRIPTION',
|
|
421
|
+
orgId: 'org-id',
|
|
422
|
+
},
|
|
423
|
+
orgId: 'org-id',
|
|
424
|
+
trackingId: 'notifs_tracking-id',
|
|
425
|
+
type: 'REAL_TIME_TRANSCRIPTION',
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
const existingTaskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
|
|
429
|
+
|
|
430
|
+
taskManager.handleRealtimeWebsocketEvent(JSON.stringify(realtimePayload));
|
|
431
|
+
|
|
432
|
+
expect(existingTaskEmitSpy).not.toHaveBeenCalled();
|
|
124
433
|
});
|
|
125
434
|
|
|
126
435
|
it('should not re-emit agent related events', () => {
|
|
@@ -138,7 +447,7 @@ describe('TaskManager', () => {
|
|
|
138
447
|
|
|
139
448
|
webSocketManagerMock.emit('message', JSON.stringify(dummyPayload));
|
|
140
449
|
|
|
141
|
-
expect(taskEmitSpy).not.
|
|
450
|
+
expect(taskEmitSpy).not.toHaveBeenCalled();
|
|
142
451
|
});
|
|
143
452
|
|
|
144
453
|
it('should handle WebSocket message for AGENT_CONTACT_RESERVED and emit task:incoming for browser case', () => {
|
|
@@ -159,14 +468,12 @@ describe('TaskManager', () => {
|
|
|
159
468
|
},
|
|
160
469
|
};
|
|
161
470
|
|
|
162
|
-
const
|
|
471
|
+
const incomingHandler = jest.fn();
|
|
472
|
+
taskManager.on(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
163
473
|
|
|
164
474
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
165
475
|
|
|
166
|
-
expect(
|
|
167
|
-
TASK_EVENTS.TASK_INCOMING,
|
|
168
|
-
taskManager.getTask(payload.data.interactionId)
|
|
169
|
-
);
|
|
476
|
+
expect(incomingHandler).toHaveBeenCalledWith(taskManager.getTask(payload.data.interactionId));
|
|
170
477
|
expect(taskManager.getTask(payload.data.interactionId)).toBe(taskManager.getTask(taskId));
|
|
171
478
|
expect(taskManager.getAllTasks()).toHaveProperty(payload.data.interactionId);
|
|
172
479
|
|
|
@@ -187,7 +494,11 @@ describe('TaskManager', () => {
|
|
|
187
494
|
},
|
|
188
495
|
};
|
|
189
496
|
|
|
190
|
-
const currentTaskAssignedSpy = jest.spyOn(
|
|
497
|
+
const currentTaskAssignedSpy = jest.spyOn(
|
|
498
|
+
taskManager.getTask(payload.data.interactionId),
|
|
499
|
+
'emit'
|
|
500
|
+
);
|
|
501
|
+
const taskManagerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
191
502
|
|
|
192
503
|
webSocketManagerMock.emit('message', JSON.stringify(assignedPayload));
|
|
193
504
|
|
|
@@ -195,6 +506,10 @@ describe('TaskManager', () => {
|
|
|
195
506
|
TASK_EVENTS.TASK_ASSIGNED,
|
|
196
507
|
taskManager.getTask(taskId)
|
|
197
508
|
);
|
|
509
|
+
expect(taskManagerEmitSpy).toHaveBeenCalledWith(
|
|
510
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
511
|
+
taskManager.getTask(taskId)
|
|
512
|
+
);
|
|
198
513
|
});
|
|
199
514
|
|
|
200
515
|
it('should handle WebSocket message for AGENT_CONTACT_RESERVED and emit task:incoming for extension case', () => {
|
|
@@ -216,16 +531,64 @@ describe('TaskManager', () => {
|
|
|
216
531
|
},
|
|
217
532
|
};
|
|
218
533
|
|
|
219
|
-
const
|
|
534
|
+
const incomingHandler = jest.fn();
|
|
535
|
+
taskManager.on(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
220
536
|
|
|
221
537
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
222
538
|
|
|
223
|
-
expect(
|
|
224
|
-
TASK_EVENTS.TASK_INCOMING,
|
|
225
|
-
taskManager.getTask(taskId)
|
|
226
|
-
);
|
|
539
|
+
expect(incomingHandler).toHaveBeenCalledWith(taskManager.getTask(taskId));
|
|
227
540
|
expect(taskManager.getTask(payload.data.interactionId)).toBe(taskManager.getTask(taskId));
|
|
228
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);
|
|
229
592
|
});
|
|
230
593
|
|
|
231
594
|
it('should return task by ID', () => {
|
|
@@ -310,9 +673,30 @@ describe('TaskManager', () => {
|
|
|
310
673
|
it('test call listeners being switched off on call end', () => {
|
|
311
674
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
312
675
|
|
|
313
|
-
const
|
|
314
|
-
|
|
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');
|
|
315
698
|
const callOffSpy = jest.spyOn(mockCall, 'off');
|
|
699
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
316
700
|
const payload = {
|
|
317
701
|
data: {
|
|
318
702
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
@@ -330,12 +714,25 @@ describe('TaskManager', () => {
|
|
|
330
714
|
},
|
|
331
715
|
};
|
|
332
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
|
+
|
|
333
729
|
taskManager.getTask(taskId).data = payload.data;
|
|
334
|
-
const task = taskManager.getTask(taskId)
|
|
335
730
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
336
|
-
|
|
337
|
-
|
|
731
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
732
|
+
sendStateMachineEventSpy,
|
|
733
|
+
TaskEvent.CONTACT_ENDED
|
|
338
734
|
);
|
|
735
|
+
expect(stateMachineEvent?.taskData.wrapUpRequired).toBe(false);
|
|
339
736
|
expect(webCallListenerSpy).toHaveBeenCalledWith();
|
|
340
737
|
expect(callOffSpy).toHaveBeenCalledWith(
|
|
341
738
|
CALL_EVENT_KEYS.REMOTE_MEDIA,
|
|
@@ -346,12 +743,13 @@ describe('TaskManager', () => {
|
|
|
346
743
|
expect(offSpy.mock.calls.length).toBe(2); // 1 for incoming call and 1 for remote media
|
|
347
744
|
expect(offSpy).toHaveBeenCalledWith(CALL_EVENT_KEYS.REMOTE_MEDIA, offSpy.mock.calls[0][1]);
|
|
348
745
|
expect(offSpy).toHaveBeenCalledWith(LINE_EVENTS.INCOMING_CALL, offSpy.mock.calls[1][1]);
|
|
746
|
+
sendStateMachineEventSpy.mockRestore();
|
|
349
747
|
});
|
|
350
748
|
|
|
351
749
|
it('should emit TASK_END event with wrapupRequired on regular call end', () => {
|
|
352
750
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
353
|
-
|
|
354
|
-
const
|
|
751
|
+
const task = taskManager.getTask(taskId);
|
|
752
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
355
753
|
const payload = {
|
|
356
754
|
data: {
|
|
357
755
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
@@ -366,80 +764,77 @@ describe('TaskManager', () => {
|
|
|
366
764
|
destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
|
|
367
765
|
owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
368
766
|
queueMgr: 'aqm',
|
|
767
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
369
768
|
},
|
|
370
769
|
};
|
|
371
770
|
|
|
372
|
-
|
|
771
|
+
task.updateTaskData(payload.data);
|
|
373
772
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
);
|
|
378
|
-
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
379
|
-
TASK_EVENTS.TASK_END,
|
|
380
|
-
taskManager.getTask(taskId)
|
|
773
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
774
|
+
sendStateMachineEventSpy,
|
|
775
|
+
TaskEvent.CONTACT_ENDED
|
|
381
776
|
);
|
|
777
|
+
expect(stateMachineEvent?.taskData.wrapUpRequired).toBe(true);
|
|
778
|
+
sendStateMachineEventSpy.mockRestore();
|
|
382
779
|
});
|
|
383
780
|
|
|
384
781
|
it('should emit TASK_REJECT event on AGENT_INVITE_FAILED event', () => {
|
|
385
782
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
386
783
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
784
|
+
const task = taskManager.getTask(taskId);
|
|
785
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
786
|
+
const payload = {
|
|
787
|
+
data: {
|
|
788
|
+
type: CC_EVENTS.AGENT_INVITE_FAILED,
|
|
789
|
+
agentId: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
790
|
+
eventTime: 1733211616959,
|
|
791
|
+
eventType: 'RoutingMessage',
|
|
792
|
+
interaction: {state: 'connected'},
|
|
793
|
+
interactionId: taskId,
|
|
794
|
+
orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a',
|
|
795
|
+
trackingId: '575c0ec2-618c-42af-a61c-53aeb0a221ee',
|
|
796
|
+
mediaResourceId: '0ae913a4-c857-4705-8d49-76dd3dde75e4',
|
|
797
|
+
destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
|
|
798
|
+
owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
799
|
+
queueMgr: 'aqm',
|
|
800
|
+
reason: 'INVITE_FAILED',
|
|
801
|
+
},
|
|
802
|
+
};
|
|
406
803
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
payload.data.reason
|
|
416
|
-
);
|
|
417
|
-
// Verify the correct metric event name is used for AGENT_INVITE_FAILED
|
|
418
|
-
expect(metricsTrackSpy).toHaveBeenCalled();
|
|
419
|
-
expect(metricsTrackSpy.mock.calls[0][0]).toBe('Agent Invite Failed');
|
|
804
|
+
task.updateTaskData(payload.data);
|
|
805
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
806
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
807
|
+
sendStateMachineEventSpy,
|
|
808
|
+
TaskEvent.INVITE_FAILED
|
|
809
|
+
);
|
|
810
|
+
expect(stateMachineEvent?.reason).toBe(payload.data.reason);
|
|
811
|
+
sendStateMachineEventSpy.mockRestore();
|
|
420
812
|
});
|
|
421
813
|
|
|
422
|
-
|
|
423
|
-
it('should not emit TASK_HYDRATE if task is already present in taskManager', () => {
|
|
814
|
+
it('should emit TASK_HYDRATE even if task is already present in taskManager', () => {
|
|
424
815
|
const payload = {
|
|
425
816
|
data: {
|
|
426
817
|
...initalPayload.data,
|
|
427
818
|
type: CC_EVENTS.AGENT_CONTACT,
|
|
428
819
|
},
|
|
429
820
|
};
|
|
430
|
-
const
|
|
821
|
+
const existingTask = taskManager.getTask(taskId);
|
|
822
|
+
const sendStateMachineEventSpy = jest.spyOn(existingTask, 'sendStateMachineEvent');
|
|
431
823
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
432
824
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
825
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
826
|
+
sendStateMachineEventSpy,
|
|
827
|
+
TaskEvent.HYDRATE
|
|
436
828
|
);
|
|
829
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
830
|
+
expect(existingTask).toBe(taskManager.getTask(taskId));
|
|
437
831
|
expect(taskManager.taskCollection[payload.data.interactionId]).toBe(
|
|
438
832
|
taskManager.getTask(taskId)
|
|
439
833
|
);
|
|
834
|
+
sendStateMachineEventSpy.mockRestore();
|
|
440
835
|
});
|
|
441
836
|
|
|
442
|
-
it('should emit
|
|
837
|
+
it('should emit TASK_HYDRATE event on AGENT_CONTACT when task is created from payload', () => {
|
|
443
838
|
taskManager.taskCollection = [];
|
|
444
839
|
const payload = {
|
|
445
840
|
data: {
|
|
@@ -449,13 +844,15 @@ describe('TaskManager', () => {
|
|
|
449
844
|
},
|
|
450
845
|
};
|
|
451
846
|
|
|
452
|
-
const taskEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
453
847
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
454
848
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
849
|
+
const createdTask = taskManager.getTask(taskId);
|
|
850
|
+
const sendStateMachineEventSpy = createdTask.sendStateMachineEvent as jest.Mock;
|
|
851
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
852
|
+
sendStateMachineEventSpy,
|
|
853
|
+
TaskEvent.HYDRATE
|
|
458
854
|
);
|
|
855
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
459
856
|
expect(taskManager.taskCollection[payload.data.interactionId]).toBe(
|
|
460
857
|
taskManager.getTask(taskId)
|
|
461
858
|
);
|
|
@@ -470,10 +867,15 @@ describe('TaskManager', () => {
|
|
|
470
867
|
},
|
|
471
868
|
};
|
|
472
869
|
|
|
473
|
-
const taskEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
474
870
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
475
871
|
|
|
476
|
-
|
|
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);
|
|
477
879
|
expect(taskManager.taskCollection[payload.data.interactionId]).toBe(
|
|
478
880
|
taskManager.getTask(taskId)
|
|
479
881
|
);
|
|
@@ -483,7 +885,7 @@ describe('TaskManager', () => {
|
|
|
483
885
|
const testAgentId = '723a8ffb-a26e-496d-b14a-ff44fb83b64f';
|
|
484
886
|
taskManager.setAgentId(testAgentId);
|
|
485
887
|
taskManager.taskCollection = [];
|
|
486
|
-
|
|
888
|
+
|
|
487
889
|
const payload = {
|
|
488
890
|
data: {
|
|
489
891
|
...initalPayload.data,
|
|
@@ -492,9 +894,9 @@ describe('TaskManager', () => {
|
|
|
492
894
|
mediaType: 'telephony',
|
|
493
895
|
state: 'conference',
|
|
494
896
|
participants: {
|
|
495
|
-
[testAgentId]: {
|
|
496
|
-
'agent-2': {
|
|
497
|
-
'customer-1': {
|
|
897
|
+
[testAgentId]: {pType: 'Agent', hasLeft: false},
|
|
898
|
+
'agent-2': {pType: 'Agent', hasLeft: false},
|
|
899
|
+
'customer-1': {pType: 'Customer', hasLeft: false},
|
|
498
900
|
},
|
|
499
901
|
media: {
|
|
500
902
|
[taskId]: {
|
|
@@ -517,7 +919,7 @@ describe('TaskManager', () => {
|
|
|
517
919
|
const testAgentId = '723a8ffb-a26e-496d-b14a-ff44fb83b64f';
|
|
518
920
|
taskManager.setAgentId(testAgentId);
|
|
519
921
|
taskManager.taskCollection = [];
|
|
520
|
-
|
|
922
|
+
|
|
521
923
|
const payload = {
|
|
522
924
|
data: {
|
|
523
925
|
...initalPayload.data,
|
|
@@ -526,8 +928,8 @@ describe('TaskManager', () => {
|
|
|
526
928
|
mediaType: 'telephony',
|
|
527
929
|
state: 'connected',
|
|
528
930
|
participants: {
|
|
529
|
-
[testAgentId]: {
|
|
530
|
-
'customer-1': {
|
|
931
|
+
[testAgentId]: {pType: 'Agent', hasLeft: false},
|
|
932
|
+
'customer-1': {pType: 'Customer', hasLeft: false},
|
|
531
933
|
},
|
|
532
934
|
media: {
|
|
533
935
|
[taskId]: {
|
|
@@ -546,7 +948,7 @@ describe('TaskManager', () => {
|
|
|
546
948
|
expect(createdTask.data.isConferenceInProgress).toBe(false);
|
|
547
949
|
});
|
|
548
950
|
|
|
549
|
-
it('should emit
|
|
951
|
+
it('should emit TASK_WRAPUP event on AGENT_WRAPUP event', () => {
|
|
550
952
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
551
953
|
|
|
552
954
|
const wrapupPayload = {
|
|
@@ -563,18 +965,20 @@ describe('TaskManager', () => {
|
|
|
563
965
|
destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
|
|
564
966
|
owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
565
967
|
queueMgr: 'aqm',
|
|
566
|
-
wrapUpRequired: true
|
|
968
|
+
wrapUpRequired: true,
|
|
567
969
|
},
|
|
568
970
|
};
|
|
569
971
|
|
|
570
972
|
const task = taskManager.getTask(taskId);
|
|
571
|
-
const updateTaskDataSpy = jest.
|
|
572
|
-
|
|
973
|
+
const updateTaskDataSpy = task.updateTaskData as jest.Mock;
|
|
974
|
+
updateTaskDataSpy.mockClear();
|
|
975
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
573
976
|
|
|
574
977
|
webSocketManagerMock.emit('message', JSON.stringify(wrapupPayload));
|
|
575
978
|
|
|
576
979
|
expect(updateTaskDataSpy).toHaveBeenCalledWith(wrapupPayload.data);
|
|
577
|
-
|
|
980
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.TASK_WRAPUP);
|
|
981
|
+
sendStateMachineEventSpy.mockRestore();
|
|
578
982
|
});
|
|
579
983
|
|
|
580
984
|
it('should emit TASK_HOLD event on AGENT_CONTACT_HELD event', () => {
|
|
@@ -597,13 +1001,20 @@ describe('TaskManager', () => {
|
|
|
597
1001
|
},
|
|
598
1002
|
};
|
|
599
1003
|
|
|
600
|
-
const
|
|
601
|
-
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');
|
|
602
1008
|
|
|
603
1009
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
604
1010
|
|
|
605
1011
|
expect(taskUpdateTaskDataSpy).toHaveBeenCalledWith(payload.data);
|
|
606
|
-
|
|
1012
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1013
|
+
sendStateMachineEventSpy,
|
|
1014
|
+
TaskEvent.HOLD_SUCCESS
|
|
1015
|
+
);
|
|
1016
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1017
|
+
sendStateMachineEventSpy.mockRestore();
|
|
607
1018
|
});
|
|
608
1019
|
|
|
609
1020
|
it('should emit TASK_RESUME event on AGENT_CONTACT_UNHELD event', () => {
|
|
@@ -626,11 +1037,18 @@ describe('TaskManager', () => {
|
|
|
626
1037
|
},
|
|
627
1038
|
};
|
|
628
1039
|
|
|
629
|
-
const
|
|
630
|
-
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');
|
|
631
1044
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
632
1045
|
expect(taskUpdateTaskDataSpy).toHaveBeenCalledWith(payload.data);
|
|
633
|
-
|
|
1046
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1047
|
+
sendStateMachineEventSpy,
|
|
1048
|
+
TaskEvent.UNHOLD_SUCCESS
|
|
1049
|
+
);
|
|
1050
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1051
|
+
sendStateMachineEventSpy.mockRestore();
|
|
634
1052
|
});
|
|
635
1053
|
|
|
636
1054
|
it('handle AGENT_CONSULT_CREATED event', () => {
|
|
@@ -643,17 +1061,16 @@ describe('TaskManager', () => {
|
|
|
643
1061
|
|
|
644
1062
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
645
1063
|
const task = taskManager.getTask(taskId);
|
|
646
|
-
const
|
|
647
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
1064
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
648
1065
|
|
|
649
1066
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
650
1067
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
expect(
|
|
656
|
-
|
|
1068
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1069
|
+
sendStateMachineEventSpy,
|
|
1070
|
+
TaskEvent.CONSULT_CREATED
|
|
1071
|
+
);
|
|
1072
|
+
expect(stateMachineEvent?.taskData).toEqual({...payload.data, isConsulted: false});
|
|
1073
|
+
sendStateMachineEventSpy.mockRestore();
|
|
657
1074
|
});
|
|
658
1075
|
|
|
659
1076
|
it('handle AGENT_OFFER_CONTACT event', () => {
|
|
@@ -666,11 +1083,17 @@ describe('TaskManager', () => {
|
|
|
666
1083
|
|
|
667
1084
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
668
1085
|
|
|
669
|
-
const
|
|
1086
|
+
const task = taskManager.getTask(taskId);
|
|
1087
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
670
1088
|
|
|
671
1089
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
672
1090
|
|
|
673
|
-
|
|
1091
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1092
|
+
sendStateMachineEventSpy,
|
|
1093
|
+
TaskEvent.TASK_OFFERED
|
|
1094
|
+
);
|
|
1095
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1096
|
+
sendStateMachineEventSpy.mockRestore();
|
|
674
1097
|
});
|
|
675
1098
|
|
|
676
1099
|
describe('Auto-Answer Functionality', () => {
|
|
@@ -680,8 +1103,8 @@ describe('TaskManager', () => {
|
|
|
680
1103
|
|
|
681
1104
|
const task = taskManager.getTask(taskId);
|
|
682
1105
|
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
683
|
-
const taskManagerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
684
1106
|
const taskAcceptSpy = jest.spyOn(task, 'accept').mockResolvedValue(undefined);
|
|
1107
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
685
1108
|
|
|
686
1109
|
// Step 2: Trigger AGENT_OFFER_CONTACT with auto-answer
|
|
687
1110
|
const autoAnswerPayload = {
|
|
@@ -705,9 +1128,14 @@ describe('TaskManager', () => {
|
|
|
705
1128
|
// Verify accept was called
|
|
706
1129
|
expect(taskAcceptSpy).toHaveBeenCalledTimes(1);
|
|
707
1130
|
|
|
708
|
-
|
|
709
|
-
|
|
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
|
|
710
1137
|
expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_AUTO_ANSWERED, task);
|
|
1138
|
+
sendStateMachineEventSpy.mockRestore();
|
|
711
1139
|
});
|
|
712
1140
|
|
|
713
1141
|
it('should NOT emit TASK_AUTO_ANSWERED event when auto-answer fails', async () => {
|
|
@@ -716,7 +1144,9 @@ describe('TaskManager', () => {
|
|
|
716
1144
|
|
|
717
1145
|
const task = taskManager.getTask(taskId);
|
|
718
1146
|
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
719
|
-
const taskAcceptSpy = jest
|
|
1147
|
+
const taskAcceptSpy = jest
|
|
1148
|
+
.spyOn(task, 'accept')
|
|
1149
|
+
.mockRejectedValue(new Error('Accept failed'));
|
|
720
1150
|
|
|
721
1151
|
// Step 2: Trigger AGENT_OFFER_CONTACT with auto-answer (will fail)
|
|
722
1152
|
const autoAnswerPayload = {
|
|
@@ -751,6 +1181,7 @@ describe('TaskManager', () => {
|
|
|
751
1181
|
const task = taskManager.getTask(taskId);
|
|
752
1182
|
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
753
1183
|
const taskAcceptSpy = jest.spyOn(task, 'accept').mockResolvedValue(undefined);
|
|
1184
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
754
1185
|
|
|
755
1186
|
// Step 2: Trigger AGENT_OFFER_CONSULT with auto-answer
|
|
756
1187
|
const consultAutoAnswerPayload = {
|
|
@@ -775,12 +1206,20 @@ describe('TaskManager', () => {
|
|
|
775
1206
|
// Verify accept was called
|
|
776
1207
|
expect(taskAcceptSpy).toHaveBeenCalledTimes(1);
|
|
777
1208
|
|
|
778
|
-
|
|
779
|
-
|
|
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
|
|
780
1218
|
expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_AUTO_ANSWERED, task);
|
|
781
|
-
|
|
1219
|
+
|
|
782
1220
|
// Verify isConsulted flag is set correctly
|
|
783
1221
|
expect(task.data.isConsulted).toBe(true);
|
|
1222
|
+
sendStateMachineEventSpy.mockRestore();
|
|
784
1223
|
});
|
|
785
1224
|
|
|
786
1225
|
it('should NOT emit TASK_AUTO_ANSWERED when isAutoAnswering is false', async () => {
|
|
@@ -814,28 +1253,27 @@ describe('TaskManager', () => {
|
|
|
814
1253
|
expect(taskAcceptSpy).not.toHaveBeenCalled();
|
|
815
1254
|
|
|
816
1255
|
// Verify TASK_AUTO_ANSWERED event was NOT emitted
|
|
817
|
-
expect(taskEmitSpy).not.toHaveBeenCalledWith(
|
|
1256
|
+
expect(taskEmitSpy).not.toHaveBeenCalledWith(
|
|
1257
|
+
TASK_EVENTS.TASK_AUTO_ANSWERED,
|
|
1258
|
+
expect.anything()
|
|
1259
|
+
);
|
|
818
1260
|
});
|
|
819
1261
|
});
|
|
820
1262
|
|
|
821
|
-
it('should
|
|
1263
|
+
it('should remove OUTDIAL task from taskCollection on AGENT_OUTBOUND_FAILED when terminated', () => {
|
|
822
1264
|
const task = taskManager.getTask(taskId);
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
...task.data,
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
state: 'new',
|
|
832
|
-
isTerminated: true,
|
|
833
|
-
},
|
|
834
|
-
};
|
|
835
|
-
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'],
|
|
836
1273
|
});
|
|
837
1274
|
task.unregisterWebCallListeners = jest.fn();
|
|
838
1275
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
1276
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
839
1277
|
|
|
840
1278
|
const payload = {
|
|
841
1279
|
data: {
|
|
@@ -848,6 +1286,7 @@ describe('TaskManager', () => {
|
|
|
848
1286
|
state: 'new',
|
|
849
1287
|
isTerminated: true,
|
|
850
1288
|
},
|
|
1289
|
+
agentsPendingWrapUp: ['agent-123'],
|
|
851
1290
|
interactionId: taskId,
|
|
852
1291
|
orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a',
|
|
853
1292
|
trackingId: '575c0ec2-618c-42af-a61c-53aeb0a221ee',
|
|
@@ -862,23 +1301,61 @@ describe('TaskManager', () => {
|
|
|
862
1301
|
|
|
863
1302
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
864
1303
|
|
|
865
|
-
|
|
866
|
-
|
|
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();
|
|
867
1314
|
});
|
|
868
1315
|
|
|
869
1316
|
it('should emit TASK_OUTDIAL_FAILED event on AGENT_OUTBOUND_FAILED', () => {
|
|
870
1317
|
const task = taskManager.getTask(taskId);
|
|
871
|
-
|
|
872
|
-
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');
|
|
873
1338
|
const payload = {
|
|
874
1339
|
data: {
|
|
875
1340
|
type: CC_EVENTS.AGENT_OUTBOUND_FAILED,
|
|
876
1341
|
interactionId: taskId,
|
|
877
1342
|
reason: 'CUSTOMER_BUSY',
|
|
1343
|
+
agentsPendingWrapUp: ['agent-123'],
|
|
1344
|
+
interaction: {
|
|
1345
|
+
outboundType: 'OUTDIAL',
|
|
1346
|
+
isTerminated: true,
|
|
1347
|
+
},
|
|
878
1348
|
},
|
|
879
1349
|
};
|
|
880
1350
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
881
|
-
|
|
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();
|
|
882
1359
|
});
|
|
883
1360
|
|
|
884
1361
|
it('should handle AGENT_OUTBOUND_FAILED gracefully when task is undefined', () => {
|
|
@@ -897,19 +1374,14 @@ describe('TaskManager', () => {
|
|
|
897
1374
|
|
|
898
1375
|
it('should NOT remove OUTDIAL task on CONTACT_ENDED when agentsPendingWrapUp exists', () => {
|
|
899
1376
|
const task = taskManager.getTask(taskId);
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
...task.data,
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
mediaType: 'telephony',
|
|
909
|
-
},
|
|
910
|
-
agentsPendingWrapUp: ['agent-123'],
|
|
911
|
-
};
|
|
912
|
-
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'],
|
|
913
1385
|
});
|
|
914
1386
|
task.unregisterWebCallListeners = jest.fn();
|
|
915
1387
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
@@ -923,7 +1395,7 @@ describe('TaskManager', () => {
|
|
|
923
1395
|
state: 'new',
|
|
924
1396
|
mediaType: 'telephony',
|
|
925
1397
|
},
|
|
926
|
-
agentsPendingWrapUp: ['agent-
|
|
1398
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
927
1399
|
},
|
|
928
1400
|
};
|
|
929
1401
|
|
|
@@ -935,19 +1407,14 @@ describe('TaskManager', () => {
|
|
|
935
1407
|
|
|
936
1408
|
it('should remove OUTDIAL task on CONTACT_ENDED when agentsPendingWrapUp is empty', () => {
|
|
937
1409
|
const task = taskManager.getTask(taskId);
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
...task.data,
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
mediaType: 'telephony',
|
|
947
|
-
},
|
|
948
|
-
agentsPendingWrapUp: [],
|
|
949
|
-
};
|
|
950
|
-
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: [],
|
|
951
1418
|
});
|
|
952
1419
|
task.unregisterWebCallListeners = jest.fn();
|
|
953
1420
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
@@ -972,19 +1439,13 @@ describe('TaskManager', () => {
|
|
|
972
1439
|
|
|
973
1440
|
it('should remove OUTDIAL task on CONTACT_ENDED when agentsPendingWrapUp is undefined', () => {
|
|
974
1441
|
const task = taskManager.getTask(taskId);
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
...task.data,
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
state: 'new',
|
|
983
|
-
mediaType: 'telephony',
|
|
984
|
-
},
|
|
985
|
-
// agentsPendingWrapUp is undefined
|
|
986
|
-
};
|
|
987
|
-
return task;
|
|
1442
|
+
Object.assign(task.data, {
|
|
1443
|
+
interaction: {
|
|
1444
|
+
...task.data.interaction,
|
|
1445
|
+
outboundType: 'OUTDIAL',
|
|
1446
|
+
state: 'new',
|
|
1447
|
+
mediaType: 'telephony',
|
|
1448
|
+
},
|
|
988
1449
|
});
|
|
989
1450
|
task.unregisterWebCallListeners = jest.fn();
|
|
990
1451
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
@@ -1025,19 +1486,13 @@ describe('TaskManager', () => {
|
|
|
1025
1486
|
|
|
1026
1487
|
it('should remove OUTDIAL task from taskCollection on AGENT_CONTACT_ASSIGN_FAILED when NOT terminated (user-declined)', () => {
|
|
1027
1488
|
const task = taskManager.getTask(taskId);
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
...task.data,
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
outboundType: 'OUTDIAL',
|
|
1036
|
-
state: 'new',
|
|
1037
|
-
isTerminated: false,
|
|
1038
|
-
},
|
|
1039
|
-
};
|
|
1040
|
-
return task;
|
|
1489
|
+
Object.assign(task.data, {
|
|
1490
|
+
interaction: {
|
|
1491
|
+
...task.data.interaction,
|
|
1492
|
+
outboundType: 'OUTDIAL',
|
|
1493
|
+
state: 'new',
|
|
1494
|
+
isTerminated: false,
|
|
1495
|
+
},
|
|
1041
1496
|
});
|
|
1042
1497
|
task.unregisterWebCallListeners = jest.fn();
|
|
1043
1498
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
@@ -1081,20 +1536,16 @@ describe('TaskManager', () => {
|
|
|
1081
1536
|
|
|
1082
1537
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1083
1538
|
const task = taskManager.getTask(taskId);
|
|
1084
|
-
|
|
1085
|
-
task.data = {...newData, isConsulted: true};
|
|
1086
|
-
return task;
|
|
1087
|
-
});
|
|
1088
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
1539
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1089
1540
|
|
|
1090
1541
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1091
1542
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
expect(
|
|
1097
|
-
|
|
1543
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1544
|
+
sendStateMachineEventSpy,
|
|
1545
|
+
TaskEvent.OFFER_CONSULT
|
|
1546
|
+
);
|
|
1547
|
+
expect(stateMachineEvent?.taskData).toEqual({...payload.data, isConsulted: true});
|
|
1548
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1098
1549
|
});
|
|
1099
1550
|
|
|
1100
1551
|
it('should emit TASK_CONSULT_ACCEPTED event on AGENT_CONSULTING event', () => {
|
|
@@ -1113,19 +1564,18 @@ describe('TaskManager', () => {
|
|
|
1113
1564
|
};
|
|
1114
1565
|
|
|
1115
1566
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1116
|
-
taskManager.getTask(taskId).updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
1117
|
-
taskManager.getTask(taskId).data = {...newData, isConsulted: true};
|
|
1118
|
-
return taskManager.getTask(taskId);
|
|
1119
|
-
});
|
|
1120
1567
|
|
|
1121
|
-
const
|
|
1568
|
+
const task = taskManager.getTask(taskId);
|
|
1569
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1570
|
+
|
|
1122
1571
|
webSocketManagerMock.emit('message', JSON.stringify(initialConsultingPayload));
|
|
1123
1572
|
webSocketManagerMock.emit('message', JSON.stringify(consultingPayload));
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
taskManager.getTask(taskId)
|
|
1573
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1574
|
+
sendStateMachineEventSpy,
|
|
1575
|
+
TaskEvent.CONSULTING_ACTIVE
|
|
1128
1576
|
);
|
|
1577
|
+
expect(stateMachineEvent?.taskData).toEqual(consultingPayload.data);
|
|
1578
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1129
1579
|
});
|
|
1130
1580
|
|
|
1131
1581
|
it('should emit TASK_CONSULT_ENDED event on AGENT_CONSULT_ENDED event', () => {
|
|
@@ -1137,11 +1587,16 @@ describe('TaskManager', () => {
|
|
|
1137
1587
|
};
|
|
1138
1588
|
|
|
1139
1589
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1140
|
-
|
|
1141
|
-
const
|
|
1590
|
+
taskManager.getTask(taskId).data.isConsulted = true;
|
|
1591
|
+
const task = taskManager.getTask(taskId);
|
|
1592
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1142
1593
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1143
|
-
|
|
1144
|
-
|
|
1594
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1595
|
+
sendStateMachineEventSpy,
|
|
1596
|
+
TaskEvent.CONSULT_END
|
|
1597
|
+
);
|
|
1598
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1599
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1145
1600
|
});
|
|
1146
1601
|
|
|
1147
1602
|
it('should emit TASK_CONSULT_ENDED event and remove currentTask when on AGENT_CONSULT_ENDED event when requested for a consult', () => {
|
|
@@ -1154,19 +1609,14 @@ describe('TaskManager', () => {
|
|
|
1154
1609
|
|
|
1155
1610
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1156
1611
|
|
|
1157
|
-
taskManager.getTask(taskId).
|
|
1158
|
-
taskManager.getTask(taskId).data = {...newData, isConsulted: true};
|
|
1159
|
-
return taskManager.getTask(taskId);
|
|
1160
|
-
});
|
|
1612
|
+
taskManager.getTask(taskId).data.isConsulted = true;
|
|
1161
1613
|
const task = taskManager.getTask(taskId);
|
|
1162
1614
|
|
|
1163
|
-
const
|
|
1164
|
-
const taskUpdateTaskDataSpy = jest.spyOn(taskManager.getTask(taskId), 'updateTaskData');
|
|
1615
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1165
1616
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1166
|
-
|
|
1167
|
-
expect(taskEmitSpy).toHaveBeenCalledWith(CC_EVENTS.AGENT_CONSULT_ENDED, payload.data);
|
|
1168
|
-
expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONSULT_END, task);
|
|
1617
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.CONSULT_END);
|
|
1169
1618
|
expect(taskManager.getTask(taskId)).toBeUndefined(); // Ensure task is removed from the task collection after the consult ends
|
|
1619
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1170
1620
|
});
|
|
1171
1621
|
|
|
1172
1622
|
it('should emit TASK_CANCELLED event on AGENT_CTQ_CANCELLED event', () => {
|
|
@@ -1178,14 +1628,15 @@ describe('TaskManager', () => {
|
|
|
1178
1628
|
};
|
|
1179
1629
|
|
|
1180
1630
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1181
|
-
const
|
|
1182
|
-
const
|
|
1631
|
+
const task = taskManager.getTask(taskId);
|
|
1632
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1183
1633
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
taskManager.getTask(taskId)
|
|
1634
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1635
|
+
sendStateMachineEventSpy,
|
|
1636
|
+
TaskEvent.CTQ_CANCEL
|
|
1188
1637
|
);
|
|
1638
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1639
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1189
1640
|
});
|
|
1190
1641
|
|
|
1191
1642
|
it('should handle AGENT_CONSULT_FAILED event', () => {
|
|
@@ -1199,9 +1650,15 @@ describe('TaskManager', () => {
|
|
|
1199
1650
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1200
1651
|
|
|
1201
1652
|
// Always spy on the updated task object after CONTACT_RESERVED is emitted
|
|
1202
|
-
const
|
|
1653
|
+
const task = taskManager.getTask(taskId);
|
|
1654
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1203
1655
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1204
|
-
|
|
1656
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1657
|
+
sendStateMachineEventSpy,
|
|
1658
|
+
TaskEvent.CONSULT_FAILED
|
|
1659
|
+
);
|
|
1660
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1661
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1205
1662
|
});
|
|
1206
1663
|
|
|
1207
1664
|
it('should emit TASK_CONSULT_QUEUE_FAILED on AGENT_CTQ_CANCEL_FAILED event', () => {
|
|
@@ -1213,14 +1670,15 @@ describe('TaskManager', () => {
|
|
|
1213
1670
|
};
|
|
1214
1671
|
|
|
1215
1672
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1216
|
-
const
|
|
1217
|
-
const
|
|
1673
|
+
const task = taskManager.getTask(taskId);
|
|
1674
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1218
1675
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
taskManager.getTask(taskId)
|
|
1676
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1677
|
+
sendStateMachineEventSpy,
|
|
1678
|
+
TaskEvent.CTQ_CANCEL_FAILED
|
|
1223
1679
|
);
|
|
1680
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1681
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1224
1682
|
});
|
|
1225
1683
|
|
|
1226
1684
|
it('should emit TASK_REJECT event on AGENT_CONTACT_OFFER_RONA event', () => {
|
|
@@ -1263,15 +1721,14 @@ describe('TaskManager', () => {
|
|
|
1263
1721
|
};
|
|
1264
1722
|
|
|
1265
1723
|
taskManager.taskCollection[taskId] = taskManager.getTask(taskId);
|
|
1266
|
-
const
|
|
1267
|
-
const
|
|
1724
|
+
const task = taskManager.getTask(taskId);
|
|
1725
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1268
1726
|
|
|
1269
1727
|
webSocketManagerMock.emit('message', JSON.stringify(ronaPayload));
|
|
1270
1728
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
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();
|
|
1275
1732
|
});
|
|
1276
1733
|
|
|
1277
1734
|
it('should emit TASK_REJECT event on AGENT_CONTACT_ASSIGN_FAILED event', () => {
|
|
@@ -1315,17 +1772,16 @@ describe('TaskManager', () => {
|
|
|
1315
1772
|
|
|
1316
1773
|
taskManager.taskCollection[taskId] = taskManager.getTask(taskId);
|
|
1317
1774
|
const task = taskManager.getTask(taskId);
|
|
1318
|
-
const
|
|
1319
|
-
const taskUpdateDataSpy = jest.spyOn(task, 'updateTaskData');
|
|
1320
|
-
const metricsTrackSpy = jest.spyOn(taskManager.metricsManager, 'trackEvent');
|
|
1775
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1321
1776
|
|
|
1322
1777
|
webSocketManagerMock.emit('message', JSON.stringify(assignFailedPayload));
|
|
1323
1778
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
expect(
|
|
1779
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1780
|
+
sendStateMachineEventSpy,
|
|
1781
|
+
TaskEvent.ASSIGN_FAILED
|
|
1782
|
+
);
|
|
1783
|
+
expect(stateMachineEvent?.reason).toBe(assignFailedPayload.data.reason);
|
|
1784
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1329
1785
|
});
|
|
1330
1786
|
|
|
1331
1787
|
it('should remove currentTask from taskCollection on AGENT_WRAPPEDUP event', () => {
|
|
@@ -1348,11 +1804,12 @@ describe('TaskManager', () => {
|
|
|
1348
1804
|
|
|
1349
1805
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1350
1806
|
const task = taskManager.getTask(taskId);
|
|
1351
|
-
const
|
|
1807
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1352
1808
|
|
|
1353
1809
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1354
1810
|
|
|
1355
|
-
|
|
1811
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.WRAPUP_COMPLETE);
|
|
1812
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1356
1813
|
expect(taskManager.getTask(taskId)).toBeUndefined();
|
|
1357
1814
|
});
|
|
1358
1815
|
|
|
@@ -1378,7 +1835,8 @@ describe('TaskManager', () => {
|
|
|
1378
1835
|
};
|
|
1379
1836
|
|
|
1380
1837
|
const taskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
|
|
1381
|
-
const taskUpdateTaskDataSpy =
|
|
1838
|
+
const taskUpdateTaskDataSpy = taskManager.getTask(taskId).updateTaskData as jest.Mock;
|
|
1839
|
+
taskUpdateTaskDataSpy.mockClear();
|
|
1382
1840
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1383
1841
|
expect(taskEmitSpy).not.toHaveBeenCalled();
|
|
1384
1842
|
expect(taskUpdateTaskDataSpy).not.toHaveBeenCalled();
|
|
@@ -1387,7 +1845,9 @@ describe('TaskManager', () => {
|
|
|
1387
1845
|
it('should emit TASK_CONSULTING event when agent is consulting', () => {
|
|
1388
1846
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1389
1847
|
taskManager.getTask(taskId).data.isConsulted = false;
|
|
1390
|
-
const
|
|
1848
|
+
const task = taskManager.getTask(taskId);
|
|
1849
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1850
|
+
const taskManagerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
1391
1851
|
const consultingPayload = {
|
|
1392
1852
|
data: {
|
|
1393
1853
|
...initalPayload.data,
|
|
@@ -1396,13 +1856,21 @@ describe('TaskManager', () => {
|
|
|
1396
1856
|
},
|
|
1397
1857
|
};
|
|
1398
1858
|
webSocketManagerMock.emit('message', JSON.stringify(consultingPayload));
|
|
1399
|
-
|
|
1400
|
-
expect(
|
|
1859
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.CONSULTING_ACTIVE);
|
|
1860
|
+
expect(taskManagerEmitSpy).toHaveBeenCalledWith(
|
|
1861
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
1862
|
+
taskManager.getTask(taskId)
|
|
1863
|
+
);
|
|
1864
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1401
1865
|
});
|
|
1402
1866
|
|
|
1403
|
-
it('should
|
|
1867
|
+
it('should update task data on AGENT_CONTACT_UNASSIGNED', () => {
|
|
1404
1868
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1405
|
-
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();
|
|
1406
1874
|
const unassignedPayload = {
|
|
1407
1875
|
data: {
|
|
1408
1876
|
type: CC_EVENTS.AGENT_CONTACT_UNASSIGNED,
|
|
@@ -1420,8 +1888,76 @@ describe('TaskManager', () => {
|
|
|
1420
1888
|
},
|
|
1421
1889
|
};
|
|
1422
1890
|
webSocketManagerMock.emit('message', JSON.stringify(unassignedPayload));
|
|
1423
|
-
expect(
|
|
1424
|
-
expect(
|
|
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
|
+
})
|
|
1960
|
+
);
|
|
1425
1961
|
});
|
|
1426
1962
|
|
|
1427
1963
|
it('should handle chat interaction and emit TASK_INCOMING immediately', () => {
|
|
@@ -1429,20 +1965,20 @@ describe('TaskManager', () => {
|
|
|
1429
1965
|
const chatPayload = {
|
|
1430
1966
|
data: {
|
|
1431
1967
|
...initalPayload.data,
|
|
1432
|
-
interaction: {
|
|
1968
|
+
interaction: {mediaType: 'chat'},
|
|
1433
1969
|
},
|
|
1434
1970
|
};
|
|
1435
1971
|
|
|
1436
|
-
const taskIncomingSpy = jest.spyOn(taskManager, 'emit');
|
|
1437
|
-
|
|
1438
1972
|
// Simulate receiving a chat task
|
|
1439
1973
|
webSocketManagerMock.emit('message', JSON.stringify(chatPayload));
|
|
1440
1974
|
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
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
|
|
1445
1980
|
);
|
|
1981
|
+
expect(stateMachineEvent?.taskData).toEqual(chatPayload.data);
|
|
1446
1982
|
expect(taskManager.getAllTasks()).toHaveProperty(chatPayload.data.interactionId);
|
|
1447
1983
|
});
|
|
1448
1984
|
|
|
@@ -1451,20 +1987,20 @@ describe('TaskManager', () => {
|
|
|
1451
1987
|
const emailPayload = {
|
|
1452
1988
|
data: {
|
|
1453
1989
|
...initalPayload.data,
|
|
1454
|
-
interaction: {
|
|
1990
|
+
interaction: {mediaType: 'email'},
|
|
1455
1991
|
},
|
|
1456
1992
|
};
|
|
1457
1993
|
|
|
1458
|
-
const taskIncomingSpy = jest.spyOn(taskManager, 'emit');
|
|
1459
|
-
|
|
1460
1994
|
// Simulate receiving an email task
|
|
1461
1995
|
webSocketManagerMock.emit('message', JSON.stringify(emailPayload));
|
|
1462
1996
|
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
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
|
|
1467
2002
|
);
|
|
2003
|
+
expect(stateMachineEvent?.taskData).toEqual(emailPayload.data);
|
|
1468
2004
|
expect(taskManager.getAllTasks()).toHaveProperty(emailPayload.data.interactionId);
|
|
1469
2005
|
});
|
|
1470
2006
|
|
|
@@ -1474,18 +2010,16 @@ describe('TaskManager', () => {
|
|
|
1474
2010
|
data: {
|
|
1475
2011
|
...initalPayload.data,
|
|
1476
2012
|
type: CC_EVENTS.AGENT_CONTACT_RESERVED,
|
|
1477
|
-
interaction: {
|
|
2013
|
+
interaction: {mediaType: 'chat'},
|
|
1478
2014
|
},
|
|
1479
2015
|
};
|
|
1480
|
-
|
|
1481
|
-
const taskIncomingSpy = jest.spyOn(taskManager, 'emit');
|
|
2016
|
+
|
|
1482
2017
|
webSocketManagerMock.emit('message', JSON.stringify(chatReservedPayload));
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
2018
|
+
const task = taskManager.getTask(chatReservedPayload.data.interactionId);
|
|
2019
|
+
const sendStateMachineEventSpy = task.sendStateMachineEvent as jest.Mock;
|
|
2020
|
+
|
|
2021
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.TASK_INCOMING);
|
|
2022
|
+
|
|
1489
2023
|
// 2. Chat task is assigned
|
|
1490
2024
|
const chatAssignedPayload = {
|
|
1491
2025
|
data: {
|
|
@@ -1493,26 +2027,26 @@ describe('TaskManager', () => {
|
|
|
1493
2027
|
type: CC_EVENTS.AGENT_CONTACT_ASSIGNED,
|
|
1494
2028
|
},
|
|
1495
2029
|
};
|
|
1496
|
-
|
|
1497
|
-
const task = taskManager.getTask(chatReservedPayload.data.interactionId);
|
|
1498
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
1499
|
-
|
|
2030
|
+
|
|
1500
2031
|
webSocketManagerMock.emit('message', JSON.stringify(chatAssignedPayload));
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
2032
|
+
|
|
2033
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.ASSIGN);
|
|
2034
|
+
|
|
1504
2035
|
// 3. Chat task is ended with state 'new' to trigger cleanup
|
|
1505
2036
|
const chatEndedPayload = {
|
|
1506
2037
|
data: {
|
|
1507
2038
|
...chatReservedPayload.data,
|
|
1508
2039
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
1509
|
-
interaction: {
|
|
2040
|
+
interaction: {mediaType: 'chat', state: 'new'}, // Change to 'new' state
|
|
2041
|
+
wrapUpRequired: false,
|
|
1510
2042
|
},
|
|
1511
2043
|
};
|
|
1512
2044
|
|
|
2045
|
+
// Simulate state on the task to allow cleanup logic
|
|
2046
|
+
task.data.interaction.state = 'new';
|
|
1513
2047
|
webSocketManagerMock.emit('message', JSON.stringify(chatEndedPayload));
|
|
1514
2048
|
|
|
1515
|
-
|
|
2049
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.CONTACT_ENDED);
|
|
1516
2050
|
// Verify task is removed since it was in a 'new' state
|
|
1517
2051
|
expect(taskManager.getTask(chatReservedPayload.data.interactionId)).toBeUndefined();
|
|
1518
2052
|
});
|
|
@@ -1523,40 +2057,46 @@ describe('TaskManager', () => {
|
|
|
1523
2057
|
data: {
|
|
1524
2058
|
...initalPayload.data,
|
|
1525
2059
|
interactionId: 'telephony-task-id',
|
|
1526
|
-
interaction: {
|
|
2060
|
+
interaction: {mediaType: 'telephony'},
|
|
1527
2061
|
},
|
|
1528
2062
|
};
|
|
1529
|
-
|
|
2063
|
+
|
|
1530
2064
|
const chatPayload = {
|
|
1531
2065
|
data: {
|
|
1532
2066
|
...initalPayload.data,
|
|
1533
2067
|
interactionId: 'chat-task-id',
|
|
1534
|
-
interaction: {
|
|
2068
|
+
interaction: {mediaType: 'chat'},
|
|
1535
2069
|
},
|
|
1536
2070
|
};
|
|
1537
|
-
|
|
2071
|
+
|
|
1538
2072
|
const emailPayload = {
|
|
1539
2073
|
data: {
|
|
1540
2074
|
...initalPayload.data,
|
|
1541
2075
|
interactionId: 'email-task-id',
|
|
1542
|
-
interaction: {
|
|
2076
|
+
interaction: {mediaType: 'email'},
|
|
1543
2077
|
},
|
|
1544
2078
|
};
|
|
1545
|
-
|
|
2079
|
+
|
|
1546
2080
|
// Simulate receiving tasks of different types
|
|
1547
2081
|
webSocketManagerMock.emit('message', JSON.stringify(telephonyPayload));
|
|
1548
2082
|
webSocketManagerMock.emit('message', JSON.stringify(chatPayload));
|
|
1549
2083
|
webSocketManagerMock.emit('message', JSON.stringify(emailPayload));
|
|
1550
|
-
|
|
2084
|
+
|
|
1551
2085
|
// Verify all tasks are in the collection
|
|
1552
2086
|
expect(taskManager.getAllTasks()).toHaveProperty(telephonyPayload.data.interactionId);
|
|
1553
2087
|
expect(taskManager.getAllTasks()).toHaveProperty(chatPayload.data.interactionId);
|
|
1554
2088
|
expect(taskManager.getAllTasks()).toHaveProperty(emailPayload.data.interactionId);
|
|
1555
|
-
|
|
2089
|
+
|
|
1556
2090
|
// Verify the task media types are correctly set
|
|
1557
|
-
expect(
|
|
1558
|
-
|
|
1559
|
-
|
|
2091
|
+
expect(
|
|
2092
|
+
taskManager.getTask(telephonyPayload.data.interactionId).data.interaction.mediaType
|
|
2093
|
+
).toBe('telephony');
|
|
2094
|
+
expect(taskManager.getTask(chatPayload.data.interactionId).data.interaction.mediaType).toBe(
|
|
2095
|
+
'chat'
|
|
2096
|
+
);
|
|
2097
|
+
expect(taskManager.getTask(emailPayload.data.interactionId).data.interaction.mediaType).toBe(
|
|
2098
|
+
'email'
|
|
2099
|
+
);
|
|
1560
2100
|
});
|
|
1561
2101
|
|
|
1562
2102
|
it('should properly handle one task ending when multiple tasks are active', () => {
|
|
@@ -1565,102 +2105,102 @@ describe('TaskManager', () => {
|
|
|
1565
2105
|
data: {
|
|
1566
2106
|
...initalPayload.data,
|
|
1567
2107
|
interactionId: 'task-id-1',
|
|
1568
|
-
interaction: {
|
|
2108
|
+
interaction: {mediaType: 'telephony'},
|
|
1569
2109
|
},
|
|
1570
2110
|
};
|
|
1571
|
-
|
|
2111
|
+
|
|
1572
2112
|
const task2Payload = {
|
|
1573
2113
|
data: {
|
|
1574
2114
|
...initalPayload.data,
|
|
1575
2115
|
interactionId: 'task-id-2',
|
|
1576
|
-
interaction: {
|
|
2116
|
+
interaction: {mediaType: 'chat'},
|
|
1577
2117
|
},
|
|
1578
2118
|
};
|
|
1579
|
-
|
|
2119
|
+
|
|
1580
2120
|
const task3Payload = {
|
|
1581
2121
|
data: {
|
|
1582
2122
|
...initalPayload.data,
|
|
1583
2123
|
interactionId: 'task-id-3',
|
|
1584
|
-
interaction: {
|
|
2124
|
+
interaction: {mediaType: 'email'},
|
|
1585
2125
|
},
|
|
1586
2126
|
};
|
|
1587
|
-
|
|
2127
|
+
|
|
1588
2128
|
// Initialize all tasks
|
|
1589
2129
|
webSocketManagerMock.emit('message', JSON.stringify(task1Payload));
|
|
1590
2130
|
webSocketManagerMock.emit('message', JSON.stringify(task2Payload));
|
|
1591
2131
|
webSocketManagerMock.emit('message', JSON.stringify(task3Payload));
|
|
1592
|
-
|
|
2132
|
+
|
|
1593
2133
|
// Verify all tasks are in the collection
|
|
1594
2134
|
expect(taskManager.getAllTasks()).toHaveProperty(task1Payload.data.interactionId);
|
|
1595
2135
|
expect(taskManager.getAllTasks()).toHaveProperty(task2Payload.data.interactionId);
|
|
1596
2136
|
expect(taskManager.getAllTasks()).toHaveProperty(task3Payload.data.interactionId);
|
|
1597
|
-
|
|
1598
|
-
// Create spies for all tasks
|
|
1599
|
-
const task1EmitSpy = jest.spyOn(taskManager.getTask(task1Payload.data.interactionId), 'emit');
|
|
1600
|
-
const task2EmitSpy = jest.spyOn(taskManager.getTask(task2Payload.data.interactionId), 'emit');
|
|
1601
|
-
const task3EmitSpy = jest.spyOn(taskManager.getTask(task3Payload.data.interactionId), 'emit');
|
|
1602
|
-
|
|
1603
|
-
// Store reference to task2 before it gets removed
|
|
2137
|
+
|
|
1604
2138
|
const task2 = taskManager.getTask(task2Payload.data.interactionId);
|
|
1605
|
-
|
|
2139
|
+
const task2SendStateMachineEventSpy = task2.sendStateMachineEvent as jest.Mock;
|
|
2140
|
+
|
|
1606
2141
|
// End only the second task (chat task)
|
|
1607
2142
|
const chatEndedPayload = {
|
|
1608
2143
|
data: {
|
|
1609
2144
|
...task2Payload.data,
|
|
1610
2145
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
1611
|
-
interaction: {
|
|
2146
|
+
interaction: {mediaType: 'chat', state: 'new'}, // Using 'new' to trigger cleanup
|
|
2147
|
+
wrapUpRequired: false,
|
|
1612
2148
|
},
|
|
1613
2149
|
};
|
|
1614
|
-
|
|
2150
|
+
|
|
2151
|
+
task2.data.interaction.state = 'new';
|
|
1615
2152
|
webSocketManagerMock.emit('message', JSON.stringify(chatEndedPayload));
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
2153
|
+
|
|
2154
|
+
const firstEndEvent = expectLastStateMachineEvent(
|
|
2155
|
+
task2SendStateMachineEventSpy,
|
|
2156
|
+
TaskEvent.CONTACT_ENDED
|
|
2157
|
+
);
|
|
2158
|
+
expect(firstEndEvent?.taskData).toEqual(chatEndedPayload.data);
|
|
2159
|
+
|
|
1622
2160
|
// Verify task2 was removed from collection (since state was 'new')
|
|
1623
2161
|
expect(taskManager.getTask(task2Payload.data.interactionId)).toBeUndefined();
|
|
1624
|
-
|
|
2162
|
+
|
|
1625
2163
|
// Verify other tasks remain in the collection
|
|
1626
2164
|
expect(taskManager.getTask(task1Payload.data.interactionId)).toBeDefined();
|
|
1627
2165
|
expect(taskManager.getTask(task3Payload.data.interactionId)).toBeDefined();
|
|
1628
|
-
|
|
2166
|
+
|
|
1629
2167
|
// Store reference to task3 before we end it
|
|
1630
2168
|
const task3 = taskManager.getTask(task3Payload.data.interactionId);
|
|
1631
|
-
|
|
2169
|
+
const task3SendStateMachineEventSpy = task3.sendStateMachineEvent as jest.Mock;
|
|
2170
|
+
|
|
1632
2171
|
// Now end task3 with a state that doesn't trigger cleanup
|
|
1633
2172
|
const emailEndedPayload = {
|
|
1634
2173
|
data: {
|
|
1635
2174
|
...task3Payload.data,
|
|
1636
2175
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
1637
|
-
interaction: {
|
|
2176
|
+
interaction: {mediaType: 'email', state: 'connected'}, // Using 'connected' to NOT trigger cleanup
|
|
2177
|
+
wrapUpRequired: true,
|
|
2178
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
1638
2179
|
},
|
|
1639
2180
|
};
|
|
1640
|
-
|
|
2181
|
+
|
|
2182
|
+
task3.data.interaction.state = 'connected';
|
|
1641
2183
|
webSocketManagerMock.emit('message', JSON.stringify(emailEndedPayload));
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
2184
|
+
|
|
2185
|
+
const secondEndEvent = expectLastStateMachineEvent(
|
|
2186
|
+
task3SendStateMachineEventSpy,
|
|
2187
|
+
TaskEvent.CONTACT_ENDED
|
|
2188
|
+
);
|
|
2189
|
+
expect(secondEndEvent?.taskData).toEqual(emailEndedPayload.data);
|
|
2190
|
+
|
|
1646
2191
|
// Verify task3 is still in collection (since state was 'connected')
|
|
1647
2192
|
expect(taskManager.getTask(task3Payload.data.interactionId)).toBeDefined();
|
|
1648
|
-
|
|
2193
|
+
|
|
1649
2194
|
// Verify task1 remains unaffected
|
|
1650
|
-
expect(task1EmitSpy).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_END);
|
|
1651
2195
|
expect(taskManager.getTask(task1Payload.data.interactionId)).toBeDefined();
|
|
1652
2196
|
});
|
|
1653
2197
|
|
|
1654
|
-
it('should emit
|
|
2198
|
+
it('should emit TRANSFER_SUCCESS event on AGENT_VTEAM_TRANSFERRED event', () => {
|
|
1655
2199
|
// First create a task by emitting the initial payload
|
|
1656
2200
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1657
|
-
|
|
1658
|
-
// Get a reference to the task from taskCollection
|
|
1659
2201
|
const task = taskManager.getTask(taskId);
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
1663
|
-
|
|
2202
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
2203
|
+
|
|
1664
2204
|
const vteamTransferredPayload = {
|
|
1665
2205
|
data: {
|
|
1666
2206
|
type: CC_EVENTS.AGENT_VTEAM_TRANSFERRED,
|
|
@@ -1677,34 +2217,39 @@ describe('TaskManager', () => {
|
|
|
1677
2217
|
queueMgr: initalPayload.data.queueMgr,
|
|
1678
2218
|
},
|
|
1679
2219
|
};
|
|
1680
|
-
|
|
2220
|
+
|
|
1681
2221
|
// No need to explicitly set the task in the collection as it's already there
|
|
1682
2222
|
// from the initial message processing
|
|
1683
|
-
|
|
2223
|
+
|
|
1684
2224
|
webSocketManagerMock.emit('message', JSON.stringify(vteamTransferredPayload));
|
|
1685
|
-
|
|
1686
|
-
// Check that
|
|
1687
|
-
|
|
1688
|
-
|
|
2225
|
+
|
|
2226
|
+
// Check that the state machine received the END event
|
|
2227
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.TRANSFER_SUCCESS);
|
|
2228
|
+
sendStateMachineEventSpy.mockRestore();
|
|
2229
|
+
|
|
1689
2230
|
// The task should still exist in the collection based on current implementation
|
|
1690
2231
|
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
1691
2232
|
});
|
|
1692
2233
|
|
|
1693
2234
|
it('should update task data on AGENT_WRAPUP event', () => {
|
|
1694
2235
|
const payload = {
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
2236
|
+
data: {
|
|
2237
|
+
type: CC_EVENTS.AGENT_WRAPUP,
|
|
2238
|
+
interactionId: taskId,
|
|
2239
|
+
wrapUpRequired: true,
|
|
2240
|
+
},
|
|
1700
2241
|
};
|
|
1701
2242
|
const task = taskManager.getTask(taskId);
|
|
1702
|
-
const
|
|
1703
|
-
task.data = { ...(task.data || {}), ...(data || {}) };
|
|
1704
|
-
return task;
|
|
1705
|
-
});
|
|
2243
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1706
2244
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1707
|
-
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();
|
|
1708
2253
|
});
|
|
1709
2254
|
|
|
1710
2255
|
it('should not attempt cleanup twice when AGENT_CONTACT_UNASSIGNED is followed by AGENT_WRAPUP', () => {
|
|
@@ -1716,7 +2261,7 @@ describe('TaskManager', () => {
|
|
|
1716
2261
|
data: {
|
|
1717
2262
|
type: CC_EVENTS.AGENT_CONTACT_UNASSIGNED,
|
|
1718
2263
|
agentId: initalPayload.data.agentId,
|
|
1719
|
-
interaction: {
|
|
2264
|
+
interaction: {mediaType: 'telephony'},
|
|
1720
2265
|
interactionId: initalPayload.data.interactionId,
|
|
1721
2266
|
orgId: initalPayload.data.orgId,
|
|
1722
2267
|
trackingId: initalPayload.data.trackingId,
|
|
@@ -1735,7 +2280,7 @@ describe('TaskManager', () => {
|
|
|
1735
2280
|
data: {
|
|
1736
2281
|
type: CC_EVENTS.AGENT_WRAPUP,
|
|
1737
2282
|
interactionId: taskId,
|
|
1738
|
-
interaction: {
|
|
2283
|
+
interaction: {mediaType: 'telephony'},
|
|
1739
2284
|
},
|
|
1740
2285
|
};
|
|
1741
2286
|
webSocketManagerMock.emit('message', JSON.stringify(wrapupPayload));
|
|
@@ -1752,7 +2297,7 @@ describe('TaskManager', () => {
|
|
|
1752
2297
|
data: {
|
|
1753
2298
|
type: CC_EVENTS.AGENT_VTEAM_TRANSFERRED,
|
|
1754
2299
|
agentId: initalPayload.data.agentId,
|
|
1755
|
-
interaction: {
|
|
2300
|
+
interaction: {mediaType: 'telephony'},
|
|
1756
2301
|
interactionId: initalPayload.data.interactionId,
|
|
1757
2302
|
orgId: initalPayload.data.orgId,
|
|
1758
2303
|
trackingId: initalPayload.data.trackingId,
|
|
@@ -1771,7 +2316,7 @@ describe('TaskManager', () => {
|
|
|
1771
2316
|
data: {
|
|
1772
2317
|
type: CC_EVENTS.AGENT_WRAPUP,
|
|
1773
2318
|
interactionId: taskId,
|
|
1774
|
-
interaction: {
|
|
2319
|
+
interaction: {mediaType: 'telephony'},
|
|
1775
2320
|
},
|
|
1776
2321
|
};
|
|
1777
2322
|
webSocketManagerMock.emit('message', JSON.stringify(wrapupPayload));
|
|
@@ -1780,704 +2325,157 @@ describe('TaskManager', () => {
|
|
|
1780
2325
|
});
|
|
1781
2326
|
|
|
1782
2327
|
describe('should emit appropriate task events for recording events', () => {
|
|
1783
|
-
|
|
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) => {
|
|
1784
2337
|
const ccEvent = CC_EVENTS[`CONTACT_RECORDING_${suffix}`];
|
|
1785
|
-
const
|
|
1786
|
-
it(`should
|
|
2338
|
+
const expectedTaskEvent = eventMap[suffix];
|
|
2339
|
+
it(`should ${expectedTaskEvent ? 'send' : 'not send'} ${
|
|
2340
|
+
expectedTaskEvent ?? 'a'
|
|
2341
|
+
} state machine event on ${ccEvent} event`, () => {
|
|
1787
2342
|
const payload = {data: {...initalPayload.data, type: ccEvent}};
|
|
1788
2343
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1789
2344
|
const task = taskManager.getTask(taskId);
|
|
1790
|
-
const
|
|
2345
|
+
const sendStateMachineEventSpy = task.sendStateMachineEvent as jest.Mock;
|
|
2346
|
+
sendStateMachineEventSpy.mockClear();
|
|
1791
2347
|
|
|
1792
2348
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1793
|
-
|
|
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
|
+
}
|
|
1794
2358
|
});
|
|
1795
2359
|
});
|
|
1796
|
-
});
|
|
2360
|
+
});
|
|
1797
2361
|
|
|
1798
2362
|
describe('Conference event handling', () => {
|
|
1799
2363
|
let task;
|
|
1800
|
-
|
|
1801
|
-
|
|
2364
|
+
|
|
1802
2365
|
beforeEach(() => {
|
|
1803
|
-
// Set the agentId on taskManager before tests run
|
|
1804
|
-
taskManager.setAgentId(agentId);
|
|
1805
|
-
|
|
1806
2366
|
task = {
|
|
1807
|
-
data: {
|
|
2367
|
+
data: {interactionId: taskId},
|
|
1808
2368
|
emit: jest.fn(),
|
|
1809
|
-
updateTaskData: jest.fn()
|
|
1810
|
-
|
|
1811
|
-
task.data = { ...task.data, ...updatedData };
|
|
1812
|
-
return task;
|
|
1813
|
-
}),
|
|
2369
|
+
updateTaskData: jest.fn(),
|
|
2370
|
+
sendStateMachineEvent: jest.fn(),
|
|
1814
2371
|
};
|
|
1815
|
-
taskManager.taskCollection[taskId] = task;
|
|
2372
|
+
taskManager.taskCollection[taskId] = task as any;
|
|
1816
2373
|
});
|
|
1817
2374
|
|
|
1818
|
-
it('
|
|
2375
|
+
it('sends AGENT_CONSULT_CONFERENCED to state machine as CONFERENCE_START', () => {
|
|
1819
2376
|
const payload = {
|
|
1820
|
-
data: {
|
|
1821
|
-
type: CC_EVENTS.AGENT_CONSULT_CONFERENCED,
|
|
1822
|
-
interactionId: taskId,
|
|
1823
|
-
isConferencing: true,
|
|
1824
|
-
},
|
|
2377
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCED, interactionId: taskId},
|
|
1825
2378
|
};
|
|
1826
|
-
|
|
1827
2379
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
expect(
|
|
2380
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
2381
|
+
const call = task.sendStateMachineEvent.mock.calls[0][0];
|
|
2382
|
+
expect(call.type).toBe(TaskEvent.CONFERENCE_START);
|
|
1831
2383
|
});
|
|
1832
2384
|
|
|
1833
|
-
it('
|
|
2385
|
+
it('sends PARTICIPANT_JOINED_CONFERENCE to state machine as CONFERENCE_START', () => {
|
|
1834
2386
|
const payload = {
|
|
1835
|
-
data: {
|
|
1836
|
-
type: CC_EVENTS.AGENT_CONSULT_CONFERENCING,
|
|
1837
|
-
interactionId: taskId,
|
|
1838
|
-
isConferencing: true,
|
|
1839
|
-
},
|
|
2387
|
+
data: {type: CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE, interactionId: taskId},
|
|
1840
2388
|
};
|
|
1841
|
-
|
|
1842
2389
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
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);
|
|
1847
2393
|
});
|
|
1848
2394
|
|
|
1849
|
-
it('
|
|
2395
|
+
it('sends AGENT_CONSULT_CONFERENCE_FAILED to state machine as CONFERENCE_FAILED', () => {
|
|
1850
2396
|
const payload = {
|
|
1851
|
-
data: {
|
|
1852
|
-
type: CC_EVENTS.AGENT_CONSULT_CONFERENCE_FAILED,
|
|
1853
|
-
interactionId: taskId,
|
|
1854
|
-
reason: 'Network error',
|
|
1855
|
-
},
|
|
2397
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCE_FAILED, interactionId: taskId},
|
|
1856
2398
|
};
|
|
1857
|
-
|
|
1858
2399
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
2400
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
2401
|
+
const call = task.sendStateMachineEvent.mock.calls[0][0];
|
|
2402
|
+
expect(call.type).toBe(TaskEvent.CONFERENCE_FAILED);
|
|
1862
2403
|
});
|
|
1863
2404
|
|
|
1864
|
-
it('
|
|
2405
|
+
it('sends PARTICIPANT_LEFT_CONFERENCE to state machine as PARTICIPANT_LEAVE', () => {
|
|
1865
2406
|
const payload = {
|
|
1866
|
-
data: {
|
|
1867
|
-
type: CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE,
|
|
1868
|
-
interactionId: taskId,
|
|
1869
|
-
participantId: 'new-participant-123',
|
|
1870
|
-
participantType: 'agent',
|
|
1871
|
-
interaction: {
|
|
1872
|
-
participants: {
|
|
1873
|
-
[agentId]: { pType: 'Agent', hasLeft: false },
|
|
1874
|
-
'new-participant-123': { pType: 'Agent', hasLeft: false },
|
|
1875
|
-
},
|
|
1876
|
-
media: {
|
|
1877
|
-
[taskId]: {
|
|
1878
|
-
mType: 'mainCall',
|
|
1879
|
-
participants: [agentId, 'new-participant-123'],
|
|
1880
|
-
},
|
|
1881
|
-
},
|
|
1882
|
-
},
|
|
1883
|
-
},
|
|
2407
|
+
data: {type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, interactionId: taskId},
|
|
1884
2408
|
};
|
|
1885
|
-
|
|
1886
2409
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
expect(
|
|
1890
|
-
// 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);
|
|
1891
2413
|
});
|
|
1892
2414
|
|
|
1893
|
-
it('
|
|
2415
|
+
it('handles AGENT_CONSULT_CONFERENCING event without errors', () => {
|
|
1894
2416
|
const payload = {
|
|
1895
|
-
data: {
|
|
1896
|
-
type: CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE,
|
|
1897
|
-
interactionId: taskId,
|
|
1898
|
-
participantId: 'new-agent-789',
|
|
1899
|
-
interaction: {
|
|
1900
|
-
participants: {
|
|
1901
|
-
[agentId]: { pType: 'Agent', hasLeft: false },
|
|
1902
|
-
'agent-2': { pType: 'Agent', hasLeft: false },
|
|
1903
|
-
'new-agent-789': { pType: 'Agent', hasLeft: false },
|
|
1904
|
-
'customer-1': { pType: 'Customer', hasLeft: false },
|
|
1905
|
-
},
|
|
1906
|
-
media: {
|
|
1907
|
-
[taskId]: {
|
|
1908
|
-
mType: 'mainCall',
|
|
1909
|
-
participants: [agentId, 'agent-2', 'new-agent-789', 'customer-1'],
|
|
1910
|
-
},
|
|
1911
|
-
},
|
|
1912
|
-
},
|
|
1913
|
-
},
|
|
2417
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCING, interactionId: taskId},
|
|
1914
2418
|
};
|
|
1915
|
-
|
|
1916
|
-
const updateTaskDataSpy = jest.spyOn(task, 'updateTaskData');
|
|
1917
|
-
|
|
1918
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1919
|
-
|
|
1920
|
-
// Verify updateTaskData was called exactly once
|
|
1921
|
-
expect(updateTaskDataSpy).toHaveBeenCalledTimes(1);
|
|
1922
|
-
|
|
1923
|
-
// Verify it was called with isConferenceInProgress already calculated
|
|
1924
|
-
expect(updateTaskDataSpy).toHaveBeenCalledWith(
|
|
1925
|
-
expect.objectContaining({
|
|
1926
|
-
participantId: 'new-agent-789',
|
|
1927
|
-
isConferenceInProgress: true, // 3 active agents
|
|
1928
|
-
})
|
|
1929
|
-
);
|
|
1930
|
-
|
|
1931
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_JOINED, task);
|
|
1932
|
-
});
|
|
1933
|
-
|
|
1934
|
-
describe('PARTICIPANT_LEFT_CONFERENCE event handling', () => {
|
|
1935
|
-
it('should call updateTaskData only once for PARTICIPANT_LEFT_CONFERENCE with pre-calculated isConferenceInProgress', () => {
|
|
1936
|
-
const payload = {
|
|
1937
|
-
data: {
|
|
1938
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
1939
|
-
interactionId: taskId,
|
|
1940
|
-
interaction: {
|
|
1941
|
-
participants: {
|
|
1942
|
-
[agentId]: { pType: 'Agent', hasLeft: false },
|
|
1943
|
-
'agent-2': { pType: 'Agent', hasLeft: true }, // This agent left
|
|
1944
|
-
'customer-1': { pType: 'Customer', hasLeft: false },
|
|
1945
|
-
},
|
|
1946
|
-
media: {
|
|
1947
|
-
[taskId]: {
|
|
1948
|
-
mType: 'mainCall',
|
|
1949
|
-
participants: [agentId, 'customer-1'], // agent-2 removed from participants
|
|
1950
|
-
},
|
|
1951
|
-
},
|
|
1952
|
-
},
|
|
1953
|
-
},
|
|
1954
|
-
};
|
|
1955
|
-
|
|
1956
|
-
const updateTaskDataSpy = jest.spyOn(task, 'updateTaskData');
|
|
1957
|
-
|
|
1958
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1959
|
-
|
|
1960
|
-
// Verify updateTaskData was called exactly once
|
|
1961
|
-
expect(updateTaskDataSpy).toHaveBeenCalledTimes(1);
|
|
1962
|
-
|
|
1963
|
-
// Verify it was called with isConferenceInProgress already calculated
|
|
1964
|
-
expect(updateTaskDataSpy).toHaveBeenCalledWith(
|
|
1965
|
-
expect.objectContaining({
|
|
1966
|
-
isConferenceInProgress: false, // Only 1 active agent remains
|
|
1967
|
-
})
|
|
1968
|
-
);
|
|
1969
|
-
|
|
1970
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
1971
|
-
});
|
|
1972
|
-
|
|
1973
|
-
it('should emit TASK_PARTICIPANT_LEFT event when participant leaves conference', () => {
|
|
1974
|
-
const payload = {
|
|
1975
|
-
data: {
|
|
1976
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
1977
|
-
interactionId: taskId,
|
|
1978
|
-
interaction: {
|
|
1979
|
-
participants: {
|
|
1980
|
-
[agentId]: {
|
|
1981
|
-
hasLeft: false,
|
|
1982
|
-
},
|
|
1983
|
-
},
|
|
1984
|
-
},
|
|
1985
|
-
},
|
|
1986
|
-
};
|
|
1987
|
-
|
|
1988
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1989
|
-
|
|
1990
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
1991
|
-
});
|
|
1992
|
-
|
|
1993
|
-
it('should NOT remove task when agent is still in interaction', () => {
|
|
1994
|
-
const payload = {
|
|
1995
|
-
data: {
|
|
1996
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
1997
|
-
interactionId: taskId,
|
|
1998
|
-
interaction: {
|
|
1999
|
-
participants: {
|
|
2000
|
-
[agentId]: {
|
|
2001
|
-
hasLeft: false,
|
|
2002
|
-
},
|
|
2003
|
-
},
|
|
2004
|
-
},
|
|
2005
|
-
},
|
|
2006
|
-
};
|
|
2007
|
-
|
|
2008
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2009
|
-
|
|
2010
|
-
// Task should still exist in collection
|
|
2011
|
-
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
2012
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2013
|
-
});
|
|
2014
|
-
|
|
2015
|
-
it('should NOT remove task when agent left but is in main interaction', () => {
|
|
2016
|
-
const payload = {
|
|
2017
|
-
data: {
|
|
2018
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2019
|
-
interactionId: taskId,
|
|
2020
|
-
interaction: {
|
|
2021
|
-
participants: {
|
|
2022
|
-
[agentId]: {
|
|
2023
|
-
hasLeft: true,
|
|
2024
|
-
},
|
|
2025
|
-
},
|
|
2026
|
-
media: {
|
|
2027
|
-
[taskId]: {
|
|
2028
|
-
mType: 'mainCall',
|
|
2029
|
-
participants: [agentId],
|
|
2030
|
-
},
|
|
2031
|
-
},
|
|
2032
|
-
},
|
|
2033
|
-
},
|
|
2034
|
-
};
|
|
2035
|
-
|
|
2036
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2037
|
-
|
|
2038
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2039
|
-
|
|
2040
|
-
// Task should still exist - not removed
|
|
2041
|
-
expect(removeTaskSpy).not.toHaveBeenCalled();
|
|
2042
|
-
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
2043
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2044
|
-
});
|
|
2045
|
-
|
|
2046
|
-
it('should NOT remove task when agent left but is primary (owner)', () => {
|
|
2047
|
-
const payload = {
|
|
2048
|
-
data: {
|
|
2049
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2050
|
-
interactionId: taskId,
|
|
2051
|
-
interaction: {
|
|
2052
|
-
participants: {
|
|
2053
|
-
[agentId]: {
|
|
2054
|
-
hasLeft: true,
|
|
2055
|
-
},
|
|
2056
|
-
},
|
|
2057
|
-
owner: agentId,
|
|
2058
|
-
media: {
|
|
2059
|
-
[taskId]: {
|
|
2060
|
-
mType: 'consultCall',
|
|
2061
|
-
participants: ['other-agent'],
|
|
2062
|
-
},
|
|
2063
|
-
},
|
|
2064
|
-
},
|
|
2065
|
-
},
|
|
2066
|
-
};
|
|
2067
|
-
|
|
2068
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2069
|
-
|
|
2419
|
+
expect(() => {
|
|
2070
2420
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2071
|
-
|
|
2072
|
-
// Task should still exist - not removed because agent is primary
|
|
2073
|
-
expect(removeTaskSpy).not.toHaveBeenCalled();
|
|
2074
|
-
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
2075
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2076
|
-
});
|
|
2077
|
-
|
|
2078
|
-
it('should remove task when agent left and is NOT in main interaction and is NOT primary', () => {
|
|
2079
|
-
const payload = {
|
|
2080
|
-
data: {
|
|
2081
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2082
|
-
interactionId: taskId,
|
|
2083
|
-
interaction: {
|
|
2084
|
-
participants: {
|
|
2085
|
-
[agentId]: {
|
|
2086
|
-
hasLeft: true,
|
|
2087
|
-
},
|
|
2088
|
-
},
|
|
2089
|
-
owner: 'another-agent-id',
|
|
2090
|
-
media: {
|
|
2091
|
-
[taskId]: {
|
|
2092
|
-
mType: 'mainCall',
|
|
2093
|
-
participants: ['another-agent-id'],
|
|
2094
|
-
},
|
|
2095
|
-
},
|
|
2096
|
-
},
|
|
2097
|
-
},
|
|
2098
|
-
};
|
|
2099
|
-
|
|
2100
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2101
|
-
|
|
2102
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2103
|
-
|
|
2104
|
-
// Task should be removed
|
|
2105
|
-
expect(removeTaskSpy).toHaveBeenCalled();
|
|
2106
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2107
|
-
});
|
|
2108
|
-
|
|
2109
|
-
it('should remove task when agent is not in participants list', () => {
|
|
2110
|
-
const payload = {
|
|
2111
|
-
data: {
|
|
2112
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2113
|
-
interactionId: taskId,
|
|
2114
|
-
interaction: {
|
|
2115
|
-
participants: {
|
|
2116
|
-
'other-agent-id': {
|
|
2117
|
-
hasLeft: false,
|
|
2118
|
-
},
|
|
2119
|
-
},
|
|
2120
|
-
owner: 'another-agent-id',
|
|
2121
|
-
},
|
|
2122
|
-
},
|
|
2123
|
-
};
|
|
2124
|
-
|
|
2125
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2126
|
-
|
|
2127
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2128
|
-
|
|
2129
|
-
// Task should be removed because agent is not in participants
|
|
2130
|
-
expect(removeTaskSpy).toHaveBeenCalled();
|
|
2131
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2132
|
-
});
|
|
2133
|
-
|
|
2134
|
-
it('should update isConferenceInProgress based on remaining active agents', () => {
|
|
2135
|
-
const payload = {
|
|
2136
|
-
data: {
|
|
2137
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2138
|
-
interactionId: taskId,
|
|
2139
|
-
interaction: {
|
|
2140
|
-
participants: {
|
|
2141
|
-
[agentId]: {
|
|
2142
|
-
hasLeft: false,
|
|
2143
|
-
pType: 'Agent',
|
|
2144
|
-
},
|
|
2145
|
-
'agent-2': {
|
|
2146
|
-
hasLeft: false,
|
|
2147
|
-
pType: 'Agent',
|
|
2148
|
-
},
|
|
2149
|
-
'customer-1': {
|
|
2150
|
-
hasLeft: false,
|
|
2151
|
-
pType: 'Customer',
|
|
2152
|
-
},
|
|
2153
|
-
},
|
|
2154
|
-
media: {
|
|
2155
|
-
[taskId]: {
|
|
2156
|
-
mType: 'mainCall',
|
|
2157
|
-
participants: [agentId, 'agent-2', 'customer-1'],
|
|
2158
|
-
},
|
|
2159
|
-
},
|
|
2160
|
-
},
|
|
2161
|
-
},
|
|
2162
|
-
};
|
|
2163
|
-
|
|
2164
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2165
|
-
|
|
2166
|
-
// isConferenceInProgress should be true (2 active agents)
|
|
2167
|
-
expect(task.data.isConferenceInProgress).toBe(true);
|
|
2168
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2169
|
-
});
|
|
2170
|
-
|
|
2171
|
-
it('should set isConferenceInProgress to false when only one agent remains', () => {
|
|
2172
|
-
const payload = {
|
|
2173
|
-
data: {
|
|
2174
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2175
|
-
interactionId: taskId,
|
|
2176
|
-
interaction: {
|
|
2177
|
-
participants: {
|
|
2178
|
-
[agentId]: {
|
|
2179
|
-
hasLeft: false,
|
|
2180
|
-
pType: 'Agent',
|
|
2181
|
-
},
|
|
2182
|
-
'agent-2': {
|
|
2183
|
-
hasLeft: true,
|
|
2184
|
-
pType: 'Agent',
|
|
2185
|
-
},
|
|
2186
|
-
'customer-1': {
|
|
2187
|
-
hasLeft: false,
|
|
2188
|
-
pType: 'Customer',
|
|
2189
|
-
},
|
|
2190
|
-
},
|
|
2191
|
-
media: {
|
|
2192
|
-
[taskId]: {
|
|
2193
|
-
mType: 'mainCall',
|
|
2194
|
-
participants: [agentId, 'customer-1'],
|
|
2195
|
-
},
|
|
2196
|
-
},
|
|
2197
|
-
},
|
|
2198
|
-
},
|
|
2199
|
-
};
|
|
2200
|
-
|
|
2201
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2202
|
-
|
|
2203
|
-
// isConferenceInProgress should be false (only 1 active agent)
|
|
2204
|
-
expect(task.data.isConferenceInProgress).toBe(false);
|
|
2205
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2206
|
-
});
|
|
2207
|
-
|
|
2208
|
-
it('should handle participant left when no participants data exists', () => {
|
|
2209
|
-
const payload = {
|
|
2210
|
-
data: {
|
|
2211
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2212
|
-
interactionId: taskId,
|
|
2213
|
-
interaction: {},
|
|
2214
|
-
},
|
|
2215
|
-
};
|
|
2216
|
-
|
|
2217
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
2218
|
-
|
|
2219
|
-
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2220
|
-
|
|
2221
|
-
// When no participants data exists, checkParticipantNotInInteraction returns true
|
|
2222
|
-
// Since agent won't be in main interaction either, task should be removed
|
|
2223
|
-
expect(removeTaskSpy).toHaveBeenCalled();
|
|
2224
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
2225
|
-
});
|
|
2421
|
+
}).not.toThrow();
|
|
2226
2422
|
});
|
|
2227
2423
|
|
|
2228
|
-
it('
|
|
2424
|
+
it('handles PARTICIPANT_LEFT_CONFERENCE_FAILED event without errors', () => {
|
|
2229
2425
|
const payload = {
|
|
2230
|
-
data: {
|
|
2231
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE_FAILED,
|
|
2232
|
-
interactionId: taskId,
|
|
2233
|
-
reason: 'Exit failed',
|
|
2234
|
-
},
|
|
2426
|
+
data: {type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE_FAILED, interactionId: taskId},
|
|
2235
2427
|
};
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
expect(task.data.reason).toBe('Exit failed');
|
|
2240
|
-
// No event emission expected for failure - handled by contact method promise rejection
|
|
2428
|
+
expect(() => {
|
|
2429
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2430
|
+
}).not.toThrow();
|
|
2241
2431
|
});
|
|
2242
2432
|
|
|
2243
|
-
it('
|
|
2433
|
+
it('only routes conference events to matching tasks', () => {
|
|
2244
2434
|
const otherTaskId = 'other-task-id';
|
|
2245
|
-
const otherTask = {
|
|
2246
|
-
data: {
|
|
2435
|
+
const otherTask: any = {
|
|
2436
|
+
data: {interactionId: otherTaskId},
|
|
2247
2437
|
emit: jest.fn(),
|
|
2438
|
+
updateTaskData: jest.fn(),
|
|
2439
|
+
sendStateMachineEvent: jest.fn(),
|
|
2248
2440
|
};
|
|
2249
2441
|
taskManager.taskCollection[otherTaskId] = otherTask;
|
|
2250
2442
|
|
|
2251
2443
|
const payload = {
|
|
2252
|
-
data: {
|
|
2253
|
-
type: CC_EVENTS.AGENT_CONSULT_CONFERENCED,
|
|
2254
|
-
interactionId: taskId,
|
|
2255
|
-
isConferencing: true,
|
|
2256
|
-
},
|
|
2444
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCED, interactionId: taskId},
|
|
2257
2445
|
};
|
|
2258
|
-
|
|
2259
2446
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2260
2447
|
|
|
2261
|
-
|
|
2262
|
-
expect(
|
|
2263
|
-
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_CONFERENCE_STARTED, task);
|
|
2264
|
-
|
|
2265
|
-
// Other task should not be affected
|
|
2266
|
-
expect(otherTask.data.isConferencing).toBeUndefined();
|
|
2267
|
-
expect(otherTask.emit).not.toHaveBeenCalled();
|
|
2448
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
2449
|
+
expect(otherTask.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
2268
2450
|
});
|
|
2269
2451
|
});
|
|
2270
2452
|
|
|
2271
|
-
describe('
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
beforeEach(() => {
|
|
2277
|
-
// Create initial task
|
|
2278
|
-
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
2279
|
-
task = taskManager.getTask(taskId);
|
|
2280
|
-
taskEmitSpy = jest.spyOn(task, 'emit');
|
|
2281
|
-
managerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
2282
|
-
});
|
|
2283
|
-
|
|
2284
|
-
it('should update existing task data and emit TASK_MERGED event when CONTACT_MERGED is received', () => {
|
|
2285
|
-
const mergedPayload = {
|
|
2286
|
-
data: {
|
|
2287
|
-
type: CC_EVENTS.CONTACT_MERGED,
|
|
2288
|
-
interactionId: taskId,
|
|
2289
|
-
agentId: taskDataMock.agentId,
|
|
2290
|
-
interaction: {
|
|
2291
|
-
...taskDataMock.interaction,
|
|
2292
|
-
state: 'merged',
|
|
2293
|
-
customField: 'updated-value',
|
|
2294
|
-
},
|
|
2295
|
-
},
|
|
2296
|
-
};
|
|
2297
|
-
|
|
2298
|
-
webSocketManagerMock.emit('message', JSON.stringify(mergedPayload));
|
|
2299
|
-
|
|
2300
|
-
const updatedTask = taskManager.getTask(taskId);
|
|
2301
|
-
expect(updatedTask).toBeDefined();
|
|
2302
|
-
expect(updatedTask.data.interaction.customField).toBe('updated-value');
|
|
2303
|
-
expect(updatedTask.data.interaction.state).toBe('merged');
|
|
2304
|
-
expect(managerEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_MERGED, updatedTask);
|
|
2305
|
-
});
|
|
2306
|
-
|
|
2307
|
-
it('should create new task when CONTACT_MERGED is received for non-existing task', () => {
|
|
2308
|
-
const newMergedTaskId = 'new-merged-task-id';
|
|
2309
|
-
const mergedPayload = {
|
|
2310
|
-
data: {
|
|
2311
|
-
type: CC_EVENTS.CONTACT_MERGED,
|
|
2312
|
-
interactionId: newMergedTaskId,
|
|
2313
|
-
agentId: taskDataMock.agentId,
|
|
2314
|
-
interaction: {
|
|
2315
|
-
mediaType: 'telephony',
|
|
2316
|
-
state: 'merged',
|
|
2317
|
-
participants: {
|
|
2318
|
-
[taskDataMock.agentId]: {
|
|
2319
|
-
isWrapUp: false,
|
|
2320
|
-
hasJoined: true,
|
|
2321
|
-
},
|
|
2322
|
-
},
|
|
2323
|
-
},
|
|
2324
|
-
},
|
|
2325
|
-
};
|
|
2326
|
-
|
|
2327
|
-
// Verify task doesn't exist before
|
|
2328
|
-
expect(taskManager.getTask(newMergedTaskId)).toBeUndefined();
|
|
2329
|
-
|
|
2330
|
-
webSocketManagerMock.emit('message', JSON.stringify(mergedPayload));
|
|
2331
|
-
|
|
2332
|
-
// Verify task was created
|
|
2333
|
-
const newTask = taskManager.getTask(newMergedTaskId);
|
|
2334
|
-
expect(newTask).toBeDefined();
|
|
2335
|
-
expect(newTask.data.interactionId).toBe(newMergedTaskId);
|
|
2336
|
-
expect(managerEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_MERGED, newTask);
|
|
2337
|
-
});
|
|
2338
|
-
|
|
2339
|
-
it('should remove child task when childInteractionId is present in CONTACT_MERGED', () => {
|
|
2340
|
-
const childTaskId = 'child-task-id';
|
|
2341
|
-
const parentTaskId = 'parent-task-id';
|
|
2342
|
-
|
|
2343
|
-
// Create child task
|
|
2344
|
-
const childPayload = {
|
|
2345
|
-
data: {
|
|
2346
|
-
type: CC_EVENTS.AGENT_CONTACT_RESERVED,
|
|
2347
|
-
interactionId: childTaskId,
|
|
2348
|
-
agentId: taskDataMock.agentId,
|
|
2349
|
-
interaction: {mediaType: 'telephony'},
|
|
2350
|
-
},
|
|
2351
|
-
};
|
|
2352
|
-
webSocketManagerMock.emit('message', JSON.stringify(childPayload));
|
|
2353
|
-
|
|
2354
|
-
// Verify child task exists
|
|
2355
|
-
expect(taskManager.getTask(childTaskId)).toBeDefined();
|
|
2356
|
-
|
|
2357
|
-
// Create parent task
|
|
2358
|
-
const parentPayload = {
|
|
2359
|
-
data: {
|
|
2360
|
-
type: CC_EVENTS.AGENT_CONTACT_RESERVED,
|
|
2361
|
-
interactionId: parentTaskId,
|
|
2362
|
-
agentId: taskDataMock.agentId,
|
|
2363
|
-
interaction: {mediaType: 'telephony'},
|
|
2364
|
-
},
|
|
2365
|
-
};
|
|
2366
|
-
webSocketManagerMock.emit('message', JSON.stringify(parentPayload));
|
|
2367
|
-
|
|
2368
|
-
// Send CONTACT_MERGED with childInteractionId
|
|
2369
|
-
const mergedPayload = {
|
|
2370
|
-
data: {
|
|
2371
|
-
type: CC_EVENTS.CONTACT_MERGED,
|
|
2372
|
-
interactionId: parentTaskId,
|
|
2373
|
-
childInteractionId: childTaskId,
|
|
2374
|
-
agentId: taskDataMock.agentId,
|
|
2375
|
-
interaction: {
|
|
2376
|
-
mediaType: 'telephony',
|
|
2377
|
-
state: 'merged',
|
|
2378
|
-
},
|
|
2379
|
-
},
|
|
2380
|
-
};
|
|
2381
|
-
|
|
2382
|
-
webSocketManagerMock.emit('message', JSON.stringify(mergedPayload));
|
|
2383
|
-
|
|
2384
|
-
// Verify child task was removed
|
|
2385
|
-
expect(taskManager.getTask(childTaskId)).toBeUndefined();
|
|
2386
|
-
|
|
2387
|
-
// Verify parent task still exists
|
|
2388
|
-
expect(taskManager.getTask(parentTaskId)).toBeDefined();
|
|
2389
|
-
|
|
2390
|
-
// Verify TASK_MERGED event was emitted
|
|
2391
|
-
expect(managerEmitSpy).toHaveBeenCalledWith(
|
|
2392
|
-
TASK_EVENTS.TASK_MERGED,
|
|
2393
|
-
expect.objectContaining({
|
|
2394
|
-
data: expect.objectContaining({
|
|
2395
|
-
interactionId: parentTaskId,
|
|
2396
|
-
}),
|
|
2397
|
-
})
|
|
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}
|
|
2398
2458
|
);
|
|
2399
|
-
});
|
|
2400
|
-
|
|
2401
|
-
it('should handle CONTACT_MERGED with EP-DN participant correctly', () => {
|
|
2402
|
-
const epdnTaskId = 'epdn-merged-task';
|
|
2403
|
-
const mergedPayload = {
|
|
2404
|
-
data: {
|
|
2405
|
-
type: CC_EVENTS.CONTACT_MERGED,
|
|
2406
|
-
interactionId: epdnTaskId,
|
|
2407
|
-
agentId: taskDataMock.agentId,
|
|
2408
|
-
interaction: {
|
|
2409
|
-
mediaType: 'telephony',
|
|
2410
|
-
state: 'merged',
|
|
2411
|
-
participants: {
|
|
2412
|
-
[taskDataMock.agentId]: {
|
|
2413
|
-
type: 'Agent',
|
|
2414
|
-
isWrapUp: false,
|
|
2415
|
-
hasJoined: true,
|
|
2416
|
-
},
|
|
2417
|
-
'epdn-participant': {
|
|
2418
|
-
type: 'EpDn',
|
|
2419
|
-
epId: 'entry-point-123',
|
|
2420
|
-
isWrapUp: false,
|
|
2421
|
-
},
|
|
2422
|
-
},
|
|
2423
|
-
},
|
|
2424
|
-
},
|
|
2425
|
-
};
|
|
2426
|
-
|
|
2427
|
-
webSocketManagerMock.emit('message', JSON.stringify(mergedPayload));
|
|
2428
2459
|
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
expect(managerEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_MERGED, mergedTask);
|
|
2460
|
+
expect(mapped).toEqual({
|
|
2461
|
+
type: TaskEvent.ASSIGN,
|
|
2462
|
+
taskData: {...taskDataMock, type: CC_EVENTS.AGENT_CONTACT_ASSIGNED},
|
|
2463
|
+
});
|
|
2434
2464
|
});
|
|
2435
2465
|
|
|
2436
|
-
it('
|
|
2437
|
-
const
|
|
2438
|
-
const
|
|
2439
|
-
|
|
2440
|
-
type: CC_EVENTS.AGENT_CONTACT_RESERVED,
|
|
2441
|
-
interactionId: otherTaskId,
|
|
2442
|
-
agentId: taskDataMock.agentId,
|
|
2443
|
-
interaction: {mediaType: 'chat'},
|
|
2444
|
-
},
|
|
2445
|
-
};
|
|
2446
|
-
webSocketManagerMock.emit('message', JSON.stringify(otherPayload));
|
|
2447
|
-
|
|
2448
|
-
const otherTask = taskManager.getTask(otherTaskId);
|
|
2449
|
-
const otherTaskEmitSpy = jest.spyOn(otherTask, 'emit');
|
|
2466
|
+
it('sends mapped events to the task state machine service', () => {
|
|
2467
|
+
const payload = {...taskDataMock, type: CC_EVENTS.AGENT_CONTACT_ASSIGNED};
|
|
2468
|
+
const task = taskManager.getTask(taskId);
|
|
2469
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
2450
2470
|
|
|
2451
|
-
|
|
2452
|
-
const mergedPayload = {
|
|
2453
|
-
data: {
|
|
2454
|
-
type: CC_EVENTS.CONTACT_MERGED,
|
|
2455
|
-
interactionId: taskId,
|
|
2456
|
-
agentId: taskDataMock.agentId,
|
|
2457
|
-
interaction: {
|
|
2458
|
-
mediaType: 'telephony',
|
|
2459
|
-
state: 'merged',
|
|
2460
|
-
},
|
|
2461
|
-
},
|
|
2462
|
-
};
|
|
2471
|
+
webSocketManagerMock.emit('message', JSON.stringify({data: payload}));
|
|
2463
2472
|
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
// Verify original task was updated
|
|
2471
|
-
expect(managerEmitSpy).toHaveBeenCalledWith(
|
|
2472
|
-
TASK_EVENTS.TASK_MERGED,
|
|
2473
|
-
expect.objectContaining({
|
|
2474
|
-
data: expect.objectContaining({
|
|
2475
|
-
interactionId: taskId,
|
|
2476
|
-
}),
|
|
2477
|
-
})
|
|
2478
|
-
);
|
|
2473
|
+
expect(sendStateMachineEventSpy).toHaveBeenCalledWith({
|
|
2474
|
+
type: TaskEvent.ASSIGN,
|
|
2475
|
+
taskData: payload,
|
|
2476
|
+
});
|
|
2477
|
+
|
|
2478
|
+
sendStateMachineEventSpy.mockRestore();
|
|
2479
2479
|
});
|
|
2480
2480
|
});
|
|
2481
|
-
|
|
2482
2481
|
});
|
|
2483
|
-
|