@webex/contact-center 3.11.0 → 3.12.0-auth-prejoin-fetch.1
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 +883 -0
- package/AGENTS.md +94 -0
- package/ai-docs/ARCHITECTURE.md +168 -0
- package/ai-docs/CONTRACTS.md +50 -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 +359 -0
- package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -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 +818 -59
- package/dist/cc.js.map +1 -1
- package/dist/config.js +13 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +31 -3
- 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 +127 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +34 -3
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/AddressBook.js +18 -15
- package/dist/services/AddressBook.js.map +1 -1
- package/dist/services/AnswerCallOnWebexService.js +174 -0
- package/dist/services/AnswerCallOnWebexService.js.map +1 -0
- package/dist/services/ApiAiAssistant.js +318 -0
- package/dist/services/ApiAiAssistant.js.map +1 -0
- package/dist/services/EntryPoint.js +46 -68
- package/dist/services/EntryPoint.js.map +1 -1
- package/dist/services/Queue.js +27 -22
- package/dist/services/Queue.js.map +1 -1
- package/dist/services/UserPreference.js +427 -0
- package/dist/services/UserPreference.js.map +1 -0
- package/dist/services/WebexCrossClientService.js +171 -0
- package/dist/services/WebexCrossClientService.js.map +1 -0
- package/dist/services/WxAppTelephonyMercurySync.js +93 -0
- package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
- package/dist/services/agent/types.js.map +1 -1
- package/dist/services/config/Util.js +11 -4
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/constants.js +45 -8
- 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 +70 -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/WebexRequest.js +6 -2
- package/dist/services/core/WebexRequest.js.map +1 -1
- package/dist/services/core/aqm-reqs.js +119 -30
- package/dist/services/core/aqm-reqs.js.map +1 -1
- package/dist/services/core/types.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 +754 -0
- package/dist/services/task/Task.js.map +1 -0
- package/dist/services/task/TaskFactory.js +49 -0
- package/dist/services/task/TaskFactory.js.map +1 -0
- package/dist/services/task/TaskManager.js +1073 -447
- 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/WebexCallingUtils.js +70 -0
- package/dist/services/task/WebexCallingUtils.js.map +1 -0
- package/dist/services/task/constants.js +26 -2
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/contact.js +29 -0
- package/dist/services/task/contact.js.map +1 -1
- package/dist/services/task/dialer.js +129 -0
- package/dist/services/task/dialer.js.map +1 -1
- package/dist/services/task/digital/Digital.js +78 -0
- package/dist/services/task/digital/Digital.js.map +1 -0
- package/dist/services/task/state-machine/TaskStateMachine.js +971 -0
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
- package/dist/services/task/state-machine/actions.js +572 -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 +409 -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 +703 -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 +243 -5
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +1380 -0
- package/dist/services/task/voice/Voice.js.map +1 -0
- package/dist/services/task/voice/WebRTC.js +152 -0
- package/dist/services/task/voice/WebRTC.js.map +1 -0
- package/dist/services/task/voice/wxAppVoiceMethods.js +201 -0
- package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
- package/dist/services/wxAppTelephonyUtils.js +19 -0
- package/dist/services/wxAppTelephonyUtils.js.map +1 -0
- package/dist/types/cc.d.ts +967 -0
- package/dist/types/config.d.ts +79 -0
- package/dist/types/constants.d.ts +74 -0
- package/dist/types/index.d.ts +201 -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 +183 -0
- package/dist/types/services/AddressBook.d.ts +75 -0
- package/dist/types/services/AnswerCallOnWebexService.d.ts +37 -0
- package/dist/types/services/ApiAiAssistant.d.ts +49 -0
- package/dist/types/services/EntryPoint.d.ts +69 -0
- package/dist/types/services/Queue.d.ts +78 -0
- package/dist/types/services/UserPreference.d.ts +118 -0
- package/dist/types/services/WebCallingService.d.ts +1 -0
- package/dist/types/services/WebexCrossClientService.d.ts +28 -0
- package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -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 +273 -0
- package/dist/types/services/config/index.d.ts +177 -0
- package/dist/types/services/config/types.d.ts +1381 -0
- package/dist/types/services/constants.d.ts +110 -0
- package/dist/types/services/core/Err.d.ts +127 -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 +23 -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 +49 -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 +175 -0
- package/dist/types/services/task/TaskFactory.d.ts +13 -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/WebexCallingUtils.d.ts +11 -0
- package/dist/types/services/task/constants.d.ts +94 -0
- package/dist/types/services/task/contact.d.ts +73 -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 +1398 -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 +103 -0
- package/dist/types/services/task/state-machine/index.d.ts +13 -0
- package/dist/types/services/task/state-machine/types.d.ts +277 -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 +1933 -0
- package/dist/types/services/task/voice/Voice.d.ts +223 -0
- package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
- package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +52 -0
- package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
- package/dist/types/types.d.ts +784 -0
- package/dist/types/utils/PageCache.d.ts +190 -0
- package/dist/types/webex-config.d.ts +53 -0
- package/dist/types/webex.d.ts +8 -0
- package/dist/types.js +137 -3
- package/dist/types.js.map +1 -1
- package/dist/utils/PageCache.js +19 -5
- package/dist/utils/PageCache.js.map +1 -1
- package/dist/webex.js +14 -2
- package/dist/webex.js.map +1 -1
- package/package.json +16 -12
- package/src/cc.ts +983 -60
- package/src/config.ts +13 -0
- package/src/constants.ts +29 -1
- package/src/index.ts +26 -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 +860 -0
- package/src/metrics/behavioral-events.ts +134 -0
- package/src/metrics/constants.ts +39 -3
- package/src/services/AddressBook.ts +17 -6
- package/src/services/AnswerCallOnWebexService.ts +206 -0
- package/src/services/ApiAiAssistant.ts +412 -0
- package/src/services/EntryPoint.ts +59 -60
- package/src/services/Queue.ts +29 -12
- package/src/services/UserPreference.ts +509 -0
- package/src/services/WebexCrossClientService.ts +212 -0
- package/src/services/WxAppTelephonyMercurySync.ts +115 -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 +497 -0
- package/src/services/config/Util.ts +13 -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 +675 -0
- package/src/services/config/constants.ts +47 -7
- package/src/services/config/index.ts +45 -1
- package/src/services/config/types.ts +253 -11
- package/src/services/constants.ts +29 -0
- package/src/services/core/Err.ts +4 -0
- package/src/services/core/Utils.ts +143 -30
- package/src/services/core/WebexRequest.ts +3 -1
- 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 +787 -0
- package/src/services/core/aqm-reqs.ts +125 -32
- package/src/services/core/types.ts +2 -0
- 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 +908 -0
- package/src/services/task/TaskFactory.ts +60 -0
- package/src/services/task/TaskManager.ts +1291 -513
- package/src/services/task/TaskUtils.ts +314 -24
- package/src/services/task/WebexCallingUtils.ts +136 -0
- package/src/services/task/ai-docs/AGENTS.md +457 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +595 -0
- package/src/services/task/ai-docs/task-spec.md +1469 -0
- package/src/services/task/constants.ts +26 -0
- package/src/services/task/contact.ts +30 -0
- package/src/services/task/dialer.ts +136 -1
- package/src/services/task/digital/Digital.ts +97 -0
- package/src/services/task/state-machine/TaskStateMachine.ts +1313 -0
- package/src/services/task/state-machine/actions.ts +741 -0
- package/src/services/task/state-machine/ai-docs/AGENTS.md +462 -0
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1146 -0
- package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2209 -0
- package/src/services/task/state-machine/constants.ts +172 -0
- package/src/services/task/state-machine/guards.ts +498 -0
- package/src/services/task/state-machine/index.ts +28 -0
- package/src/services/task/state-machine/types.ts +258 -0
- package/src/services/task/state-machine/uiControlsComputer.ts +1135 -0
- package/src/services/task/taskDataNormalizer.ts +137 -0
- package/src/services/task/types.ts +843 -70
- package/src/services/task/voice/Voice.ts +1720 -0
- package/src/services/task/voice/WebRTC.ts +191 -0
- package/src/services/task/voice/wxAppVoiceMethods.ts +307 -0
- package/src/services/wxAppTelephonyUtils.ts +14 -0
- package/src/types.ts +238 -11
- package/src/utils/AGENTS.md +289 -0
- package/src/utils/PageCache.ts +38 -5
- package/src/utils/ai-docs/utils-spec.md +391 -0
- package/src/webex.js +2 -0
- package/test/unit/spec/cc.ts +1856 -122
- package/test/unit/spec/logger-proxy.ts +70 -0
- package/test/unit/spec/metrics/behavioral-events.ts +18 -0
- package/test/unit/spec/services/AddressBook.ts +37 -6
- package/test/unit/spec/services/AnswerCallOnWebexService.ts +223 -0
- package/test/unit/spec/services/ApiAiAssistant.ts +273 -0
- package/test/unit/spec/services/EntryPoint.ts +87 -40
- package/test/unit/spec/services/Queue.ts +123 -12
- package/test/unit/spec/services/UserPreference.ts +401 -0
- package/test/unit/spec/services/WebCallingService.ts +7 -1
- package/test/unit/spec/services/WebexCrossClientService.ts +261 -0
- package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +113 -0
- package/test/unit/spec/services/config/Util.ts +85 -0
- 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/WebexRequest.ts +3 -1
- package/test/unit/spec/services/core/aqm-reqs.ts +113 -1
- 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 +677 -0
- package/test/unit/spec/services/task/TaskFactory.ts +99 -0
- package/test/unit/spec/services/task/TaskManager.ts +2209 -918
- package/test/unit/spec/services/task/TaskUtils.ts +235 -0
- package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
- package/test/unit/spec/services/task/contact.ts +33 -0
- package/test/unit/spec/services/task/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 +3433 -0
- package/test/unit/spec/services/task/state-machine/guards.ts +839 -0
- package/test/unit/spec/services/task/state-machine/types.ts +18 -0
- package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +3101 -0
- package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
- package/test/unit/spec/services/task/voice/Voice.ts +1523 -0
- package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
- package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +459 -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,111 @@ 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.CONTACT_OWNER_CHANGED]: TASK_EVENTS.TASK_HYDRATE,
|
|
55
|
+
[TaskEvent.ASSIGN]: TASK_EVENTS.TASK_ASSIGNED,
|
|
56
|
+
[TaskEvent.HOLD_SUCCESS]: TASK_EVENTS.TASK_HOLD,
|
|
57
|
+
[TaskEvent.UNHOLD_SUCCESS]: TASK_EVENTS.TASK_RESUME,
|
|
58
|
+
[TaskEvent.CONSULT_CREATED]: TASK_EVENTS.TASK_CONSULT_CREATED,
|
|
59
|
+
[TaskEvent.CONSULTING_ACTIVE]: TASK_EVENTS.TASK_CONSULT_ACCEPTED,
|
|
60
|
+
[TaskEvent.CONSULT_END]: TASK_EVENTS.TASK_CONSULT_END,
|
|
61
|
+
[TaskEvent.CONSULT_FAILED]: CC_EVENTS.AGENT_CONSULT_FAILED,
|
|
62
|
+
[TaskEvent.CTQ_CANCEL]: TASK_EVENTS.TASK_CONSULT_QUEUE_CANCELLED,
|
|
63
|
+
[TaskEvent.CTQ_CANCEL_FAILED]: TASK_EVENTS.TASK_CONSULT_QUEUE_FAILED,
|
|
64
|
+
[TaskEvent.END]: TASK_EVENTS.TASK_END,
|
|
65
|
+
[TaskEvent.CONTACT_ENDED]: TASK_EVENTS.TASK_END,
|
|
66
|
+
[TaskEvent.ASSIGN_FAILED]: TASK_EVENTS.TASK_REJECT,
|
|
67
|
+
[TaskEvent.INVITE_FAILED]: TASK_EVENTS.TASK_REJECT,
|
|
68
|
+
[TaskEvent.RONA]: TASK_EVENTS.TASK_REJECT,
|
|
69
|
+
[TaskEvent.OUTBOUND_FAILED]: TASK_EVENTS.TASK_OUTDIAL_FAILED,
|
|
70
|
+
[TaskEvent.RECORDING_STARTED]: TASK_EVENTS.TASK_RECORDING_STARTED,
|
|
71
|
+
[TaskEvent.PAUSE_RECORDING]: TASK_EVENTS.TASK_RECORDING_PAUSED,
|
|
72
|
+
[TaskEvent.RESUME_RECORDING]: TASK_EVENTS.TASK_RECORDING_RESUMED,
|
|
73
|
+
[TaskEvent.WRAPUP_COMPLETE]: TASK_EVENTS.TASK_WRAPPEDUP,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
task.sendStateMachineEvent = jest.fn().mockImplementation((event) => {
|
|
77
|
+
if (event.taskData) {
|
|
78
|
+
task.updateTaskData(event.taskData);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const mappedEvent = taskEventMap[event.type as TaskEvent];
|
|
82
|
+
if (mappedEvent) {
|
|
83
|
+
if (
|
|
84
|
+
[TaskEvent.ASSIGN_FAILED, TaskEvent.RONA, TaskEvent.INVITE_FAILED].includes(
|
|
85
|
+
event.type as TaskEvent
|
|
86
|
+
)
|
|
87
|
+
) {
|
|
88
|
+
task.emit(mappedEvent, event.reason ?? event.taskData?.reason);
|
|
89
|
+
} else if (event.type === TaskEvent.OUTBOUND_FAILED) {
|
|
90
|
+
if (!event.taskData?.suppressOutdialFailedPopup) {
|
|
91
|
+
task.emit(mappedEvent, event.reason);
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
task.emit(mappedEvent, task);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if ([TaskEvent.ASSIGN, TaskEvent.CONSULTING_ACTIVE].includes(event.type as TaskEvent)) {
|
|
99
|
+
task.emit(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, task);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Auto-answer is now handled at the Task layer (triggered by state machine actions)
|
|
103
|
+
if (
|
|
104
|
+
[TaskEvent.TASK_OFFERED, TaskEvent.OFFER_CONSULT].includes(event.type as TaskEvent) &&
|
|
105
|
+
(event.taskData?.isAutoAnswering === true || event.taskData?.isAutoAnswering === 'true')
|
|
106
|
+
) {
|
|
107
|
+
Promise.resolve(task.accept())
|
|
108
|
+
.then(() => {
|
|
109
|
+
task.emit(TASK_EVENTS.TASK_AUTO_ANSWERED, task);
|
|
110
|
+
})
|
|
111
|
+
.catch(() => undefined);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Cleanup is now emitted by state machine actions (Task layer).
|
|
115
|
+
// Simulate the TASK_CLEANUP emission for unit tests using mock tasks.
|
|
116
|
+
const eventType = event.type as TaskEvent;
|
|
117
|
+
const shouldCleanup =
|
|
118
|
+
eventType === TaskEvent.CONTACT_ENDED ||
|
|
119
|
+
eventType === TaskEvent.END ||
|
|
120
|
+
eventType === TaskEvent.TASK_WRAPUP ||
|
|
121
|
+
eventType === TaskEvent.WRAPUP_COMPLETE ||
|
|
122
|
+
eventType === TaskEvent.ASSIGN_FAILED ||
|
|
123
|
+
eventType === TaskEvent.INVITE_FAILED ||
|
|
124
|
+
eventType === TaskEvent.RONA ||
|
|
125
|
+
eventType === TaskEvent.OUTBOUND_FAILED ||
|
|
126
|
+
(eventType === TaskEvent.CONSULT_END && task.data?.isConsulted === true);
|
|
127
|
+
|
|
128
|
+
if (shouldCleanup) {
|
|
129
|
+
const removeFromCollection = eventType !== TaskEvent.CONTACT_ENDED;
|
|
130
|
+
task.emit(TASK_EVENTS.TASK_CLEANUP, task, {removeFromCollection});
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return task;
|
|
135
|
+
};
|
|
136
|
+
|
|
26
137
|
taskDataMock = {
|
|
27
138
|
type: CC_EVENTS.AGENT_CONTACT_RESERVED,
|
|
28
139
|
agentId: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
@@ -42,9 +153,21 @@ describe('TaskManager', () => {
|
|
|
42
153
|
data: taskDataMock,
|
|
43
154
|
};
|
|
44
155
|
|
|
156
|
+
const expectLastStateMachineEvent = (
|
|
157
|
+
spy: jest.SpyInstance | jest.Mock,
|
|
158
|
+
expectedType: TaskEvent
|
|
159
|
+
) => {
|
|
160
|
+
expect(spy).toHaveBeenCalled();
|
|
161
|
+
const lastCall = spy.mock.calls[spy.mock.calls.length - 1] || [];
|
|
162
|
+
const event = lastCall[3]?.type ? lastCall[3] : lastCall[0];
|
|
163
|
+
expect(event?.type).toBe(expectedType);
|
|
164
|
+
return event;
|
|
165
|
+
};
|
|
166
|
+
|
|
45
167
|
beforeEach(() => {
|
|
46
168
|
contactMock = contact;
|
|
47
169
|
webSocketManagerMock = new EventEmitter();
|
|
170
|
+
rtdWebSocketManagerMock = new EventEmitter();
|
|
48
171
|
|
|
49
172
|
webex = {
|
|
50
173
|
logger: {
|
|
@@ -74,15 +197,25 @@ describe('TaskManager', () => {
|
|
|
74
197
|
onSpy = jest.spyOn(webCallingService, 'on');
|
|
75
198
|
offSpy = jest.spyOn(webCallingService, 'off');
|
|
76
199
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
emit: jest.fn(),
|
|
80
|
-
accept: jest.fn(),
|
|
81
|
-
decline: jest.fn(),
|
|
82
|
-
updateTaskData: jest.fn(),
|
|
83
|
-
data: taskDataMock,
|
|
200
|
+
mockApiAIAssistant = {
|
|
201
|
+
sendEvent: jest.fn().mockResolvedValue({}),
|
|
84
202
|
};
|
|
203
|
+
|
|
204
|
+
taskManager = new TaskManager(
|
|
205
|
+
mockApiAIAssistant as any,
|
|
206
|
+
contactMock,
|
|
207
|
+
webCallingService,
|
|
208
|
+
webSocketManagerMock as any,
|
|
209
|
+
rtdWebSocketManagerMock as any
|
|
210
|
+
);
|
|
211
|
+
taskManager.taskCollection[taskId] = createMockTask(taskDataMock);
|
|
212
|
+
(taskManager as any).setupTaskListeners?.(taskManager.taskCollection[taskId]);
|
|
85
213
|
taskManager.call = mockCall;
|
|
214
|
+
taskManager.setAgentId('test-agent-id');
|
|
215
|
+
|
|
216
|
+
jest
|
|
217
|
+
.spyOn(TaskFactory, 'createTask')
|
|
218
|
+
.mockImplementation((contact, webCallingService, data, configFlags) => createMockTask(data));
|
|
86
219
|
});
|
|
87
220
|
|
|
88
221
|
afterEach(() => {
|
|
@@ -93,7 +226,8 @@ describe('TaskManager', () => {
|
|
|
93
226
|
it('should initialize TaskManager and register listeners', () => {
|
|
94
227
|
webSocketManagerMock.emit('message', JSON.stringify({data: taskDataMock}));
|
|
95
228
|
const incomingCallCb = onSpy.mock.calls[0][1];
|
|
96
|
-
const
|
|
229
|
+
const incomingHandler = jest.fn();
|
|
230
|
+
taskManager.on(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
97
231
|
|
|
98
232
|
expect(taskManager).toBeInstanceOf(TaskManager);
|
|
99
233
|
expect(webCallingService.listenerCount(LINE_EVENTS.INCOMING_CALL)).toBe(1);
|
|
@@ -102,14 +236,13 @@ describe('TaskManager', () => {
|
|
|
102
236
|
|
|
103
237
|
incomingCallCb(mockCall);
|
|
104
238
|
|
|
105
|
-
expect(
|
|
239
|
+
expect(incomingHandler).toHaveBeenCalledWith(taskManager.getTask(taskId));
|
|
240
|
+
taskManager.off(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
106
241
|
});
|
|
107
242
|
|
|
108
243
|
it('should re-emit task related events', () => {
|
|
109
244
|
const dummyPayload = {
|
|
110
|
-
data: {...taskDataMock,
|
|
111
|
-
type: CC_TASK_EVENTS.AGENT_CONSULTING,
|
|
112
|
-
},
|
|
245
|
+
data: {...taskDataMock, type: CC_TASK_EVENTS.AGENT_CONSULTING},
|
|
113
246
|
};
|
|
114
247
|
webSocketManagerMock.emit('message', JSON.stringify({data: taskDataMock}));
|
|
115
248
|
const taskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
|
|
@@ -120,7 +253,186 @@ describe('TaskManager', () => {
|
|
|
120
253
|
|
|
121
254
|
webSocketManagerMock.emit('message', JSON.stringify(dummyPayload));
|
|
122
255
|
|
|
123
|
-
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
256
|
+
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
257
|
+
TASK_EVENTS.TASK_CONSULT_ACCEPTED,
|
|
258
|
+
taskManager.getTask(taskId)
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should invoke sendEvent for configured start/stop backend events', () => {
|
|
263
|
+
taskManager.setConfigFlags({
|
|
264
|
+
isEndTaskEnabled: true,
|
|
265
|
+
isEndConsultEnabled: true,
|
|
266
|
+
webRtcEnabled: true,
|
|
267
|
+
autoWrapup: false,
|
|
268
|
+
aiFeature: {
|
|
269
|
+
id: 'ai-feature-1',
|
|
270
|
+
realtimeTranscripts: {
|
|
271
|
+
enable: true,
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const interactionId = taskId;
|
|
277
|
+
const message = (type: CC_EVENTS) =>
|
|
278
|
+
JSON.stringify({
|
|
279
|
+
data: {
|
|
280
|
+
...taskDataMock,
|
|
281
|
+
interactionId,
|
|
282
|
+
type,
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONTACT_ASSIGNED));
|
|
287
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULTING));
|
|
288
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULT_CONFERENCED));
|
|
289
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULT_ENDED));
|
|
290
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE));
|
|
291
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_WRAPUP));
|
|
292
|
+
|
|
293
|
+
expect(mockApiAIAssistant.sendEvent).toHaveBeenCalledTimes(6);
|
|
294
|
+
expect(mockApiAIAssistant.sendEvent).toHaveBeenCalledWith(
|
|
295
|
+
'test-agent-id',
|
|
296
|
+
interactionId,
|
|
297
|
+
'CUSTOM_EVENT',
|
|
298
|
+
'GET_TRANSCRIPTS',
|
|
299
|
+
{action: 'START'}
|
|
300
|
+
);
|
|
301
|
+
expect(mockApiAIAssistant.sendEvent).toHaveBeenCalledWith(
|
|
302
|
+
'test-agent-id',
|
|
303
|
+
interactionId,
|
|
304
|
+
'CUSTOM_EVENT',
|
|
305
|
+
'GET_TRANSCRIPTS',
|
|
306
|
+
{action: 'STOP'}
|
|
307
|
+
);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('should not invoke sendEvent when realtime transcripts are disabled in aiFeature', () => {
|
|
311
|
+
taskManager.setConfigFlags({
|
|
312
|
+
isEndTaskEnabled: true,
|
|
313
|
+
isEndConsultEnabled: true,
|
|
314
|
+
webRtcEnabled: true,
|
|
315
|
+
autoWrapup: false,
|
|
316
|
+
aiFeature: {
|
|
317
|
+
id: 'ai-feature-1',
|
|
318
|
+
realtimeTranscripts: {
|
|
319
|
+
enable: false,
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const message = (type: CC_EVENTS) =>
|
|
325
|
+
JSON.stringify({
|
|
326
|
+
data: {
|
|
327
|
+
...taskDataMock,
|
|
328
|
+
interactionId: taskId,
|
|
329
|
+
type,
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONTACT_ASSIGNED));
|
|
334
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULTING));
|
|
335
|
+
|
|
336
|
+
expect(mockApiAIAssistant.sendEvent).not.toHaveBeenCalled();
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('should not invoke sendEvent when realtime transcripts config is missing', () => {
|
|
340
|
+
taskManager.setConfigFlags({
|
|
341
|
+
isEndTaskEnabled: true,
|
|
342
|
+
isEndConsultEnabled: true,
|
|
343
|
+
webRtcEnabled: true,
|
|
344
|
+
autoWrapup: false,
|
|
345
|
+
aiFeature: {
|
|
346
|
+
id: 'ai-feature-1',
|
|
347
|
+
suggestedResponses: {
|
|
348
|
+
enable: true,
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const message = (type: CC_EVENTS) =>
|
|
354
|
+
JSON.stringify({
|
|
355
|
+
data: {
|
|
356
|
+
...taskDataMock,
|
|
357
|
+
interactionId: taskId,
|
|
358
|
+
type,
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONTACT_ASSIGNED));
|
|
363
|
+
webSocketManagerMock.emit('message', message(CC_EVENTS.AGENT_CONSULTING));
|
|
364
|
+
|
|
365
|
+
expect(mockApiAIAssistant.sendEvent).not.toHaveBeenCalled();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('should emit REAL_TIME_TRANSCRIPTION from RTD websocket payload', () => {
|
|
369
|
+
const task = taskManager.getTask(taskId);
|
|
370
|
+
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
371
|
+
const realtimePayload = {
|
|
372
|
+
data: {
|
|
373
|
+
agentId: 'test-agent-id',
|
|
374
|
+
data: {
|
|
375
|
+
content: 'Thank you. Okay.',
|
|
376
|
+
conversationId: taskId,
|
|
377
|
+
isFinal: true,
|
|
378
|
+
languageCode: 'en-US',
|
|
379
|
+
messageId: '1',
|
|
380
|
+
orgId: 'org-id',
|
|
381
|
+
publishTimestamp: 1773807297475,
|
|
382
|
+
role: 'AGENT',
|
|
383
|
+
trackingId: 'tracking-id',
|
|
384
|
+
utteranceId: 'utterance-id',
|
|
385
|
+
},
|
|
386
|
+
notifDetails: {
|
|
387
|
+
actionEvent: 'REAL_TIME_TRANSCRIPTION',
|
|
388
|
+
},
|
|
389
|
+
notifType: 'REAL_TIME_TRANSCRIPTION',
|
|
390
|
+
orgId: 'org-id',
|
|
391
|
+
},
|
|
392
|
+
orgId: 'org-id',
|
|
393
|
+
trackingId: 'notifs_tracking-id',
|
|
394
|
+
type: 'REAL_TIME_TRANSCRIPTION',
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
taskManager.handleRealtimeWebsocketEvent(JSON.stringify(realtimePayload));
|
|
398
|
+
|
|
399
|
+
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
400
|
+
CC_EVENTS.REAL_TIME_TRANSCRIPTION,
|
|
401
|
+
realtimePayload.data
|
|
402
|
+
);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('should ignore RTD transcript events when task is not found', () => {
|
|
406
|
+
const realtimePayload = {
|
|
407
|
+
data: {
|
|
408
|
+
data: {
|
|
409
|
+
content: 'Thank you. Okay.',
|
|
410
|
+
conversationId: 'missing-task-id',
|
|
411
|
+
isFinal: true,
|
|
412
|
+
languageCode: 'en-US',
|
|
413
|
+
messageId: '1',
|
|
414
|
+
orgId: 'org-id',
|
|
415
|
+
publishTimestamp: 1773807297475,
|
|
416
|
+
role: 'AGENT',
|
|
417
|
+
trackingId: 'tracking-id',
|
|
418
|
+
utteranceId: 'utterance-id',
|
|
419
|
+
},
|
|
420
|
+
notifDetails: {
|
|
421
|
+
actionEvent: 'REAL_TIME_TRANSCRIPTION',
|
|
422
|
+
},
|
|
423
|
+
notifType: 'REAL_TIME_TRANSCRIPTION',
|
|
424
|
+
orgId: 'org-id',
|
|
425
|
+
},
|
|
426
|
+
orgId: 'org-id',
|
|
427
|
+
trackingId: 'notifs_tracking-id',
|
|
428
|
+
type: 'REAL_TIME_TRANSCRIPTION',
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const existingTaskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
|
|
432
|
+
|
|
433
|
+
taskManager.handleRealtimeWebsocketEvent(JSON.stringify(realtimePayload));
|
|
434
|
+
|
|
435
|
+
expect(existingTaskEmitSpy).not.toHaveBeenCalled();
|
|
124
436
|
});
|
|
125
437
|
|
|
126
438
|
it('should not re-emit agent related events', () => {
|
|
@@ -138,7 +450,7 @@ describe('TaskManager', () => {
|
|
|
138
450
|
|
|
139
451
|
webSocketManagerMock.emit('message', JSON.stringify(dummyPayload));
|
|
140
452
|
|
|
141
|
-
expect(taskEmitSpy).not.
|
|
453
|
+
expect(taskEmitSpy).not.toHaveBeenCalled();
|
|
142
454
|
});
|
|
143
455
|
|
|
144
456
|
it('should handle WebSocket message for AGENT_CONTACT_RESERVED and emit task:incoming for browser case', () => {
|
|
@@ -159,14 +471,12 @@ describe('TaskManager', () => {
|
|
|
159
471
|
},
|
|
160
472
|
};
|
|
161
473
|
|
|
162
|
-
const
|
|
474
|
+
const incomingHandler = jest.fn();
|
|
475
|
+
taskManager.on(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
163
476
|
|
|
164
477
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
165
478
|
|
|
166
|
-
expect(
|
|
167
|
-
TASK_EVENTS.TASK_INCOMING,
|
|
168
|
-
taskManager.getTask(payload.data.interactionId)
|
|
169
|
-
);
|
|
479
|
+
expect(incomingHandler).toHaveBeenCalledWith(taskManager.getTask(payload.data.interactionId));
|
|
170
480
|
expect(taskManager.getTask(payload.data.interactionId)).toBe(taskManager.getTask(taskId));
|
|
171
481
|
expect(taskManager.getAllTasks()).toHaveProperty(payload.data.interactionId);
|
|
172
482
|
|
|
@@ -187,7 +497,11 @@ describe('TaskManager', () => {
|
|
|
187
497
|
},
|
|
188
498
|
};
|
|
189
499
|
|
|
190
|
-
const currentTaskAssignedSpy = jest.spyOn(
|
|
500
|
+
const currentTaskAssignedSpy = jest.spyOn(
|
|
501
|
+
taskManager.getTask(payload.data.interactionId),
|
|
502
|
+
'emit'
|
|
503
|
+
);
|
|
504
|
+
const taskManagerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
191
505
|
|
|
192
506
|
webSocketManagerMock.emit('message', JSON.stringify(assignedPayload));
|
|
193
507
|
|
|
@@ -195,6 +509,10 @@ describe('TaskManager', () => {
|
|
|
195
509
|
TASK_EVENTS.TASK_ASSIGNED,
|
|
196
510
|
taskManager.getTask(taskId)
|
|
197
511
|
);
|
|
512
|
+
expect(taskManagerEmitSpy).toHaveBeenCalledWith(
|
|
513
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
514
|
+
taskManager.getTask(taskId)
|
|
515
|
+
);
|
|
198
516
|
});
|
|
199
517
|
|
|
200
518
|
it('should handle WebSocket message for AGENT_CONTACT_RESERVED and emit task:incoming for extension case', () => {
|
|
@@ -216,16 +534,64 @@ describe('TaskManager', () => {
|
|
|
216
534
|
},
|
|
217
535
|
};
|
|
218
536
|
|
|
219
|
-
const
|
|
537
|
+
const incomingHandler = jest.fn();
|
|
538
|
+
taskManager.on(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
220
539
|
|
|
221
540
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
222
541
|
|
|
223
|
-
expect(
|
|
224
|
-
TASK_EVENTS.TASK_INCOMING,
|
|
225
|
-
taskManager.getTask(taskId)
|
|
226
|
-
);
|
|
542
|
+
expect(incomingHandler).toHaveBeenCalledWith(taskManager.getTask(taskId));
|
|
227
543
|
expect(taskManager.getTask(payload.data.interactionId)).toBe(taskManager.getTask(taskId));
|
|
228
544
|
expect(taskManager.getAllTasks()).toHaveProperty(payload.data.interactionId);
|
|
545
|
+
taskManager.off(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it('should send mapped events through the state machine without duplicate updates', () => {
|
|
549
|
+
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
550
|
+
const task = taskManager.getTask(taskId);
|
|
551
|
+
const updateSpy = task.updateTaskData as jest.Mock;
|
|
552
|
+
updateSpy.mockClear();
|
|
553
|
+
const sendSpy = task.sendStateMachineEvent as jest.Mock;
|
|
554
|
+
sendSpy.mockClear();
|
|
555
|
+
const cleanupSpy = jest.spyOn(taskManager as any, 'handleTaskCleanup');
|
|
556
|
+
|
|
557
|
+
const assignFailedPayload = {
|
|
558
|
+
data: {
|
|
559
|
+
...initalPayload.data,
|
|
560
|
+
type: CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED,
|
|
561
|
+
reason: 'ASSIGN_FAILED',
|
|
562
|
+
},
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
webSocketManagerMock.emit('message', JSON.stringify(assignFailedPayload));
|
|
566
|
+
|
|
567
|
+
const stateMachineEvent = expectLastStateMachineEvent(sendSpy, TaskEvent.ASSIGN_FAILED);
|
|
568
|
+
expect(stateMachineEvent).toEqual({
|
|
569
|
+
type: TaskEvent.ASSIGN_FAILED,
|
|
570
|
+
reason: assignFailedPayload.data.reason,
|
|
571
|
+
});
|
|
572
|
+
expect(updateSpy).toHaveBeenCalledWith(assignFailedPayload.data);
|
|
573
|
+
expect(cleanupSpy).toHaveBeenCalledWith(task);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it('should update task data directly when no state machine mapping exists', () => {
|
|
577
|
+
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
578
|
+
const task = taskManager.getTask(taskId);
|
|
579
|
+
const updateSpy = task.updateTaskData as jest.Mock;
|
|
580
|
+
updateSpy.mockClear();
|
|
581
|
+
const sendSpy = task.sendStateMachineEvent as jest.Mock;
|
|
582
|
+
sendSpy.mockClear();
|
|
583
|
+
|
|
584
|
+
const participantMovedPayload = {
|
|
585
|
+
data: {
|
|
586
|
+
...initalPayload.data,
|
|
587
|
+
type: CC_EVENTS.CONSULTED_PARTICIPANT_MOVING,
|
|
588
|
+
},
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
webSocketManagerMock.emit('message', JSON.stringify(participantMovedPayload));
|
|
592
|
+
|
|
593
|
+
expect(sendSpy).not.toHaveBeenCalled();
|
|
594
|
+
expect(updateSpy).toHaveBeenCalledWith(participantMovedPayload.data);
|
|
229
595
|
});
|
|
230
596
|
|
|
231
597
|
it('should return task by ID', () => {
|
|
@@ -310,9 +676,30 @@ describe('TaskManager', () => {
|
|
|
310
676
|
it('test call listeners being switched off on call end', () => {
|
|
311
677
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
312
678
|
|
|
313
|
-
const
|
|
314
|
-
|
|
679
|
+
const webrtcTask = new WebRTC(contactMock, webCallingService, taskDataMock, {
|
|
680
|
+
isEndTaskEnabled: true,
|
|
681
|
+
isEndConsultEnabled: true,
|
|
682
|
+
});
|
|
683
|
+
(taskManager as any).taskCollection[taskId] = webrtcTask;
|
|
684
|
+
// TaskManager must listen to task-level cleanup events emitted by the state machine.
|
|
685
|
+
// This is normally wired when TaskManager creates the task via TaskFactory.
|
|
686
|
+
(taskManager as any).setupTaskListeners(webrtcTask);
|
|
687
|
+
|
|
688
|
+
const task = taskManager.getTask(taskId)!;
|
|
689
|
+
// This test doesn't validate UI controls; avoid requiring full interaction.media
|
|
690
|
+
// shape for WebRTC UI controls computation.
|
|
691
|
+
jest.spyOn(task as any, 'updateUiControls').mockImplementation(() => undefined);
|
|
692
|
+
const originalEmit = task.emit;
|
|
693
|
+
jest.spyOn(task, 'emit').mockImplementation((event, arg) => {
|
|
694
|
+
if (event === CC_EVENTS.CONTACT_ENDED) {
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
return originalEmit.call(task, event, arg);
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
const webCallListenerSpy = jest.spyOn(task, 'unregisterWebCallListeners');
|
|
315
701
|
const callOffSpy = jest.spyOn(mockCall, 'off');
|
|
702
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
316
703
|
const payload = {
|
|
317
704
|
data: {
|
|
318
705
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
@@ -330,12 +717,25 @@ describe('TaskManager', () => {
|
|
|
330
717
|
},
|
|
331
718
|
};
|
|
332
719
|
|
|
720
|
+
// Ensure the state machine is hydrated into a connected state before CONTACT_ENDED
|
|
721
|
+
const hydratePayload = {
|
|
722
|
+
data: {
|
|
723
|
+
...payload.data,
|
|
724
|
+
type: CC_EVENTS.AGENT_CONTACT,
|
|
725
|
+
interaction: {state: 'connected', mediaType: 'telephony'},
|
|
726
|
+
},
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
taskManager.getTask(taskId).data = hydratePayload.data;
|
|
730
|
+
webSocketManagerMock.emit('message', JSON.stringify(hydratePayload));
|
|
731
|
+
|
|
333
732
|
taskManager.getTask(taskId).data = payload.data;
|
|
334
|
-
const task = taskManager.getTask(taskId)
|
|
335
733
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
336
|
-
|
|
337
|
-
|
|
734
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
735
|
+
sendStateMachineEventSpy,
|
|
736
|
+
TaskEvent.CONTACT_ENDED
|
|
338
737
|
);
|
|
738
|
+
expect(stateMachineEvent?.taskData.wrapUpRequired).toBe(false);
|
|
339
739
|
expect(webCallListenerSpy).toHaveBeenCalledWith();
|
|
340
740
|
expect(callOffSpy).toHaveBeenCalledWith(
|
|
341
741
|
CALL_EVENT_KEYS.REMOTE_MEDIA,
|
|
@@ -346,12 +746,13 @@ describe('TaskManager', () => {
|
|
|
346
746
|
expect(offSpy.mock.calls.length).toBe(2); // 1 for incoming call and 1 for remote media
|
|
347
747
|
expect(offSpy).toHaveBeenCalledWith(CALL_EVENT_KEYS.REMOTE_MEDIA, offSpy.mock.calls[0][1]);
|
|
348
748
|
expect(offSpy).toHaveBeenCalledWith(LINE_EVENTS.INCOMING_CALL, offSpy.mock.calls[1][1]);
|
|
749
|
+
sendStateMachineEventSpy.mockRestore();
|
|
349
750
|
});
|
|
350
751
|
|
|
351
752
|
it('should emit TASK_END event with wrapupRequired on regular call end', () => {
|
|
352
753
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
353
|
-
|
|
354
|
-
const
|
|
754
|
+
const task = taskManager.getTask(taskId);
|
|
755
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
355
756
|
const payload = {
|
|
356
757
|
data: {
|
|
357
758
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
@@ -366,80 +767,77 @@ describe('TaskManager', () => {
|
|
|
366
767
|
destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
|
|
367
768
|
owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
368
769
|
queueMgr: 'aqm',
|
|
770
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
369
771
|
},
|
|
370
772
|
};
|
|
371
773
|
|
|
372
|
-
|
|
774
|
+
task.updateTaskData(payload.data);
|
|
373
775
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
);
|
|
378
|
-
expect(taskEmitSpy).toHaveBeenCalledWith(
|
|
379
|
-
TASK_EVENTS.TASK_END,
|
|
380
|
-
taskManager.getTask(taskId)
|
|
776
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
777
|
+
sendStateMachineEventSpy,
|
|
778
|
+
TaskEvent.CONTACT_ENDED
|
|
381
779
|
);
|
|
780
|
+
expect(stateMachineEvent?.taskData.wrapUpRequired).toBe(true);
|
|
781
|
+
sendStateMachineEventSpy.mockRestore();
|
|
382
782
|
});
|
|
383
783
|
|
|
384
784
|
it('should emit TASK_REJECT event on AGENT_INVITE_FAILED event', () => {
|
|
385
785
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
386
786
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
787
|
+
const task = taskManager.getTask(taskId);
|
|
788
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
789
|
+
const payload = {
|
|
790
|
+
data: {
|
|
791
|
+
type: CC_EVENTS.AGENT_INVITE_FAILED,
|
|
792
|
+
agentId: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
793
|
+
eventTime: 1733211616959,
|
|
794
|
+
eventType: 'RoutingMessage',
|
|
795
|
+
interaction: {state: 'connected'},
|
|
796
|
+
interactionId: taskId,
|
|
797
|
+
orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a',
|
|
798
|
+
trackingId: '575c0ec2-618c-42af-a61c-53aeb0a221ee',
|
|
799
|
+
mediaResourceId: '0ae913a4-c857-4705-8d49-76dd3dde75e4',
|
|
800
|
+
destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
|
|
801
|
+
owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
802
|
+
queueMgr: 'aqm',
|
|
803
|
+
reason: 'INVITE_FAILED',
|
|
804
|
+
},
|
|
805
|
+
};
|
|
406
806
|
|
|
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');
|
|
807
|
+
task.updateTaskData(payload.data);
|
|
808
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
809
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
810
|
+
sendStateMachineEventSpy,
|
|
811
|
+
TaskEvent.INVITE_FAILED
|
|
812
|
+
);
|
|
813
|
+
expect(stateMachineEvent?.reason).toBe(payload.data.reason);
|
|
814
|
+
sendStateMachineEventSpy.mockRestore();
|
|
420
815
|
});
|
|
421
816
|
|
|
422
|
-
|
|
423
|
-
it('should not emit TASK_HYDRATE if task is already present in taskManager', () => {
|
|
817
|
+
it('should emit TASK_HYDRATE even if task is already present in taskManager', () => {
|
|
424
818
|
const payload = {
|
|
425
819
|
data: {
|
|
426
820
|
...initalPayload.data,
|
|
427
821
|
type: CC_EVENTS.AGENT_CONTACT,
|
|
428
822
|
},
|
|
429
823
|
};
|
|
430
|
-
const
|
|
824
|
+
const existingTask = taskManager.getTask(taskId);
|
|
825
|
+
const sendStateMachineEventSpy = jest.spyOn(existingTask, 'sendStateMachineEvent');
|
|
431
826
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
432
827
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
828
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
829
|
+
sendStateMachineEventSpy,
|
|
830
|
+
TaskEvent.HYDRATE
|
|
436
831
|
);
|
|
832
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
833
|
+
expect(existingTask).toBe(taskManager.getTask(taskId));
|
|
437
834
|
expect(taskManager.taskCollection[payload.data.interactionId]).toBe(
|
|
438
835
|
taskManager.getTask(taskId)
|
|
439
836
|
);
|
|
837
|
+
sendStateMachineEventSpy.mockRestore();
|
|
440
838
|
});
|
|
441
839
|
|
|
442
|
-
it('should emit
|
|
840
|
+
it('should emit TASK_HYDRATE event on AGENT_CONTACT when task is created from payload', () => {
|
|
443
841
|
taskManager.taskCollection = [];
|
|
444
842
|
const payload = {
|
|
445
843
|
data: {
|
|
@@ -449,13 +847,15 @@ describe('TaskManager', () => {
|
|
|
449
847
|
},
|
|
450
848
|
};
|
|
451
849
|
|
|
452
|
-
const taskEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
453
850
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
454
851
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
852
|
+
const createdTask = taskManager.getTask(taskId);
|
|
853
|
+
const sendStateMachineEventSpy = createdTask.sendStateMachineEvent as jest.Mock;
|
|
854
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
855
|
+
sendStateMachineEventSpy,
|
|
856
|
+
TaskEvent.HYDRATE
|
|
458
857
|
);
|
|
858
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
459
859
|
expect(taskManager.taskCollection[payload.data.interactionId]).toBe(
|
|
460
860
|
taskManager.getTask(taskId)
|
|
461
861
|
);
|
|
@@ -470,10 +870,15 @@ describe('TaskManager', () => {
|
|
|
470
870
|
},
|
|
471
871
|
};
|
|
472
872
|
|
|
473
|
-
const taskEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
474
873
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
475
874
|
|
|
476
|
-
|
|
875
|
+
const createdTask = taskManager.getTask(taskId);
|
|
876
|
+
const sendStateMachineEventSpy = createdTask.sendStateMachineEvent as jest.Mock;
|
|
877
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
878
|
+
sendStateMachineEventSpy,
|
|
879
|
+
TaskEvent.HYDRATE
|
|
880
|
+
);
|
|
881
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
477
882
|
expect(taskManager.taskCollection[payload.data.interactionId]).toBe(
|
|
478
883
|
taskManager.getTask(taskId)
|
|
479
884
|
);
|
|
@@ -483,7 +888,7 @@ describe('TaskManager', () => {
|
|
|
483
888
|
const testAgentId = '723a8ffb-a26e-496d-b14a-ff44fb83b64f';
|
|
484
889
|
taskManager.setAgentId(testAgentId);
|
|
485
890
|
taskManager.taskCollection = [];
|
|
486
|
-
|
|
891
|
+
|
|
487
892
|
const payload = {
|
|
488
893
|
data: {
|
|
489
894
|
...initalPayload.data,
|
|
@@ -492,9 +897,9 @@ describe('TaskManager', () => {
|
|
|
492
897
|
mediaType: 'telephony',
|
|
493
898
|
state: 'conference',
|
|
494
899
|
participants: {
|
|
495
|
-
[testAgentId]: {
|
|
496
|
-
'agent-2': {
|
|
497
|
-
'customer-1': {
|
|
900
|
+
[testAgentId]: {pType: 'Agent', hasLeft: false},
|
|
901
|
+
'agent-2': {pType: 'Agent', hasLeft: false},
|
|
902
|
+
'customer-1': {pType: 'Customer', hasLeft: false},
|
|
498
903
|
},
|
|
499
904
|
media: {
|
|
500
905
|
[taskId]: {
|
|
@@ -517,7 +922,7 @@ describe('TaskManager', () => {
|
|
|
517
922
|
const testAgentId = '723a8ffb-a26e-496d-b14a-ff44fb83b64f';
|
|
518
923
|
taskManager.setAgentId(testAgentId);
|
|
519
924
|
taskManager.taskCollection = [];
|
|
520
|
-
|
|
925
|
+
|
|
521
926
|
const payload = {
|
|
522
927
|
data: {
|
|
523
928
|
...initalPayload.data,
|
|
@@ -526,8 +931,8 @@ describe('TaskManager', () => {
|
|
|
526
931
|
mediaType: 'telephony',
|
|
527
932
|
state: 'connected',
|
|
528
933
|
participants: {
|
|
529
|
-
[testAgentId]: {
|
|
530
|
-
'customer-1': {
|
|
934
|
+
[testAgentId]: {pType: 'Agent', hasLeft: false},
|
|
935
|
+
'customer-1': {pType: 'Customer', hasLeft: false},
|
|
531
936
|
},
|
|
532
937
|
media: {
|
|
533
938
|
[taskId]: {
|
|
@@ -546,7 +951,7 @@ describe('TaskManager', () => {
|
|
|
546
951
|
expect(createdTask.data.isConferenceInProgress).toBe(false);
|
|
547
952
|
});
|
|
548
953
|
|
|
549
|
-
it('should emit
|
|
954
|
+
it('should emit TASK_WRAPUP event on AGENT_WRAPUP event', () => {
|
|
550
955
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
551
956
|
|
|
552
957
|
const wrapupPayload = {
|
|
@@ -563,18 +968,20 @@ describe('TaskManager', () => {
|
|
|
563
968
|
destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
|
|
564
969
|
owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
|
|
565
970
|
queueMgr: 'aqm',
|
|
566
|
-
wrapUpRequired: true
|
|
971
|
+
wrapUpRequired: true,
|
|
567
972
|
},
|
|
568
973
|
};
|
|
569
974
|
|
|
570
975
|
const task = taskManager.getTask(taskId);
|
|
571
|
-
const updateTaskDataSpy = jest.
|
|
572
|
-
|
|
976
|
+
const updateTaskDataSpy = task.updateTaskData as jest.Mock;
|
|
977
|
+
updateTaskDataSpy.mockClear();
|
|
978
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
573
979
|
|
|
574
980
|
webSocketManagerMock.emit('message', JSON.stringify(wrapupPayload));
|
|
575
981
|
|
|
576
982
|
expect(updateTaskDataSpy).toHaveBeenCalledWith(wrapupPayload.data);
|
|
577
|
-
|
|
983
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.TASK_WRAPUP);
|
|
984
|
+
sendStateMachineEventSpy.mockRestore();
|
|
578
985
|
});
|
|
579
986
|
|
|
580
987
|
it('should emit TASK_HOLD event on AGENT_CONTACT_HELD event', () => {
|
|
@@ -597,13 +1004,20 @@ describe('TaskManager', () => {
|
|
|
597
1004
|
},
|
|
598
1005
|
};
|
|
599
1006
|
|
|
600
|
-
const
|
|
601
|
-
const taskUpdateTaskDataSpy = jest.
|
|
1007
|
+
const task = taskManager.getTask(taskId);
|
|
1008
|
+
const taskUpdateTaskDataSpy = task.updateTaskData as jest.Mock;
|
|
1009
|
+
taskUpdateTaskDataSpy.mockClear();
|
|
1010
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
602
1011
|
|
|
603
1012
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
604
1013
|
|
|
605
1014
|
expect(taskUpdateTaskDataSpy).toHaveBeenCalledWith(payload.data);
|
|
606
|
-
|
|
1015
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1016
|
+
sendStateMachineEventSpy,
|
|
1017
|
+
TaskEvent.HOLD_SUCCESS
|
|
1018
|
+
);
|
|
1019
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1020
|
+
sendStateMachineEventSpy.mockRestore();
|
|
607
1021
|
});
|
|
608
1022
|
|
|
609
1023
|
it('should emit TASK_RESUME event on AGENT_CONTACT_UNHELD event', () => {
|
|
@@ -626,11 +1040,18 @@ describe('TaskManager', () => {
|
|
|
626
1040
|
},
|
|
627
1041
|
};
|
|
628
1042
|
|
|
629
|
-
const
|
|
630
|
-
const taskUpdateTaskDataSpy = jest.
|
|
1043
|
+
const task = taskManager.getTask(taskId);
|
|
1044
|
+
const taskUpdateTaskDataSpy = task.updateTaskData as jest.Mock;
|
|
1045
|
+
taskUpdateTaskDataSpy.mockClear();
|
|
1046
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
631
1047
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
632
1048
|
expect(taskUpdateTaskDataSpy).toHaveBeenCalledWith(payload.data);
|
|
633
|
-
|
|
1049
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1050
|
+
sendStateMachineEventSpy,
|
|
1051
|
+
TaskEvent.UNHOLD_SUCCESS
|
|
1052
|
+
);
|
|
1053
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1054
|
+
sendStateMachineEventSpy.mockRestore();
|
|
634
1055
|
});
|
|
635
1056
|
|
|
636
1057
|
it('handle AGENT_CONSULT_CREATED event', () => {
|
|
@@ -643,17 +1064,16 @@ describe('TaskManager', () => {
|
|
|
643
1064
|
|
|
644
1065
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
645
1066
|
const task = taskManager.getTask(taskId);
|
|
646
|
-
const
|
|
647
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
1067
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
648
1068
|
|
|
649
1069
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
650
1070
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
expect(
|
|
656
|
-
|
|
1071
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1072
|
+
sendStateMachineEventSpy,
|
|
1073
|
+
TaskEvent.CONSULT_CREATED
|
|
1074
|
+
);
|
|
1075
|
+
expect(stateMachineEvent?.taskData).toEqual({...payload.data, isConsulted: false});
|
|
1076
|
+
sendStateMachineEventSpy.mockRestore();
|
|
657
1077
|
});
|
|
658
1078
|
|
|
659
1079
|
it('handle AGENT_OFFER_CONTACT event', () => {
|
|
@@ -666,11 +1086,17 @@ describe('TaskManager', () => {
|
|
|
666
1086
|
|
|
667
1087
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
668
1088
|
|
|
669
|
-
const
|
|
1089
|
+
const task = taskManager.getTask(taskId);
|
|
1090
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
670
1091
|
|
|
671
1092
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
672
1093
|
|
|
673
|
-
|
|
1094
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1095
|
+
sendStateMachineEventSpy,
|
|
1096
|
+
TaskEvent.TASK_OFFERED
|
|
1097
|
+
);
|
|
1098
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1099
|
+
sendStateMachineEventSpy.mockRestore();
|
|
674
1100
|
});
|
|
675
1101
|
|
|
676
1102
|
describe('Auto-Answer Functionality', () => {
|
|
@@ -680,8 +1106,8 @@ describe('TaskManager', () => {
|
|
|
680
1106
|
|
|
681
1107
|
const task = taskManager.getTask(taskId);
|
|
682
1108
|
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
683
|
-
const taskManagerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
684
1109
|
const taskAcceptSpy = jest.spyOn(task, 'accept').mockResolvedValue(undefined);
|
|
1110
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
685
1111
|
|
|
686
1112
|
// Step 2: Trigger AGENT_OFFER_CONTACT with auto-answer
|
|
687
1113
|
const autoAnswerPayload = {
|
|
@@ -705,9 +1131,14 @@ describe('TaskManager', () => {
|
|
|
705
1131
|
// Verify accept was called
|
|
706
1132
|
expect(taskAcceptSpy).toHaveBeenCalledTimes(1);
|
|
707
1133
|
|
|
708
|
-
|
|
709
|
-
|
|
1134
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1135
|
+
sendStateMachineEventSpy,
|
|
1136
|
+
TaskEvent.TASK_OFFERED
|
|
1137
|
+
);
|
|
1138
|
+
expect(stateMachineEvent?.taskData).toEqual(autoAnswerPayload.data);
|
|
1139
|
+
// Verify task auto-answer event was emitted
|
|
710
1140
|
expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_AUTO_ANSWERED, task);
|
|
1141
|
+
sendStateMachineEventSpy.mockRestore();
|
|
711
1142
|
});
|
|
712
1143
|
|
|
713
1144
|
it('should NOT emit TASK_AUTO_ANSWERED event when auto-answer fails', async () => {
|
|
@@ -716,7 +1147,9 @@ describe('TaskManager', () => {
|
|
|
716
1147
|
|
|
717
1148
|
const task = taskManager.getTask(taskId);
|
|
718
1149
|
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
719
|
-
const taskAcceptSpy = jest
|
|
1150
|
+
const taskAcceptSpy = jest
|
|
1151
|
+
.spyOn(task, 'accept')
|
|
1152
|
+
.mockRejectedValue(new Error('Accept failed'));
|
|
720
1153
|
|
|
721
1154
|
// Step 2: Trigger AGENT_OFFER_CONTACT with auto-answer (will fail)
|
|
722
1155
|
const autoAnswerPayload = {
|
|
@@ -751,6 +1184,7 @@ describe('TaskManager', () => {
|
|
|
751
1184
|
const task = taskManager.getTask(taskId);
|
|
752
1185
|
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
753
1186
|
const taskAcceptSpy = jest.spyOn(task, 'accept').mockResolvedValue(undefined);
|
|
1187
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
754
1188
|
|
|
755
1189
|
// Step 2: Trigger AGENT_OFFER_CONSULT with auto-answer
|
|
756
1190
|
const consultAutoAnswerPayload = {
|
|
@@ -775,12 +1209,20 @@ describe('TaskManager', () => {
|
|
|
775
1209
|
// Verify accept was called
|
|
776
1210
|
expect(taskAcceptSpy).toHaveBeenCalledTimes(1);
|
|
777
1211
|
|
|
778
|
-
|
|
779
|
-
|
|
1212
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1213
|
+
sendStateMachineEventSpy,
|
|
1214
|
+
TaskEvent.OFFER_CONSULT
|
|
1215
|
+
);
|
|
1216
|
+
expect(stateMachineEvent?.taskData).toEqual({
|
|
1217
|
+
...consultAutoAnswerPayload.data,
|
|
1218
|
+
isConsulted: true,
|
|
1219
|
+
});
|
|
1220
|
+
// Verify task auto-answer event was emitted
|
|
780
1221
|
expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_AUTO_ANSWERED, task);
|
|
781
|
-
|
|
1222
|
+
|
|
782
1223
|
// Verify isConsulted flag is set correctly
|
|
783
1224
|
expect(task.data.isConsulted).toBe(true);
|
|
1225
|
+
sendStateMachineEventSpy.mockRestore();
|
|
784
1226
|
});
|
|
785
1227
|
|
|
786
1228
|
it('should NOT emit TASK_AUTO_ANSWERED when isAutoAnswering is false', async () => {
|
|
@@ -814,28 +1256,27 @@ describe('TaskManager', () => {
|
|
|
814
1256
|
expect(taskAcceptSpy).not.toHaveBeenCalled();
|
|
815
1257
|
|
|
816
1258
|
// Verify TASK_AUTO_ANSWERED event was NOT emitted
|
|
817
|
-
expect(taskEmitSpy).not.toHaveBeenCalledWith(
|
|
1259
|
+
expect(taskEmitSpy).not.toHaveBeenCalledWith(
|
|
1260
|
+
TASK_EVENTS.TASK_AUTO_ANSWERED,
|
|
1261
|
+
expect.anything()
|
|
1262
|
+
);
|
|
818
1263
|
});
|
|
819
1264
|
});
|
|
820
1265
|
|
|
821
|
-
it('should
|
|
1266
|
+
it('should remove OUTDIAL task from taskCollection on AGENT_OUTBOUND_FAILED when terminated', () => {
|
|
822
1267
|
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;
|
|
1268
|
+
Object.assign(task.data, {
|
|
1269
|
+
interaction: {
|
|
1270
|
+
...task.data.interaction,
|
|
1271
|
+
outboundType: 'OUTDIAL',
|
|
1272
|
+
state: 'new',
|
|
1273
|
+
isTerminated: true,
|
|
1274
|
+
},
|
|
1275
|
+
agentsPendingWrapUp: ['agent-123'],
|
|
836
1276
|
});
|
|
837
1277
|
task.unregisterWebCallListeners = jest.fn();
|
|
838
1278
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
1279
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
839
1280
|
|
|
840
1281
|
const payload = {
|
|
841
1282
|
data: {
|
|
@@ -848,6 +1289,7 @@ describe('TaskManager', () => {
|
|
|
848
1289
|
state: 'new',
|
|
849
1290
|
isTerminated: true,
|
|
850
1291
|
},
|
|
1292
|
+
agentsPendingWrapUp: ['agent-123'],
|
|
851
1293
|
interactionId: taskId,
|
|
852
1294
|
orgId: '6ecef209-9a34-4ed1-a07a-7ddd1dbe925a',
|
|
853
1295
|
trackingId: '575c0ec2-618c-42af-a61c-53aeb0a221ee',
|
|
@@ -862,23 +1304,61 @@ describe('TaskManager', () => {
|
|
|
862
1304
|
|
|
863
1305
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
864
1306
|
|
|
865
|
-
|
|
866
|
-
|
|
1307
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1308
|
+
|
|
1309
|
+
expect(taskManager.getTask(taskId)).toBeUndefined();
|
|
1310
|
+
expect(removeTaskSpy).toHaveBeenCalled();
|
|
1311
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1312
|
+
sendStateMachineEventSpy,
|
|
1313
|
+
TaskEvent.OUTBOUND_FAILED
|
|
1314
|
+
);
|
|
1315
|
+
expect(stateMachineEvent?.reason).toBe('CUSTOMER_BUSY');
|
|
1316
|
+
sendStateMachineEventSpy.mockRestore();
|
|
867
1317
|
});
|
|
868
1318
|
|
|
869
1319
|
it('should emit TASK_OUTDIAL_FAILED event on AGENT_OUTBOUND_FAILED', () => {
|
|
870
1320
|
const task = taskManager.getTask(taskId);
|
|
871
|
-
|
|
872
|
-
const
|
|
1321
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1322
|
+
const payload = {
|
|
1323
|
+
data: {
|
|
1324
|
+
type: CC_EVENTS.AGENT_OUTBOUND_FAILED,
|
|
1325
|
+
interactionId: taskId,
|
|
1326
|
+
reason: 'CUSTOMER_BUSY',
|
|
1327
|
+
},
|
|
1328
|
+
};
|
|
1329
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1330
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1331
|
+
sendStateMachineEventSpy,
|
|
1332
|
+
TaskEvent.OUTBOUND_FAILED
|
|
1333
|
+
);
|
|
1334
|
+
expect(stateMachineEvent?.reason).toBe('CUSTOMER_BUSY');
|
|
1335
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
it('should pass taskData in OUTBOUND_FAILED event for shouldWrapUp guard evaluation', () => {
|
|
1339
|
+
const task = taskManager.getTask(taskId);
|
|
1340
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
873
1341
|
const payload = {
|
|
874
1342
|
data: {
|
|
875
1343
|
type: CC_EVENTS.AGENT_OUTBOUND_FAILED,
|
|
876
1344
|
interactionId: taskId,
|
|
877
1345
|
reason: 'CUSTOMER_BUSY',
|
|
1346
|
+
agentsPendingWrapUp: ['agent-123'],
|
|
1347
|
+
interaction: {
|
|
1348
|
+
outboundType: 'OUTDIAL',
|
|
1349
|
+
isTerminated: true,
|
|
1350
|
+
},
|
|
878
1351
|
},
|
|
879
1352
|
};
|
|
880
1353
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
881
|
-
|
|
1354
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1355
|
+
sendStateMachineEventSpy,
|
|
1356
|
+
TaskEvent.OUTBOUND_FAILED
|
|
1357
|
+
);
|
|
1358
|
+
expect(stateMachineEvent?.taskData).toBeDefined();
|
|
1359
|
+
expect(stateMachineEvent?.taskData?.agentsPendingWrapUp).toEqual(['agent-123']);
|
|
1360
|
+
expect(stateMachineEvent?.taskData?.interaction?.outboundType).toBe('OUTDIAL');
|
|
1361
|
+
sendStateMachineEventSpy.mockRestore();
|
|
882
1362
|
});
|
|
883
1363
|
|
|
884
1364
|
it('should handle AGENT_OUTBOUND_FAILED gracefully when task is undefined', () => {
|
|
@@ -897,19 +1377,14 @@ describe('TaskManager', () => {
|
|
|
897
1377
|
|
|
898
1378
|
it('should NOT remove OUTDIAL task on CONTACT_ENDED when agentsPendingWrapUp exists', () => {
|
|
899
1379
|
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;
|
|
1380
|
+
Object.assign(task.data, {
|
|
1381
|
+
interaction: {
|
|
1382
|
+
...task.data.interaction,
|
|
1383
|
+
outboundType: 'OUTDIAL',
|
|
1384
|
+
state: 'new',
|
|
1385
|
+
mediaType: 'telephony',
|
|
1386
|
+
},
|
|
1387
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
913
1388
|
});
|
|
914
1389
|
task.unregisterWebCallListeners = jest.fn();
|
|
915
1390
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
@@ -923,7 +1398,7 @@ describe('TaskManager', () => {
|
|
|
923
1398
|
state: 'new',
|
|
924
1399
|
mediaType: 'telephony',
|
|
925
1400
|
},
|
|
926
|
-
agentsPendingWrapUp: ['agent-
|
|
1401
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
927
1402
|
},
|
|
928
1403
|
};
|
|
929
1404
|
|
|
@@ -935,19 +1410,14 @@ describe('TaskManager', () => {
|
|
|
935
1410
|
|
|
936
1411
|
it('should remove OUTDIAL task on CONTACT_ENDED when agentsPendingWrapUp is empty', () => {
|
|
937
1412
|
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;
|
|
1413
|
+
Object.assign(task.data, {
|
|
1414
|
+
interaction: {
|
|
1415
|
+
...task.data.interaction,
|
|
1416
|
+
outboundType: 'OUTDIAL',
|
|
1417
|
+
state: 'new',
|
|
1418
|
+
mediaType: 'telephony',
|
|
1419
|
+
},
|
|
1420
|
+
agentsPendingWrapUp: [],
|
|
951
1421
|
});
|
|
952
1422
|
task.unregisterWebCallListeners = jest.fn();
|
|
953
1423
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
@@ -972,19 +1442,13 @@ describe('TaskManager', () => {
|
|
|
972
1442
|
|
|
973
1443
|
it('should remove OUTDIAL task on CONTACT_ENDED when agentsPendingWrapUp is undefined', () => {
|
|
974
1444
|
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;
|
|
1445
|
+
Object.assign(task.data, {
|
|
1446
|
+
interaction: {
|
|
1447
|
+
...task.data.interaction,
|
|
1448
|
+
outboundType: 'OUTDIAL',
|
|
1449
|
+
state: 'new',
|
|
1450
|
+
mediaType: 'telephony',
|
|
1451
|
+
},
|
|
988
1452
|
});
|
|
989
1453
|
task.unregisterWebCallListeners = jest.fn();
|
|
990
1454
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
@@ -1007,7 +1471,369 @@ describe('TaskManager', () => {
|
|
|
1007
1471
|
expect(removeTaskSpy).toHaveBeenCalled();
|
|
1008
1472
|
});
|
|
1009
1473
|
|
|
1010
|
-
it('should
|
|
1474
|
+
it('should map non-wxApp agent-terminated OUTDIAL ContactEnded to CONTACT_ENDED (not OUTBOUND_FAILED)', () => {
|
|
1475
|
+
const task = taskManager.getTask(taskId);
|
|
1476
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1477
|
+
const outdialFailedSpy = jest.fn();
|
|
1478
|
+
task.on(TASK_EVENTS.TASK_OUTDIAL_FAILED, outdialFailedSpy);
|
|
1479
|
+
|
|
1480
|
+
const payload = {
|
|
1481
|
+
data: {
|
|
1482
|
+
type: CC_EVENTS.CONTACT_ENDED,
|
|
1483
|
+
interactionId: taskId,
|
|
1484
|
+
terminatingParty: 'Agent',
|
|
1485
|
+
interaction: {
|
|
1486
|
+
outboundType: 'OUTDIAL',
|
|
1487
|
+
state: 'wrapUp',
|
|
1488
|
+
mediaType: 'telephony',
|
|
1489
|
+
},
|
|
1490
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
1491
|
+
},
|
|
1492
|
+
};
|
|
1493
|
+
|
|
1494
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1495
|
+
|
|
1496
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.CONTACT_ENDED);
|
|
1497
|
+
expect(outdialFailedSpy).not.toHaveBeenCalled();
|
|
1498
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
it('should map non-wxApp agent-terminated OUTDIAL AgentOutboundFailed to OUTBOUND_FAILED with popup suppressed', () => {
|
|
1502
|
+
const task = taskManager.getTask(taskId);
|
|
1503
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1504
|
+
const outdialFailedSpy = jest.fn();
|
|
1505
|
+
task.on(TASK_EVENTS.TASK_OUTDIAL_FAILED, outdialFailedSpy);
|
|
1506
|
+
|
|
1507
|
+
const payload = {
|
|
1508
|
+
data: {
|
|
1509
|
+
type: CC_EVENTS.AGENT_OUTBOUND_FAILED,
|
|
1510
|
+
interactionId: taskId,
|
|
1511
|
+
terminatingParty: 'Agent',
|
|
1512
|
+
reason: 'AGENT_ENDS',
|
|
1513
|
+
interaction: {
|
|
1514
|
+
outboundType: 'OUTDIAL',
|
|
1515
|
+
state: 'wrapUp',
|
|
1516
|
+
mediaType: 'telephony',
|
|
1517
|
+
},
|
|
1518
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
1519
|
+
},
|
|
1520
|
+
};
|
|
1521
|
+
|
|
1522
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1523
|
+
|
|
1524
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1525
|
+
sendStateMachineEventSpy,
|
|
1526
|
+
TaskEvent.OUTBOUND_FAILED
|
|
1527
|
+
);
|
|
1528
|
+
expect(stateMachineEvent?.reason).toBe('AGENT_ENDS');
|
|
1529
|
+
expect(stateMachineEvent?.taskData?.suppressOutdialFailedPopup).toBe(true);
|
|
1530
|
+
expect(outdialFailedSpy).not.toHaveBeenCalled();
|
|
1531
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
it('should map wxApp agent-terminated OUTDIAL AgentOutboundFailed to OUTBOUND_FAILED with AGENT_ENDS', () => {
|
|
1535
|
+
const task = taskManager.getTask(taskId);
|
|
1536
|
+
task.uiControlConfig = {enableWxBetterTogether: true};
|
|
1537
|
+
task.data = {
|
|
1538
|
+
...task.data,
|
|
1539
|
+
agentId: 'test-agent-id',
|
|
1540
|
+
interaction: {
|
|
1541
|
+
...task.data.interaction,
|
|
1542
|
+
outboundType: 'OUTDIAL',
|
|
1543
|
+
participants: {
|
|
1544
|
+
'test-agent-id': {
|
|
1545
|
+
id: 'test-agent-id',
|
|
1546
|
+
deviceType: 'wxApp',
|
|
1547
|
+
deviceId: 'device-1',
|
|
1548
|
+
deviceCallId: 'call-1',
|
|
1549
|
+
},
|
|
1550
|
+
},
|
|
1551
|
+
},
|
|
1552
|
+
};
|
|
1553
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1554
|
+
const outdialFailedSpy = jest.fn();
|
|
1555
|
+
task.on(TASK_EVENTS.TASK_OUTDIAL_FAILED, outdialFailedSpy);
|
|
1556
|
+
|
|
1557
|
+
const payload = {
|
|
1558
|
+
data: {
|
|
1559
|
+
type: CC_EVENTS.AGENT_OUTBOUND_FAILED,
|
|
1560
|
+
interactionId: taskId,
|
|
1561
|
+
terminatingParty: 'Agent',
|
|
1562
|
+
reason: 'AGENT_ENDS',
|
|
1563
|
+
interaction: {
|
|
1564
|
+
outboundType: 'OUTDIAL',
|
|
1565
|
+
state: 'wrapUp',
|
|
1566
|
+
mediaType: 'telephony',
|
|
1567
|
+
participants: {
|
|
1568
|
+
'test-agent-id': {
|
|
1569
|
+
id: 'test-agent-id',
|
|
1570
|
+
deviceType: 'wxApp',
|
|
1571
|
+
deviceId: 'device-1',
|
|
1572
|
+
deviceCallId: 'call-1',
|
|
1573
|
+
},
|
|
1574
|
+
},
|
|
1575
|
+
},
|
|
1576
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
1577
|
+
},
|
|
1578
|
+
};
|
|
1579
|
+
|
|
1580
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1581
|
+
|
|
1582
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1583
|
+
sendStateMachineEventSpy,
|
|
1584
|
+
TaskEvent.OUTBOUND_FAILED
|
|
1585
|
+
);
|
|
1586
|
+
expect(stateMachineEvent?.reason).toBe('AGENT_ENDS');
|
|
1587
|
+
expect(outdialFailedSpy).toHaveBeenCalledWith('AGENT_ENDS');
|
|
1588
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1589
|
+
});
|
|
1590
|
+
|
|
1591
|
+
it('should map wxApp agent-terminated OUTDIAL ContactEnded to OUTBOUND_FAILED with AGENT_ENDS', () => {
|
|
1592
|
+
const task = taskManager.getTask(taskId);
|
|
1593
|
+
task.uiControlConfig = {enableWxBetterTogether: true};
|
|
1594
|
+
task.data = {
|
|
1595
|
+
...task.data,
|
|
1596
|
+
agentId: 'test-agent-id',
|
|
1597
|
+
interaction: {
|
|
1598
|
+
...task.data.interaction,
|
|
1599
|
+
outboundType: 'OUTDIAL',
|
|
1600
|
+
participants: {
|
|
1601
|
+
'test-agent-id': {
|
|
1602
|
+
id: 'test-agent-id',
|
|
1603
|
+
deviceType: 'wxApp',
|
|
1604
|
+
deviceId: 'device-1',
|
|
1605
|
+
deviceCallId: 'call-1',
|
|
1606
|
+
},
|
|
1607
|
+
},
|
|
1608
|
+
},
|
|
1609
|
+
};
|
|
1610
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1611
|
+
const outdialFailedSpy = jest.fn();
|
|
1612
|
+
task.on(TASK_EVENTS.TASK_OUTDIAL_FAILED, outdialFailedSpy);
|
|
1613
|
+
|
|
1614
|
+
const payload = {
|
|
1615
|
+
data: {
|
|
1616
|
+
type: CC_EVENTS.CONTACT_ENDED,
|
|
1617
|
+
interactionId: taskId,
|
|
1618
|
+
terminatingParty: 'Agent',
|
|
1619
|
+
interaction: {
|
|
1620
|
+
outboundType: 'OUTDIAL',
|
|
1621
|
+
state: 'wrapUp',
|
|
1622
|
+
mediaType: 'telephony',
|
|
1623
|
+
participants: {
|
|
1624
|
+
'test-agent-id': {
|
|
1625
|
+
id: 'test-agent-id',
|
|
1626
|
+
deviceType: 'wxApp',
|
|
1627
|
+
deviceId: 'device-1',
|
|
1628
|
+
deviceCallId: 'call-1',
|
|
1629
|
+
},
|
|
1630
|
+
},
|
|
1631
|
+
},
|
|
1632
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
1633
|
+
},
|
|
1634
|
+
};
|
|
1635
|
+
|
|
1636
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1637
|
+
|
|
1638
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1639
|
+
sendStateMachineEventSpy,
|
|
1640
|
+
TaskEvent.OUTBOUND_FAILED
|
|
1641
|
+
);
|
|
1642
|
+
expect(stateMachineEvent?.reason).toBe('AGENT_ENDS');
|
|
1643
|
+
expect(stateMachineEvent?.taskData?.wrapUpRequired).toBe(true);
|
|
1644
|
+
expect(outdialFailedSpy).toHaveBeenCalledWith('AGENT_ENDS');
|
|
1645
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1646
|
+
});
|
|
1647
|
+
|
|
1648
|
+
it('should map offer-stage OUTDIAL decline ContactEnded to CONTACT_ENDED (not wrapup)', () => {
|
|
1649
|
+
const task = taskManager.getTask(taskId);
|
|
1650
|
+
task.stateMachineService = {
|
|
1651
|
+
getSnapshot: () => ({value: 'OFFERED'}),
|
|
1652
|
+
};
|
|
1653
|
+
task.uiControlConfig = {enableWxBetterTogether: true, wxAppAnswerPending: false};
|
|
1654
|
+
task.data = {
|
|
1655
|
+
...task.data,
|
|
1656
|
+
agentId: 'test-agent-id',
|
|
1657
|
+
interaction: {
|
|
1658
|
+
...task.data.interaction,
|
|
1659
|
+
outboundType: 'OUTDIAL',
|
|
1660
|
+
participants: {
|
|
1661
|
+
'test-agent-id': {
|
|
1662
|
+
id: 'test-agent-id',
|
|
1663
|
+
deviceType: 'wxApp',
|
|
1664
|
+
deviceId: 'device-1',
|
|
1665
|
+
deviceCallId: 'call-1',
|
|
1666
|
+
},
|
|
1667
|
+
},
|
|
1668
|
+
},
|
|
1669
|
+
};
|
|
1670
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1671
|
+
const outdialFailedSpy = jest.fn();
|
|
1672
|
+
task.on(TASK_EVENTS.TASK_OUTDIAL_FAILED, outdialFailedSpy);
|
|
1673
|
+
|
|
1674
|
+
const payload = {
|
|
1675
|
+
data: {
|
|
1676
|
+
type: CC_EVENTS.CONTACT_ENDED,
|
|
1677
|
+
interactionId: taskId,
|
|
1678
|
+
terminatingParty: 'Agent',
|
|
1679
|
+
interaction: {
|
|
1680
|
+
outboundType: 'OUTDIAL',
|
|
1681
|
+
state: 'wrapUp',
|
|
1682
|
+
mediaType: 'telephony',
|
|
1683
|
+
},
|
|
1684
|
+
agentsPendingWrapUp: [],
|
|
1685
|
+
},
|
|
1686
|
+
};
|
|
1687
|
+
|
|
1688
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1689
|
+
|
|
1690
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.CONTACT_ENDED);
|
|
1691
|
+
expect(outdialFailedSpy).not.toHaveBeenCalled();
|
|
1692
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1693
|
+
});
|
|
1694
|
+
|
|
1695
|
+
it('should map post-accept OUTDIAL ContactEnded to OUTBOUND_FAILED when still OFFERED with wxAppAnswerPending', () => {
|
|
1696
|
+
const task = taskManager.getTask(taskId);
|
|
1697
|
+
task.stateMachineService = {
|
|
1698
|
+
getSnapshot: () => ({value: 'OFFERED'}),
|
|
1699
|
+
};
|
|
1700
|
+
task.uiControlConfig = {enableWxBetterTogether: true, wxAppAnswerPending: true};
|
|
1701
|
+
task.data = {
|
|
1702
|
+
...task.data,
|
|
1703
|
+
agentId: 'test-agent-id',
|
|
1704
|
+
interaction: {
|
|
1705
|
+
...task.data.interaction,
|
|
1706
|
+
outboundType: 'OUTDIAL',
|
|
1707
|
+
participants: {
|
|
1708
|
+
'test-agent-id': {
|
|
1709
|
+
id: 'test-agent-id',
|
|
1710
|
+
deviceType: 'wxApp',
|
|
1711
|
+
deviceId: 'device-1',
|
|
1712
|
+
deviceCallId: 'call-1',
|
|
1713
|
+
},
|
|
1714
|
+
},
|
|
1715
|
+
},
|
|
1716
|
+
};
|
|
1717
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1718
|
+
const outdialFailedSpy = jest.fn();
|
|
1719
|
+
task.on(TASK_EVENTS.TASK_OUTDIAL_FAILED, outdialFailedSpy);
|
|
1720
|
+
|
|
1721
|
+
const payload = {
|
|
1722
|
+
data: {
|
|
1723
|
+
type: CC_EVENTS.CONTACT_ENDED,
|
|
1724
|
+
interactionId: taskId,
|
|
1725
|
+
terminatingParty: 'Agent',
|
|
1726
|
+
interaction: {
|
|
1727
|
+
outboundType: 'OUTDIAL',
|
|
1728
|
+
state: 'offered',
|
|
1729
|
+
mediaType: 'telephony',
|
|
1730
|
+
participants: {
|
|
1731
|
+
'test-agent-id': {
|
|
1732
|
+
id: 'test-agent-id',
|
|
1733
|
+
deviceType: 'wxApp',
|
|
1734
|
+
deviceId: 'device-1',
|
|
1735
|
+
deviceCallId: 'call-1',
|
|
1736
|
+
},
|
|
1737
|
+
},
|
|
1738
|
+
},
|
|
1739
|
+
},
|
|
1740
|
+
};
|
|
1741
|
+
|
|
1742
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1743
|
+
|
|
1744
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1745
|
+
sendStateMachineEventSpy,
|
|
1746
|
+
TaskEvent.OUTBOUND_FAILED
|
|
1747
|
+
);
|
|
1748
|
+
expect(stateMachineEvent?.reason).toBe('AGENT_ENDS');
|
|
1749
|
+
expect(outdialFailedSpy).toHaveBeenCalledWith('AGENT_ENDS');
|
|
1750
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1751
|
+
});
|
|
1752
|
+
|
|
1753
|
+
it('should map post-accept agent-terminated OUTDIAL ContactEnded to OUTBOUND_FAILED when CONNECTED', () => {
|
|
1754
|
+
const task = taskManager.getTask(taskId);
|
|
1755
|
+
task.stateMachineService = {
|
|
1756
|
+
getSnapshot: () => ({value: 'CONNECTED'}),
|
|
1757
|
+
};
|
|
1758
|
+
task.uiControlConfig = {enableWxBetterTogether: true};
|
|
1759
|
+
task.data = {
|
|
1760
|
+
...task.data,
|
|
1761
|
+
agentId: 'test-agent-id',
|
|
1762
|
+
interaction: {
|
|
1763
|
+
...task.data.interaction,
|
|
1764
|
+
outboundType: 'OUTDIAL',
|
|
1765
|
+
participants: {
|
|
1766
|
+
'test-agent-id': {
|
|
1767
|
+
id: 'test-agent-id',
|
|
1768
|
+
deviceType: 'wxApp',
|
|
1769
|
+
deviceId: 'device-1',
|
|
1770
|
+
deviceCallId: 'call-1',
|
|
1771
|
+
},
|
|
1772
|
+
},
|
|
1773
|
+
},
|
|
1774
|
+
};
|
|
1775
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1776
|
+
const outdialFailedSpy = jest.fn();
|
|
1777
|
+
task.on(TASK_EVENTS.TASK_OUTDIAL_FAILED, outdialFailedSpy);
|
|
1778
|
+
|
|
1779
|
+
const payload = {
|
|
1780
|
+
data: {
|
|
1781
|
+
type: CC_EVENTS.CONTACT_ENDED,
|
|
1782
|
+
interactionId: taskId,
|
|
1783
|
+
terminatingParty: 'Agent',
|
|
1784
|
+
interaction: {
|
|
1785
|
+
outboundType: 'OUTDIAL',
|
|
1786
|
+
state: 'wrapUp',
|
|
1787
|
+
mediaType: 'telephony',
|
|
1788
|
+
participants: {
|
|
1789
|
+
'test-agent-id': {
|
|
1790
|
+
id: 'test-agent-id',
|
|
1791
|
+
deviceType: 'wxApp',
|
|
1792
|
+
deviceId: 'device-1',
|
|
1793
|
+
deviceCallId: 'call-1',
|
|
1794
|
+
},
|
|
1795
|
+
},
|
|
1796
|
+
},
|
|
1797
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
1798
|
+
},
|
|
1799
|
+
};
|
|
1800
|
+
|
|
1801
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1802
|
+
|
|
1803
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1804
|
+
sendStateMachineEventSpy,
|
|
1805
|
+
TaskEvent.OUTBOUND_FAILED
|
|
1806
|
+
);
|
|
1807
|
+
expect(stateMachineEvent?.reason).toBe('AGENT_ENDS');
|
|
1808
|
+
expect(outdialFailedSpy).toHaveBeenCalledWith('AGENT_ENDS');
|
|
1809
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1810
|
+
});
|
|
1811
|
+
|
|
1812
|
+
it('should map customer-terminated OUTDIAL ContactEnded to CONTACT_ENDED (no AGENT_ENDS remap)', () => {
|
|
1813
|
+
const task = taskManager.getTask(taskId);
|
|
1814
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1815
|
+
|
|
1816
|
+
const payload = {
|
|
1817
|
+
data: {
|
|
1818
|
+
type: CC_EVENTS.CONTACT_ENDED,
|
|
1819
|
+
interactionId: taskId,
|
|
1820
|
+
terminatingParty: 'Customer',
|
|
1821
|
+
interaction: {
|
|
1822
|
+
outboundType: 'OUTDIAL',
|
|
1823
|
+
state: 'new',
|
|
1824
|
+
mediaType: 'telephony',
|
|
1825
|
+
},
|
|
1826
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
1827
|
+
},
|
|
1828
|
+
};
|
|
1829
|
+
|
|
1830
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1831
|
+
|
|
1832
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.CONTACT_ENDED);
|
|
1833
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1834
|
+
});
|
|
1835
|
+
|
|
1836
|
+
it('should handle CONTACT_ENDED gracefully when task is undefined', () => {
|
|
1011
1837
|
const payload = {
|
|
1012
1838
|
data: {
|
|
1013
1839
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
@@ -1025,19 +1851,13 @@ describe('TaskManager', () => {
|
|
|
1025
1851
|
|
|
1026
1852
|
it('should remove OUTDIAL task from taskCollection on AGENT_CONTACT_ASSIGN_FAILED when NOT terminated (user-declined)', () => {
|
|
1027
1853
|
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;
|
|
1854
|
+
Object.assign(task.data, {
|
|
1855
|
+
interaction: {
|
|
1856
|
+
...task.data.interaction,
|
|
1857
|
+
outboundType: 'OUTDIAL',
|
|
1858
|
+
state: 'new',
|
|
1859
|
+
isTerminated: false,
|
|
1860
|
+
},
|
|
1041
1861
|
});
|
|
1042
1862
|
task.unregisterWebCallListeners = jest.fn();
|
|
1043
1863
|
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
@@ -1081,20 +1901,16 @@ describe('TaskManager', () => {
|
|
|
1081
1901
|
|
|
1082
1902
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1083
1903
|
const task = taskManager.getTask(taskId);
|
|
1084
|
-
|
|
1085
|
-
task.data = {...newData, isConsulted: true};
|
|
1086
|
-
return task;
|
|
1087
|
-
});
|
|
1088
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
1904
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1089
1905
|
|
|
1090
1906
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1091
1907
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
expect(
|
|
1097
|
-
|
|
1908
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1909
|
+
sendStateMachineEventSpy,
|
|
1910
|
+
TaskEvent.OFFER_CONSULT
|
|
1911
|
+
);
|
|
1912
|
+
expect(stateMachineEvent?.taskData).toEqual({...payload.data, isConsulted: true});
|
|
1913
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1098
1914
|
});
|
|
1099
1915
|
|
|
1100
1916
|
it('should emit TASK_CONSULT_ACCEPTED event on AGENT_CONSULTING event', () => {
|
|
@@ -1113,19 +1929,18 @@ describe('TaskManager', () => {
|
|
|
1113
1929
|
};
|
|
1114
1930
|
|
|
1115
1931
|
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
1932
|
|
|
1121
|
-
const
|
|
1933
|
+
const task = taskManager.getTask(taskId);
|
|
1934
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1935
|
+
|
|
1122
1936
|
webSocketManagerMock.emit('message', JSON.stringify(initialConsultingPayload));
|
|
1123
1937
|
webSocketManagerMock.emit('message', JSON.stringify(consultingPayload));
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
taskManager.getTask(taskId)
|
|
1938
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1939
|
+
sendStateMachineEventSpy,
|
|
1940
|
+
TaskEvent.CONSULTING_ACTIVE
|
|
1128
1941
|
);
|
|
1942
|
+
expect(stateMachineEvent?.taskData).toEqual(consultingPayload.data);
|
|
1943
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1129
1944
|
});
|
|
1130
1945
|
|
|
1131
1946
|
it('should emit TASK_CONSULT_ENDED event on AGENT_CONSULT_ENDED event', () => {
|
|
@@ -1137,11 +1952,16 @@ describe('TaskManager', () => {
|
|
|
1137
1952
|
};
|
|
1138
1953
|
|
|
1139
1954
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1140
|
-
|
|
1141
|
-
const
|
|
1955
|
+
taskManager.getTask(taskId).data.isConsulted = true;
|
|
1956
|
+
const task = taskManager.getTask(taskId);
|
|
1957
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1142
1958
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1143
|
-
|
|
1144
|
-
|
|
1959
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
1960
|
+
sendStateMachineEventSpy,
|
|
1961
|
+
TaskEvent.CONSULT_END
|
|
1962
|
+
);
|
|
1963
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
1964
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1145
1965
|
});
|
|
1146
1966
|
|
|
1147
1967
|
it('should emit TASK_CONSULT_ENDED event and remove currentTask when on AGENT_CONSULT_ENDED event when requested for a consult', () => {
|
|
@@ -1154,19 +1974,14 @@ describe('TaskManager', () => {
|
|
|
1154
1974
|
|
|
1155
1975
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1156
1976
|
|
|
1157
|
-
taskManager.getTask(taskId).
|
|
1158
|
-
taskManager.getTask(taskId).data = {...newData, isConsulted: true};
|
|
1159
|
-
return taskManager.getTask(taskId);
|
|
1160
|
-
});
|
|
1977
|
+
taskManager.getTask(taskId).data.isConsulted = true;
|
|
1161
1978
|
const task = taskManager.getTask(taskId);
|
|
1162
1979
|
|
|
1163
|
-
const
|
|
1164
|
-
const taskUpdateTaskDataSpy = jest.spyOn(taskManager.getTask(taskId), 'updateTaskData');
|
|
1980
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1165
1981
|
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);
|
|
1982
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.CONSULT_END);
|
|
1169
1983
|
expect(taskManager.getTask(taskId)).toBeUndefined(); // Ensure task is removed from the task collection after the consult ends
|
|
1984
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1170
1985
|
});
|
|
1171
1986
|
|
|
1172
1987
|
it('should emit TASK_CANCELLED event on AGENT_CTQ_CANCELLED event', () => {
|
|
@@ -1178,14 +1993,15 @@ describe('TaskManager', () => {
|
|
|
1178
1993
|
};
|
|
1179
1994
|
|
|
1180
1995
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1181
|
-
const
|
|
1182
|
-
const
|
|
1996
|
+
const task = taskManager.getTask(taskId);
|
|
1997
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1183
1998
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
taskManager.getTask(taskId)
|
|
1999
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
2000
|
+
sendStateMachineEventSpy,
|
|
2001
|
+
TaskEvent.CTQ_CANCEL
|
|
1188
2002
|
);
|
|
2003
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
2004
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1189
2005
|
});
|
|
1190
2006
|
|
|
1191
2007
|
it('should handle AGENT_CONSULT_FAILED event', () => {
|
|
@@ -1199,9 +2015,15 @@ describe('TaskManager', () => {
|
|
|
1199
2015
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1200
2016
|
|
|
1201
2017
|
// Always spy on the updated task object after CONTACT_RESERVED is emitted
|
|
1202
|
-
const
|
|
2018
|
+
const task = taskManager.getTask(taskId);
|
|
2019
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1203
2020
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1204
|
-
|
|
2021
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
2022
|
+
sendStateMachineEventSpy,
|
|
2023
|
+
TaskEvent.CONSULT_FAILED
|
|
2024
|
+
);
|
|
2025
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
2026
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1205
2027
|
});
|
|
1206
2028
|
|
|
1207
2029
|
it('should emit TASK_CONSULT_QUEUE_FAILED on AGENT_CTQ_CANCEL_FAILED event', () => {
|
|
@@ -1213,14 +2035,15 @@ describe('TaskManager', () => {
|
|
|
1213
2035
|
};
|
|
1214
2036
|
|
|
1215
2037
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1216
|
-
const
|
|
1217
|
-
const
|
|
2038
|
+
const task = taskManager.getTask(taskId);
|
|
2039
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1218
2040
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
taskManager.getTask(taskId)
|
|
2041
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
2042
|
+
sendStateMachineEventSpy,
|
|
2043
|
+
TaskEvent.CTQ_CANCEL_FAILED
|
|
1223
2044
|
);
|
|
2045
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
2046
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1224
2047
|
});
|
|
1225
2048
|
|
|
1226
2049
|
it('should emit TASK_REJECT event on AGENT_CONTACT_OFFER_RONA event', () => {
|
|
@@ -1263,15 +2086,14 @@ describe('TaskManager', () => {
|
|
|
1263
2086
|
};
|
|
1264
2087
|
|
|
1265
2088
|
taskManager.taskCollection[taskId] = taskManager.getTask(taskId);
|
|
1266
|
-
const
|
|
1267
|
-
const
|
|
2089
|
+
const task = taskManager.getTask(taskId);
|
|
2090
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1268
2091
|
|
|
1269
2092
|
webSocketManagerMock.emit('message', JSON.stringify(ronaPayload));
|
|
1270
2093
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
expect(metricsTrackSpy.mock.calls[0][0]).toBe('Agent RONA');
|
|
2094
|
+
const stateMachineEvent = expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.RONA);
|
|
2095
|
+
expect(stateMachineEvent?.reason).toBe(ronaPayload.data.reason);
|
|
2096
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1275
2097
|
});
|
|
1276
2098
|
|
|
1277
2099
|
it('should emit TASK_REJECT event on AGENT_CONTACT_ASSIGN_FAILED event', () => {
|
|
@@ -1315,17 +2137,16 @@ describe('TaskManager', () => {
|
|
|
1315
2137
|
|
|
1316
2138
|
taskManager.taskCollection[taskId] = taskManager.getTask(taskId);
|
|
1317
2139
|
const task = taskManager.getTask(taskId);
|
|
1318
|
-
const
|
|
1319
|
-
const taskUpdateDataSpy = jest.spyOn(task, 'updateTaskData');
|
|
1320
|
-
const metricsTrackSpy = jest.spyOn(taskManager.metricsManager, 'trackEvent');
|
|
2140
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1321
2141
|
|
|
1322
2142
|
webSocketManagerMock.emit('message', JSON.stringify(assignFailedPayload));
|
|
1323
2143
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
expect(
|
|
2144
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
2145
|
+
sendStateMachineEventSpy,
|
|
2146
|
+
TaskEvent.ASSIGN_FAILED
|
|
2147
|
+
);
|
|
2148
|
+
expect(stateMachineEvent?.reason).toBe(assignFailedPayload.data.reason);
|
|
2149
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1329
2150
|
});
|
|
1330
2151
|
|
|
1331
2152
|
it('should remove currentTask from taskCollection on AGENT_WRAPPEDUP event', () => {
|
|
@@ -1348,11 +2169,12 @@ describe('TaskManager', () => {
|
|
|
1348
2169
|
|
|
1349
2170
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1350
2171
|
const task = taskManager.getTask(taskId);
|
|
1351
|
-
const
|
|
2172
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1352
2173
|
|
|
1353
2174
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1354
2175
|
|
|
1355
|
-
|
|
2176
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.WRAPUP_COMPLETE);
|
|
2177
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1356
2178
|
expect(taskManager.getTask(taskId)).toBeUndefined();
|
|
1357
2179
|
});
|
|
1358
2180
|
|
|
@@ -1378,7 +2200,8 @@ describe('TaskManager', () => {
|
|
|
1378
2200
|
};
|
|
1379
2201
|
|
|
1380
2202
|
const taskEmitSpy = jest.spyOn(taskManager.getTask(taskId), 'emit');
|
|
1381
|
-
const taskUpdateTaskDataSpy =
|
|
2203
|
+
const taskUpdateTaskDataSpy = taskManager.getTask(taskId).updateTaskData as jest.Mock;
|
|
2204
|
+
taskUpdateTaskDataSpy.mockClear();
|
|
1382
2205
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1383
2206
|
expect(taskEmitSpy).not.toHaveBeenCalled();
|
|
1384
2207
|
expect(taskUpdateTaskDataSpy).not.toHaveBeenCalled();
|
|
@@ -1387,7 +2210,9 @@ describe('TaskManager', () => {
|
|
|
1387
2210
|
it('should emit TASK_CONSULTING event when agent is consulting', () => {
|
|
1388
2211
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1389
2212
|
taskManager.getTask(taskId).data.isConsulted = false;
|
|
1390
|
-
const
|
|
2213
|
+
const task = taskManager.getTask(taskId);
|
|
2214
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
2215
|
+
const taskManagerEmitSpy = jest.spyOn(taskManager, 'emit');
|
|
1391
2216
|
const consultingPayload = {
|
|
1392
2217
|
data: {
|
|
1393
2218
|
...initalPayload.data,
|
|
@@ -1396,13 +2221,21 @@ describe('TaskManager', () => {
|
|
|
1396
2221
|
},
|
|
1397
2222
|
};
|
|
1398
2223
|
webSocketManagerMock.emit('message', JSON.stringify(consultingPayload));
|
|
1399
|
-
|
|
1400
|
-
expect(
|
|
2224
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.CONSULTING_ACTIVE);
|
|
2225
|
+
expect(taskManagerEmitSpy).toHaveBeenCalledWith(
|
|
2226
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
2227
|
+
taskManager.getTask(taskId)
|
|
2228
|
+
);
|
|
2229
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1401
2230
|
});
|
|
1402
2231
|
|
|
1403
|
-
it('should
|
|
2232
|
+
it('should update task data on AGENT_CONTACT_UNASSIGNED', () => {
|
|
1404
2233
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1405
|
-
const
|
|
2234
|
+
const task = taskManager.getTask(taskId);
|
|
2235
|
+
const sendStateMachineEventSpy = task.sendStateMachineEvent as jest.Mock;
|
|
2236
|
+
sendStateMachineEventSpy.mockClear();
|
|
2237
|
+
const updateTaskDataSpy = task.updateTaskData as jest.Mock;
|
|
2238
|
+
updateTaskDataSpy.mockClear();
|
|
1406
2239
|
const unassignedPayload = {
|
|
1407
2240
|
data: {
|
|
1408
2241
|
type: CC_EVENTS.AGENT_CONTACT_UNASSIGNED,
|
|
@@ -1420,8 +2253,76 @@ describe('TaskManager', () => {
|
|
|
1420
2253
|
},
|
|
1421
2254
|
};
|
|
1422
2255
|
webSocketManagerMock.emit('message', JSON.stringify(unassignedPayload));
|
|
1423
|
-
expect(
|
|
1424
|
-
expect(
|
|
2256
|
+
expect(sendStateMachineEventSpy).not.toHaveBeenCalled();
|
|
2257
|
+
expect(updateTaskDataSpy).toHaveBeenCalledWith(unassignedPayload.data);
|
|
2258
|
+
});
|
|
2259
|
+
|
|
2260
|
+
it('preserves consult fields from state context during consulting payload refresh', () => {
|
|
2261
|
+
const task = createMockTask({
|
|
2262
|
+
...taskDataMock,
|
|
2263
|
+
interaction: {state: 'consulting'} as any,
|
|
2264
|
+
interactionId: taskId,
|
|
2265
|
+
});
|
|
2266
|
+
task.stateMachineService = {
|
|
2267
|
+
getSnapshot: () => ({
|
|
2268
|
+
value: 'CONSULTING',
|
|
2269
|
+
context: {
|
|
2270
|
+
consultDestinationAgentId: 'agent-preserved',
|
|
2271
|
+
consultDestinationType: 'agent',
|
|
2272
|
+
},
|
|
2273
|
+
}),
|
|
2274
|
+
};
|
|
2275
|
+
|
|
2276
|
+
const incomingTaskData = {
|
|
2277
|
+
...taskDataMock,
|
|
2278
|
+
interaction: {state: 'consulting'} as any,
|
|
2279
|
+
interactionId: taskId,
|
|
2280
|
+
destAgentId: null,
|
|
2281
|
+
destinationType: null,
|
|
2282
|
+
};
|
|
2283
|
+
|
|
2284
|
+
(taskManager as any).updateTaskData(task, incomingTaskData);
|
|
2285
|
+
|
|
2286
|
+
expect(task.updateTaskData).toHaveBeenCalledWith(
|
|
2287
|
+
expect.objectContaining({
|
|
2288
|
+
destAgentId: 'agent-preserved',
|
|
2289
|
+
destinationType: 'agent',
|
|
2290
|
+
})
|
|
2291
|
+
);
|
|
2292
|
+
});
|
|
2293
|
+
|
|
2294
|
+
it('does not preserve stale consult fields once consult is no longer active', () => {
|
|
2295
|
+
const task = createMockTask({
|
|
2296
|
+
...taskDataMock,
|
|
2297
|
+
interaction: {state: 'connected'} as any,
|
|
2298
|
+
interactionId: taskId,
|
|
2299
|
+
});
|
|
2300
|
+
task.stateMachineService = {
|
|
2301
|
+
getSnapshot: () => ({
|
|
2302
|
+
value: 'CONNECTED',
|
|
2303
|
+
context: {
|
|
2304
|
+
consultDestinationAgentId: 'agent-stale',
|
|
2305
|
+
consultDestinationType: 'agent',
|
|
2306
|
+
},
|
|
2307
|
+
}),
|
|
2308
|
+
};
|
|
2309
|
+
|
|
2310
|
+
const incomingTaskData = {
|
|
2311
|
+
...taskDataMock,
|
|
2312
|
+
interaction: {state: 'connected'} as any,
|
|
2313
|
+
interactionId: taskId,
|
|
2314
|
+
destAgentId: null,
|
|
2315
|
+
destinationType: null,
|
|
2316
|
+
};
|
|
2317
|
+
|
|
2318
|
+
(taskManager as any).updateTaskData(task, incomingTaskData);
|
|
2319
|
+
|
|
2320
|
+
expect(task.updateTaskData).toHaveBeenCalledWith(
|
|
2321
|
+
expect.objectContaining({
|
|
2322
|
+
destAgentId: null,
|
|
2323
|
+
destinationType: null,
|
|
2324
|
+
})
|
|
2325
|
+
);
|
|
1425
2326
|
});
|
|
1426
2327
|
|
|
1427
2328
|
it('should handle chat interaction and emit TASK_INCOMING immediately', () => {
|
|
@@ -1429,20 +2330,20 @@ describe('TaskManager', () => {
|
|
|
1429
2330
|
const chatPayload = {
|
|
1430
2331
|
data: {
|
|
1431
2332
|
...initalPayload.data,
|
|
1432
|
-
interaction: {
|
|
2333
|
+
interaction: {mediaType: 'chat'},
|
|
1433
2334
|
},
|
|
1434
2335
|
};
|
|
1435
2336
|
|
|
1436
|
-
const taskIncomingSpy = jest.spyOn(taskManager, 'emit');
|
|
1437
|
-
|
|
1438
2337
|
// Simulate receiving a chat task
|
|
1439
2338
|
webSocketManagerMock.emit('message', JSON.stringify(chatPayload));
|
|
1440
2339
|
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
2340
|
+
const chatTask = taskManager.getTask(chatPayload.data.interactionId);
|
|
2341
|
+
const sendStateMachineEventSpy = chatTask.sendStateMachineEvent as jest.Mock;
|
|
2342
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
2343
|
+
sendStateMachineEventSpy,
|
|
2344
|
+
TaskEvent.TASK_INCOMING
|
|
1445
2345
|
);
|
|
2346
|
+
expect(stateMachineEvent?.taskData).toEqual(chatPayload.data);
|
|
1446
2347
|
expect(taskManager.getAllTasks()).toHaveProperty(chatPayload.data.interactionId);
|
|
1447
2348
|
});
|
|
1448
2349
|
|
|
@@ -1451,20 +2352,20 @@ describe('TaskManager', () => {
|
|
|
1451
2352
|
const emailPayload = {
|
|
1452
2353
|
data: {
|
|
1453
2354
|
...initalPayload.data,
|
|
1454
|
-
interaction: {
|
|
2355
|
+
interaction: {mediaType: 'email'},
|
|
1455
2356
|
},
|
|
1456
2357
|
};
|
|
1457
2358
|
|
|
1458
|
-
const taskIncomingSpy = jest.spyOn(taskManager, 'emit');
|
|
1459
|
-
|
|
1460
2359
|
// Simulate receiving an email task
|
|
1461
2360
|
webSocketManagerMock.emit('message', JSON.stringify(emailPayload));
|
|
1462
2361
|
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
2362
|
+
const emailTask = taskManager.getTask(emailPayload.data.interactionId);
|
|
2363
|
+
const sendStateMachineEventSpy = emailTask.sendStateMachineEvent as jest.Mock;
|
|
2364
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
2365
|
+
sendStateMachineEventSpy,
|
|
2366
|
+
TaskEvent.TASK_INCOMING
|
|
1467
2367
|
);
|
|
2368
|
+
expect(stateMachineEvent?.taskData).toEqual(emailPayload.data);
|
|
1468
2369
|
expect(taskManager.getAllTasks()).toHaveProperty(emailPayload.data.interactionId);
|
|
1469
2370
|
});
|
|
1470
2371
|
|
|
@@ -1474,18 +2375,16 @@ describe('TaskManager', () => {
|
|
|
1474
2375
|
data: {
|
|
1475
2376
|
...initalPayload.data,
|
|
1476
2377
|
type: CC_EVENTS.AGENT_CONTACT_RESERVED,
|
|
1477
|
-
interaction: {
|
|
2378
|
+
interaction: {mediaType: 'chat'},
|
|
1478
2379
|
},
|
|
1479
2380
|
};
|
|
1480
|
-
|
|
1481
|
-
const taskIncomingSpy = jest.spyOn(taskManager, 'emit');
|
|
2381
|
+
|
|
1482
2382
|
webSocketManagerMock.emit('message', JSON.stringify(chatReservedPayload));
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
2383
|
+
const task = taskManager.getTask(chatReservedPayload.data.interactionId);
|
|
2384
|
+
const sendStateMachineEventSpy = task.sendStateMachineEvent as jest.Mock;
|
|
2385
|
+
|
|
2386
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.TASK_INCOMING);
|
|
2387
|
+
|
|
1489
2388
|
// 2. Chat task is assigned
|
|
1490
2389
|
const chatAssignedPayload = {
|
|
1491
2390
|
data: {
|
|
@@ -1493,26 +2392,26 @@ describe('TaskManager', () => {
|
|
|
1493
2392
|
type: CC_EVENTS.AGENT_CONTACT_ASSIGNED,
|
|
1494
2393
|
},
|
|
1495
2394
|
};
|
|
1496
|
-
|
|
1497
|
-
const task = taskManager.getTask(chatReservedPayload.data.interactionId);
|
|
1498
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
1499
|
-
|
|
2395
|
+
|
|
1500
2396
|
webSocketManagerMock.emit('message', JSON.stringify(chatAssignedPayload));
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
2397
|
+
|
|
2398
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.ASSIGN);
|
|
2399
|
+
|
|
1504
2400
|
// 3. Chat task is ended with state 'new' to trigger cleanup
|
|
1505
2401
|
const chatEndedPayload = {
|
|
1506
2402
|
data: {
|
|
1507
2403
|
...chatReservedPayload.data,
|
|
1508
2404
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
1509
|
-
interaction: {
|
|
2405
|
+
interaction: {mediaType: 'chat', state: 'new'}, // Change to 'new' state
|
|
2406
|
+
wrapUpRequired: false,
|
|
1510
2407
|
},
|
|
1511
2408
|
};
|
|
1512
2409
|
|
|
2410
|
+
// Simulate state on the task to allow cleanup logic
|
|
2411
|
+
task.data.interaction.state = 'new';
|
|
1513
2412
|
webSocketManagerMock.emit('message', JSON.stringify(chatEndedPayload));
|
|
1514
2413
|
|
|
1515
|
-
|
|
2414
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.CONTACT_ENDED);
|
|
1516
2415
|
// Verify task is removed since it was in a 'new' state
|
|
1517
2416
|
expect(taskManager.getTask(chatReservedPayload.data.interactionId)).toBeUndefined();
|
|
1518
2417
|
});
|
|
@@ -1523,40 +2422,46 @@ describe('TaskManager', () => {
|
|
|
1523
2422
|
data: {
|
|
1524
2423
|
...initalPayload.data,
|
|
1525
2424
|
interactionId: 'telephony-task-id',
|
|
1526
|
-
interaction: {
|
|
2425
|
+
interaction: {mediaType: 'telephony'},
|
|
1527
2426
|
},
|
|
1528
2427
|
};
|
|
1529
|
-
|
|
2428
|
+
|
|
1530
2429
|
const chatPayload = {
|
|
1531
2430
|
data: {
|
|
1532
2431
|
...initalPayload.data,
|
|
1533
2432
|
interactionId: 'chat-task-id',
|
|
1534
|
-
interaction: {
|
|
2433
|
+
interaction: {mediaType: 'chat'},
|
|
1535
2434
|
},
|
|
1536
2435
|
};
|
|
1537
|
-
|
|
2436
|
+
|
|
1538
2437
|
const emailPayload = {
|
|
1539
2438
|
data: {
|
|
1540
2439
|
...initalPayload.data,
|
|
1541
2440
|
interactionId: 'email-task-id',
|
|
1542
|
-
interaction: {
|
|
2441
|
+
interaction: {mediaType: 'email'},
|
|
1543
2442
|
},
|
|
1544
2443
|
};
|
|
1545
|
-
|
|
2444
|
+
|
|
1546
2445
|
// Simulate receiving tasks of different types
|
|
1547
2446
|
webSocketManagerMock.emit('message', JSON.stringify(telephonyPayload));
|
|
1548
2447
|
webSocketManagerMock.emit('message', JSON.stringify(chatPayload));
|
|
1549
2448
|
webSocketManagerMock.emit('message', JSON.stringify(emailPayload));
|
|
1550
|
-
|
|
2449
|
+
|
|
1551
2450
|
// Verify all tasks are in the collection
|
|
1552
2451
|
expect(taskManager.getAllTasks()).toHaveProperty(telephonyPayload.data.interactionId);
|
|
1553
2452
|
expect(taskManager.getAllTasks()).toHaveProperty(chatPayload.data.interactionId);
|
|
1554
2453
|
expect(taskManager.getAllTasks()).toHaveProperty(emailPayload.data.interactionId);
|
|
1555
|
-
|
|
2454
|
+
|
|
1556
2455
|
// Verify the task media types are correctly set
|
|
1557
|
-
expect(
|
|
1558
|
-
|
|
1559
|
-
|
|
2456
|
+
expect(
|
|
2457
|
+
taskManager.getTask(telephonyPayload.data.interactionId).data.interaction.mediaType
|
|
2458
|
+
).toBe('telephony');
|
|
2459
|
+
expect(taskManager.getTask(chatPayload.data.interactionId).data.interaction.mediaType).toBe(
|
|
2460
|
+
'chat'
|
|
2461
|
+
);
|
|
2462
|
+
expect(taskManager.getTask(emailPayload.data.interactionId).data.interaction.mediaType).toBe(
|
|
2463
|
+
'email'
|
|
2464
|
+
);
|
|
1560
2465
|
});
|
|
1561
2466
|
|
|
1562
2467
|
it('should properly handle one task ending when multiple tasks are active', () => {
|
|
@@ -1565,102 +2470,102 @@ describe('TaskManager', () => {
|
|
|
1565
2470
|
data: {
|
|
1566
2471
|
...initalPayload.data,
|
|
1567
2472
|
interactionId: 'task-id-1',
|
|
1568
|
-
interaction: {
|
|
2473
|
+
interaction: {mediaType: 'telephony'},
|
|
1569
2474
|
},
|
|
1570
2475
|
};
|
|
1571
|
-
|
|
2476
|
+
|
|
1572
2477
|
const task2Payload = {
|
|
1573
2478
|
data: {
|
|
1574
2479
|
...initalPayload.data,
|
|
1575
2480
|
interactionId: 'task-id-2',
|
|
1576
|
-
interaction: {
|
|
2481
|
+
interaction: {mediaType: 'chat'},
|
|
1577
2482
|
},
|
|
1578
2483
|
};
|
|
1579
|
-
|
|
2484
|
+
|
|
1580
2485
|
const task3Payload = {
|
|
1581
2486
|
data: {
|
|
1582
2487
|
...initalPayload.data,
|
|
1583
2488
|
interactionId: 'task-id-3',
|
|
1584
|
-
interaction: {
|
|
2489
|
+
interaction: {mediaType: 'email'},
|
|
1585
2490
|
},
|
|
1586
2491
|
};
|
|
1587
|
-
|
|
2492
|
+
|
|
1588
2493
|
// Initialize all tasks
|
|
1589
2494
|
webSocketManagerMock.emit('message', JSON.stringify(task1Payload));
|
|
1590
2495
|
webSocketManagerMock.emit('message', JSON.stringify(task2Payload));
|
|
1591
2496
|
webSocketManagerMock.emit('message', JSON.stringify(task3Payload));
|
|
1592
|
-
|
|
2497
|
+
|
|
1593
2498
|
// Verify all tasks are in the collection
|
|
1594
2499
|
expect(taskManager.getAllTasks()).toHaveProperty(task1Payload.data.interactionId);
|
|
1595
2500
|
expect(taskManager.getAllTasks()).toHaveProperty(task2Payload.data.interactionId);
|
|
1596
2501
|
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
|
|
2502
|
+
|
|
1604
2503
|
const task2 = taskManager.getTask(task2Payload.data.interactionId);
|
|
1605
|
-
|
|
2504
|
+
const task2SendStateMachineEventSpy = task2.sendStateMachineEvent as jest.Mock;
|
|
2505
|
+
|
|
1606
2506
|
// End only the second task (chat task)
|
|
1607
2507
|
const chatEndedPayload = {
|
|
1608
2508
|
data: {
|
|
1609
2509
|
...task2Payload.data,
|
|
1610
2510
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
1611
|
-
interaction: {
|
|
2511
|
+
interaction: {mediaType: 'chat', state: 'new'}, // Using 'new' to trigger cleanup
|
|
2512
|
+
wrapUpRequired: false,
|
|
1612
2513
|
},
|
|
1613
2514
|
};
|
|
1614
|
-
|
|
2515
|
+
|
|
2516
|
+
task2.data.interaction.state = 'new';
|
|
1615
2517
|
webSocketManagerMock.emit('message', JSON.stringify(chatEndedPayload));
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
2518
|
+
|
|
2519
|
+
const firstEndEvent = expectLastStateMachineEvent(
|
|
2520
|
+
task2SendStateMachineEventSpy,
|
|
2521
|
+
TaskEvent.CONTACT_ENDED
|
|
2522
|
+
);
|
|
2523
|
+
expect(firstEndEvent?.taskData).toEqual(chatEndedPayload.data);
|
|
2524
|
+
|
|
1622
2525
|
// Verify task2 was removed from collection (since state was 'new')
|
|
1623
2526
|
expect(taskManager.getTask(task2Payload.data.interactionId)).toBeUndefined();
|
|
1624
|
-
|
|
2527
|
+
|
|
1625
2528
|
// Verify other tasks remain in the collection
|
|
1626
2529
|
expect(taskManager.getTask(task1Payload.data.interactionId)).toBeDefined();
|
|
1627
2530
|
expect(taskManager.getTask(task3Payload.data.interactionId)).toBeDefined();
|
|
1628
|
-
|
|
2531
|
+
|
|
1629
2532
|
// Store reference to task3 before we end it
|
|
1630
2533
|
const task3 = taskManager.getTask(task3Payload.data.interactionId);
|
|
1631
|
-
|
|
2534
|
+
const task3SendStateMachineEventSpy = task3.sendStateMachineEvent as jest.Mock;
|
|
2535
|
+
|
|
1632
2536
|
// Now end task3 with a state that doesn't trigger cleanup
|
|
1633
2537
|
const emailEndedPayload = {
|
|
1634
2538
|
data: {
|
|
1635
2539
|
...task3Payload.data,
|
|
1636
2540
|
type: CC_EVENTS.CONTACT_ENDED,
|
|
1637
|
-
interaction: {
|
|
2541
|
+
interaction: {mediaType: 'email', state: 'connected'}, // Using 'connected' to NOT trigger cleanup
|
|
2542
|
+
wrapUpRequired: true,
|
|
2543
|
+
agentsPendingWrapUp: ['test-agent-id'],
|
|
1638
2544
|
},
|
|
1639
2545
|
};
|
|
1640
|
-
|
|
2546
|
+
|
|
2547
|
+
task3.data.interaction.state = 'connected';
|
|
1641
2548
|
webSocketManagerMock.emit('message', JSON.stringify(emailEndedPayload));
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
2549
|
+
|
|
2550
|
+
const secondEndEvent = expectLastStateMachineEvent(
|
|
2551
|
+
task3SendStateMachineEventSpy,
|
|
2552
|
+
TaskEvent.CONTACT_ENDED
|
|
2553
|
+
);
|
|
2554
|
+
expect(secondEndEvent?.taskData).toEqual(emailEndedPayload.data);
|
|
2555
|
+
|
|
1646
2556
|
// Verify task3 is still in collection (since state was 'connected')
|
|
1647
2557
|
expect(taskManager.getTask(task3Payload.data.interactionId)).toBeDefined();
|
|
1648
|
-
|
|
2558
|
+
|
|
1649
2559
|
// Verify task1 remains unaffected
|
|
1650
|
-
expect(task1EmitSpy).not.toHaveBeenCalledWith(TASK_EVENTS.TASK_END);
|
|
1651
2560
|
expect(taskManager.getTask(task1Payload.data.interactionId)).toBeDefined();
|
|
1652
2561
|
});
|
|
1653
2562
|
|
|
1654
|
-
it('should emit
|
|
2563
|
+
it('should emit TRANSFER_SUCCESS event on AGENT_VTEAM_TRANSFERRED event', () => {
|
|
1655
2564
|
// First create a task by emitting the initial payload
|
|
1656
2565
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1657
|
-
|
|
1658
|
-
// Get a reference to the task from taskCollection
|
|
1659
2566
|
const task = taskManager.getTask(taskId);
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
const taskEmitSpy = jest.spyOn(task, 'emit');
|
|
1663
|
-
|
|
2567
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
2568
|
+
|
|
1664
2569
|
const vteamTransferredPayload = {
|
|
1665
2570
|
data: {
|
|
1666
2571
|
type: CC_EVENTS.AGENT_VTEAM_TRANSFERRED,
|
|
@@ -1677,34 +2582,39 @@ describe('TaskManager', () => {
|
|
|
1677
2582
|
queueMgr: initalPayload.data.queueMgr,
|
|
1678
2583
|
},
|
|
1679
2584
|
};
|
|
1680
|
-
|
|
2585
|
+
|
|
1681
2586
|
// No need to explicitly set the task in the collection as it's already there
|
|
1682
2587
|
// from the initial message processing
|
|
1683
|
-
|
|
2588
|
+
|
|
1684
2589
|
webSocketManagerMock.emit('message', JSON.stringify(vteamTransferredPayload));
|
|
1685
|
-
|
|
1686
|
-
// Check that
|
|
1687
|
-
|
|
1688
|
-
|
|
2590
|
+
|
|
2591
|
+
// Check that the state machine received the END event
|
|
2592
|
+
expectLastStateMachineEvent(sendStateMachineEventSpy, TaskEvent.TRANSFER_SUCCESS);
|
|
2593
|
+
sendStateMachineEventSpy.mockRestore();
|
|
2594
|
+
|
|
1689
2595
|
// The task should still exist in the collection based on current implementation
|
|
1690
2596
|
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
1691
2597
|
});
|
|
1692
2598
|
|
|
1693
2599
|
it('should update task data on AGENT_WRAPUP event', () => {
|
|
1694
2600
|
const payload = {
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
2601
|
+
data: {
|
|
2602
|
+
type: CC_EVENTS.AGENT_WRAPUP,
|
|
2603
|
+
interactionId: taskId,
|
|
2604
|
+
wrapUpRequired: true,
|
|
2605
|
+
},
|
|
1700
2606
|
};
|
|
1701
2607
|
const task = taskManager.getTask(taskId);
|
|
1702
|
-
const
|
|
1703
|
-
task.data = { ...(task.data || {}), ...(data || {}) };
|
|
1704
|
-
return task;
|
|
1705
|
-
});
|
|
2608
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
1706
2609
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1707
|
-
expect(
|
|
2610
|
+
expect(sendStateMachineEventSpy).toHaveBeenCalled();
|
|
2611
|
+
const stateMachineEvent = sendStateMachineEventSpy.mock.calls.at(-1)?.[0];
|
|
2612
|
+
expect(stateMachineEvent?.type).toBe(TaskEvent.TASK_WRAPUP);
|
|
2613
|
+
expect(stateMachineEvent?.taskData).toEqual({
|
|
2614
|
+
...payload.data,
|
|
2615
|
+
wrapUpRequired: true,
|
|
2616
|
+
});
|
|
2617
|
+
sendStateMachineEventSpy.mockRestore();
|
|
1708
2618
|
});
|
|
1709
2619
|
|
|
1710
2620
|
it('should not attempt cleanup twice when AGENT_CONTACT_UNASSIGNED is followed by AGENT_WRAPUP', () => {
|
|
@@ -1716,7 +2626,7 @@ describe('TaskManager', () => {
|
|
|
1716
2626
|
data: {
|
|
1717
2627
|
type: CC_EVENTS.AGENT_CONTACT_UNASSIGNED,
|
|
1718
2628
|
agentId: initalPayload.data.agentId,
|
|
1719
|
-
interaction: {
|
|
2629
|
+
interaction: {mediaType: 'telephony'},
|
|
1720
2630
|
interactionId: initalPayload.data.interactionId,
|
|
1721
2631
|
orgId: initalPayload.data.orgId,
|
|
1722
2632
|
trackingId: initalPayload.data.trackingId,
|
|
@@ -1735,7 +2645,7 @@ describe('TaskManager', () => {
|
|
|
1735
2645
|
data: {
|
|
1736
2646
|
type: CC_EVENTS.AGENT_WRAPUP,
|
|
1737
2647
|
interactionId: taskId,
|
|
1738
|
-
interaction: {
|
|
2648
|
+
interaction: {mediaType: 'telephony'},
|
|
1739
2649
|
},
|
|
1740
2650
|
};
|
|
1741
2651
|
webSocketManagerMock.emit('message', JSON.stringify(wrapupPayload));
|
|
@@ -1752,7 +2662,7 @@ describe('TaskManager', () => {
|
|
|
1752
2662
|
data: {
|
|
1753
2663
|
type: CC_EVENTS.AGENT_VTEAM_TRANSFERRED,
|
|
1754
2664
|
agentId: initalPayload.data.agentId,
|
|
1755
|
-
interaction: {
|
|
2665
|
+
interaction: {mediaType: 'telephony'},
|
|
1756
2666
|
interactionId: initalPayload.data.interactionId,
|
|
1757
2667
|
orgId: initalPayload.data.orgId,
|
|
1758
2668
|
trackingId: initalPayload.data.trackingId,
|
|
@@ -1771,7 +2681,7 @@ describe('TaskManager', () => {
|
|
|
1771
2681
|
data: {
|
|
1772
2682
|
type: CC_EVENTS.AGENT_WRAPUP,
|
|
1773
2683
|
interactionId: taskId,
|
|
1774
|
-
interaction: {
|
|
2684
|
+
interaction: {mediaType: 'telephony'},
|
|
1775
2685
|
},
|
|
1776
2686
|
};
|
|
1777
2687
|
webSocketManagerMock.emit('message', JSON.stringify(wrapupPayload));
|
|
@@ -1780,704 +2690,1085 @@ describe('TaskManager', () => {
|
|
|
1780
2690
|
});
|
|
1781
2691
|
|
|
1782
2692
|
describe('should emit appropriate task events for recording events', () => {
|
|
1783
|
-
|
|
2693
|
+
const eventMap: Record<string, TaskEvent | null> = {
|
|
2694
|
+
STARTED: TaskEvent.RECORDING_STARTED,
|
|
2695
|
+
PAUSED: TaskEvent.PAUSE_RECORDING,
|
|
2696
|
+
PAUSE_FAILED: null,
|
|
2697
|
+
RESUMED: TaskEvent.RESUME_RECORDING,
|
|
2698
|
+
RESUME_FAILED: null,
|
|
2699
|
+
};
|
|
2700
|
+
|
|
2701
|
+
['STARTED', 'PAUSED', 'PAUSE_FAILED', 'RESUMED', 'RESUME_FAILED'].forEach((suffix) => {
|
|
1784
2702
|
const ccEvent = CC_EVENTS[`CONTACT_RECORDING_${suffix}`];
|
|
1785
|
-
const
|
|
1786
|
-
it(`should
|
|
2703
|
+
const expectedTaskEvent = eventMap[suffix];
|
|
2704
|
+
it(`should ${expectedTaskEvent ? 'send' : 'not send'} ${
|
|
2705
|
+
expectedTaskEvent ?? 'a'
|
|
2706
|
+
} state machine event on ${ccEvent} event`, () => {
|
|
1787
2707
|
const payload = {data: {...initalPayload.data, type: ccEvent}};
|
|
1788
2708
|
webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
|
|
1789
2709
|
const task = taskManager.getTask(taskId);
|
|
1790
|
-
const
|
|
2710
|
+
const sendStateMachineEventSpy = task.sendStateMachineEvent as jest.Mock;
|
|
2711
|
+
sendStateMachineEventSpy.mockClear();
|
|
1791
2712
|
|
|
1792
2713
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1793
|
-
|
|
2714
|
+
if (expectedTaskEvent) {
|
|
2715
|
+
const stateMachineEvent = expectLastStateMachineEvent(
|
|
2716
|
+
sendStateMachineEventSpy,
|
|
2717
|
+
expectedTaskEvent
|
|
2718
|
+
);
|
|
2719
|
+
expect(stateMachineEvent?.taskData).toEqual(payload.data);
|
|
2720
|
+
} else {
|
|
2721
|
+
expect(sendStateMachineEventSpy).not.toHaveBeenCalled();
|
|
2722
|
+
}
|
|
1794
2723
|
});
|
|
1795
2724
|
});
|
|
1796
|
-
});
|
|
2725
|
+
});
|
|
1797
2726
|
|
|
1798
|
-
describe('
|
|
1799
|
-
|
|
1800
|
-
const
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
2727
|
+
describe('Contact owner propagation', () => {
|
|
2728
|
+
const currentAgentId = 'test-agent-id';
|
|
2729
|
+
const previousOwnerId = 'previous-owner-id';
|
|
2730
|
+
const promotedOwnerId = 'promoted-owner-id';
|
|
2731
|
+
|
|
2732
|
+
const createConferenceTaskData = (
|
|
2733
|
+
interactionId: string,
|
|
2734
|
+
owner: string,
|
|
2735
|
+
mainInteractionId = taskId
|
|
2736
|
+
) => ({
|
|
2737
|
+
...taskDataMock,
|
|
2738
|
+
agentId: currentAgentId,
|
|
2739
|
+
interactionId,
|
|
2740
|
+
owner,
|
|
2741
|
+
interaction: {
|
|
2742
|
+
mediaType: 'telephony',
|
|
2743
|
+
interactionId,
|
|
2744
|
+
mainInteractionId,
|
|
2745
|
+
owner,
|
|
2746
|
+
participants: {
|
|
2747
|
+
[currentAgentId]: {
|
|
2748
|
+
id: currentAgentId,
|
|
2749
|
+
pType: 'Agent',
|
|
2750
|
+
hasJoined: true,
|
|
2751
|
+
hasLeft: false,
|
|
2752
|
+
},
|
|
2753
|
+
[previousOwnerId]: {
|
|
2754
|
+
id: previousOwnerId,
|
|
2755
|
+
pType: 'Agent',
|
|
2756
|
+
hasJoined: true,
|
|
2757
|
+
hasLeft: false,
|
|
2758
|
+
},
|
|
2759
|
+
[promotedOwnerId]: {
|
|
2760
|
+
id: promotedOwnerId,
|
|
2761
|
+
pType: 'Agent',
|
|
2762
|
+
hasJoined: true,
|
|
2763
|
+
hasLeft: false,
|
|
2764
|
+
},
|
|
2765
|
+
},
|
|
2766
|
+
media: {
|
|
2767
|
+
[mainInteractionId]: {
|
|
2768
|
+
mediaResourceId: mainInteractionId,
|
|
2769
|
+
mediaType: 'telephony',
|
|
2770
|
+
mType: 'mainCall',
|
|
2771
|
+
participants: [currentAgentId, previousOwnerId, promotedOwnerId],
|
|
2772
|
+
},
|
|
2773
|
+
},
|
|
2774
|
+
},
|
|
1816
2775
|
});
|
|
1817
2776
|
|
|
1818
|
-
|
|
1819
|
-
const
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
2777
|
+
const installTask = (data) => {
|
|
2778
|
+
const task = createMockTask(data);
|
|
2779
|
+
taskManager.taskCollection = {[data.interactionId]: task};
|
|
2780
|
+
(taskManager as any).setupTaskListeners(task);
|
|
2781
|
+
|
|
2782
|
+
return task;
|
|
2783
|
+
};
|
|
2784
|
+
|
|
2785
|
+
const createPromotedAgentOwnerChangeData = (
|
|
2786
|
+
interactionId = 'promoted-child-interaction-id',
|
|
2787
|
+
mainInteractionId = taskId
|
|
2788
|
+
) => {
|
|
2789
|
+
const data = createConferenceTaskData(interactionId, currentAgentId, mainInteractionId);
|
|
2790
|
+
|
|
2791
|
+
data.interaction.state = 'conference';
|
|
2792
|
+
|
|
2793
|
+
return {...data, type: CC_EVENTS.CONTACT_OWNER_CHANGED};
|
|
2794
|
+
};
|
|
2795
|
+
|
|
2796
|
+
const clearTasksAndFactoryHistory = () => {
|
|
2797
|
+
taskManager.taskCollection = {};
|
|
2798
|
+
(TaskFactory.createTask as jest.Mock).mockClear();
|
|
2799
|
+
};
|
|
2800
|
+
|
|
2801
|
+
it('emits one hydrate when ContactUpdated changes interaction.owner', () => {
|
|
2802
|
+
const task = installTask(createConferenceTaskData(taskId, previousOwnerId));
|
|
2803
|
+
const hydrateHandler = jest.fn();
|
|
2804
|
+
taskManager.on(TASK_EVENTS.TASK_HYDRATE, hydrateHandler);
|
|
2805
|
+
|
|
2806
|
+
webSocketManagerMock.emit(
|
|
2807
|
+
'message',
|
|
2808
|
+
JSON.stringify({
|
|
2809
|
+
data: {
|
|
2810
|
+
...createConferenceTaskData(taskId, promotedOwnerId),
|
|
2811
|
+
type: CC_EVENTS.CONTACT_UPDATED,
|
|
2812
|
+
},
|
|
2813
|
+
})
|
|
2814
|
+
);
|
|
2815
|
+
|
|
2816
|
+
expectLastStateMachineEvent(task.sendStateMachineEvent, TaskEvent.CONTACT_OWNER_CHANGED);
|
|
2817
|
+
expect(task.data.interaction.owner).toBe(promotedOwnerId);
|
|
2818
|
+
expect(hydrateHandler).toHaveBeenCalledTimes(1);
|
|
2819
|
+
expect(hydrateHandler).toHaveBeenCalledWith(task);
|
|
2820
|
+
});
|
|
2821
|
+
|
|
2822
|
+
it.each([
|
|
2823
|
+
['the same owner', previousOwnerId],
|
|
2824
|
+
['no owner', undefined],
|
|
2825
|
+
['a blank owner', ' '],
|
|
2826
|
+
])('keeps ContactUpdated data-only when it carries %s', (_description, owner) => {
|
|
2827
|
+
const task = installTask(createConferenceTaskData(taskId, previousOwnerId));
|
|
2828
|
+
const hydrateHandler = jest.fn();
|
|
2829
|
+
taskManager.on(TASK_EVENTS.TASK_HYDRATE, hydrateHandler);
|
|
2830
|
+
const interaction = createConferenceTaskData(taskId, previousOwnerId).interaction;
|
|
2831
|
+
if (owner === undefined) {
|
|
2832
|
+
delete interaction.owner;
|
|
2833
|
+
} else {
|
|
2834
|
+
interaction.owner = owner;
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
webSocketManagerMock.emit(
|
|
2838
|
+
'message',
|
|
2839
|
+
JSON.stringify({
|
|
2840
|
+
data: {
|
|
2841
|
+
...createConferenceTaskData(taskId, previousOwnerId),
|
|
2842
|
+
type: CC_EVENTS.CONTACT_UPDATED,
|
|
2843
|
+
interaction,
|
|
2844
|
+
},
|
|
2845
|
+
})
|
|
2846
|
+
);
|
|
2847
|
+
|
|
2848
|
+
expectLastStateMachineEvent(task.sendStateMachineEvent, TaskEvent.CONTACT_UPDATED);
|
|
2849
|
+
expect(hydrateHandler).not.toHaveBeenCalled();
|
|
2850
|
+
});
|
|
2851
|
+
|
|
2852
|
+
it('correlates child-keyed ContactOwnerChanged and keeps the main task identity', () => {
|
|
2853
|
+
const childTaskId = 'surviving-child-id';
|
|
2854
|
+
const promotedChildId = 'promoted-child-id';
|
|
2855
|
+
const task = installTask(
|
|
2856
|
+
createConferenceTaskData(childTaskId, previousOwnerId, taskId)
|
|
2857
|
+
);
|
|
2858
|
+
const hydrateHandler = jest.fn();
|
|
2859
|
+
taskManager.on(TASK_EVENTS.TASK_HYDRATE, hydrateHandler);
|
|
2860
|
+
|
|
2861
|
+
const ownerChangeData = createConferenceTaskData(
|
|
2862
|
+
promotedChildId,
|
|
2863
|
+
promotedOwnerId,
|
|
2864
|
+
taskId
|
|
2865
|
+
);
|
|
2866
|
+
ownerChangeData.interaction.interactionId = taskId;
|
|
2867
|
+
webSocketManagerMock.emit(
|
|
2868
|
+
'message',
|
|
2869
|
+
JSON.stringify({
|
|
2870
|
+
data: {...ownerChangeData, type: CC_EVENTS.CONTACT_OWNER_CHANGED},
|
|
2871
|
+
})
|
|
2872
|
+
);
|
|
2873
|
+
|
|
2874
|
+
expectLastStateMachineEvent(task.sendStateMachineEvent, TaskEvent.CONTACT_OWNER_CHANGED);
|
|
2875
|
+
expect(task.data.interactionId).toBe(taskId);
|
|
2876
|
+
expect(task.data.interaction.owner).toBe(promotedOwnerId);
|
|
2877
|
+
expect(taskManager.taskCollection[childTaskId]).toBeUndefined();
|
|
2878
|
+
expect(taskManager.taskCollection[promotedChildId]).toBeUndefined();
|
|
2879
|
+
expect(taskManager.taskCollection[taskId]).toBe(task);
|
|
2880
|
+
expect(Object.values(taskManager.taskCollection).filter((entry) => entry === task)).toHaveLength(
|
|
2881
|
+
1
|
|
2882
|
+
);
|
|
2883
|
+
expect(hydrateHandler).toHaveBeenCalledTimes(1);
|
|
2884
|
+
});
|
|
2885
|
+
|
|
2886
|
+
it('uses the incoming promotion snapshot to correlate a stale child-keyed task', () => {
|
|
2887
|
+
const childTaskId = 'stale-child-interaction-id';
|
|
2888
|
+
const staleTaskData = createConferenceTaskData(childTaskId, previousOwnerId, taskId);
|
|
2889
|
+
staleTaskData.interaction.media = {
|
|
2890
|
+
[childTaskId]: {
|
|
2891
|
+
mediaResourceId: childTaskId,
|
|
2892
|
+
mediaType: 'telephony',
|
|
2893
|
+
mType: 'consult',
|
|
2894
|
+
participants: [currentAgentId, promotedOwnerId],
|
|
1824
2895
|
},
|
|
1825
2896
|
};
|
|
2897
|
+
const task = installTask(staleTaskData);
|
|
2898
|
+
const hydrateHandler = jest.fn();
|
|
2899
|
+
taskManager.on(TASK_EVENTS.TASK_HYDRATE, hydrateHandler);
|
|
1826
2900
|
|
|
1827
|
-
webSocketManagerMock.emit(
|
|
2901
|
+
webSocketManagerMock.emit(
|
|
2902
|
+
'message',
|
|
2903
|
+
JSON.stringify({data: createPromotedAgentOwnerChangeData()})
|
|
2904
|
+
);
|
|
1828
2905
|
|
|
1829
|
-
|
|
1830
|
-
expect(task.
|
|
2906
|
+
expectLastStateMachineEvent(task.sendStateMachineEvent, TaskEvent.CONTACT_OWNER_CHANGED);
|
|
2907
|
+
expect(task.data.interaction.owner).toBe(currentAgentId);
|
|
2908
|
+
expect(taskManager.taskCollection[childTaskId]).toBeUndefined();
|
|
2909
|
+
expect(taskManager.taskCollection[taskId]).toBe(task);
|
|
2910
|
+
expect(TaskFactory.createTask).not.toHaveBeenCalled();
|
|
2911
|
+
expect(hydrateHandler).toHaveBeenCalledTimes(1);
|
|
1831
2912
|
});
|
|
1832
2913
|
|
|
1833
|
-
it('
|
|
1834
|
-
const
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
2914
|
+
it('reuses a task stored under the incoming main-call media key', () => {
|
|
2915
|
+
const mainMediaInteractionId = 'main-media-interaction-id';
|
|
2916
|
+
const promotedChildId = 'promoted-child-without-main-id';
|
|
2917
|
+
const task = installTask(
|
|
2918
|
+
createConferenceTaskData(
|
|
2919
|
+
mainMediaInteractionId,
|
|
2920
|
+
previousOwnerId,
|
|
2921
|
+
mainMediaInteractionId
|
|
2922
|
+
)
|
|
2923
|
+
);
|
|
2924
|
+
const hydrateHandler = jest.fn();
|
|
2925
|
+
taskManager.on(TASK_EVENTS.TASK_HYDRATE, hydrateHandler);
|
|
2926
|
+
const ownerChangeData = createPromotedAgentOwnerChangeData(
|
|
2927
|
+
promotedChildId,
|
|
2928
|
+
mainMediaInteractionId
|
|
2929
|
+
);
|
|
2930
|
+
delete ownerChangeData.interaction.mainInteractionId;
|
|
2931
|
+
|
|
2932
|
+
webSocketManagerMock.emit('message', JSON.stringify({data: ownerChangeData}));
|
|
2933
|
+
|
|
2934
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalledTimes(1);
|
|
2935
|
+
expectLastStateMachineEvent(task.sendStateMachineEvent, TaskEvent.CONTACT_OWNER_CHANGED);
|
|
2936
|
+
expect(TaskFactory.createTask).not.toHaveBeenCalled();
|
|
2937
|
+
expect(taskManager.taskCollection[mainMediaInteractionId]).toBe(task);
|
|
2938
|
+
expect(taskManager.taskCollection[promotedChildId]).toBeUndefined();
|
|
2939
|
+
expect(Object.values(taskManager.taskCollection)).toEqual([task]);
|
|
2940
|
+
expect(task.listenerCount(TASK_EVENTS.TASK_HYDRATE)).toBe(1);
|
|
2941
|
+
expect(hydrateHandler).toHaveBeenCalledTimes(1);
|
|
2942
|
+
expect(hydrateHandler).toHaveBeenCalledWith(task);
|
|
2943
|
+
});
|
|
2944
|
+
|
|
2945
|
+
it('prefers the exact task for ContactOwnerChanged over related child tasks', () => {
|
|
2946
|
+
const exactTask = installTask(createConferenceTaskData(taskId, previousOwnerId));
|
|
2947
|
+
const childTaskId = 'related-child-id';
|
|
2948
|
+
const childTask = createMockTask(
|
|
2949
|
+
createConferenceTaskData(childTaskId, previousOwnerId, taskId)
|
|
2950
|
+
);
|
|
2951
|
+
taskManager.taskCollection[childTaskId] = childTask;
|
|
2952
|
+
|
|
2953
|
+
webSocketManagerMock.emit(
|
|
2954
|
+
'message',
|
|
2955
|
+
JSON.stringify({
|
|
2956
|
+
data: {
|
|
2957
|
+
...createConferenceTaskData(taskId, promotedOwnerId),
|
|
2958
|
+
type: CC_EVENTS.CONTACT_OWNER_CHANGED,
|
|
2959
|
+
},
|
|
2960
|
+
})
|
|
2961
|
+
);
|
|
2962
|
+
|
|
2963
|
+
expectLastStateMachineEvent(exactTask.sendStateMachineEvent, TaskEvent.CONTACT_OWNER_CHANGED);
|
|
2964
|
+
expect(childTask.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
2965
|
+
});
|
|
2966
|
+
|
|
2967
|
+
it('ignores a related ContactOwnerChanged task whose current agent is consult-only', () => {
|
|
2968
|
+
const childTaskId = 'consult-only-child-id';
|
|
2969
|
+
const data = createConferenceTaskData(childTaskId, previousOwnerId, taskId);
|
|
2970
|
+
data.interaction.media[taskId].participants = [previousOwnerId, promotedOwnerId];
|
|
2971
|
+
data.interaction.media[childTaskId] = {
|
|
2972
|
+
mediaResourceId: childTaskId,
|
|
2973
|
+
mediaType: 'telephony',
|
|
2974
|
+
mType: 'consult',
|
|
2975
|
+
participants: [currentAgentId, promotedOwnerId],
|
|
1840
2976
|
};
|
|
2977
|
+
const task = installTask(data);
|
|
1841
2978
|
|
|
1842
|
-
webSocketManagerMock.emit(
|
|
2979
|
+
webSocketManagerMock.emit(
|
|
2980
|
+
'message',
|
|
2981
|
+
JSON.stringify({
|
|
2982
|
+
data: {
|
|
2983
|
+
...createConferenceTaskData('unmatched-child-id', promotedOwnerId, taskId),
|
|
2984
|
+
type: CC_EVENTS.CONTACT_OWNER_CHANGED,
|
|
2985
|
+
},
|
|
2986
|
+
})
|
|
2987
|
+
);
|
|
1843
2988
|
|
|
1844
|
-
expect(task.
|
|
1845
|
-
|
|
1846
|
-
expect(
|
|
2989
|
+
expect(task.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
2990
|
+
expect(taskManager.taskCollection[childTaskId]).toBe(task);
|
|
2991
|
+
expect(TaskFactory.createTask).not.toHaveBeenCalled();
|
|
1847
2992
|
});
|
|
1848
2993
|
|
|
1849
|
-
it(
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
reason: 'Network error',
|
|
2994
|
+
it.each([
|
|
2995
|
+
[
|
|
2996
|
+
'the current agent is marked departed',
|
|
2997
|
+
(data) => {
|
|
2998
|
+
data.interaction.participants[currentAgentId].hasLeft = true;
|
|
1855
2999
|
},
|
|
3000
|
+
],
|
|
3001
|
+
[
|
|
3002
|
+
'the current agent participant is missing',
|
|
3003
|
+
(data) => {
|
|
3004
|
+
delete data.interaction.participants[currentAgentId];
|
|
3005
|
+
},
|
|
3006
|
+
],
|
|
3007
|
+
[
|
|
3008
|
+
'the current agent is present only on a consult leg',
|
|
3009
|
+
(data) => {
|
|
3010
|
+
data.interaction.media[taskId].mType = 'consult';
|
|
3011
|
+
},
|
|
3012
|
+
],
|
|
3013
|
+
])('does not recover a promoted task when %s', (_description, mutatePayload) => {
|
|
3014
|
+
clearTasksAndFactoryHistory();
|
|
3015
|
+
const ownerChangeData = createPromotedAgentOwnerChangeData();
|
|
3016
|
+
mutatePayload(ownerChangeData);
|
|
3017
|
+
|
|
3018
|
+
webSocketManagerMock.emit('message', JSON.stringify({data: ownerChangeData}));
|
|
3019
|
+
|
|
3020
|
+
expect(TaskFactory.createTask).not.toHaveBeenCalled();
|
|
3021
|
+
expect(taskManager.getAllTasks()).toEqual({});
|
|
3022
|
+
});
|
|
3023
|
+
|
|
3024
|
+
it('ignores ambiguous active main-call matches for ContactOwnerChanged', () => {
|
|
3025
|
+
const firstTask = createMockTask(
|
|
3026
|
+
createConferenceTaskData('first-child-id', previousOwnerId, taskId)
|
|
3027
|
+
);
|
|
3028
|
+
const secondTask = createMockTask(
|
|
3029
|
+
createConferenceTaskData('second-child-id', previousOwnerId, taskId)
|
|
3030
|
+
);
|
|
3031
|
+
taskManager.taskCollection = {
|
|
3032
|
+
'first-child-id': firstTask,
|
|
3033
|
+
'second-child-id': secondTask,
|
|
1856
3034
|
};
|
|
1857
3035
|
|
|
1858
|
-
|
|
3036
|
+
const ownerChangeData = createConferenceTaskData(
|
|
3037
|
+
'unmatched-child-id',
|
|
3038
|
+
promotedOwnerId,
|
|
3039
|
+
taskId
|
|
3040
|
+
);
|
|
3041
|
+
ownerChangeData.interaction.interactionId = 'unmatched-child-id';
|
|
3042
|
+
webSocketManagerMock.emit(
|
|
3043
|
+
'message',
|
|
3044
|
+
JSON.stringify({
|
|
3045
|
+
data: {...ownerChangeData, type: CC_EVENTS.CONTACT_OWNER_CHANGED},
|
|
3046
|
+
})
|
|
3047
|
+
);
|
|
1859
3048
|
|
|
1860
|
-
expect(
|
|
1861
|
-
|
|
3049
|
+
expect(firstTask.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
3050
|
+
expect(secondTask.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
1862
3051
|
});
|
|
1863
3052
|
|
|
1864
|
-
it('
|
|
1865
|
-
const
|
|
1866
|
-
data
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
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
|
-
},
|
|
3053
|
+
it('does not recover a new task when multiple stale related tasks become eligible from the payload', () => {
|
|
3054
|
+
const createStaleRelatedTask = (interactionId: string) => {
|
|
3055
|
+
const data = createConferenceTaskData(interactionId, previousOwnerId, taskId);
|
|
3056
|
+
data.interaction.media[taskId].participants = [previousOwnerId, promotedOwnerId];
|
|
3057
|
+
|
|
3058
|
+
return createMockTask(data);
|
|
1884
3059
|
};
|
|
3060
|
+
const firstTask = createStaleRelatedTask('first-stale-child-id');
|
|
3061
|
+
const secondTask = createStaleRelatedTask('second-stale-child-id');
|
|
3062
|
+
taskManager.taskCollection = {
|
|
3063
|
+
'first-stale-child-id': firstTask,
|
|
3064
|
+
'second-stale-child-id': secondTask,
|
|
3065
|
+
};
|
|
3066
|
+
(TaskFactory.createTask as jest.Mock).mockClear();
|
|
1885
3067
|
|
|
1886
|
-
webSocketManagerMock.emit(
|
|
3068
|
+
webSocketManagerMock.emit(
|
|
3069
|
+
'message',
|
|
3070
|
+
JSON.stringify({data: createPromotedAgentOwnerChangeData()})
|
|
3071
|
+
);
|
|
1887
3072
|
|
|
1888
|
-
expect(
|
|
1889
|
-
expect(
|
|
1890
|
-
|
|
3073
|
+
expect(firstTask.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
3074
|
+
expect(secondTask.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
3075
|
+
expect(TaskFactory.createTask).not.toHaveBeenCalled();
|
|
3076
|
+
expect(Object.keys(taskManager.taskCollection)).toEqual([
|
|
3077
|
+
'first-stale-child-id',
|
|
3078
|
+
'second-stale-child-id',
|
|
3079
|
+
]);
|
|
1891
3080
|
});
|
|
1892
3081
|
|
|
1893
|
-
it('
|
|
1894
|
-
const
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
},
|
|
1906
|
-
media: {
|
|
1907
|
-
[taskId]: {
|
|
1908
|
-
mType: 'mainCall',
|
|
1909
|
-
participants: [agentId, 'agent-2', 'new-agent-789', 'customer-1'],
|
|
1910
|
-
},
|
|
1911
|
-
},
|
|
1912
|
-
},
|
|
1913
|
-
},
|
|
3082
|
+
it('ignores ContactOwnerChanged when different direct nested IDs match active tasks', () => {
|
|
3083
|
+
const firstTaskId = 'direct-main-id';
|
|
3084
|
+
const secondTaskId = 'direct-parent-id';
|
|
3085
|
+
const firstTask = createMockTask(
|
|
3086
|
+
createConferenceTaskData(firstTaskId, previousOwnerId, firstTaskId)
|
|
3087
|
+
);
|
|
3088
|
+
const secondTask = createMockTask(
|
|
3089
|
+
createConferenceTaskData(secondTaskId, previousOwnerId, secondTaskId)
|
|
3090
|
+
);
|
|
3091
|
+
taskManager.taskCollection = {
|
|
3092
|
+
[firstTaskId]: firstTask,
|
|
3093
|
+
[secondTaskId]: secondTask,
|
|
1914
3094
|
};
|
|
1915
3095
|
|
|
1916
|
-
const
|
|
1917
|
-
|
|
1918
|
-
|
|
3096
|
+
const ownerChangeData = createConferenceTaskData(
|
|
3097
|
+
'unmatched-child-id',
|
|
3098
|
+
promotedOwnerId,
|
|
3099
|
+
firstTaskId
|
|
3100
|
+
);
|
|
3101
|
+
ownerChangeData.interaction.interactionId = secondTaskId;
|
|
3102
|
+
webSocketManagerMock.emit(
|
|
3103
|
+
'message',
|
|
3104
|
+
JSON.stringify({
|
|
3105
|
+
data: {...ownerChangeData, type: CC_EVENTS.CONTACT_OWNER_CHANGED},
|
|
3106
|
+
})
|
|
3107
|
+
);
|
|
3108
|
+
|
|
3109
|
+
expect(firstTask.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
3110
|
+
expect(secondTask.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
3111
|
+
});
|
|
3112
|
+
|
|
3113
|
+
it('ignores ContactOwnerChanged without an interaction correlation identifier', () => {
|
|
3114
|
+
const task = installTask(createConferenceTaskData(taskId, previousOwnerId));
|
|
3115
|
+
|
|
3116
|
+
webSocketManagerMock.emit(
|
|
3117
|
+
'message',
|
|
3118
|
+
JSON.stringify({
|
|
3119
|
+
data: {
|
|
3120
|
+
type: CC_EVENTS.CONTACT_OWNER_CHANGED,
|
|
3121
|
+
interaction: {owner: promotedOwnerId},
|
|
3122
|
+
},
|
|
3123
|
+
})
|
|
3124
|
+
);
|
|
3125
|
+
|
|
3126
|
+
expect(task.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
3127
|
+
});
|
|
3128
|
+
|
|
3129
|
+
it('recovers a missing promoted-agent task under the main interaction and emits one hydrate', () => {
|
|
3130
|
+
clearTasksAndFactoryHistory();
|
|
3131
|
+
const hydrateHandler = jest.fn();
|
|
3132
|
+
const incomingHandler = jest.fn();
|
|
3133
|
+
taskManager.on(TASK_EVENTS.TASK_HYDRATE, hydrateHandler);
|
|
3134
|
+
taskManager.on(TASK_EVENTS.TASK_INCOMING, incomingHandler);
|
|
3135
|
+
|
|
3136
|
+
webSocketManagerMock.emit(
|
|
3137
|
+
'message',
|
|
3138
|
+
JSON.stringify({data: createPromotedAgentOwnerChangeData()})
|
|
3139
|
+
);
|
|
3140
|
+
|
|
3141
|
+
expect(TaskFactory.createTask).toHaveBeenCalledTimes(1);
|
|
3142
|
+
expect(TaskFactory.createTask).toHaveBeenCalledWith(
|
|
3143
|
+
contactMock,
|
|
3144
|
+
webCallingService,
|
|
3145
|
+
expect.objectContaining({
|
|
3146
|
+
interactionId: taskId,
|
|
3147
|
+
isAutoAnswering: false,
|
|
3148
|
+
isConsulted: false,
|
|
3149
|
+
isConferenceInProgress: true,
|
|
3150
|
+
wrapUpRequired: false,
|
|
3151
|
+
}),
|
|
3152
|
+
undefined,
|
|
3153
|
+
undefined,
|
|
3154
|
+
currentAgentId,
|
|
3155
|
+
undefined
|
|
3156
|
+
);
|
|
1919
3157
|
|
|
1920
|
-
|
|
1921
|
-
expect(
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
3158
|
+
const recoveredTask = (TaskFactory.createTask as jest.Mock).mock.results[0].value;
|
|
3159
|
+
expect(recoveredTask.sendStateMachineEvent).toHaveBeenCalledTimes(2);
|
|
3160
|
+
expect(recoveredTask.sendStateMachineEvent).toHaveBeenNthCalledWith(
|
|
3161
|
+
1,
|
|
3162
|
+
expect.objectContaining({
|
|
3163
|
+
type: TaskEvent.HYDRATE,
|
|
3164
|
+
agentId: currentAgentId,
|
|
3165
|
+
taskData: expect.objectContaining({interactionId: taskId}),
|
|
3166
|
+
})
|
|
3167
|
+
);
|
|
3168
|
+
expect(recoveredTask.sendStateMachineEvent).toHaveBeenNthCalledWith(
|
|
3169
|
+
2,
|
|
1925
3170
|
expect.objectContaining({
|
|
1926
|
-
|
|
1927
|
-
|
|
3171
|
+
type: TaskEvent.CONTACT_OWNER_CHANGED,
|
|
3172
|
+
taskData: expect.objectContaining({interactionId: taskId}),
|
|
1928
3173
|
})
|
|
1929
3174
|
);
|
|
1930
|
-
|
|
1931
|
-
expect(
|
|
3175
|
+
expect(taskManager.taskCollection[taskId]).toBe(recoveredTask);
|
|
3176
|
+
expect(taskManager.taskCollection['promoted-child-interaction-id']).toBeUndefined();
|
|
3177
|
+
expect(Object.values(taskManager.taskCollection)).toEqual([recoveredTask]);
|
|
3178
|
+
expect(recoveredTask.data.interaction.owner).toBe(currentAgentId);
|
|
3179
|
+
expect(hydrateHandler).toHaveBeenCalledTimes(1);
|
|
3180
|
+
expect(hydrateHandler).toHaveBeenCalledWith(recoveredTask);
|
|
3181
|
+
expect(incomingHandler).not.toHaveBeenCalled();
|
|
3182
|
+
});
|
|
3183
|
+
|
|
3184
|
+
it.each([
|
|
3185
|
+
[
|
|
3186
|
+
'another agent owns the interaction',
|
|
3187
|
+
(data) => {
|
|
3188
|
+
data.interaction.owner = promotedOwnerId;
|
|
3189
|
+
data.owner = promotedOwnerId;
|
|
3190
|
+
},
|
|
3191
|
+
],
|
|
3192
|
+
[
|
|
3193
|
+
'the payload has no main-call media',
|
|
3194
|
+
(data) => {
|
|
3195
|
+
data.interaction.media = {};
|
|
3196
|
+
},
|
|
3197
|
+
],
|
|
3198
|
+
[
|
|
3199
|
+
'the interaction is explicitly terminated',
|
|
3200
|
+
(data) => {
|
|
3201
|
+
data.interaction.isTerminated = true;
|
|
3202
|
+
},
|
|
3203
|
+
],
|
|
3204
|
+
])('does not create a missing task when %s', (_description, mutatePayload) => {
|
|
3205
|
+
clearTasksAndFactoryHistory();
|
|
3206
|
+
const ownerChangeData = createPromotedAgentOwnerChangeData();
|
|
3207
|
+
mutatePayload(ownerChangeData);
|
|
3208
|
+
|
|
3209
|
+
webSocketManagerMock.emit('message', JSON.stringify({data: ownerChangeData}));
|
|
3210
|
+
|
|
3211
|
+
expect(TaskFactory.createTask).not.toHaveBeenCalled();
|
|
3212
|
+
expect(taskManager.getAllTasks()).toEqual({});
|
|
3213
|
+
});
|
|
3214
|
+
|
|
3215
|
+
it('does not create a missing task for an owner-changing ContactUpdated event', () => {
|
|
3216
|
+
clearTasksAndFactoryHistory();
|
|
3217
|
+
const contactUpdatedData = {
|
|
3218
|
+
...createPromotedAgentOwnerChangeData(),
|
|
3219
|
+
type: CC_EVENTS.CONTACT_UPDATED,
|
|
3220
|
+
};
|
|
3221
|
+
|
|
3222
|
+
webSocketManagerMock.emit('message', JSON.stringify({data: contactUpdatedData}));
|
|
3223
|
+
|
|
3224
|
+
expect(TaskFactory.createTask).not.toHaveBeenCalled();
|
|
3225
|
+
expect(taskManager.getAllTasks()).toEqual({});
|
|
1932
3226
|
});
|
|
1933
3227
|
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
3228
|
+
it('reuses a recovered task for later AgentContact and ContactMerged events', () => {
|
|
3229
|
+
clearTasksAndFactoryHistory();
|
|
3230
|
+
const hydrateHandler = jest.fn();
|
|
3231
|
+
const mergedHandler = jest.fn();
|
|
3232
|
+
taskManager.on(TASK_EVENTS.TASK_HYDRATE, hydrateHandler);
|
|
3233
|
+
taskManager.on(TASK_EVENTS.TASK_MERGED, mergedHandler);
|
|
3234
|
+
|
|
3235
|
+
webSocketManagerMock.emit(
|
|
3236
|
+
'message',
|
|
3237
|
+
JSON.stringify({data: createPromotedAgentOwnerChangeData()})
|
|
3238
|
+
);
|
|
3239
|
+
|
|
3240
|
+
const recoveredTask = taskManager.taskCollection[taskId];
|
|
3241
|
+
expect(recoveredTask).toBeDefined();
|
|
3242
|
+
expect(recoveredTask.listenerCount(TASK_EVENTS.TASK_HYDRATE)).toBe(1);
|
|
3243
|
+
|
|
3244
|
+
webSocketManagerMock.emit(
|
|
3245
|
+
'message',
|
|
3246
|
+
JSON.stringify({
|
|
1937
3247
|
data: {
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
},
|
|
1951
|
-
},
|
|
1952
|
-
},
|
|
3248
|
+
...createConferenceTaskData(taskId, currentAgentId),
|
|
3249
|
+
type: CC_EVENTS.AGENT_CONTACT,
|
|
3250
|
+
},
|
|
3251
|
+
})
|
|
3252
|
+
);
|
|
3253
|
+
webSocketManagerMock.emit(
|
|
3254
|
+
'message',
|
|
3255
|
+
JSON.stringify({
|
|
3256
|
+
data: {
|
|
3257
|
+
...createConferenceTaskData(taskId, currentAgentId),
|
|
3258
|
+
type: CC_EVENTS.CONTACT_MERGED,
|
|
3259
|
+
childInteractionId: 'promoted-child-interaction-id',
|
|
1953
3260
|
},
|
|
1954
|
-
}
|
|
3261
|
+
})
|
|
3262
|
+
);
|
|
1955
3263
|
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
3264
|
+
expect(TaskFactory.createTask).toHaveBeenCalledTimes(1);
|
|
3265
|
+
expect(taskManager.taskCollection[taskId]).toBe(recoveredTask);
|
|
3266
|
+
expect(taskManager.taskCollection['promoted-child-interaction-id']).toBeUndefined();
|
|
3267
|
+
expect(Object.values(taskManager.taskCollection)).toEqual([recoveredTask]);
|
|
3268
|
+
expect(recoveredTask.listenerCount(TASK_EVENTS.TASK_HYDRATE)).toBe(1);
|
|
3269
|
+
expect(hydrateHandler).toHaveBeenCalledTimes(2);
|
|
3270
|
+
expect(mergedHandler).toHaveBeenCalledTimes(1);
|
|
3271
|
+
expect(mergedHandler).toHaveBeenCalledWith(recoveredTask);
|
|
3272
|
+
});
|
|
1959
3273
|
|
|
1960
|
-
|
|
1961
|
-
|
|
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
|
-
});
|
|
3274
|
+
it('does not let a late ParticipantLeftConference restore a departed owner', () => {
|
|
3275
|
+
const task = installTask(createConferenceTaskData(taskId, previousOwnerId));
|
|
1972
3276
|
|
|
1973
|
-
|
|
1974
|
-
|
|
3277
|
+
webSocketManagerMock.emit(
|
|
3278
|
+
'message',
|
|
3279
|
+
JSON.stringify({
|
|
1975
3280
|
data: {
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
interaction: {
|
|
1979
|
-
participants: {
|
|
1980
|
-
[agentId]: {
|
|
1981
|
-
hasLeft: false,
|
|
1982
|
-
},
|
|
1983
|
-
},
|
|
1984
|
-
},
|
|
3281
|
+
...createConferenceTaskData(taskId, promotedOwnerId),
|
|
3282
|
+
type: CC_EVENTS.CONTACT_UPDATED,
|
|
1985
3283
|
},
|
|
1986
|
-
}
|
|
3284
|
+
})
|
|
3285
|
+
);
|
|
1987
3286
|
|
|
1988
|
-
|
|
3287
|
+
const participantLeftData = createConferenceTaskData(taskId, previousOwnerId);
|
|
3288
|
+
participantLeftData.participantId = previousOwnerId;
|
|
3289
|
+
participantLeftData.interaction.participants[previousOwnerId].hasLeft = true;
|
|
3290
|
+
participantLeftData.interaction.media[taskId].participants = [
|
|
3291
|
+
currentAgentId,
|
|
3292
|
+
promotedOwnerId,
|
|
3293
|
+
];
|
|
3294
|
+
webSocketManagerMock.emit(
|
|
3295
|
+
'message',
|
|
3296
|
+
JSON.stringify({
|
|
3297
|
+
data: {...participantLeftData, type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE},
|
|
3298
|
+
})
|
|
3299
|
+
);
|
|
1989
3300
|
|
|
1990
|
-
|
|
1991
|
-
|
|
3301
|
+
const participantLeaveEvent = expectLastStateMachineEvent(
|
|
3302
|
+
task.sendStateMachineEvent,
|
|
3303
|
+
TaskEvent.PARTICIPANT_LEAVE
|
|
3304
|
+
);
|
|
3305
|
+
expect(participantLeaveEvent.taskData.interaction.owner).toBe(promotedOwnerId);
|
|
3306
|
+
expect(task.data.interaction.owner).toBe(promotedOwnerId);
|
|
3307
|
+
});
|
|
1992
3308
|
|
|
1993
|
-
|
|
1994
|
-
|
|
3309
|
+
it('does not infer that an incoming owner departed from a partial main-call roster', () => {
|
|
3310
|
+
const task = installTask(createConferenceTaskData(taskId, promotedOwnerId));
|
|
3311
|
+
const partialParticipantLeftData = createConferenceTaskData(taskId, previousOwnerId);
|
|
3312
|
+
partialParticipantLeftData.participantId = 'another-agent-id';
|
|
3313
|
+
partialParticipantLeftData.interaction.media[taskId].participants = [
|
|
3314
|
+
currentAgentId,
|
|
3315
|
+
promotedOwnerId,
|
|
3316
|
+
];
|
|
3317
|
+
|
|
3318
|
+
webSocketManagerMock.emit(
|
|
3319
|
+
'message',
|
|
3320
|
+
JSON.stringify({
|
|
1995
3321
|
data: {
|
|
3322
|
+
...partialParticipantLeftData,
|
|
1996
3323
|
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
1997
|
-
interactionId: taskId,
|
|
1998
|
-
interaction: {
|
|
1999
|
-
participants: {
|
|
2000
|
-
[agentId]: {
|
|
2001
|
-
hasLeft: false,
|
|
2002
|
-
},
|
|
2003
|
-
},
|
|
2004
|
-
},
|
|
2005
3324
|
},
|
|
2006
|
-
}
|
|
3325
|
+
})
|
|
3326
|
+
);
|
|
2007
3327
|
|
|
2008
|
-
|
|
3328
|
+
const participantLeaveEvent = expectLastStateMachineEvent(
|
|
3329
|
+
task.sendStateMachineEvent,
|
|
3330
|
+
TaskEvent.PARTICIPANT_LEAVE
|
|
3331
|
+
);
|
|
3332
|
+
expect(participantLeaveEvent.taskData.interaction.owner).toBe(previousOwnerId);
|
|
3333
|
+
expect(task.data.interaction.owner).toBe(previousOwnerId);
|
|
3334
|
+
});
|
|
3335
|
+
|
|
3336
|
+
it('applies the promoted owner when ParticipantLeftConference arrives first', () => {
|
|
3337
|
+
const task = installTask(createConferenceTaskData(taskId, previousOwnerId));
|
|
3338
|
+
const participantLeftData = createConferenceTaskData(taskId, previousOwnerId);
|
|
3339
|
+
participantLeftData.participantId = previousOwnerId;
|
|
3340
|
+
participantLeftData.interaction.participants[previousOwnerId].hasLeft = true;
|
|
3341
|
+
participantLeftData.interaction.media[taskId].participants = [
|
|
3342
|
+
currentAgentId,
|
|
3343
|
+
promotedOwnerId,
|
|
3344
|
+
];
|
|
3345
|
+
|
|
3346
|
+
webSocketManagerMock.emit(
|
|
3347
|
+
'message',
|
|
3348
|
+
JSON.stringify({
|
|
3349
|
+
data: {...participantLeftData, type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE},
|
|
3350
|
+
})
|
|
3351
|
+
);
|
|
3352
|
+
webSocketManagerMock.emit(
|
|
3353
|
+
'message',
|
|
3354
|
+
JSON.stringify({
|
|
3355
|
+
data: {
|
|
3356
|
+
...createConferenceTaskData(taskId, promotedOwnerId),
|
|
3357
|
+
type: CC_EVENTS.CONTACT_UPDATED,
|
|
3358
|
+
},
|
|
3359
|
+
})
|
|
3360
|
+
);
|
|
3361
|
+
|
|
3362
|
+
expect(task.data.interaction.owner).toBe(promotedOwnerId);
|
|
3363
|
+
expectLastStateMachineEvent(task.sendStateMachineEvent, TaskEvent.CONTACT_OWNER_CHANGED);
|
|
3364
|
+
});
|
|
3365
|
+
});
|
|
3366
|
+
|
|
3367
|
+
describe('Conference event handling', () => {
|
|
3368
|
+
let task;
|
|
3369
|
+
|
|
3370
|
+
beforeEach(() => {
|
|
3371
|
+
task = {
|
|
3372
|
+
data: {interactionId: taskId},
|
|
3373
|
+
emit: jest.fn(),
|
|
3374
|
+
updateTaskData: jest.fn(),
|
|
3375
|
+
sendStateMachineEvent: jest.fn(),
|
|
3376
|
+
};
|
|
3377
|
+
taskManager.taskCollection[taskId] = task as any;
|
|
3378
|
+
});
|
|
3379
|
+
|
|
3380
|
+
it('sends AGENT_CONSULT_CONFERENCED to state machine as CONFERENCE_START', () => {
|
|
3381
|
+
const payload = {
|
|
3382
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCED, interactionId: taskId},
|
|
3383
|
+
};
|
|
3384
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
3385
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
3386
|
+
const call = task.sendStateMachineEvent.mock.calls[0][0];
|
|
3387
|
+
expect(call.type).toBe(TaskEvent.CONFERENCE_START);
|
|
3388
|
+
});
|
|
3389
|
+
|
|
3390
|
+
it('sends PARTICIPANT_JOINED_CONFERENCE to state machine as CONFERENCE_START', () => {
|
|
3391
|
+
const payload = {
|
|
3392
|
+
data: {type: CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE, interactionId: taskId},
|
|
3393
|
+
};
|
|
3394
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
3395
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
3396
|
+
const call = task.sendStateMachineEvent.mock.calls[0][0];
|
|
3397
|
+
expect(call.type).toBe(TaskEvent.CONFERENCE_START);
|
|
3398
|
+
});
|
|
3399
|
+
|
|
3400
|
+
it('sends AGENT_CONSULT_CONFERENCE_FAILED to state machine as CONFERENCE_FAILED', () => {
|
|
3401
|
+
const payload = {
|
|
3402
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCE_FAILED, interactionId: taskId},
|
|
3403
|
+
};
|
|
3404
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
3405
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
3406
|
+
const call = task.sendStateMachineEvent.mock.calls[0][0];
|
|
3407
|
+
expect(call.type).toBe(TaskEvent.CONFERENCE_FAILED);
|
|
3408
|
+
});
|
|
3409
|
+
|
|
3410
|
+
it('sends PARTICIPANT_LEFT_CONFERENCE to state machine as PARTICIPANT_LEAVE', () => {
|
|
3411
|
+
const payload = {
|
|
3412
|
+
data: {type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, interactionId: taskId},
|
|
3413
|
+
};
|
|
3414
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
3415
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
3416
|
+
const call = task.sendStateMachineEvent.mock.calls[0][0];
|
|
3417
|
+
expect(call.type).toBe(TaskEvent.PARTICIPANT_LEAVE);
|
|
3418
|
+
});
|
|
2009
3419
|
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
3420
|
+
it('routes participant-left from a main interaction to a child-keyed task', () => {
|
|
3421
|
+
const childTaskId = 'child-interaction-id';
|
|
3422
|
+
const childTask = createMockTask({
|
|
3423
|
+
...taskDataMock,
|
|
3424
|
+
interactionId: childTaskId,
|
|
3425
|
+
isConsulted: true,
|
|
3426
|
+
interaction: {
|
|
3427
|
+
mediaType: 'telephony',
|
|
3428
|
+
interactionId: childTaskId,
|
|
3429
|
+
parentInteractionId: taskId,
|
|
3430
|
+
},
|
|
2013
3431
|
});
|
|
3432
|
+
delete taskManager.taskCollection[taskId];
|
|
3433
|
+
taskManager.taskCollection[childTaskId] = childTask;
|
|
2014
3434
|
|
|
2015
|
-
|
|
2016
|
-
|
|
3435
|
+
webSocketManagerMock.emit(
|
|
3436
|
+
'message',
|
|
3437
|
+
JSON.stringify({
|
|
2017
3438
|
data: {
|
|
2018
3439
|
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2019
3440
|
interactionId: taskId,
|
|
3441
|
+
participantId: 'test-agent-id',
|
|
2020
3442
|
interaction: {
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
},
|
|
2025
|
-
},
|
|
2026
|
-
media: {
|
|
2027
|
-
[taskId]: {
|
|
2028
|
-
mType: 'mainCall',
|
|
2029
|
-
participants: [agentId],
|
|
2030
|
-
},
|
|
2031
|
-
},
|
|
3443
|
+
mediaType: 'telephony',
|
|
3444
|
+
interactionId: taskId,
|
|
3445
|
+
mainInteractionId: taskId,
|
|
2032
3446
|
},
|
|
2033
3447
|
},
|
|
2034
|
-
}
|
|
2035
|
-
|
|
2036
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
3448
|
+
})
|
|
3449
|
+
);
|
|
2037
3450
|
|
|
2038
|
-
|
|
3451
|
+
expect(childTask.sendStateMachineEvent).toHaveBeenCalledWith(
|
|
3452
|
+
expect.objectContaining({
|
|
3453
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
3454
|
+
participantId: 'test-agent-id',
|
|
3455
|
+
})
|
|
3456
|
+
);
|
|
3457
|
+
expect(taskManager.taskCollection[childTaskId]).toBeUndefined();
|
|
3458
|
+
expect(taskManager.taskCollection[taskId]).toBe(childTask);
|
|
3459
|
+
expect(
|
|
3460
|
+
Object.values(taskManager.taskCollection).filter((entry) => entry === childTask)
|
|
3461
|
+
).toHaveLength(1);
|
|
3462
|
+
});
|
|
2039
3463
|
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
3464
|
+
it('routes consult-end from a main interaction to a child-keyed consulted task', () => {
|
|
3465
|
+
const childTaskId = 'consult-child-interaction-id';
|
|
3466
|
+
const childTask = createMockTask({
|
|
3467
|
+
...taskDataMock,
|
|
3468
|
+
interactionId: childTaskId,
|
|
3469
|
+
isConsulted: true,
|
|
3470
|
+
interaction: {
|
|
3471
|
+
mediaType: 'telephony',
|
|
3472
|
+
interactionId: childTaskId,
|
|
3473
|
+
callProcessingDetails: {parentInteractionId: taskId},
|
|
3474
|
+
},
|
|
2044
3475
|
});
|
|
3476
|
+
delete taskManager.taskCollection[taskId];
|
|
3477
|
+
taskManager.taskCollection[childTaskId] = childTask;
|
|
2045
3478
|
|
|
2046
|
-
|
|
2047
|
-
|
|
3479
|
+
webSocketManagerMock.emit(
|
|
3480
|
+
'message',
|
|
3481
|
+
JSON.stringify({
|
|
2048
3482
|
data: {
|
|
2049
|
-
type: CC_EVENTS.
|
|
3483
|
+
type: CC_EVENTS.AGENT_CONSULT_ENDED,
|
|
2050
3484
|
interactionId: taskId,
|
|
2051
3485
|
interaction: {
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
},
|
|
2056
|
-
},
|
|
2057
|
-
owner: agentId,
|
|
2058
|
-
media: {
|
|
2059
|
-
[taskId]: {
|
|
2060
|
-
mType: 'consultCall',
|
|
2061
|
-
participants: ['other-agent'],
|
|
2062
|
-
},
|
|
2063
|
-
},
|
|
3486
|
+
mediaType: 'telephony',
|
|
3487
|
+
interactionId: taskId,
|
|
3488
|
+
mainInteractionId: taskId,
|
|
2064
3489
|
},
|
|
2065
3490
|
},
|
|
2066
|
-
}
|
|
2067
|
-
|
|
2068
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
3491
|
+
})
|
|
3492
|
+
);
|
|
2069
3493
|
|
|
2070
|
-
|
|
3494
|
+
expect(childTask.sendStateMachineEvent).toHaveBeenCalledWith(
|
|
3495
|
+
expect.objectContaining({type: TaskEvent.CONSULT_END})
|
|
3496
|
+
);
|
|
3497
|
+
});
|
|
2071
3498
|
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
3499
|
+
it('deduplicates aliases of the same child task during related-interaction lookup', () => {
|
|
3500
|
+
const childTaskId = 'aliased-child-interaction-id';
|
|
3501
|
+
const childTask = createMockTask({
|
|
3502
|
+
...taskDataMock,
|
|
3503
|
+
interactionId: childTaskId,
|
|
3504
|
+
interaction: {
|
|
3505
|
+
mediaType: 'telephony',
|
|
3506
|
+
interactionId: childTaskId,
|
|
3507
|
+
mainInteractionId: taskId,
|
|
3508
|
+
},
|
|
2076
3509
|
});
|
|
3510
|
+
delete taskManager.taskCollection[taskId];
|
|
3511
|
+
taskManager.taskCollection[childTaskId] = childTask;
|
|
3512
|
+
taskManager.taskCollection['child-task-alias'] = childTask;
|
|
2077
3513
|
|
|
2078
|
-
|
|
2079
|
-
|
|
3514
|
+
webSocketManagerMock.emit(
|
|
3515
|
+
'message',
|
|
3516
|
+
JSON.stringify({
|
|
2080
3517
|
data: {
|
|
2081
3518
|
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2082
3519
|
interactionId: taskId,
|
|
2083
|
-
|
|
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
|
-
},
|
|
3520
|
+
participantId: 'test-agent-id',
|
|
2097
3521
|
},
|
|
2098
|
-
}
|
|
2099
|
-
|
|
2100
|
-
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
3522
|
+
})
|
|
3523
|
+
);
|
|
2101
3524
|
|
|
2102
|
-
|
|
3525
|
+
expect(childTask.sendStateMachineEvent).toHaveBeenCalledTimes(1);
|
|
3526
|
+
expect(
|
|
3527
|
+
Object.values(taskManager.taskCollection).filter((entry) => entry === childTask)
|
|
3528
|
+
).toHaveLength(1);
|
|
3529
|
+
});
|
|
2103
3530
|
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
3531
|
+
it('prefers an exact task match over a child task with the same main interaction', () => {
|
|
3532
|
+
const childTaskId = 'child-interaction-id';
|
|
3533
|
+
const childTask = createMockTask({
|
|
3534
|
+
...taskDataMock,
|
|
3535
|
+
interactionId: childTaskId,
|
|
3536
|
+
interaction: {
|
|
3537
|
+
mediaType: 'telephony',
|
|
3538
|
+
interactionId: childTaskId,
|
|
3539
|
+
mainInteractionId: taskId,
|
|
3540
|
+
},
|
|
2107
3541
|
});
|
|
3542
|
+
taskManager.taskCollection[childTaskId] = childTask;
|
|
2108
3543
|
|
|
2109
|
-
|
|
2110
|
-
|
|
3544
|
+
webSocketManagerMock.emit(
|
|
3545
|
+
'message',
|
|
3546
|
+
JSON.stringify({
|
|
2111
3547
|
data: {
|
|
2112
3548
|
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2113
3549
|
interactionId: taskId,
|
|
2114
|
-
|
|
2115
|
-
participants: {
|
|
2116
|
-
'other-agent-id': {
|
|
2117
|
-
hasLeft: false,
|
|
2118
|
-
},
|
|
2119
|
-
},
|
|
2120
|
-
owner: 'another-agent-id',
|
|
2121
|
-
},
|
|
3550
|
+
participantId: 'another-agent',
|
|
2122
3551
|
},
|
|
2123
|
-
}
|
|
3552
|
+
})
|
|
3553
|
+
);
|
|
2124
3554
|
|
|
2125
|
-
|
|
3555
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
3556
|
+
expect(childTask.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
3557
|
+
});
|
|
2126
3558
|
|
|
2127
|
-
|
|
3559
|
+
it('does not route a main-interaction event when multiple unique child tasks match', () => {
|
|
3560
|
+
delete taskManager.taskCollection[taskId];
|
|
3561
|
+
const createChildTask = (interactionId: string) =>
|
|
3562
|
+
createMockTask({
|
|
3563
|
+
...taskDataMock,
|
|
3564
|
+
interactionId,
|
|
3565
|
+
interaction: {
|
|
3566
|
+
mediaType: 'telephony',
|
|
3567
|
+
interactionId,
|
|
3568
|
+
mainInteractionId: taskId,
|
|
3569
|
+
},
|
|
3570
|
+
});
|
|
3571
|
+
const firstChildTask = createChildTask('child-1');
|
|
3572
|
+
const secondChildTask = createChildTask('child-2');
|
|
3573
|
+
taskManager.taskCollection['child-1'] = firstChildTask;
|
|
3574
|
+
taskManager.taskCollection['child-2'] = secondChildTask;
|
|
3575
|
+
|
|
3576
|
+
webSocketManagerMock.emit(
|
|
3577
|
+
'message',
|
|
3578
|
+
JSON.stringify({
|
|
3579
|
+
data: {
|
|
3580
|
+
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
3581
|
+
interactionId: taskId,
|
|
3582
|
+
},
|
|
3583
|
+
})
|
|
3584
|
+
);
|
|
2128
3585
|
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
});
|
|
3586
|
+
expect(firstChildTask.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
3587
|
+
expect(secondChildTask.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
3588
|
+
});
|
|
2133
3589
|
|
|
2134
|
-
|
|
2135
|
-
|
|
3590
|
+
it('does not route a lifecycle event without a correlation identifier', () => {
|
|
3591
|
+
webSocketManagerMock.emit(
|
|
3592
|
+
'message',
|
|
3593
|
+
JSON.stringify({
|
|
2136
3594
|
data: {
|
|
2137
3595
|
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
2138
|
-
|
|
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
|
-
},
|
|
3596
|
+
participantId: 'another-agent',
|
|
2161
3597
|
},
|
|
2162
|
-
}
|
|
3598
|
+
})
|
|
3599
|
+
);
|
|
2163
3600
|
|
|
2164
|
-
|
|
3601
|
+
expect(task.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
3602
|
+
});
|
|
2165
3603
|
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
3604
|
+
it('removes every collection alias for a task during terminal cleanup', () => {
|
|
3605
|
+
taskManager.taskCollection['task-alias'] = task;
|
|
3606
|
+
|
|
3607
|
+
(taskManager as any).removeTaskFromCollection(task);
|
|
2170
3608
|
|
|
2171
|
-
|
|
2172
|
-
|
|
3609
|
+
expect(taskManager.taskCollection[taskId]).toBeUndefined();
|
|
3610
|
+
expect(taskManager.taskCollection['task-alias']).toBeUndefined();
|
|
3611
|
+
});
|
|
3612
|
+
|
|
3613
|
+
it('replaces an EP-DN child task with a hydrated main task on CONTACT_MERGED', () => {
|
|
3614
|
+
const childTaskId = 'ep-dn-child-interaction-id';
|
|
3615
|
+
const childTask = createMockTask({
|
|
3616
|
+
...taskDataMock,
|
|
3617
|
+
interactionId: childTaskId,
|
|
3618
|
+
interaction: {
|
|
3619
|
+
mediaType: 'telephony',
|
|
3620
|
+
interactionId: childTaskId,
|
|
3621
|
+
parentInteractionId: taskId,
|
|
3622
|
+
},
|
|
3623
|
+
});
|
|
3624
|
+
const mergedHandler = jest.fn();
|
|
3625
|
+
delete taskManager.taskCollection[taskId];
|
|
3626
|
+
taskManager.taskCollection[childTaskId] = childTask;
|
|
3627
|
+
taskManager.on(TASK_EVENTS.TASK_MERGED, mergedHandler);
|
|
3628
|
+
|
|
3629
|
+
webSocketManagerMock.emit(
|
|
3630
|
+
'message',
|
|
3631
|
+
JSON.stringify({
|
|
2173
3632
|
data: {
|
|
2174
|
-
type: CC_EVENTS.
|
|
3633
|
+
type: CC_EVENTS.CONTACT_MERGED,
|
|
2175
3634
|
interactionId: taskId,
|
|
3635
|
+
childInteractionId: childTaskId,
|
|
2176
3636
|
interaction: {
|
|
3637
|
+
mediaType: 'telephony',
|
|
3638
|
+
interactionId: taskId,
|
|
3639
|
+
mainInteractionId: taskId,
|
|
3640
|
+
state: 'conference',
|
|
3641
|
+
owner: 'test-agent-id',
|
|
2177
3642
|
participants: {
|
|
2178
|
-
|
|
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
|
-
},
|
|
3643
|
+
'test-agent-id': {id: 'test-agent-id', pType: 'Agent', hasLeft: false},
|
|
2190
3644
|
},
|
|
2191
3645
|
media: {
|
|
2192
3646
|
[taskId]: {
|
|
3647
|
+
mediaResourceId: taskId,
|
|
3648
|
+
mediaType: 'telephony',
|
|
2193
3649
|
mType: 'mainCall',
|
|
2194
|
-
participants: [
|
|
3650
|
+
participants: ['test-agent-id'],
|
|
2195
3651
|
},
|
|
2196
3652
|
},
|
|
2197
3653
|
},
|
|
2198
3654
|
},
|
|
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
|
-
};
|
|
3655
|
+
})
|
|
3656
|
+
);
|
|
2216
3657
|
|
|
2217
|
-
|
|
3658
|
+
const mainTask = taskManager.taskCollection[taskId];
|
|
3659
|
+
expect(taskManager.taskCollection[childTaskId]).toBeUndefined();
|
|
3660
|
+
expect(mainTask).toBeDefined();
|
|
3661
|
+
expect(mainTask).not.toBe(childTask);
|
|
3662
|
+
expect(mainTask.sendStateMachineEvent).toHaveBeenCalledWith(
|
|
3663
|
+
expect.objectContaining({type: TaskEvent.HYDRATE})
|
|
3664
|
+
);
|
|
3665
|
+
expect(mergedHandler).toHaveBeenCalledWith(mainTask);
|
|
3666
|
+
});
|
|
2218
3667
|
|
|
3668
|
+
it('handles AGENT_CONSULT_CONFERENCING event without errors', () => {
|
|
3669
|
+
const payload = {
|
|
3670
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCING, interactionId: taskId},
|
|
3671
|
+
};
|
|
3672
|
+
expect(() => {
|
|
2219
3673
|
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
|
-
});
|
|
3674
|
+
}).not.toThrow();
|
|
2226
3675
|
});
|
|
2227
3676
|
|
|
2228
|
-
it('
|
|
3677
|
+
it('handles PARTICIPANT_LEFT_CONFERENCE_FAILED event without errors', () => {
|
|
2229
3678
|
const payload = {
|
|
2230
|
-
data: {
|
|
2231
|
-
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE_FAILED,
|
|
2232
|
-
interactionId: taskId,
|
|
2233
|
-
reason: 'Exit failed',
|
|
2234
|
-
},
|
|
3679
|
+
data: {type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE_FAILED, interactionId: taskId},
|
|
2235
3680
|
};
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
expect(task.data.reason).toBe('Exit failed');
|
|
2240
|
-
// No event emission expected for failure - handled by contact method promise rejection
|
|
3681
|
+
expect(() => {
|
|
3682
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
3683
|
+
}).not.toThrow();
|
|
2241
3684
|
});
|
|
2242
3685
|
|
|
2243
|
-
it('
|
|
3686
|
+
it('only routes conference events to matching tasks', () => {
|
|
2244
3687
|
const otherTaskId = 'other-task-id';
|
|
2245
|
-
const otherTask = {
|
|
2246
|
-
data: {
|
|
3688
|
+
const otherTask: any = {
|
|
3689
|
+
data: {interactionId: otherTaskId},
|
|
2247
3690
|
emit: jest.fn(),
|
|
3691
|
+
updateTaskData: jest.fn(),
|
|
3692
|
+
sendStateMachineEvent: jest.fn(),
|
|
2248
3693
|
};
|
|
2249
3694
|
taskManager.taskCollection[otherTaskId] = otherTask;
|
|
2250
3695
|
|
|
2251
3696
|
const payload = {
|
|
2252
|
-
data: {
|
|
2253
|
-
type: CC_EVENTS.AGENT_CONSULT_CONFERENCED,
|
|
2254
|
-
interactionId: taskId,
|
|
2255
|
-
isConferencing: true,
|
|
2256
|
-
},
|
|
3697
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCED, interactionId: taskId},
|
|
2257
3698
|
};
|
|
2258
|
-
|
|
2259
3699
|
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
2260
3700
|
|
|
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();
|
|
3701
|
+
expect(task.sendStateMachineEvent).toHaveBeenCalled();
|
|
3702
|
+
expect(otherTask.sendStateMachineEvent).not.toHaveBeenCalled();
|
|
2268
3703
|
});
|
|
2269
3704
|
});
|
|
2270
3705
|
|
|
2271
|
-
describe('
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
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));
|
|
3706
|
+
describe('state machine integration', () => {
|
|
3707
|
+
it('maps CC events to task state machine events using normalized payload', () => {
|
|
3708
|
+
const mapped = (TaskManager as any).mapEventToTaskStateMachineEvent(
|
|
3709
|
+
CC_EVENTS.AGENT_CONTACT_ASSIGNED,
|
|
3710
|
+
{...taskDataMock, type: CC_EVENTS.AGENT_CONTACT_ASSIGNED}
|
|
3711
|
+
);
|
|
2299
3712
|
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
expect(managerEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_MERGED, updatedTask);
|
|
3713
|
+
expect(mapped).toEqual({
|
|
3714
|
+
type: TaskEvent.ASSIGN,
|
|
3715
|
+
taskData: {...taskDataMock, type: CC_EVENTS.AGENT_CONTACT_ASSIGNED},
|
|
3716
|
+
});
|
|
2305
3717
|
});
|
|
2306
3718
|
|
|
2307
|
-
it('
|
|
2308
|
-
const
|
|
2309
|
-
const
|
|
2310
|
-
|
|
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
|
-
};
|
|
3719
|
+
it('sends mapped events to the task state machine service', () => {
|
|
3720
|
+
const payload = {...taskDataMock, type: CC_EVENTS.AGENT_CONTACT_ASSIGNED};
|
|
3721
|
+
const task = taskManager.getTask(taskId);
|
|
3722
|
+
const sendStateMachineEventSpy = jest.spyOn(task, 'sendStateMachineEvent');
|
|
2326
3723
|
|
|
2327
|
-
|
|
2328
|
-
expect(taskManager.getTask(newMergedTaskId)).toBeUndefined();
|
|
3724
|
+
webSocketManagerMock.emit('message', JSON.stringify({data: payload}));
|
|
2329
3725
|
|
|
2330
|
-
|
|
3726
|
+
expect(sendStateMachineEventSpy).toHaveBeenCalledWith({
|
|
3727
|
+
type: TaskEvent.ASSIGN,
|
|
3728
|
+
taskData: payload,
|
|
3729
|
+
});
|
|
2331
3730
|
|
|
2332
|
-
|
|
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);
|
|
3731
|
+
sendStateMachineEventSpy.mockRestore();
|
|
2337
3732
|
});
|
|
3733
|
+
});
|
|
2338
3734
|
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
const
|
|
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
|
-
})
|
|
2398
|
-
);
|
|
2399
|
-
});
|
|
3735
|
+
describe('applyEnableWxBetterTogether', () => {
|
|
3736
|
+
it('updates config flags and propagates to active tasks', () => {
|
|
3737
|
+
const taskOne = {setEnableWxBetterTogether: jest.fn()};
|
|
3738
|
+
const taskTwo = {setEnableWxBetterTogether: jest.fn()};
|
|
2400
3739
|
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
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
|
-
},
|
|
3740
|
+
taskManager.setConfigFlags({
|
|
3741
|
+
isEndTaskEnabled: true,
|
|
3742
|
+
isEndConsultEnabled: true,
|
|
3743
|
+
enableWxBetterTogether: true,
|
|
3744
|
+
});
|
|
3745
|
+
taskManager['taskCollection'] = {
|
|
3746
|
+
[taskId]: taskOne,
|
|
3747
|
+
'task-2': taskTwo,
|
|
2425
3748
|
};
|
|
2426
3749
|
|
|
2427
|
-
|
|
3750
|
+
taskManager.applyEnableWxBetterTogether(false);
|
|
2428
3751
|
|
|
2429
|
-
|
|
2430
|
-
expect(
|
|
2431
|
-
expect(
|
|
2432
|
-
expect(mergedTask.data.interaction.participants['epdn-participant'].type).toBe('EpDn');
|
|
2433
|
-
expect(managerEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_MERGED, mergedTask);
|
|
3752
|
+
expect(taskManager['configFlags']?.enableWxBetterTogether).toBe(false);
|
|
3753
|
+
expect(taskOne.setEnableWxBetterTogether).toHaveBeenCalledWith(false);
|
|
3754
|
+
expect(taskTwo.setEnableWxBetterTogether).toHaveBeenCalledWith(false);
|
|
2434
3755
|
});
|
|
3756
|
+
});
|
|
2435
3757
|
|
|
2436
|
-
|
|
2437
|
-
|
|
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');
|
|
3758
|
+
describe('applyWxAppMuteStateFromSync', () => {
|
|
3759
|
+
it('propagates mute state to all tasks', () => {
|
|
3760
|
+
const taskOne = {applyWxAppMuteStateFromSync: jest.fn()};
|
|
3761
|
+
const taskTwo = {applyWxAppMuteStateFromSync: jest.fn()};
|
|
2450
3762
|
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
type: CC_EVENTS.CONTACT_MERGED,
|
|
2455
|
-
interactionId: taskId,
|
|
2456
|
-
agentId: taskDataMock.agentId,
|
|
2457
|
-
interaction: {
|
|
2458
|
-
mediaType: 'telephony',
|
|
2459
|
-
state: 'merged',
|
|
2460
|
-
},
|
|
2461
|
-
},
|
|
3763
|
+
taskManager['taskCollection'] = {
|
|
3764
|
+
[taskId]: taskOne,
|
|
3765
|
+
'task-2': taskTwo,
|
|
2462
3766
|
};
|
|
2463
3767
|
|
|
2464
|
-
|
|
3768
|
+
taskManager.applyWxAppMuteStateFromSync('call-1', true);
|
|
2465
3769
|
|
|
2466
|
-
|
|
2467
|
-
expect(
|
|
2468
|
-
expect(otherTask.data.interaction.mediaType).toBe('chat');
|
|
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
|
-
);
|
|
3770
|
+
expect(taskOne.applyWxAppMuteStateFromSync).toHaveBeenCalledWith('call-1', true);
|
|
3771
|
+
expect(taskTwo.applyWxAppMuteStateFromSync).toHaveBeenCalledWith('call-1', true);
|
|
2479
3772
|
});
|
|
2480
3773
|
});
|
|
2481
|
-
|
|
2482
3774
|
});
|
|
2483
|
-
|