@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
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import 'jsdom-global/register';
|
|
2
|
+
import {LocalMicrophoneStream, CALL_EVENT_KEYS} from '@webex/calling';
|
|
3
|
+
import WebRTC from '../../../../../../src/services/task/voice/WebRTC';
|
|
4
|
+
import WebCallingService from '../../../../../../src/services/WebCallingService';
|
|
5
|
+
import {TaskData, TASK_EVENTS} from '../../../../../../src/services/task/types';
|
|
6
|
+
import type {WebexSDK} from '../../../../../../src/types';
|
|
7
|
+
import {TaskEvent, TaskEventPayload} from '../../../../../../src/services/task/state-machine';
|
|
8
|
+
import {createTaskData} from '../taskTestUtils';
|
|
9
|
+
|
|
10
|
+
jest.mock('@webex/calling', () => ({
|
|
11
|
+
LocalMicrophoneStream: class {
|
|
12
|
+
constructor(stream) {
|
|
13
|
+
this.outputStream = stream;
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
CALL_EVENT_KEYS: {REMOTE_MEDIA: 'remoteMedia'},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
navigator.mediaDevices = {getUserMedia: jest.fn()};
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
global.MediaStream = class {
|
|
23
|
+
constructor(private tracks: MediaStreamTrack[]) {}
|
|
24
|
+
getAudioTracks() {
|
|
25
|
+
return this.tracks;
|
|
26
|
+
}
|
|
27
|
+
} as typeof MediaStream;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
jest.mock('../../../../../../src/services/core/WebexRequest', () => ({
|
|
31
|
+
__esModule: true,
|
|
32
|
+
default: {
|
|
33
|
+
getInstance: jest.fn().mockReturnValue({uploadLogs: jest.fn()}),
|
|
34
|
+
},
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
const sendStateEvents = (task: WebRTC, events: TaskEventPayload[]) => {
|
|
38
|
+
events.forEach((event) => {
|
|
39
|
+
if (!event) {
|
|
40
|
+
throw new Error('Task event payload is required');
|
|
41
|
+
}
|
|
42
|
+
task.stateMachineService?.send(event);
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
describe('WebRTC Task', () => {
|
|
47
|
+
const dummyContact = {} as any;
|
|
48
|
+
let taskData: TaskData;
|
|
49
|
+
let webCallingService: WebCallingService;
|
|
50
|
+
let onSpy: jest.SpyInstance;
|
|
51
|
+
let offSpy: jest.SpyInstance;
|
|
52
|
+
let answerSpy: jest.SpyInstance;
|
|
53
|
+
let declineSpy: jest.SpyInstance;
|
|
54
|
+
let muteSpy: jest.SpyInstance;
|
|
55
|
+
|
|
56
|
+
let webRtc: WebRTC;
|
|
57
|
+
|
|
58
|
+
beforeAll(() => {
|
|
59
|
+
const webex = {
|
|
60
|
+
logger: {
|
|
61
|
+
log: jest.fn(),
|
|
62
|
+
error: jest.fn(),
|
|
63
|
+
info: jest.fn(),
|
|
64
|
+
},
|
|
65
|
+
} as unknown as WebexSDK;
|
|
66
|
+
|
|
67
|
+
webCallingService = new WebCallingService(webex);
|
|
68
|
+
onSpy = jest.spyOn(webCallingService, 'on');
|
|
69
|
+
offSpy = jest.spyOn(webCallingService, 'off');
|
|
70
|
+
answerSpy = jest.spyOn(webCallingService, 'answerCall');
|
|
71
|
+
declineSpy = jest.spyOn(webCallingService, 'declineCall');
|
|
72
|
+
muteSpy = jest.spyOn(webCallingService, 'muteUnmuteCall');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
taskData = createTaskData();
|
|
77
|
+
webRtc = new WebRTC(dummyContact, webCallingService, taskData, {
|
|
78
|
+
isEndTaskEnabled: true,
|
|
79
|
+
isEndConsultEnabled: true,
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('accept() obtains media and answers call', async () => {
|
|
84
|
+
const fakeTrack = {} as any;
|
|
85
|
+
const fakeStream = {getAudioTracks: () => [fakeTrack]} as any;
|
|
86
|
+
jest.spyOn(navigator.mediaDevices, 'getUserMedia').mockResolvedValue(fakeStream);
|
|
87
|
+
await webRtc.accept();
|
|
88
|
+
expect(answerSpy).toHaveBeenCalled();
|
|
89
|
+
const [[streamArg, interactionIdArg]] = webCallingService.answerCall.mock.calls;
|
|
90
|
+
expect(streamArg).toBeInstanceOf(LocalMicrophoneStream);
|
|
91
|
+
expect(interactionIdArg).toBe(taskData.interactionId);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('decline() calls declineCall and unregisters listeners', async () => {
|
|
95
|
+
jest.spyOn(webRtc, 'unregisterWebCallListeners');
|
|
96
|
+
const res = await webRtc.decline();
|
|
97
|
+
expect(declineSpy).toHaveBeenCalledWith(taskData.interactionId);
|
|
98
|
+
expect(webRtc.unregisterWebCallListeners).toHaveBeenCalled();
|
|
99
|
+
expect(res).toBeUndefined();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('toggleMute() calls muteUnmuteCall with stored localAudioStream', async () => {
|
|
103
|
+
const dummyStream = {} as any;
|
|
104
|
+
webRtc.localAudioStream = dummyStream;
|
|
105
|
+
await webRtc.toggleMute();
|
|
106
|
+
expect(muteSpy).toHaveBeenCalledWith(dummyStream);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('WebRTC internal methods', () => {
|
|
110
|
+
it('registerWebCallListeners binds remote media event', () => {
|
|
111
|
+
webRtc.registerWebCallListeners();
|
|
112
|
+
expect(onSpy).toHaveBeenCalledWith(CALL_EVENT_KEYS.REMOTE_MEDIA, webRtc.handleRemoteMedia);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('handleRemoteMedia emits TASK_MEDIA event', () => {
|
|
116
|
+
const fakeTrack = {} as any;
|
|
117
|
+
jest.spyOn(webRtc, 'emit');
|
|
118
|
+
webRtc.handleRemoteMedia(fakeTrack);
|
|
119
|
+
expect(webRtc.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_MEDIA, fakeTrack);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('UI controls', () => {
|
|
124
|
+
beforeEach(() => {
|
|
125
|
+
taskData = createTaskData();
|
|
126
|
+
webRtc = new WebRTC(dummyContact, webCallingService, taskData, {
|
|
127
|
+
isEndTaskEnabled: true,
|
|
128
|
+
isEndConsultEnabled: true,
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('initialiseUIControls sets accept and decline visible', () => {
|
|
133
|
+
sendStateEvents(webRtc, [{type: TaskEvent.TASK_INCOMING, taskData}]);
|
|
134
|
+
expect(webRtc.uiControls.main.accept.isVisible).toBe(true);
|
|
135
|
+
expect(webRtc.uiControls.main.decline.isVisible).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('setUIControls for AGENT_CONTACT_ASSIGNED shows mute enabled', () => {
|
|
139
|
+
sendStateEvents(webRtc, [
|
|
140
|
+
{type: TaskEvent.TASK_INCOMING, taskData},
|
|
141
|
+
{type: TaskEvent.ASSIGN, taskData},
|
|
142
|
+
]);
|
|
143
|
+
expect(webRtc.uiControls.main.mute.isVisible).toBe(true);
|
|
144
|
+
expect(webRtc.uiControls.main.mute.isEnabled).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('default setUIControls hides mute when wrapup visible', () => {
|
|
148
|
+
sendStateEvents(webRtc, [
|
|
149
|
+
{type: TaskEvent.TASK_INCOMING, taskData},
|
|
150
|
+
{type: TaskEvent.ASSIGN, taskData},
|
|
151
|
+
{type: TaskEvent.TASK_WRAPUP},
|
|
152
|
+
]);
|
|
153
|
+
expect(webRtc.uiControls.main.wrapup.isVisible).toBe(true);
|
|
154
|
+
expect(webRtc.uiControls.main.mute.isVisible).toBe(false);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('setUIControls for AGENT_CONTACT_HELD disables mute', () => {
|
|
158
|
+
sendStateEvents(webRtc, [
|
|
159
|
+
{type: TaskEvent.TASK_INCOMING, taskData},
|
|
160
|
+
{type: TaskEvent.ASSIGN, taskData},
|
|
161
|
+
{type: TaskEvent.HOLD_INITIATED, mediaResourceId: taskData.mediaResourceId},
|
|
162
|
+
{type: TaskEvent.HOLD_SUCCESS, mediaResourceId: taskData.mediaResourceId},
|
|
163
|
+
]);
|
|
164
|
+
expect(webRtc.uiControls.main.mute.isVisible).toBe(true);
|
|
165
|
+
expect(webRtc.uiControls.main.mute.isEnabled).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('setUIControls for AGENT_CONTACT_UNHELD re-enables mute', () => {
|
|
169
|
+
sendStateEvents(webRtc, [
|
|
170
|
+
{type: TaskEvent.TASK_INCOMING, taskData},
|
|
171
|
+
{type: TaskEvent.ASSIGN, taskData},
|
|
172
|
+
{type: TaskEvent.HOLD_INITIATED, mediaResourceId: taskData.mediaResourceId},
|
|
173
|
+
{type: TaskEvent.HOLD_SUCCESS, mediaResourceId: taskData.mediaResourceId},
|
|
174
|
+
{type: TaskEvent.UNHOLD_INITIATED, mediaResourceId: taskData.mediaResourceId},
|
|
175
|
+
{type: TaskEvent.UNHOLD_SUCCESS, mediaResourceId: taskData.mediaResourceId},
|
|
176
|
+
]);
|
|
177
|
+
expect(webRtc.uiControls.main.mute.isVisible).toBe(true);
|
|
178
|
+
expect(webRtc.uiControls.main.mute.isEnabled).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('setUIControls for AGENT_OFFER_CONTACT shows accept and decline', () => {
|
|
182
|
+
sendStateEvents(webRtc, [{type: TaskEvent.TASK_INCOMING, taskData}]);
|
|
183
|
+
expect(webRtc.uiControls.main.accept.isVisible).toBe(true);
|
|
184
|
+
expect(webRtc.uiControls.main.decline.isVisible).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('setUIControls for AGENT_OFFER_CONSULT shows accept and decline', () => {
|
|
188
|
+
const consultedTaskData = {...taskData, isConsulted: true};
|
|
189
|
+
sendStateEvents(webRtc, [
|
|
190
|
+
{type: TaskEvent.TASK_INCOMING, taskData},
|
|
191
|
+
{type: TaskEvent.OFFER_CONSULT, taskData: consultedTaskData},
|
|
192
|
+
]);
|
|
193
|
+
expect(webRtc.uiControls.main.accept.isVisible).toBe(true);
|
|
194
|
+
expect(webRtc.uiControls.main.decline.isVisible).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('setUIControls for AGENT_CONSULTING hides accept/decline and shows mute when consulted', () => {
|
|
198
|
+
const consultedTaskData = {...taskData, isConsulted: true};
|
|
199
|
+
sendStateEvents(webRtc, [
|
|
200
|
+
{type: TaskEvent.TASK_INCOMING, taskData},
|
|
201
|
+
{type: TaskEvent.OFFER_CONSULT, taskData: consultedTaskData},
|
|
202
|
+
{
|
|
203
|
+
type: TaskEvent.CONSULTING_ACTIVE,
|
|
204
|
+
consultDestinationAgentJoined: true,
|
|
205
|
+
taskData: consultedTaskData,
|
|
206
|
+
},
|
|
207
|
+
]);
|
|
208
|
+
expect(webRtc.uiControls.main.accept.isVisible).toBe(false);
|
|
209
|
+
expect(webRtc.uiControls.main.decline.isVisible).toBe(false);
|
|
210
|
+
expect(webRtc.uiControls.main.mute.isVisible).toBe(true);
|
|
211
|
+
expect(webRtc.uiControls.main.mute.isEnabled).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('setUIControls for AGENT_CONSULT_ENDED returns mute to connected state behavior', () => {
|
|
215
|
+
const consultedTaskData = {...taskData, isConsulted: true};
|
|
216
|
+
sendStateEvents(webRtc, [
|
|
217
|
+
{type: TaskEvent.TASK_INCOMING, taskData},
|
|
218
|
+
{type: TaskEvent.OFFER_CONSULT, taskData: consultedTaskData},
|
|
219
|
+
{
|
|
220
|
+
type: TaskEvent.CONSULTING_ACTIVE,
|
|
221
|
+
consultDestinationAgentJoined: true,
|
|
222
|
+
taskData: consultedTaskData,
|
|
223
|
+
},
|
|
224
|
+
{type: TaskEvent.CONSULT_END},
|
|
225
|
+
]);
|
|
226
|
+
expect(webRtc.uiControls.main.mute.isVisible).toBe(false);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('setUIControls for AGENT_CONTACT_OFFER_RONA hides accept and decline', () => {
|
|
230
|
+
sendStateEvents(webRtc, [{type: TaskEvent.TASK_INCOMING, taskData}, {type: TaskEvent.RONA}]);
|
|
231
|
+
expect(webRtc.uiControls.main.accept.isVisible).toBe(false);
|
|
232
|
+
expect(webRtc.uiControls.main.decline.isVisible).toBe(false);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
});
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import {expect} from '@jest/globals';
|
|
2
|
+
import {
|
|
3
|
+
isWebexAppCallingOffer,
|
|
4
|
+
getCallingDeviceDetails,
|
|
5
|
+
getWebexCallingCallId,
|
|
6
|
+
acceptOnWebex,
|
|
7
|
+
rejectOnWebex,
|
|
8
|
+
toggleMuteOnWebex,
|
|
9
|
+
transmitDtmfOnWebex,
|
|
10
|
+
runWxAppAccept,
|
|
11
|
+
runWxAppReject,
|
|
12
|
+
mapWxAppVoiceError,
|
|
13
|
+
WxAppVoiceDeps,
|
|
14
|
+
WxAppVoiceLifecycle,
|
|
15
|
+
} from '../../../../../../src/services/task/voice/wxAppVoiceMethods';
|
|
16
|
+
import {METHODS} from '../../../../../../src/constants';
|
|
17
|
+
import {TaskState} from '../../../../../../src/services/task/state-machine';
|
|
18
|
+
import {TaskData} from '../../../../../../src/services/task/types';
|
|
19
|
+
import AnswerCallOnWebexService from '../../../../../../src/services/AnswerCallOnWebexService';
|
|
20
|
+
|
|
21
|
+
const WX_APP_DEVICE = {
|
|
22
|
+
deviceType: 'wxApp',
|
|
23
|
+
deviceId: 'device-id-1',
|
|
24
|
+
deviceCallId: 'call-id-1',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function makeParticipants() {
|
|
28
|
+
return {
|
|
29
|
+
'agent-1': {id: 'agent-1', ...WX_APP_DEVICE},
|
|
30
|
+
'customer-1': {id: 'customer-1', pType: 'Customer'},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function makeTaskData(overrides: Partial<TaskData> = {}): TaskData {
|
|
35
|
+
return {
|
|
36
|
+
interactionId: 'interaction-1',
|
|
37
|
+
agentId: 'agent-1',
|
|
38
|
+
mediaResourceId: 'media-1',
|
|
39
|
+
interaction: {
|
|
40
|
+
participants: makeParticipants(),
|
|
41
|
+
},
|
|
42
|
+
...overrides,
|
|
43
|
+
} as unknown as TaskData;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function makeMockService(): jest.Mocked<AnswerCallOnWebexService> {
|
|
47
|
+
return {
|
|
48
|
+
answerCall: jest.fn().mockResolvedValue({}),
|
|
49
|
+
rejectCall: jest.fn().mockResolvedValue({}),
|
|
50
|
+
muteCall: jest.fn().mockResolvedValue({}),
|
|
51
|
+
unmuteCall: jest.fn().mockResolvedValue({}),
|
|
52
|
+
transmitDtmf: jest.fn().mockResolvedValue({}),
|
|
53
|
+
} as unknown as jest.Mocked<AnswerCallOnWebexService>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function makeDeps(overrides: Partial<WxAppVoiceDeps> = {}): WxAppVoiceDeps {
|
|
57
|
+
let muted = false;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
enableWxBetterTogether: true,
|
|
61
|
+
answerCallOnWebexService: makeMockService(),
|
|
62
|
+
agentId: 'agent-1',
|
|
63
|
+
getTaskData: () => makeTaskData(),
|
|
64
|
+
getTaskState: () => TaskState.OFFERED,
|
|
65
|
+
getWxAppMuted: () => muted,
|
|
66
|
+
setWxAppMuted: (val) => {
|
|
67
|
+
muted = val;
|
|
68
|
+
},
|
|
69
|
+
...overrides,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function makeLifecycle(overrides: Partial<WxAppVoiceLifecycle> = {}): WxAppVoiceLifecycle {
|
|
74
|
+
return {
|
|
75
|
+
setWxAppAcceptInFlight: jest.fn(),
|
|
76
|
+
setWxAppAnswerPending: jest.fn(),
|
|
77
|
+
resetWxAppMuted: jest.fn(),
|
|
78
|
+
syncWxAppMuteFromCallDetails: jest.fn().mockResolvedValue(undefined),
|
|
79
|
+
mapWxAppVoiceError: jest.fn((error: unknown) => {
|
|
80
|
+
throw error;
|
|
81
|
+
}),
|
|
82
|
+
...overrides,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
describe('isWebexAppCallingOffer', () => {
|
|
87
|
+
it('returns true when enableWxBetterTogether is true, state is OFFERED, and wxApp device details exist', () => {
|
|
88
|
+
expect(isWebexAppCallingOffer(makeDeps())).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('returns false when enableWxBetterTogether is false', () => {
|
|
92
|
+
expect(isWebexAppCallingOffer(makeDeps({enableWxBetterTogether: false}))).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('returns false when task state is not OFFERED', () => {
|
|
96
|
+
expect(isWebexAppCallingOffer(makeDeps({getTaskState: () => TaskState.CONNECTED}))).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('returns false when no wxApp participant is found', () => {
|
|
100
|
+
const deps = makeDeps({
|
|
101
|
+
getTaskData: () =>
|
|
102
|
+
makeTaskData({
|
|
103
|
+
interaction: {
|
|
104
|
+
participants: {'customer-1': {id: 'customer-1'}},
|
|
105
|
+
} as any,
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(isWebexAppCallingOffer(deps)).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('getCallingDeviceDetails', () => {
|
|
114
|
+
it('returns device details for wxApp participant', () => {
|
|
115
|
+
const result = getCallingDeviceDetails(makeDeps());
|
|
116
|
+
|
|
117
|
+
expect(result).toEqual({
|
|
118
|
+
deviceType: 'wxApp',
|
|
119
|
+
deviceId: 'device-id-1',
|
|
120
|
+
deviceCallId: 'call-id-1',
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('returns undefined when no wxApp participant is present', () => {
|
|
125
|
+
const deps = makeDeps({
|
|
126
|
+
getTaskData: () =>
|
|
127
|
+
makeTaskData({
|
|
128
|
+
interaction: {
|
|
129
|
+
participants: {'customer-1': {id: 'customer-1'}},
|
|
130
|
+
} as any,
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(getCallingDeviceDetails(deps)).toBeUndefined();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('getWebexCallingCallId', () => {
|
|
139
|
+
it('returns null when state is OFFERED', () => {
|
|
140
|
+
expect(getWebexCallingCallId(makeDeps({getTaskState: () => TaskState.OFFERED}))).toBeNull();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('returns null when state is IDLE', () => {
|
|
144
|
+
expect(getWebexCallingCallId(makeDeps({getTaskState: () => TaskState.IDLE}))).toBeNull();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('returns null when state is TERMINATED', () => {
|
|
148
|
+
expect(getWebexCallingCallId(makeDeps({getTaskState: () => TaskState.TERMINATED}))).toBeNull();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('returns null when state is COMPLETED', () => {
|
|
152
|
+
expect(getWebexCallingCallId(makeDeps({getTaskState: () => TaskState.COMPLETED}))).toBeNull();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('returns null when state is WRAPPING_UP', () => {
|
|
156
|
+
expect(getWebexCallingCallId(makeDeps({getTaskState: () => TaskState.WRAPPING_UP}))).toBeNull();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('returns the deviceCallId when state is CONNECTED', () => {
|
|
160
|
+
expect(getWebexCallingCallId(makeDeps({getTaskState: () => TaskState.CONNECTED}))).toBe(
|
|
161
|
+
'call-id-1'
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('returns null when state is CONNECTED but no wxApp participant', () => {
|
|
166
|
+
const deps = makeDeps({
|
|
167
|
+
getTaskState: () => TaskState.CONNECTED,
|
|
168
|
+
getTaskData: () =>
|
|
169
|
+
makeTaskData({
|
|
170
|
+
interaction: {
|
|
171
|
+
participants: {'customer-1': {id: 'customer-1'}},
|
|
172
|
+
} as any,
|
|
173
|
+
}),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect(getWebexCallingCallId(deps)).toBeNull();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('acceptOnWebex', () => {
|
|
181
|
+
it('calls answerCall with deviceCallId and deviceId', async () => {
|
|
182
|
+
const mockSvc = makeMockService();
|
|
183
|
+
const deps = makeDeps({answerCallOnWebexService: mockSvc});
|
|
184
|
+
|
|
185
|
+
await acceptOnWebex(deps);
|
|
186
|
+
|
|
187
|
+
expect(mockSvc.answerCall).toHaveBeenCalledWith({
|
|
188
|
+
callId: 'call-id-1',
|
|
189
|
+
endpointId: 'device-id-1',
|
|
190
|
+
lineOwnerId: undefined,
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('passes lineOwnerId when provided', async () => {
|
|
195
|
+
const mockSvc = makeMockService();
|
|
196
|
+
const deps = makeDeps({answerCallOnWebexService: mockSvc});
|
|
197
|
+
|
|
198
|
+
await acceptOnWebex(deps, {lineOwnerId: 'lo-1'});
|
|
199
|
+
|
|
200
|
+
expect(mockSvc.answerCall).toHaveBeenCalledWith(
|
|
201
|
+
expect.objectContaining({lineOwnerId: 'lo-1'})
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('defaults lineOwnerId from encoded participant owner when not provided', async () => {
|
|
206
|
+
const encodedOwner = Buffer.from('people/lo-decoded').toString('base64');
|
|
207
|
+
const mockSvc = makeMockService();
|
|
208
|
+
const deps = makeDeps({
|
|
209
|
+
answerCallOnWebexService: mockSvc,
|
|
210
|
+
getTaskData: () =>
|
|
211
|
+
makeTaskData({
|
|
212
|
+
interaction: {
|
|
213
|
+
participants: {
|
|
214
|
+
'agent-1': {id: 'agent-1', ...WX_APP_DEVICE, lineOwnerId: encodedOwner},
|
|
215
|
+
'customer-1': {id: 'customer-1', pType: 'Customer'},
|
|
216
|
+
},
|
|
217
|
+
} as any,
|
|
218
|
+
}),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
await acceptOnWebex(deps);
|
|
222
|
+
|
|
223
|
+
expect(mockSvc.answerCall).toHaveBeenCalledWith(
|
|
224
|
+
expect.objectContaining({lineOwnerId: 'lo-decoded'})
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('throws when enableWxBetterTogether is false', async () => {
|
|
229
|
+
const deps = makeDeps({enableWxBetterTogether: false});
|
|
230
|
+
|
|
231
|
+
await expect(acceptOnWebex(deps)).rejects.toThrow();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('throws when task is not in OFFERED state', async () => {
|
|
235
|
+
const deps = makeDeps({getTaskState: () => TaskState.CONNECTED});
|
|
236
|
+
|
|
237
|
+
await expect(acceptOnWebex(deps)).rejects.toThrow();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('throws when device details are unavailable', async () => {
|
|
241
|
+
const deps = makeDeps({
|
|
242
|
+
getTaskData: () =>
|
|
243
|
+
makeTaskData({
|
|
244
|
+
interaction: {
|
|
245
|
+
participants: {'customer-1': {id: 'customer-1'}},
|
|
246
|
+
} as any,
|
|
247
|
+
}),
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
await expect(acceptOnWebex(deps)).rejects.toThrow();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('rejectOnWebex', () => {
|
|
255
|
+
it('calls rejectCall with deviceCallId', async () => {
|
|
256
|
+
const mockSvc = makeMockService();
|
|
257
|
+
const deps = makeDeps({answerCallOnWebexService: mockSvc});
|
|
258
|
+
|
|
259
|
+
await rejectOnWebex(deps);
|
|
260
|
+
|
|
261
|
+
expect(mockSvc.rejectCall).toHaveBeenCalledWith({
|
|
262
|
+
callId: 'call-id-1',
|
|
263
|
+
lineOwnerId: undefined,
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('throws when task is not in OFFERED state', async () => {
|
|
268
|
+
const deps = makeDeps({getTaskState: () => TaskState.CONNECTED});
|
|
269
|
+
|
|
270
|
+
await expect(rejectOnWebex(deps)).rejects.toThrow();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('throws when enableWxBetterTogether is false', async () => {
|
|
274
|
+
const deps = makeDeps({enableWxBetterTogether: false});
|
|
275
|
+
|
|
276
|
+
await expect(rejectOnWebex(deps)).rejects.toThrow();
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('toggleMuteOnWebex', () => {
|
|
281
|
+
it('calls muteCall when not currently muted', async () => {
|
|
282
|
+
const mockSvc = makeMockService();
|
|
283
|
+
let muted = false;
|
|
284
|
+
const deps = makeDeps({
|
|
285
|
+
answerCallOnWebexService: mockSvc,
|
|
286
|
+
getTaskState: () => TaskState.CONNECTED,
|
|
287
|
+
getWxAppMuted: () => muted,
|
|
288
|
+
setWxAppMuted: (val) => {
|
|
289
|
+
muted = val;
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
await toggleMuteOnWebex(deps);
|
|
294
|
+
|
|
295
|
+
expect(mockSvc.muteCall).toHaveBeenCalledWith({callId: 'call-id-1', lineOwnerId: undefined});
|
|
296
|
+
expect(muted).toBe(true);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('calls unmuteCall when currently muted', async () => {
|
|
300
|
+
const mockSvc = makeMockService();
|
|
301
|
+
let muted = true;
|
|
302
|
+
const deps = makeDeps({
|
|
303
|
+
answerCallOnWebexService: mockSvc,
|
|
304
|
+
getTaskState: () => TaskState.CONNECTED,
|
|
305
|
+
getWxAppMuted: () => muted,
|
|
306
|
+
setWxAppMuted: (val) => {
|
|
307
|
+
muted = val;
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
await toggleMuteOnWebex(deps);
|
|
312
|
+
|
|
313
|
+
expect(mockSvc.unmuteCall).toHaveBeenCalled();
|
|
314
|
+
expect(muted).toBe(false);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('throws when call ID is unavailable (state is OFFERED)', async () => {
|
|
318
|
+
const deps = makeDeps({getTaskState: () => TaskState.OFFERED});
|
|
319
|
+
|
|
320
|
+
await expect(toggleMuteOnWebex(deps)).rejects.toThrow('WxApp call ID is unavailable');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('throws when enableWxBetterTogether is false', async () => {
|
|
324
|
+
const deps = makeDeps({enableWxBetterTogether: false, getTaskState: () => TaskState.CONNECTED});
|
|
325
|
+
|
|
326
|
+
await expect(toggleMuteOnWebex(deps)).rejects.toThrow();
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('uses options.muted=true even when internal wxAppMuted is true (desync regression)', async () => {
|
|
330
|
+
const mockSvc = makeMockService();
|
|
331
|
+
let muted = true;
|
|
332
|
+
const deps = makeDeps({
|
|
333
|
+
answerCallOnWebexService: mockSvc,
|
|
334
|
+
getTaskState: () => TaskState.CONNECTED,
|
|
335
|
+
getWxAppMuted: () => muted,
|
|
336
|
+
setWxAppMuted: (val) => {
|
|
337
|
+
muted = val;
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
await toggleMuteOnWebex(deps, {muted: true});
|
|
342
|
+
|
|
343
|
+
expect(mockSvc.muteCall).toHaveBeenCalledWith({callId: 'call-id-1', lineOwnerId: undefined});
|
|
344
|
+
expect(mockSvc.unmuteCall).not.toHaveBeenCalled();
|
|
345
|
+
expect(muted).toBe(true);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('uses options.muted=false even when internal wxAppMuted is false (desync regression)', async () => {
|
|
349
|
+
const mockSvc = makeMockService();
|
|
350
|
+
let muted = false;
|
|
351
|
+
const deps = makeDeps({
|
|
352
|
+
answerCallOnWebexService: mockSvc,
|
|
353
|
+
getTaskState: () => TaskState.CONNECTED,
|
|
354
|
+
getWxAppMuted: () => muted,
|
|
355
|
+
setWxAppMuted: (val) => {
|
|
356
|
+
muted = val;
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
await toggleMuteOnWebex(deps, {muted: false});
|
|
361
|
+
|
|
362
|
+
expect(mockSvc.unmuteCall).toHaveBeenCalledWith({callId: 'call-id-1', lineOwnerId: undefined});
|
|
363
|
+
expect(mockSvc.muteCall).not.toHaveBeenCalled();
|
|
364
|
+
expect(muted).toBe(false);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
describe('transmitDtmfOnWebex', () => {
|
|
369
|
+
it('calls transmitDtmf with callId and dtmf', async () => {
|
|
370
|
+
const mockSvc = makeMockService();
|
|
371
|
+
const deps = makeDeps({
|
|
372
|
+
answerCallOnWebexService: mockSvc,
|
|
373
|
+
getTaskState: () => TaskState.CONNECTED,
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
await transmitDtmfOnWebex(deps, {dtmf: '5'});
|
|
377
|
+
|
|
378
|
+
expect(mockSvc.transmitDtmf).toHaveBeenCalledWith({
|
|
379
|
+
callId: 'call-id-1',
|
|
380
|
+
dtmf: '5',
|
|
381
|
+
lineOwnerId: undefined,
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('passes lineOwnerId when provided', async () => {
|
|
386
|
+
const mockSvc = makeMockService();
|
|
387
|
+
const deps = makeDeps({
|
|
388
|
+
answerCallOnWebexService: mockSvc,
|
|
389
|
+
getTaskState: () => TaskState.CONNECTED,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
await transmitDtmfOnWebex(deps, {dtmf: '9', lineOwnerId: 'lo-1'});
|
|
393
|
+
|
|
394
|
+
expect(mockSvc.transmitDtmf).toHaveBeenCalledWith(
|
|
395
|
+
expect.objectContaining({lineOwnerId: 'lo-1'})
|
|
396
|
+
);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('throws when call ID is unavailable', async () => {
|
|
400
|
+
const deps = makeDeps({getTaskState: () => TaskState.OFFERED});
|
|
401
|
+
|
|
402
|
+
await expect(transmitDtmfOnWebex(deps, {dtmf: '1'})).rejects.toThrow(
|
|
403
|
+
'WxApp call ID is unavailable'
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
describe('runWxAppAccept', () => {
|
|
409
|
+
it('sets in-flight and pending flags, resets mute, and syncs after answer', async () => {
|
|
410
|
+
const deps = makeDeps();
|
|
411
|
+
const lifecycle = makeLifecycle();
|
|
412
|
+
|
|
413
|
+
await runWxAppAccept(deps, lifecycle);
|
|
414
|
+
|
|
415
|
+
expect(lifecycle.setWxAppAcceptInFlight).toHaveBeenNthCalledWith(1, true);
|
|
416
|
+
expect(lifecycle.setWxAppAnswerPending).toHaveBeenCalledWith(true);
|
|
417
|
+
expect(deps.answerCallOnWebexService!.answerCall).toHaveBeenCalled();
|
|
418
|
+
expect(lifecycle.resetWxAppMuted).toHaveBeenCalled();
|
|
419
|
+
expect(lifecycle.syncWxAppMuteFromCallDetails).toHaveBeenCalled();
|
|
420
|
+
expect(lifecycle.setWxAppAcceptInFlight).toHaveBeenLastCalledWith(false);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('clears pending and maps error when answer fails', async () => {
|
|
424
|
+
const deps = makeDeps();
|
|
425
|
+
const error = new Error('answer failed');
|
|
426
|
+
deps.answerCallOnWebexService!.answerCall = jest.fn().mockRejectedValue(error);
|
|
427
|
+
const lifecycle = makeLifecycle();
|
|
428
|
+
|
|
429
|
+
await expect(runWxAppAccept(deps, lifecycle)).rejects.toThrow('answer failed');
|
|
430
|
+
|
|
431
|
+
expect(lifecycle.setWxAppAnswerPending).toHaveBeenCalledWith(false);
|
|
432
|
+
expect(lifecycle.mapWxAppVoiceError).toHaveBeenCalledWith(error, METHODS.ACCEPT);
|
|
433
|
+
expect(lifecycle.setWxAppAcceptInFlight).toHaveBeenLastCalledWith(false);
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
describe('runWxAppReject', () => {
|
|
438
|
+
it('maps error when reject fails', async () => {
|
|
439
|
+
const deps = makeDeps();
|
|
440
|
+
const error = new Error('reject failed');
|
|
441
|
+
deps.answerCallOnWebexService!.rejectCall = jest.fn().mockRejectedValue(error);
|
|
442
|
+
const lifecycle = makeLifecycle();
|
|
443
|
+
|
|
444
|
+
await expect(runWxAppReject(deps, lifecycle)).rejects.toThrow('reject failed');
|
|
445
|
+
|
|
446
|
+
expect(lifecycle.mapWxAppVoiceError).toHaveBeenCalledWith(error, METHODS.REJECT);
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
describe('mapWxAppVoiceError', () => {
|
|
451
|
+
it('rethrows normalized wxApp telephony errors without remapping', () => {
|
|
452
|
+
const normalized = Object.assign(new Error('TELEPHONY_ERROR'), {
|
|
453
|
+
isWxAppTelephonyError: true,
|
|
454
|
+
trackingId: 'track-wxapp-1',
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
expect(() => mapWxAppVoiceError(normalized, 'accept', 'cc')).toThrow(normalized);
|
|
458
|
+
});
|
|
459
|
+
});
|