@webex/contact-center 3.12.0-next.13 → 3.12.0-next.130
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 +884 -0
- package/AGENTS.md +94 -0
- package/ai-docs/ARCHITECTURE.md +168 -0
- package/ai-docs/CONTRACTS.md +53 -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 +52 -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 +382 -0
- package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -0
- package/ai-docs/features/contact-owner-change-refactor.md +78 -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 +971 -42
- 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 +45 -15
- package/dist/index.js.map +1 -1
- package/dist/metrics/MetricsManager.js +16 -0
- package/dist/metrics/MetricsManager.js.map +1 -1
- package/dist/metrics/behavioral-events.js +205 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +48 -4
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/AddressBook.js +18 -15
- package/dist/services/AddressBook.js.map +1 -1
- package/dist/services/AnswerCallOnWebexService.js +202 -0
- package/dist/services/AnswerCallOnWebexService.js.map +1 -0
- package/dist/services/ApiAiAssistant.js +269 -9
- package/dist/services/ApiAiAssistant.js.map +1 -1
- 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 +222 -0
- package/dist/services/WebexCrossClientService.js.map +1 -0
- package/dist/services/WxAppTelephonyMercurySync.js +108 -0
- package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
- package/dist/services/config/Util.js +11 -4
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/constants.js +41 -7
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/index.js +51 -0
- package/dist/services/config/index.js.map +1 -1
- package/dist/services/config/types.js +57 -12
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/core/Utils.js +111 -32
- 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 +28 -14
- 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 +2 -1
- package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
- package/dist/services/core/websocket/types.js.map +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/task/Task.js +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 +1172 -521
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +211 -27
- 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 +12 -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 +78 -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 +982 -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 +379 -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 +228 -5
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +1485 -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 +354 -0
- package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
- package/dist/services/wxAppDiagnosticLogging.js +122 -0
- package/dist/services/wxAppDiagnosticLogging.js.map +1 -0
- package/dist/services/wxAppTelephonyUtils.js +19 -0
- package/dist/services/wxAppTelephonyUtils.js.map +1 -0
- package/dist/types/cc.d.ts +193 -6
- package/dist/types/config.d.ts +13 -0
- package/dist/types/constants.d.ts +29 -1
- package/dist/types/index.d.ts +35 -8
- package/dist/types/metrics/MetricsManager.d.ts +6 -0
- package/dist/types/metrics/constants.d.ts +43 -1
- package/dist/types/services/AddressBook.d.ts +2 -1
- package/dist/types/services/AnswerCallOnWebexService.d.ts +38 -0
- package/dist/types/services/ApiAiAssistant.d.ts +56 -5
- package/dist/types/services/EntryPoint.d.ts +7 -5
- package/dist/types/services/Queue.d.ts +5 -3
- package/dist/types/services/UserPreference.d.ts +118 -0
- package/dist/types/services/WebexCrossClientService.d.ts +32 -0
- package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
- package/dist/types/services/config/constants.d.ts +34 -5
- package/dist/types/services/config/types.d.ts +197 -12
- package/dist/types/services/core/Err.d.ts +6 -0
- package/dist/types/services/core/Utils.d.ts +35 -13
- package/dist/types/services/core/WebexRequest.d.ts +1 -0
- package/dist/types/services/core/types.d.ts +2 -0
- package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
- package/dist/types/services/core/websocket/types.d.ts +1 -1
- package/dist/types/services/index.d.ts +1 -1
- package/dist/types/services/task/Task.d.ts +175 -0
- package/dist/types/services/task/TaskFactory.d.ts +13 -0
- package/dist/types/services/task/TaskUtils.d.ts +48 -2
- package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
- package/dist/types/services/task/constants.d.ts +10 -0
- package/dist/types/services/task/contact.d.ts +4 -0
- package/dist/types/services/task/dialer.d.ts +30 -0
- package/dist/types/services/task/digital/Digital.d.ts +22 -0
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1442 -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 +680 -66
- package/dist/types/services/task/voice/Voice.d.ts +227 -0
- package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
- package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +55 -0
- package/dist/types/services/wxAppDiagnosticLogging.d.ts +51 -0
- package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
- package/dist/types/types.d.ts +205 -9
- package/dist/types/utils/PageCache.d.ts +20 -3
- package/dist/types/webex.d.ts +1 -0
- package/dist/types.js +162 -4
- 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 +22 -2
- package/dist/webex.js.map +1 -1
- package/package.json +16 -12
- package/src/cc.ts +1289 -112
- package/src/config.ts +13 -0
- package/src/constants.ts +29 -1
- package/src/index.ts +36 -5
- package/src/metrics/MetricsManager.ts +16 -0
- 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 +921 -0
- package/src/metrics/behavioral-events.ts +212 -0
- package/src/metrics/constants.ts +52 -4
- package/src/services/AddressBook.ts +17 -6
- package/src/services/AnswerCallOnWebexService.ts +239 -0
- package/src/services/ApiAiAssistant.ts +356 -9
- 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 +293 -0
- package/src/services/WxAppTelephonyMercurySync.ts +142 -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 +505 -0
- package/src/services/ai-docs/AGENTS.md +386 -0
- package/src/services/ai-docs/services-spec.md +505 -0
- package/src/services/config/Util.ts +12 -3
- 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 +686 -0
- package/src/services/config/constants.ts +46 -6
- package/src/services/config/index.ts +65 -0
- package/src/services/config/types.ts +197 -13
- package/src/services/core/Err.ts +3 -0
- package/src/services/core/Utils.ts +127 -37
- 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 +30 -15
- package/src/services/core/types.ts +2 -0
- package/src/services/core/websocket/WebSocketManager.ts +2 -0
- package/src/services/core/websocket/types.ts +1 -1
- package/src/services/index.ts +1 -1
- package/src/services/task/Task.ts +908 -0
- package/src/services/task/TaskFactory.ts +60 -0
- package/src/services/task/TaskManager.ts +1419 -608
- package/src/services/task/TaskUtils.ts +303 -25
- 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 +1474 -0
- package/src/services/task/constants.ts +10 -0
- package/src/services/task/contact.ts +30 -0
- package/src/services/task/dialer.ts +80 -0
- package/src/services/task/digital/Digital.ts +97 -0
- package/src/services/task/state-machine/TaskStateMachine.ts +1320 -0
- package/src/services/task/state-machine/actions.ts +741 -0
- package/src/services/task/state-machine/ai-docs/AGENTS.md +460 -0
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1156 -0
- package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2247 -0
- package/src/services/task/state-machine/constants.ts +172 -0
- package/src/services/task/state-machine/guards.ts +442 -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 +819 -70
- package/src/services/task/voice/Voice.ts +1851 -0
- package/src/services/task/voice/WebRTC.ts +191 -0
- package/src/services/task/voice/wxAppVoiceMethods.ts +516 -0
- package/src/services/wxAppDiagnosticLogging.ts +174 -0
- package/src/services/wxAppTelephonyUtils.ts +14 -0
- package/src/types.ts +262 -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 +10 -0
- package/test/unit/spec/cc.ts +2210 -119
- package/test/unit/spec/logger-proxy.ts +70 -0
- package/test/unit/spec/metrics/MetricsManager.ts +25 -0
- package/test/unit/spec/metrics/behavioral-events.ts +60 -0
- package/test/unit/spec/services/AddressBook.ts +37 -6
- package/test/unit/spec/services/AnswerCallOnWebexService.ts +264 -0
- package/test/unit/spec/services/ApiAiAssistant.ts +311 -21
- 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 +467 -0
- package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +136 -0
- package/test/unit/spec/services/config/Util.ts +129 -0
- package/test/unit/spec/services/config/index.ts +97 -29
- package/test/unit/spec/services/core/Utils.ts +425 -8
- 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 +66 -40
- package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
- package/test/unit/spec/services/task/Task.ts +745 -0
- package/test/unit/spec/services/task/TaskFactory.ts +99 -0
- package/test/unit/spec/services/task/TaskManager.ts +2518 -1494
- 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 +190 -0
- package/test/unit/spec/services/task/digital/Digital.ts +105 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +4060 -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 +2061 -0
- package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
- package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +603 -0
- package/test/unit/spec/services/wxAppDiagnosticLogging.ts +143 -0
- package/test/unit/spec/wellnessSampleUtils.ts +288 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
- package/dist/services/task/index.js +0 -1525
- package/dist/services/task/index.js.map +0 -1
- package/dist/types/services/task/index.d.ts +0 -650
- package/src/services/task/index.ts +0 -1801
- package/test/unit/spec/services/task/index.ts +0 -2184
package/test/unit/spec/cc.ts
CHANGED
|
@@ -59,6 +59,7 @@ global.URL.createObjectURL = jest.fn(() => 'blob:http://localhost:3000/12345');
|
|
|
59
59
|
|
|
60
60
|
describe('webex.cc', () => {
|
|
61
61
|
let webex;
|
|
62
|
+
let mockApiAIAssistant;
|
|
62
63
|
let mockContact;
|
|
63
64
|
let mockTaskManager;
|
|
64
65
|
let mockMetricsManager;
|
|
@@ -109,6 +110,14 @@ describe('webex.cc', () => {
|
|
|
109
110
|
cancelCtq: jest.fn(),
|
|
110
111
|
};
|
|
111
112
|
|
|
113
|
+
mockApiAIAssistant = {
|
|
114
|
+
sendEvent: jest.fn(),
|
|
115
|
+
getRealTimeAssistance: jest.fn(),
|
|
116
|
+
fetchHistoricTranscripts: jest.fn(),
|
|
117
|
+
setAIFeatureFlags: jest.fn(),
|
|
118
|
+
setAgentId: jest.fn(),
|
|
119
|
+
};
|
|
120
|
+
|
|
112
121
|
// Mock Services instance
|
|
113
122
|
const mockServicesInstance = {
|
|
114
123
|
agent: {
|
|
@@ -121,6 +130,7 @@ describe('webex.cc', () => {
|
|
|
121
130
|
config: {
|
|
122
131
|
getAgentConfig: jest.fn(),
|
|
123
132
|
getOutdialAniEntries: jest.fn(),
|
|
133
|
+
getWellbeingBreakIdleCode: jest.fn(),
|
|
124
134
|
},
|
|
125
135
|
webSocketManager: mockWebSocketManager,
|
|
126
136
|
rtdWebSocketManager: {
|
|
@@ -139,6 +149,8 @@ describe('webex.cc', () => {
|
|
|
139
149
|
dialer: {
|
|
140
150
|
startOutdial: jest.fn(),
|
|
141
151
|
acceptPreviewContact: jest.fn(),
|
|
152
|
+
skipPreviewContact: jest.fn(),
|
|
153
|
+
removePreviewContact: jest.fn(),
|
|
142
154
|
},
|
|
143
155
|
apiAIAssistant: {
|
|
144
156
|
sendEvent: jest.fn(),
|
|
@@ -147,21 +159,24 @@ describe('webex.cc', () => {
|
|
|
147
159
|
};
|
|
148
160
|
|
|
149
161
|
mockTaskManager = {
|
|
162
|
+
apiAIAssistant: mockApiAIAssistant,
|
|
150
163
|
contact: mockContact,
|
|
151
164
|
call: undefined,
|
|
152
165
|
taskCollection: {},
|
|
153
166
|
webCallingService: undefined,
|
|
154
167
|
webSocketManager: mockWebSocketManager,
|
|
155
168
|
task: undefined,
|
|
169
|
+
setConfigFlags: jest.fn(),
|
|
156
170
|
setWrapupData: jest.fn(),
|
|
157
171
|
setAgentId: jest.fn(),
|
|
158
172
|
setWebRtcEnabled: jest.fn(),
|
|
159
|
-
|
|
160
|
-
setApiAIAssistant: jest.fn(),
|
|
173
|
+
setAnswerCallOnWebexService: jest.fn(),
|
|
161
174
|
registerIncomingCallEvent: jest.fn(),
|
|
162
175
|
registerTaskListeners: jest.fn(),
|
|
163
176
|
getTask: jest.fn(),
|
|
164
177
|
getActiveTasks: jest.fn(),
|
|
178
|
+
applyEnableWxBetterTogether: jest.fn(),
|
|
179
|
+
syncWxAppMuteFromCallDetailsForAllTasks: jest.fn(),
|
|
165
180
|
on: jest.fn(),
|
|
166
181
|
off: jest.fn(),
|
|
167
182
|
emit: jest.fn(),
|
|
@@ -200,6 +215,35 @@ describe('webex.cc', () => {
|
|
|
200
215
|
webex.emit('ready');
|
|
201
216
|
});
|
|
202
217
|
|
|
218
|
+
it('should throw when WebRTC registration is disabled without multi-login', () => {
|
|
219
|
+
const invalidWebex = MockWebex({
|
|
220
|
+
children: {
|
|
221
|
+
mercury: Mercury,
|
|
222
|
+
},
|
|
223
|
+
logger: {
|
|
224
|
+
log: jest.fn(),
|
|
225
|
+
error: jest.fn(),
|
|
226
|
+
info: jest.fn(),
|
|
227
|
+
},
|
|
228
|
+
credentials: {
|
|
229
|
+
getOrgId: jest.fn(() => 'mockOrgId'),
|
|
230
|
+
},
|
|
231
|
+
config: {
|
|
232
|
+
...config,
|
|
233
|
+
cc: {
|
|
234
|
+
...config.cc,
|
|
235
|
+
allowMultiLogin: false,
|
|
236
|
+
disableWebRTCRegistration: true,
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
once: jest.fn((event, callback) => callback()),
|
|
240
|
+
}) as unknown as WebexSDK;
|
|
241
|
+
|
|
242
|
+
expect(() => new ContactCenter({parent: invalidWebex})).toThrow(
|
|
243
|
+
'Invalid Contact Center configuration: disableWebRTCRegistration cannot be true when allowMultiLogin is false. Enable allowMultiLogin or allow WebRTC registration so an SDK instance can receive Mobius/WebRTC task events.'
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
|
|
203
247
|
describe('cc.getDeviceId', () => {
|
|
204
248
|
it('should return dialNumber when loginOption is EXTENSION', () => {
|
|
205
249
|
const loginOption = LoginOption.EXTENSION;
|
|
@@ -267,14 +311,18 @@ describe('webex.cc', () => {
|
|
|
267
311
|
isAgentAvailableAfterOutdial: false,
|
|
268
312
|
isCampaignManagementEnabled: false,
|
|
269
313
|
outDialEp: '',
|
|
270
|
-
|
|
314
|
+
isEndTaskEnabled: false,
|
|
271
315
|
isEndConsultEnabled: false,
|
|
272
316
|
agentDbId: '',
|
|
273
317
|
allowConsultToQueue: false,
|
|
318
|
+
accessQueue: 'SPECIFIC',
|
|
319
|
+
accessEntryPoint: 'ALL',
|
|
320
|
+
accessBuddyTeam: 'ALL',
|
|
274
321
|
agentPersonalStatsEnabled: false,
|
|
275
322
|
isTimeoutDesktopInactivityEnabled: false,
|
|
276
323
|
webRtcEnabled: true,
|
|
277
324
|
lostConnectionRecoveryTimeout: 0,
|
|
325
|
+
isWellnessBreakEnabled: false,
|
|
278
326
|
};
|
|
279
327
|
|
|
280
328
|
it('should register successfully and return agent profile', async () => {
|
|
@@ -300,7 +348,7 @@ describe('webex.cc', () => {
|
|
|
300
348
|
const result = await webex.cc.register();
|
|
301
349
|
|
|
302
350
|
// Verify logging calls
|
|
303
|
-
expect(LoggerProxy.
|
|
351
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith('Starting CC SDK registration', {
|
|
304
352
|
module: CC_FILE,
|
|
305
353
|
method: 'register',
|
|
306
354
|
});
|
|
@@ -334,6 +382,10 @@ describe('webex.cc', () => {
|
|
|
334
382
|
TASK_EVENTS.TASK_HYDRATE,
|
|
335
383
|
expect.any(Function)
|
|
336
384
|
);
|
|
385
|
+
expect(mockTaskManager.on).toHaveBeenCalledWith(
|
|
386
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
387
|
+
expect.any(Function)
|
|
388
|
+
);
|
|
337
389
|
expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
|
|
338
390
|
expect(webex.cc.services.rtdWebSocketManager.initWebSocket).toHaveBeenCalledWith({
|
|
339
391
|
body: {
|
|
@@ -354,6 +406,20 @@ describe('webex.cc', () => {
|
|
|
354
406
|
module: CC_FILE,
|
|
355
407
|
method: 'connectWebsocket',
|
|
356
408
|
});
|
|
409
|
+
expect(mockTaskManager.setConfigFlags).toHaveBeenCalledWith({
|
|
410
|
+
isEndTaskEnabled: mockAgentProfile.isEndTaskEnabled,
|
|
411
|
+
isEndConsultEnabled: mockAgentProfile.isEndConsultEnabled,
|
|
412
|
+
webRtcEnabled: mockAgentProfile.webRtcEnabled,
|
|
413
|
+
autoWrapup: mockAgentProfile.wrapUpData.wrapUpProps.autoWrapup ?? false,
|
|
414
|
+
aiFeature: mockAgentProfile.aiFeature,
|
|
415
|
+
consultTransfer: {
|
|
416
|
+
allowConsultToQueue: mockAgentProfile.allowConsultToQueue,
|
|
417
|
+
accessQueue: mockAgentProfile.accessQueue,
|
|
418
|
+
accessEntryPoint: mockAgentProfile.accessEntryPoint,
|
|
419
|
+
accessBuddyTeam: mockAgentProfile.accessBuddyTeam,
|
|
420
|
+
},
|
|
421
|
+
enableWxBetterTogether: false,
|
|
422
|
+
});
|
|
357
423
|
expect(reloadSpy).toHaveBeenCalled();
|
|
358
424
|
expect(result).toEqual(mockAgentProfile);
|
|
359
425
|
expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
|
|
@@ -413,7 +479,7 @@ describe('webex.cc', () => {
|
|
|
413
479
|
|
|
414
480
|
await expect(webex.cc.register()).rejects.toThrow('Error while performing register');
|
|
415
481
|
|
|
416
|
-
expect(LoggerProxy.
|
|
482
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith('Starting CC SDK registration', {
|
|
417
483
|
module: CC_FILE,
|
|
418
484
|
method: 'register',
|
|
419
485
|
});
|
|
@@ -432,6 +498,25 @@ describe('webex.cc', () => {
|
|
|
432
498
|
);
|
|
433
499
|
});
|
|
434
500
|
|
|
501
|
+
it('clears wellness session context when registration fails after relogin', async () => {
|
|
502
|
+
const mockError = new Error('Error after silent relogin');
|
|
503
|
+
webex.cc.$config = {...webex.cc.$config, allowAutomatedRelogin: true};
|
|
504
|
+
jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue({
|
|
505
|
+
...mockAgentProfile,
|
|
506
|
+
webRtcEnabled: false,
|
|
507
|
+
aiFeature: undefined,
|
|
508
|
+
} as Profile);
|
|
509
|
+
mockWebSocketManager.initWebSocket.mockResolvedValue({agentId: 'agent123'});
|
|
510
|
+
jest.spyOn(webex.cc as any, 'silentRelogin').mockImplementation(async () => {
|
|
511
|
+
webex.cc['updateWellnessSession']('partial-session');
|
|
512
|
+
throw mockError;
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
await expect(webex.cc.register()).rejects.toThrow(mockError);
|
|
516
|
+
|
|
517
|
+
expect(webex.cc['currentAgentSessionId']).toBeUndefined();
|
|
518
|
+
});
|
|
519
|
+
|
|
435
520
|
it('should log error if mercury connect fails but cc.register() should not fail', async () => {
|
|
436
521
|
const mockError = new Error('Error while performing mercury connect');
|
|
437
522
|
jest.spyOn(webex.internal.mercury, 'connect').mockRejectedValue(mockError);
|
|
@@ -482,6 +567,10 @@ describe('webex.cc', () => {
|
|
|
482
567
|
TASK_EVENTS.TASK_HYDRATE,
|
|
483
568
|
expect.any(Function)
|
|
484
569
|
);
|
|
570
|
+
expect(mockTaskManager.on).toHaveBeenCalledWith(
|
|
571
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
572
|
+
expect.any(Function)
|
|
573
|
+
);
|
|
485
574
|
expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
|
|
486
575
|
|
|
487
576
|
expect(configSpy).toHaveBeenCalled();
|
|
@@ -493,6 +582,36 @@ describe('webex.cc', () => {
|
|
|
493
582
|
expect(result).toEqual(mockAgentProfile);
|
|
494
583
|
});
|
|
495
584
|
|
|
585
|
+
it('should skip mercury connection when disableWebRTCRegistration is enabled', async () => {
|
|
586
|
+
webex.cc.$config = {
|
|
587
|
+
...webex.cc.$config,
|
|
588
|
+
allowAutomatedRelogin: false,
|
|
589
|
+
disableWebRTCRegistration: true,
|
|
590
|
+
};
|
|
591
|
+
mockAgentProfile.webRtcEnabled = true;
|
|
592
|
+
const mercurySpy = jest.spyOn(webex.internal.mercury, 'connect');
|
|
593
|
+
const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
|
|
594
|
+
const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
|
|
595
|
+
jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue(mockAgentProfile);
|
|
596
|
+
mockWebSocketManager.initWebSocket.mockResolvedValue({
|
|
597
|
+
agentId: 'agent123',
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
const result = await webex.cc.register();
|
|
601
|
+
|
|
602
|
+
expect(connectWebsocketSpy).toHaveBeenCalled();
|
|
603
|
+
expect(setupEventListenersSpy).toHaveBeenCalled();
|
|
604
|
+
expect(mercurySpy).not.toHaveBeenCalled();
|
|
605
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith(
|
|
606
|
+
'Skipping Mobius registration because disableWebRTCRegistration is enabled',
|
|
607
|
+
{
|
|
608
|
+
module: CC_FILE,
|
|
609
|
+
method: 'connectWebsocket',
|
|
610
|
+
}
|
|
611
|
+
);
|
|
612
|
+
expect(result).toEqual(mockAgentProfile);
|
|
613
|
+
});
|
|
614
|
+
|
|
496
615
|
it('should not attempt for mercury connection when webrtc is disabled', async () => {
|
|
497
616
|
mockAgentProfile.webRtcEnabled = false;
|
|
498
617
|
mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: false}} as any;
|
|
@@ -631,7 +750,7 @@ describe('webex.cc', () => {
|
|
|
631
750
|
expect(registerWebCallingLineSpy).toHaveBeenCalled();
|
|
632
751
|
expect(stationLoginMock).toHaveBeenCalledWith({
|
|
633
752
|
data: {
|
|
634
|
-
dialNumber:
|
|
753
|
+
dialNumber: `${WEB_RTC_PREFIX}agentId`,
|
|
635
754
|
teamId: 'teamId',
|
|
636
755
|
deviceType: LoginOption.BROWSER,
|
|
637
756
|
isExtension: false,
|
|
@@ -649,6 +768,7 @@ describe('webex.cc', () => {
|
|
|
649
768
|
METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
|
|
650
769
|
]);
|
|
651
770
|
expect(result).toEqual(responseMock);
|
|
771
|
+
expect(webex.cc.agentConfig.deviceType).toBe(LoginOption.BROWSER);
|
|
652
772
|
|
|
653
773
|
const onSpy = jest.spyOn(mockTaskManager, 'on');
|
|
654
774
|
const emitSpy = jest.spyOn(webex.cc, 'trigger');
|
|
@@ -661,10 +781,8 @@ describe('webex.cc', () => {
|
|
|
661
781
|
|
|
662
782
|
expect(emitSpy).toHaveBeenCalledTimes(1);
|
|
663
783
|
expect(emitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_INCOMING, mockTask);
|
|
664
|
-
// Verify message
|
|
665
|
-
const messageCallback =
|
|
666
|
-
(call) => call[0] === 'message'
|
|
667
|
-
)[1];
|
|
784
|
+
// Verify websocket message handling
|
|
785
|
+
const messageCallback = webex.cc['handleWebsocketMessage'];
|
|
668
786
|
const agentStateChangeEventData = {
|
|
669
787
|
type: CC_EVENTS.AGENT_STATE_CHANGE,
|
|
670
788
|
data: {some: 'data'},
|
|
@@ -692,6 +810,59 @@ describe('webex.cc', () => {
|
|
|
692
810
|
);
|
|
693
811
|
});
|
|
694
812
|
|
|
813
|
+
it('should skip web calling line registration when disableWebRTCRegistration is enabled', async () => {
|
|
814
|
+
const options = {
|
|
815
|
+
teamId: 'teamId',
|
|
816
|
+
loginOption: LoginOption.BROWSER,
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
webex.cc.$config = {
|
|
820
|
+
...webex.cc.$config,
|
|
821
|
+
disableWebRTCRegistration: true,
|
|
822
|
+
};
|
|
823
|
+
webex.cc.agentConfig = {
|
|
824
|
+
agentId: 'agentId',
|
|
825
|
+
webRtcEnabled: true,
|
|
826
|
+
loginVoiceOptions: ['BROWSER', 'EXTENSION', 'AGENT_DN'],
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
const registerWebCallingLineSpy = jest.spyOn(
|
|
830
|
+
webex.cc.webCallingService,
|
|
831
|
+
'registerWebCallingLine'
|
|
832
|
+
);
|
|
833
|
+
|
|
834
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
835
|
+
data: {
|
|
836
|
+
loginOption: LoginOption.BROWSER,
|
|
837
|
+
agentId: 'agentId',
|
|
838
|
+
teamId: 'teamId',
|
|
839
|
+
siteId: 'siteId',
|
|
840
|
+
roles: [AGENT],
|
|
841
|
+
channelsMap: {
|
|
842
|
+
chat: [],
|
|
843
|
+
email: [],
|
|
844
|
+
social: [],
|
|
845
|
+
telephony: [],
|
|
846
|
+
},
|
|
847
|
+
},
|
|
848
|
+
trackingId: 'notifs_52628',
|
|
849
|
+
orgId: 'orgId',
|
|
850
|
+
type: 'StationLoginSuccess',
|
|
851
|
+
eventType: 'STATION_LOGIN',
|
|
852
|
+
} as unknown as StationLoginSuccess);
|
|
853
|
+
|
|
854
|
+
await webex.cc.stationLogin(options);
|
|
855
|
+
|
|
856
|
+
expect(registerWebCallingLineSpy).not.toHaveBeenCalled();
|
|
857
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith(
|
|
858
|
+
'Skipping web calling line registration because disableWebRTCRegistration is enabled',
|
|
859
|
+
{
|
|
860
|
+
module: CC_FILE,
|
|
861
|
+
method: 'stationLogin',
|
|
862
|
+
}
|
|
863
|
+
);
|
|
864
|
+
});
|
|
865
|
+
|
|
695
866
|
it('should not attempt mobius registration for LoginOption.BROWSER if webrtc is disabled', async () => {
|
|
696
867
|
const options = {
|
|
697
868
|
teamId: 'teamId',
|
|
@@ -739,7 +910,7 @@ describe('webex.cc', () => {
|
|
|
739
910
|
expect(registerWebCallingLineSpy).not.toHaveBeenCalled();
|
|
740
911
|
expect(stationLoginSpy).toHaveBeenCalledWith({
|
|
741
912
|
data: {
|
|
742
|
-
dialNumber:
|
|
913
|
+
dialNumber: `${WEB_RTC_PREFIX}agentId`,
|
|
743
914
|
teamId: 'teamId',
|
|
744
915
|
deviceType: LoginOption.BROWSER,
|
|
745
916
|
isExtension: false,
|
|
@@ -814,10 +985,13 @@ describe('webex.cc', () => {
|
|
|
814
985
|
const result = await webex.cc.stationLogin(options);
|
|
815
986
|
|
|
816
987
|
// Verify logging calls
|
|
817
|
-
expect(LoggerProxy.
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
988
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
989
|
+
`Starting agent station login | loginOption: ${options.loginOption} teamId: ${options.teamId}`,
|
|
990
|
+
{
|
|
991
|
+
module: CC_FILE,
|
|
992
|
+
method: 'stationLogin',
|
|
993
|
+
}
|
|
994
|
+
);
|
|
821
995
|
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
822
996
|
`Agent station login completed successfully agentId: ${mockData.data.agentId} loginOption: ${mockData.data.loginOption} teamId: ${mockData.data.teamId}`,
|
|
823
997
|
{
|
|
@@ -868,125 +1042,971 @@ describe('webex.cc', () => {
|
|
|
868
1042
|
|
|
869
1043
|
await expect(webex.cc.stationLogin(options)).rejects.toThrow(error.details.data.reason);
|
|
870
1044
|
|
|
871
|
-
expect(LoggerProxy.
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1045
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
1046
|
+
`Starting agent station login | loginOption: ${options.loginOption} teamId: ${options.teamId}`,
|
|
1047
|
+
{
|
|
1048
|
+
module: CC_FILE,
|
|
1049
|
+
method: 'stationLogin',
|
|
1050
|
+
}
|
|
1051
|
+
);
|
|
875
1052
|
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
876
1053
|
`stationLogin failed with reason: ${error.details.data.reason}`,
|
|
877
1054
|
{module: CC_FILE, method: 'stationLogin', trackingId: error.details.trackingId}
|
|
878
1055
|
);
|
|
879
1056
|
});
|
|
880
|
-
});
|
|
881
1057
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1058
|
+
it('should publish usersub and subscribe Mercury on station login when enableWxBetterTogether is true at init', async () => {
|
|
1059
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1060
|
+
webex.internal.device = {
|
|
1061
|
+
userId: 'user-123',
|
|
1062
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
1063
|
+
registered: true,
|
|
1064
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
1065
|
+
};
|
|
1066
|
+
webex.internal.mercury.connected = true;
|
|
1067
|
+
webex.internal.mercury.connect = jest.fn().mockResolvedValue(undefined);
|
|
1068
|
+
webex.cc.agentConfig = {
|
|
1069
|
+
agentId: 'agentId',
|
|
1070
|
+
webRtcEnabled: false,
|
|
1071
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1072
|
+
};
|
|
1073
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
886
1074
|
|
|
887
|
-
const
|
|
888
|
-
.spyOn(webex.cc
|
|
889
|
-
.mockResolvedValue(
|
|
1075
|
+
const publishSpy = jest
|
|
1076
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1077
|
+
.mockResolvedValue(undefined);
|
|
1078
|
+
const subscribeSpy = jest
|
|
1079
|
+
.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe')
|
|
1080
|
+
.mockImplementation(() => {});
|
|
1081
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
890
1082
|
|
|
891
|
-
|
|
1083
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1084
|
+
data: {
|
|
1085
|
+
agentId: 'agentId',
|
|
1086
|
+
teamId: 'teamId',
|
|
1087
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1088
|
+
},
|
|
1089
|
+
trackingId: 'track-1',
|
|
1090
|
+
} as StationLoginSuccess);
|
|
892
1091
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
});
|
|
898
|
-
expect(LoggerProxy.log).toHaveBeenCalledWith('Agent station logout completed successfully', {
|
|
899
|
-
module: CC_FILE,
|
|
900
|
-
method: 'stationLogout',
|
|
1092
|
+
await webex.cc.stationLogin({
|
|
1093
|
+
teamId: 'teamId',
|
|
1094
|
+
loginOption: LoginOption.EXTENSION,
|
|
1095
|
+
dialNumber: '1001',
|
|
901
1096
|
});
|
|
902
1097
|
|
|
903
|
-
expect(
|
|
904
|
-
|
|
905
|
-
// expect(mockTaskManager.unregisterIncomingCallEvent).toHaveBeenCalledWith();
|
|
906
|
-
// expect(mockTaskManager.off).toHaveBeenCalledWith(
|
|
907
|
-
// TASK_EVENTS.TASK_INCOMING,
|
|
908
|
-
// expect.any(Function)
|
|
909
|
-
// );
|
|
910
|
-
// expect(mockTaskManager.off).toHaveBeenCalledWith(
|
|
911
|
-
// TASK_EVENTS.TASK_HYDRATE,
|
|
912
|
-
// expect.any(Function)
|
|
913
|
-
// );
|
|
914
|
-
// expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
|
|
915
|
-
expect(result).toEqual(response);
|
|
916
|
-
expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
|
|
917
|
-
METRIC_EVENT_NAMES.STATION_LOGOUT_SUCCESS,
|
|
918
|
-
METRIC_EVENT_NAMES.STATION_LOGOUT_FAILED,
|
|
919
|
-
]);
|
|
1098
|
+
expect(publishSpy).toHaveBeenCalledWith(true, {userId: 'user-123', trackPublishMetrics: true});
|
|
1099
|
+
expect(subscribeSpy).toHaveBeenCalledWith('agentId', expect.any(Function));
|
|
920
1100
|
});
|
|
921
1101
|
|
|
922
|
-
it('should
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
},
|
|
930
|
-
},
|
|
1102
|
+
it('should rollback wxApp config when post-station-login init fails', async () => {
|
|
1103
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1104
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1105
|
+
webex.cc.agentConfig = {
|
|
1106
|
+
agentId: 'agentId',
|
|
1107
|
+
webRtcEnabled: false,
|
|
1108
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
931
1109
|
};
|
|
1110
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1111
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
932
1112
|
|
|
933
|
-
jest
|
|
1113
|
+
jest
|
|
1114
|
+
.spyOn(webex.cc as never, 'publishAnswerOnWebexCrossClientState' as never)
|
|
1115
|
+
.mockRejectedValue(new Error('usersub publish failed'));
|
|
1116
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
934
1117
|
|
|
935
|
-
|
|
1118
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1119
|
+
data: {
|
|
1120
|
+
agentId: 'agentId',
|
|
1121
|
+
teamId: 'teamId',
|
|
1122
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1123
|
+
},
|
|
1124
|
+
trackingId: 'track-1',
|
|
1125
|
+
} as StationLoginSuccess);
|
|
936
1126
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1127
|
+
await webex.cc.stationLogin({
|
|
1128
|
+
teamId: 'teamId',
|
|
1129
|
+
loginOption: LoginOption.EXTENSION,
|
|
1130
|
+
dialNumber: '1001',
|
|
940
1131
|
});
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
);
|
|
1132
|
+
|
|
1133
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
1134
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
1135
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenCalledWith(false);
|
|
945
1136
|
});
|
|
946
|
-
});
|
|
947
1137
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1138
|
+
it('should reset wxApp config when Mercury fails before usersub publish on station login', async () => {
|
|
1139
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1140
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1141
|
+
webex.cc.agentConfig = {
|
|
1142
|
+
agentId: 'agentId',
|
|
1143
|
+
webRtcEnabled: false,
|
|
1144
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
955
1145
|
};
|
|
1146
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1147
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1148
|
+
|
|
1149
|
+
const publishSpy = jest
|
|
1150
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1151
|
+
.mockResolvedValue(undefined);
|
|
1152
|
+
jest
|
|
1153
|
+
.spyOn(webex.cc as any, 'ensureWxAppMercuryConnected')
|
|
1154
|
+
.mockRejectedValue(new Error('mercury connect failed'));
|
|
1155
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
1156
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
1157
|
+
jest
|
|
1158
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
1159
|
+
.mockResolvedValue(undefined);
|
|
1160
|
+
const metricSpy = jest.spyOn(mockMetricsManager, 'trackEvent');
|
|
956
1161
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1162
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1163
|
+
data: {
|
|
1164
|
+
agentId: 'agentId',
|
|
1165
|
+
teamId: 'teamId',
|
|
1166
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1167
|
+
},
|
|
1168
|
+
trackingId: 'track-1',
|
|
1169
|
+
} as StationLoginSuccess);
|
|
962
1170
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1171
|
+
await webex.cc.stationLogin({
|
|
1172
|
+
teamId: 'teamId',
|
|
1173
|
+
loginOption: LoginOption.EXTENSION,
|
|
1174
|
+
dialNumber: '1001',
|
|
967
1175
|
});
|
|
968
|
-
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
969
|
-
`Agent state changed successfully to auxCodeId: ${expectedPayload.auxCodeId}`,
|
|
970
|
-
{
|
|
971
|
-
module: CC_FILE,
|
|
972
|
-
method: 'setAgentState',
|
|
973
|
-
}
|
|
974
|
-
);
|
|
975
1176
|
|
|
976
|
-
expect(
|
|
977
|
-
expect(
|
|
978
|
-
expect(
|
|
979
|
-
|
|
980
|
-
METRIC_EVENT_NAMES.
|
|
981
|
-
|
|
1177
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
1178
|
+
expect(teardownSpy).not.toHaveBeenCalled();
|
|
1179
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
1180
|
+
expect(metricSpy).toHaveBeenCalledWith(
|
|
1181
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
|
|
1182
|
+
expect.objectContaining({skipReason: 'mercury_subscribe_failed'}),
|
|
1183
|
+
['operational', 'behavioral']
|
|
1184
|
+
);
|
|
1185
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
1186
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenCalledWith(false);
|
|
982
1187
|
});
|
|
983
1188
|
|
|
984
|
-
it('should
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
agentId: '
|
|
989
|
-
|
|
1189
|
+
it('should record publish_failed when usersub publish fails after Mercury init', async () => {
|
|
1190
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1191
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1192
|
+
webex.cc.agentConfig = {
|
|
1193
|
+
agentId: 'agentId',
|
|
1194
|
+
webRtcEnabled: false,
|
|
1195
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1196
|
+
};
|
|
1197
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1198
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1199
|
+
|
|
1200
|
+
jest.spyOn(webex.cc as any, 'ensureWxAppMercuryConnected').mockResolvedValue(undefined);
|
|
1201
|
+
jest
|
|
1202
|
+
.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe')
|
|
1203
|
+
.mockImplementation(() => {});
|
|
1204
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
1205
|
+
jest
|
|
1206
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1207
|
+
.mockRejectedValue(new Error('publish failed'));
|
|
1208
|
+
jest
|
|
1209
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
1210
|
+
.mockResolvedValue(undefined);
|
|
1211
|
+
const metricSpy = jest.spyOn(mockMetricsManager, 'trackEvent');
|
|
1212
|
+
|
|
1213
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1214
|
+
data: {
|
|
1215
|
+
agentId: 'agentId',
|
|
1216
|
+
teamId: 'teamId',
|
|
1217
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1218
|
+
},
|
|
1219
|
+
trackingId: 'track-1',
|
|
1220
|
+
} as StationLoginSuccess);
|
|
1221
|
+
|
|
1222
|
+
await webex.cc.stationLogin({
|
|
1223
|
+
teamId: 'teamId',
|
|
1224
|
+
loginOption: LoginOption.EXTENSION,
|
|
1225
|
+
dialNumber: '1001',
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
expect(metricSpy).toHaveBeenCalledWith(
|
|
1229
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
|
|
1230
|
+
expect.objectContaining({skipReason: 'publish_failed'}),
|
|
1231
|
+
['operational', 'behavioral']
|
|
1232
|
+
);
|
|
1233
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
it('should register device before publishing usersub true on extension station login', async () => {
|
|
1237
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1238
|
+
webex.internal.device = {
|
|
1239
|
+
userId: 'user-123',
|
|
1240
|
+
registered: false,
|
|
1241
|
+
register: jest.fn().mockImplementation(async function (this: {
|
|
1242
|
+
url?: string;
|
|
1243
|
+
registered?: boolean;
|
|
1244
|
+
}) {
|
|
1245
|
+
this.url = 'https://wdm.example.com/devices/dev-1';
|
|
1246
|
+
this.registered = true;
|
|
1247
|
+
}),
|
|
1248
|
+
};
|
|
1249
|
+
webex.internal.mercury = {
|
|
1250
|
+
connected: false,
|
|
1251
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
1252
|
+
};
|
|
1253
|
+
webex.cc.agentConfig = {
|
|
1254
|
+
agentId: 'agentId',
|
|
1255
|
+
webRtcEnabled: false,
|
|
1256
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1257
|
+
};
|
|
1258
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1259
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe').mockImplementation(() => {});
|
|
1260
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
1261
|
+
|
|
1262
|
+
const callOrder: string[] = [];
|
|
1263
|
+
jest.spyOn(webex.internal.device, 'register').mockImplementation(async function (this: {
|
|
1264
|
+
url?: string;
|
|
1265
|
+
registered?: boolean;
|
|
1266
|
+
}) {
|
|
1267
|
+
callOrder.push('device.register');
|
|
1268
|
+
this.url = 'https://wdm.example.com/devices/dev-1';
|
|
1269
|
+
this.registered = true;
|
|
1270
|
+
});
|
|
1271
|
+
jest
|
|
1272
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1273
|
+
.mockImplementation(async (enable: boolean) => {
|
|
1274
|
+
if (enable === true && !webex.internal.device.url) {
|
|
1275
|
+
throw new Error('Device URL is unavailable for cross-client publish');
|
|
1276
|
+
}
|
|
1277
|
+
callOrder.push(enable ? 'publish:true' : 'publish:false');
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1281
|
+
data: {
|
|
1282
|
+
agentId: 'agentId',
|
|
1283
|
+
teamId: 'teamId',
|
|
1284
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1285
|
+
},
|
|
1286
|
+
trackingId: 'track-1',
|
|
1287
|
+
} as StationLoginSuccess);
|
|
1288
|
+
|
|
1289
|
+
await webex.cc.stationLogin({
|
|
1290
|
+
teamId: 'teamId',
|
|
1291
|
+
loginOption: LoginOption.EXTENSION,
|
|
1292
|
+
dialNumber: '1001',
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
expect(callOrder).toEqual(['device.register', 'publish:true']);
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
it('should register device before publishing forced usersub false when wxApp is disabled at station login', async () => {
|
|
1299
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
1300
|
+
webex.internal.device = {
|
|
1301
|
+
userId: 'user-123',
|
|
1302
|
+
registered: false,
|
|
1303
|
+
register: jest.fn().mockImplementation(async function (this: {
|
|
1304
|
+
url?: string;
|
|
1305
|
+
registered?: boolean;
|
|
1306
|
+
}) {
|
|
1307
|
+
this.url = 'https://wdm.example.com/devices/dev-1';
|
|
1308
|
+
this.registered = true;
|
|
1309
|
+
}),
|
|
1310
|
+
};
|
|
1311
|
+
webex.internal.mercury = {
|
|
1312
|
+
connected: false,
|
|
1313
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
1314
|
+
};
|
|
1315
|
+
webex.cc.agentConfig = {
|
|
1316
|
+
agentId: 'agentId',
|
|
1317
|
+
webRtcEnabled: false,
|
|
1318
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1319
|
+
};
|
|
1320
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1321
|
+
jest
|
|
1322
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
1323
|
+
.mockResolvedValue(undefined);
|
|
1324
|
+
|
|
1325
|
+
const callOrder: string[] = [];
|
|
1326
|
+
jest.spyOn(webex.internal.device, 'register').mockImplementation(async function (this: {
|
|
1327
|
+
url?: string;
|
|
1328
|
+
registered?: boolean;
|
|
1329
|
+
}) {
|
|
1330
|
+
callOrder.push('device.register');
|
|
1331
|
+
this.url = 'https://wdm.example.com/devices/dev-1';
|
|
1332
|
+
this.registered = true;
|
|
1333
|
+
});
|
|
1334
|
+
jest
|
|
1335
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1336
|
+
.mockImplementation(async (enable: boolean) => {
|
|
1337
|
+
if (!webex.internal.device.url) {
|
|
1338
|
+
throw new Error('Device URL is unavailable for cross-client publish');
|
|
1339
|
+
}
|
|
1340
|
+
callOrder.push(enable ? 'publish:true' : 'publish:false');
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1344
|
+
data: {
|
|
1345
|
+
agentId: 'agentId',
|
|
1346
|
+
teamId: 'teamId',
|
|
1347
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1348
|
+
},
|
|
1349
|
+
trackingId: 'track-1',
|
|
1350
|
+
} as StationLoginSuccess);
|
|
1351
|
+
|
|
1352
|
+
await webex.cc.stationLogin({
|
|
1353
|
+
teamId: 'teamId',
|
|
1354
|
+
loginOption: LoginOption.EXTENSION,
|
|
1355
|
+
dialNumber: '1001',
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
expect(callOrder).toEqual(['device.register', 'publish:false']);
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
it('should retry forced false publish when wxApp is disabled at station login', async () => {
|
|
1362
|
+
jest.useFakeTimers();
|
|
1363
|
+
try {
|
|
1364
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
1365
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1366
|
+
webex.cc.agentConfig = {
|
|
1367
|
+
agentId: 'agentId',
|
|
1368
|
+
webRtcEnabled: false,
|
|
1369
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1370
|
+
};
|
|
1371
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1372
|
+
|
|
1373
|
+
const publishSpy = jest
|
|
1374
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1375
|
+
.mockRejectedValueOnce(new Error('usersub publish failed'))
|
|
1376
|
+
.mockResolvedValue(undefined);
|
|
1377
|
+
const scheduleRetrySpy = jest.spyOn(
|
|
1378
|
+
webex.cc as any,
|
|
1379
|
+
'scheduleForcedFalsePublishRetry'
|
|
1380
|
+
);
|
|
1381
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
1382
|
+
jest
|
|
1383
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
1384
|
+
.mockResolvedValue(undefined);
|
|
1385
|
+
|
|
1386
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1387
|
+
data: {
|
|
1388
|
+
agentId: 'agentId',
|
|
1389
|
+
teamId: 'teamId',
|
|
1390
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1391
|
+
},
|
|
1392
|
+
trackingId: 'track-1',
|
|
1393
|
+
} as StationLoginSuccess);
|
|
1394
|
+
|
|
1395
|
+
await webex.cc.stationLogin({
|
|
1396
|
+
teamId: 'teamId',
|
|
1397
|
+
loginOption: LoginOption.EXTENSION,
|
|
1398
|
+
dialNumber: '1001',
|
|
1399
|
+
});
|
|
1400
|
+
|
|
1401
|
+
expect(publishSpy).toHaveBeenCalledTimes(1);
|
|
1402
|
+
expect(scheduleRetrySpy).toHaveBeenCalled();
|
|
1403
|
+
expect(teardownSpy).not.toHaveBeenCalled();
|
|
1404
|
+
|
|
1405
|
+
jest.advanceTimersByTime(30_000);
|
|
1406
|
+
await publishSpy.mock.results[1]?.value;
|
|
1407
|
+
|
|
1408
|
+
expect(publishSpy).toHaveBeenCalledTimes(2);
|
|
1409
|
+
expect(publishSpy).toHaveBeenLastCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
1410
|
+
} finally {
|
|
1411
|
+
jest.useRealTimers();
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1415
|
+
it('should not publish usersub on BROWSER station login even when enableWxBetterTogether is true at init', async () => {
|
|
1416
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1417
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1418
|
+
webex.cc.agentConfig = {
|
|
1419
|
+
agentId: 'agentId',
|
|
1420
|
+
webRtcEnabled: true,
|
|
1421
|
+
loginVoiceOptions: ['BROWSER'],
|
|
1422
|
+
};
|
|
1423
|
+
webex.cc.webCallingService.loginOption = LoginOption.BROWSER;
|
|
1424
|
+
|
|
1425
|
+
const publishSpy = jest.spyOn(
|
|
1426
|
+
webex.cc['webexCrossClientService'],
|
|
1427
|
+
'setManageWebexCallingInWxcc'
|
|
1428
|
+
);
|
|
1429
|
+
|
|
1430
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1431
|
+
data: {
|
|
1432
|
+
agentId: 'agentId',
|
|
1433
|
+
teamId: 'teamId',
|
|
1434
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1435
|
+
},
|
|
1436
|
+
trackingId: 'track-1',
|
|
1437
|
+
} as StationLoginSuccess);
|
|
1438
|
+
|
|
1439
|
+
await webex.cc.stationLogin({
|
|
1440
|
+
teamId: 'teamId',
|
|
1441
|
+
loginOption: LoginOption.BROWSER,
|
|
1442
|
+
});
|
|
1443
|
+
|
|
1444
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
it('should force-publish usersub false on station login when enableWxBetterTogether is false at init', async () => {
|
|
1448
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
1449
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1450
|
+
webex.cc.agentConfig = {
|
|
1451
|
+
agentId: 'agentId',
|
|
1452
|
+
webRtcEnabled: false,
|
|
1453
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1454
|
+
};
|
|
1455
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1456
|
+
|
|
1457
|
+
const publishSpy = jest
|
|
1458
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1459
|
+
.mockResolvedValue(undefined);
|
|
1460
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
1461
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
1462
|
+
|
|
1463
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1464
|
+
data: {
|
|
1465
|
+
agentId: 'agentId',
|
|
1466
|
+
teamId: 'teamId',
|
|
1467
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1468
|
+
},
|
|
1469
|
+
trackingId: 'track-1',
|
|
1470
|
+
} as StationLoginSuccess);
|
|
1471
|
+
|
|
1472
|
+
await webex.cc.stationLogin({
|
|
1473
|
+
teamId: 'teamId',
|
|
1474
|
+
loginOption: LoginOption.EXTENSION,
|
|
1475
|
+
dialNumber: '1001',
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
expect(publishSpy).toHaveBeenCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
1479
|
+
expect(teardownSpy).toHaveBeenCalled();
|
|
1480
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
1481
|
+
expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
|
|
1482
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_SKIPPED,
|
|
1483
|
+
expect.objectContaining({
|
|
1484
|
+
enableWxBetterTogether: false,
|
|
1485
|
+
skipReason: 'flag_disabled',
|
|
1486
|
+
}),
|
|
1487
|
+
['operational', 'behavioral']
|
|
1488
|
+
);
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
it('should not emit WXAPP_SESSION_INIT_FAILED when flag-disabled cleanup fails', async () => {
|
|
1492
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
1493
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1494
|
+
webex.cc.agentConfig = {
|
|
1495
|
+
agentId: 'agentId',
|
|
1496
|
+
webRtcEnabled: false,
|
|
1497
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1498
|
+
};
|
|
1499
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1500
|
+
|
|
1501
|
+
jest
|
|
1502
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1503
|
+
.mockRejectedValue(new Error('forced false publish failed'));
|
|
1504
|
+
const metricSpy = jest.spyOn(mockMetricsManager, 'trackEvent');
|
|
1505
|
+
|
|
1506
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1507
|
+
data: {
|
|
1508
|
+
agentId: 'agentId',
|
|
1509
|
+
teamId: 'teamId',
|
|
1510
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1511
|
+
},
|
|
1512
|
+
trackingId: 'track-1',
|
|
1513
|
+
} as StationLoginSuccess);
|
|
1514
|
+
|
|
1515
|
+
await webex.cc.stationLogin({
|
|
1516
|
+
teamId: 'teamId',
|
|
1517
|
+
loginOption: LoginOption.EXTENSION,
|
|
1518
|
+
dialNumber: '1001',
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1521
|
+
expect(metricSpy).not.toHaveBeenCalledWith(
|
|
1522
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
|
|
1523
|
+
expect.anything(),
|
|
1524
|
+
expect.anything()
|
|
1525
|
+
);
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
it('should still publish usersub but record session init failed when Mercury is unavailable', async () => {
|
|
1529
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1530
|
+
webex.internal.device = {
|
|
1531
|
+
userId: 'user-123',
|
|
1532
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
1533
|
+
registered: true,
|
|
1534
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
1535
|
+
};
|
|
1536
|
+
delete webex.internal.mercury;
|
|
1537
|
+
webex.cc.agentConfig = {
|
|
1538
|
+
agentId: 'agentId',
|
|
1539
|
+
webRtcEnabled: false,
|
|
1540
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1541
|
+
};
|
|
1542
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1543
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1544
|
+
|
|
1545
|
+
const publishSpy = jest
|
|
1546
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1547
|
+
.mockResolvedValue(undefined);
|
|
1548
|
+
jest
|
|
1549
|
+
.spyOn(webex.cc['webexCrossClientService'], 'isAnswerCallsStateActive')
|
|
1550
|
+
.mockReturnValue(true);
|
|
1551
|
+
const metricSpy = jest.spyOn(mockMetricsManager, 'trackEvent');
|
|
1552
|
+
|
|
1553
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1554
|
+
data: {
|
|
1555
|
+
agentId: 'agentId',
|
|
1556
|
+
teamId: 'teamId',
|
|
1557
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1558
|
+
},
|
|
1559
|
+
trackingId: 'track-1',
|
|
1560
|
+
} as StationLoginSuccess);
|
|
1561
|
+
|
|
1562
|
+
await webex.cc.stationLogin({
|
|
1563
|
+
teamId: 'teamId',
|
|
1564
|
+
loginOption: LoginOption.EXTENSION,
|
|
1565
|
+
dialNumber: '1001',
|
|
1566
|
+
});
|
|
1567
|
+
|
|
1568
|
+
expect(publishSpy).toHaveBeenCalledWith(true, {userId: 'user-123', trackPublishMetrics: true});
|
|
1569
|
+
expect(metricSpy).toHaveBeenCalledWith(
|
|
1570
|
+
METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_FAILED,
|
|
1571
|
+
expect.objectContaining({error: 'Mercury is unavailable for wxApp mute sync'}),
|
|
1572
|
+
['operational', 'behavioral']
|
|
1573
|
+
);
|
|
1574
|
+
expect(metricSpy).not.toHaveBeenCalledWith(
|
|
1575
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_SUCCESS,
|
|
1576
|
+
expect.anything(),
|
|
1577
|
+
expect.anything()
|
|
1578
|
+
);
|
|
1579
|
+
expect(metricSpy).toHaveBeenCalledWith(
|
|
1580
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
|
|
1581
|
+
expect.objectContaining({skipReason: 'mercury_not_subscribed'}),
|
|
1582
|
+
['operational', 'behavioral']
|
|
1583
|
+
);
|
|
1584
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(true);
|
|
1585
|
+
});
|
|
1586
|
+
|
|
1587
|
+
it('should record session init failed when userId is missing but not emit success', async () => {
|
|
1588
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1589
|
+
webex.internal.device = {
|
|
1590
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
1591
|
+
registered: true,
|
|
1592
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
1593
|
+
};
|
|
1594
|
+
webex.internal.mercury = {
|
|
1595
|
+
connected: true,
|
|
1596
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
1597
|
+
on: jest.fn(),
|
|
1598
|
+
off: jest.fn(),
|
|
1599
|
+
};
|
|
1600
|
+
webex.cc.agentConfig = {
|
|
1601
|
+
agentId: 'agentId',
|
|
1602
|
+
webRtcEnabled: false,
|
|
1603
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1604
|
+
};
|
|
1605
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1606
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1607
|
+
|
|
1608
|
+
const publishSpy = jest
|
|
1609
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1610
|
+
.mockResolvedValue(undefined);
|
|
1611
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
1612
|
+
const metricSpy = jest.spyOn(mockMetricsManager, 'trackEvent');
|
|
1613
|
+
|
|
1614
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1615
|
+
data: {
|
|
1616
|
+
agentId: 'agentId',
|
|
1617
|
+
teamId: 'teamId',
|
|
1618
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1619
|
+
},
|
|
1620
|
+
trackingId: 'track-1',
|
|
1621
|
+
} as StationLoginSuccess);
|
|
1622
|
+
|
|
1623
|
+
await webex.cc.stationLogin({
|
|
1624
|
+
teamId: 'teamId',
|
|
1625
|
+
loginOption: LoginOption.EXTENSION,
|
|
1626
|
+
dialNumber: '1001',
|
|
1627
|
+
});
|
|
1628
|
+
|
|
1629
|
+
expect(publishSpy).not.toHaveBeenCalledWith(true, expect.anything());
|
|
1630
|
+
expect(metricSpy).not.toHaveBeenCalledWith(
|
|
1631
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_SUCCESS,
|
|
1632
|
+
expect.anything(),
|
|
1633
|
+
expect.anything()
|
|
1634
|
+
);
|
|
1635
|
+
expect(metricSpy).toHaveBeenCalledWith(
|
|
1636
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
|
|
1637
|
+
expect.objectContaining({skipReason: 'usersub_not_published'}),
|
|
1638
|
+
['operational', 'behavioral']
|
|
1639
|
+
);
|
|
1640
|
+
expect(metricSpy).toHaveBeenCalledWith(
|
|
1641
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
1642
|
+
expect.objectContaining({skipReason: 'user_id_unavailable'}),
|
|
1643
|
+
['operational', 'behavioral']
|
|
1644
|
+
);
|
|
1645
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(true);
|
|
1646
|
+
});
|
|
1647
|
+
|
|
1648
|
+
it('should clear stale usersub on login after prior session enabled wxApp then hard refresh', async () => {
|
|
1649
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1650
|
+
webex.cc.agentConfig = {
|
|
1651
|
+
agentId: 'agentId',
|
|
1652
|
+
webRtcEnabled: false,
|
|
1653
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1654
|
+
};
|
|
1655
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1656
|
+
webex.request = jest.fn().mockResolvedValue({body: {}});
|
|
1657
|
+
|
|
1658
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1659
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1660
|
+
data: {
|
|
1661
|
+
agentId: 'agentId',
|
|
1662
|
+
teamId: 'teamId',
|
|
1663
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1664
|
+
},
|
|
1665
|
+
trackingId: 'track-1',
|
|
1666
|
+
} as StationLoginSuccess);
|
|
1667
|
+
|
|
1668
|
+
await webex.cc.stationLogin({
|
|
1669
|
+
teamId: 'teamId',
|
|
1670
|
+
loginOption: LoginOption.EXTENSION,
|
|
1671
|
+
dialNumber: '1001',
|
|
1672
|
+
});
|
|
1673
|
+
|
|
1674
|
+
webex.cc['webexCrossClientService'].teardown();
|
|
1675
|
+
webex.cc['wxAppTelephonyMercurySync'].unsubscribe();
|
|
1676
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
1677
|
+
|
|
1678
|
+
const publishSpy = jest
|
|
1679
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1680
|
+
.mockResolvedValue(undefined);
|
|
1681
|
+
|
|
1682
|
+
await webex.cc.stationLogin({
|
|
1683
|
+
teamId: 'teamId',
|
|
1684
|
+
loginOption: LoginOption.EXTENSION,
|
|
1685
|
+
dialNumber: '1001',
|
|
1686
|
+
});
|
|
1687
|
+
|
|
1688
|
+
expect(publishSpy).toHaveBeenCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
1689
|
+
});
|
|
1690
|
+
});
|
|
1691
|
+
|
|
1692
|
+
describe('stationLogout', () => {
|
|
1693
|
+
it('should logout successfully', async () => {
|
|
1694
|
+
const data = {logoutReason: 'Logout reason'};
|
|
1695
|
+
const response = {};
|
|
1696
|
+
|
|
1697
|
+
const stationLogoutMock = jest
|
|
1698
|
+
.spyOn(webex.cc.services.agent, 'logout')
|
|
1699
|
+
.mockResolvedValue({} as StationLogoutResponse);
|
|
1700
|
+
|
|
1701
|
+
const result = await webex.cc.stationLogout(data);
|
|
1702
|
+
|
|
1703
|
+
// Verify logging calls
|
|
1704
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station logout', {
|
|
1705
|
+
module: CC_FILE,
|
|
1706
|
+
method: 'stationLogout',
|
|
1707
|
+
});
|
|
1708
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith('Agent station logout completed successfully', {
|
|
1709
|
+
module: CC_FILE,
|
|
1710
|
+
method: 'stationLogout',
|
|
1711
|
+
});
|
|
1712
|
+
|
|
1713
|
+
expect(stationLogoutMock).toHaveBeenCalledWith({data: data});
|
|
1714
|
+
// TODO: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-626777 Implement the de-register method and close the listener there
|
|
1715
|
+
// expect(mockTaskManager.unregisterIncomingCallEvent).toHaveBeenCalledWith();
|
|
1716
|
+
// expect(mockTaskManager.off).toHaveBeenCalledWith(
|
|
1717
|
+
// TASK_EVENTS.TASK_INCOMING,
|
|
1718
|
+
// expect.any(Function)
|
|
1719
|
+
// );
|
|
1720
|
+
// expect(mockTaskManager.off).toHaveBeenCalledWith(
|
|
1721
|
+
// TASK_EVENTS.TASK_HYDRATE,
|
|
1722
|
+
// expect.any(Function)
|
|
1723
|
+
// );
|
|
1724
|
+
// expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
|
|
1725
|
+
expect(result).toEqual(response);
|
|
1726
|
+
expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
|
|
1727
|
+
METRIC_EVENT_NAMES.STATION_LOGOUT_SUCCESS,
|
|
1728
|
+
METRIC_EVENT_NAMES.STATION_LOGOUT_FAILED,
|
|
1729
|
+
]);
|
|
1730
|
+
});
|
|
1731
|
+
|
|
1732
|
+
it('should not publish usersub false on logout when wxApp was never enabled', async () => {
|
|
1733
|
+
webex.internal.device = {userId: 'user-123'};
|
|
1734
|
+
const publishSpy = jest
|
|
1735
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1736
|
+
.mockResolvedValue(undefined);
|
|
1737
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
1738
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
1739
|
+
|
|
1740
|
+
jest.spyOn(webex.cc.services.agent, 'logout').mockResolvedValue({
|
|
1741
|
+
trackingId: 'track-1',
|
|
1742
|
+
} as StationLogoutResponse);
|
|
1743
|
+
|
|
1744
|
+
await webex.cc.stationLogout({logoutReason: 'Logout reason'});
|
|
1745
|
+
|
|
1746
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
1747
|
+
expect(teardownSpy).toHaveBeenCalled();
|
|
1748
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
1749
|
+
});
|
|
1750
|
+
|
|
1751
|
+
it('should publish usersub false and teardown wxApp sync on logout when wxApp was enabled', async () => {
|
|
1752
|
+
webex.internal.device = {
|
|
1753
|
+
userId: 'user-123',
|
|
1754
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
1755
|
+
registered: false,
|
|
1756
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
1757
|
+
unregister: jest.fn().mockResolvedValue(undefined),
|
|
1758
|
+
};
|
|
1759
|
+
webex.internal.mercury = {
|
|
1760
|
+
connected: false,
|
|
1761
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
1762
|
+
disconnect: jest.fn().mockResolvedValue(undefined),
|
|
1763
|
+
};
|
|
1764
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe').mockImplementation(() => {});
|
|
1765
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
1766
|
+
webex.request = jest.fn().mockResolvedValue({body: {}});
|
|
1767
|
+
webex.cc['agentConfig'] = {
|
|
1768
|
+
agentId: 'agent-123',
|
|
1769
|
+
deviceType: LoginOption.EXTENSION,
|
|
1770
|
+
} as Profile;
|
|
1771
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1772
|
+
|
|
1773
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(true);
|
|
1774
|
+
|
|
1775
|
+
const publishSpy = jest
|
|
1776
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1777
|
+
.mockResolvedValue(undefined);
|
|
1778
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
1779
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
1780
|
+
|
|
1781
|
+
jest.spyOn(webex.cc.services.agent, 'logout').mockResolvedValue({
|
|
1782
|
+
trackingId: 'track-1',
|
|
1783
|
+
} as StationLogoutResponse);
|
|
1784
|
+
|
|
1785
|
+
await webex.cc.stationLogout({logoutReason: 'Logout reason'});
|
|
1786
|
+
|
|
1787
|
+
expect(publishSpy).toHaveBeenCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
1788
|
+
expect(teardownSpy).toHaveBeenCalled();
|
|
1789
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
1790
|
+
});
|
|
1791
|
+
|
|
1792
|
+
it('should teardown wxApp state on logout even when usersub publish fails', async () => {
|
|
1793
|
+
webex.internal.device = {userId: 'user-123'};
|
|
1794
|
+
webex.cc['agentConfig'] = {
|
|
1795
|
+
agentId: 'agent-123',
|
|
1796
|
+
deviceType: LoginOption.EXTENSION,
|
|
1797
|
+
} as Profile;
|
|
1798
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1799
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1800
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1801
|
+
|
|
1802
|
+
jest.spyOn(webex.cc.services.agent, 'logout').mockResolvedValue({
|
|
1803
|
+
trackingId: 'track-1',
|
|
1804
|
+
} as StationLogoutResponse);
|
|
1805
|
+
jest
|
|
1806
|
+
.spyOn(webex.cc as never, 'publishAnswerOnWebexCrossClientState' as never)
|
|
1807
|
+
.mockRejectedValue(new Error('usersub publish failed'));
|
|
1808
|
+
const scheduleRetrySpy = jest.spyOn(
|
|
1809
|
+
webex.cc as any,
|
|
1810
|
+
'scheduleCompensatingFalsePublishRetry'
|
|
1811
|
+
);
|
|
1812
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
1813
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
1814
|
+
|
|
1815
|
+
const result = await webex.cc.stationLogout({logoutReason: 'Logout reason'});
|
|
1816
|
+
|
|
1817
|
+
expect(result).toEqual({trackingId: 'track-1'});
|
|
1818
|
+
expect(teardownSpy).toHaveBeenCalled();
|
|
1819
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
1820
|
+
expect(scheduleRetrySpy).toHaveBeenCalled();
|
|
1821
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(true);
|
|
1822
|
+
});
|
|
1823
|
+
|
|
1824
|
+
it('should retry compensating false publish when teardown usersub publish fails on logout', async () => {
|
|
1825
|
+
jest.useFakeTimers();
|
|
1826
|
+
try {
|
|
1827
|
+
webex.internal.device = {
|
|
1828
|
+
userId: 'user-123',
|
|
1829
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
1830
|
+
};
|
|
1831
|
+
webex.cc['agentConfig'] = {
|
|
1832
|
+
agentId: 'agent-123',
|
|
1833
|
+
deviceType: LoginOption.EXTENSION,
|
|
1834
|
+
} as Profile;
|
|
1835
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1836
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1837
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1838
|
+
webex.cc['webexCrossClientService'].answerCallsState = true;
|
|
1839
|
+
|
|
1840
|
+
jest.spyOn(webex.cc.services.agent, 'logout').mockResolvedValue({
|
|
1841
|
+
trackingId: 'track-1',
|
|
1842
|
+
} as StationLogoutResponse);
|
|
1843
|
+
|
|
1844
|
+
const publishSpy = jest
|
|
1845
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1846
|
+
.mockRejectedValueOnce(new Error('usersub publish failed'))
|
|
1847
|
+
.mockResolvedValue(undefined);
|
|
1848
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
1849
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
1850
|
+
jest
|
|
1851
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
1852
|
+
.mockResolvedValue(undefined);
|
|
1853
|
+
|
|
1854
|
+
const result = await webex.cc.stationLogout({logoutReason: 'Logout reason'});
|
|
1855
|
+
|
|
1856
|
+
expect(result).toEqual({trackingId: 'track-1'});
|
|
1857
|
+
expect(publishSpy).toHaveBeenCalledTimes(1);
|
|
1858
|
+
expect(publishSpy).toHaveBeenCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
1859
|
+
expect(teardownSpy).toHaveBeenCalled();
|
|
1860
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
1861
|
+
|
|
1862
|
+
jest.advanceTimersByTime(30_000);
|
|
1863
|
+
await publishSpy.mock.results[1]?.value;
|
|
1864
|
+
|
|
1865
|
+
expect(publishSpy).toHaveBeenCalledTimes(2);
|
|
1866
|
+
expect(publishSpy).toHaveBeenLastCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
1867
|
+
} finally {
|
|
1868
|
+
jest.useRealTimers();
|
|
1869
|
+
}
|
|
1870
|
+
});
|
|
1871
|
+
|
|
1872
|
+
it('should cancel compensating retry when station login succeeds before retry fires', async () => {
|
|
1873
|
+
jest.useFakeTimers();
|
|
1874
|
+
try {
|
|
1875
|
+
webex.internal.device = {
|
|
1876
|
+
userId: 'user-123',
|
|
1877
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
1878
|
+
registered: false,
|
|
1879
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
1880
|
+
unregister: jest.fn().mockResolvedValue(undefined),
|
|
1881
|
+
};
|
|
1882
|
+
webex.internal.mercury = {
|
|
1883
|
+
connected: false,
|
|
1884
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
1885
|
+
disconnect: jest.fn().mockResolvedValue(undefined),
|
|
1886
|
+
};
|
|
1887
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe').mockImplementation(() => {});
|
|
1888
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
1889
|
+
webex.cc['agentConfig'] = {
|
|
1890
|
+
agentId: 'agent-123',
|
|
1891
|
+
deviceType: LoginOption.EXTENSION,
|
|
1892
|
+
webRtcEnabled: false,
|
|
1893
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1894
|
+
} as Profile;
|
|
1895
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1896
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1897
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1898
|
+
webex.cc['webexCrossClientService'].answerCallsState = true;
|
|
1899
|
+
|
|
1900
|
+
jest.spyOn(webex.cc.services.agent, 'logout').mockResolvedValue({
|
|
1901
|
+
trackingId: 'track-1',
|
|
1902
|
+
} as StationLogoutResponse);
|
|
1903
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1904
|
+
data: {
|
|
1905
|
+
agentId: 'agent-123',
|
|
1906
|
+
teamId: 'team-123',
|
|
1907
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1908
|
+
},
|
|
1909
|
+
trackingId: 'track-2',
|
|
1910
|
+
} as StationLoginSuccess);
|
|
1911
|
+
|
|
1912
|
+
const publishSpy = jest
|
|
1913
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1914
|
+
.mockRejectedValueOnce(new Error('usersub publish failed'))
|
|
1915
|
+
.mockResolvedValue(undefined);
|
|
1916
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
1917
|
+
jest
|
|
1918
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
1919
|
+
.mockResolvedValue(undefined);
|
|
1920
|
+
|
|
1921
|
+
await webex.cc.stationLogout({logoutReason: 'Logout reason'});
|
|
1922
|
+
await webex.cc.stationLogin({
|
|
1923
|
+
teamId: 'team-123',
|
|
1924
|
+
loginOption: LoginOption.EXTENSION,
|
|
1925
|
+
dialNumber: '1001',
|
|
1926
|
+
});
|
|
1927
|
+
|
|
1928
|
+
expect(publishSpy).toHaveBeenCalledTimes(2);
|
|
1929
|
+
expect(publishSpy).toHaveBeenNthCalledWith(1, false, {userId: 'user-123', trackPublishMetrics: true});
|
|
1930
|
+
expect(publishSpy).toHaveBeenNthCalledWith(2, true, {userId: 'user-123', trackPublishMetrics: true});
|
|
1931
|
+
expect(teardownSpy).toHaveBeenCalledTimes(1);
|
|
1932
|
+
|
|
1933
|
+
jest.advanceTimersByTime(30_000);
|
|
1934
|
+
|
|
1935
|
+
expect(publishSpy).toHaveBeenCalledTimes(2);
|
|
1936
|
+
expect(teardownSpy).toHaveBeenCalledTimes(1);
|
|
1937
|
+
} finally {
|
|
1938
|
+
jest.useRealTimers();
|
|
1939
|
+
}
|
|
1940
|
+
});
|
|
1941
|
+
|
|
1942
|
+
it('should handle error during stationLogout', async () => {
|
|
1943
|
+
const data = {logoutReason: 'Logout reason'};
|
|
1944
|
+
const error = {
|
|
1945
|
+
details: {
|
|
1946
|
+
trackingId: '1234',
|
|
1947
|
+
data: {
|
|
1948
|
+
reason: 'Error while performing station logout',
|
|
1949
|
+
},
|
|
1950
|
+
},
|
|
1951
|
+
};
|
|
1952
|
+
|
|
1953
|
+
jest.spyOn(webex.cc.services.agent, 'logout').mockRejectedValue(error);
|
|
1954
|
+
|
|
1955
|
+
await expect(webex.cc.stationLogout(data)).rejects.toThrow(error.details.data.reason);
|
|
1956
|
+
|
|
1957
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station logout', {
|
|
1958
|
+
module: CC_FILE,
|
|
1959
|
+
method: 'stationLogout',
|
|
1960
|
+
});
|
|
1961
|
+
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
1962
|
+
`stationLogout failed with reason: ${error.details.data.reason}`,
|
|
1963
|
+
{module: CC_FILE, method: 'stationLogout', trackingId: error.details.trackingId}
|
|
1964
|
+
);
|
|
1965
|
+
});
|
|
1966
|
+
});
|
|
1967
|
+
|
|
1968
|
+
describe('setAgentStatus', () => {
|
|
1969
|
+
it('should set agent status successfully when status is Available', async () => {
|
|
1970
|
+
const expectedPayload = {
|
|
1971
|
+
state: 'Available',
|
|
1972
|
+
auxCodeId: '0',
|
|
1973
|
+
agentId: '123',
|
|
1974
|
+
lastStateChangeReason: 'Agent is available',
|
|
1975
|
+
};
|
|
1976
|
+
|
|
1977
|
+
const setAgentStatusMock = jest
|
|
1978
|
+
.spyOn(webex.cc.services.agent, 'stateChange')
|
|
1979
|
+
.mockResolvedValue({data: expectedPayload});
|
|
1980
|
+
|
|
1981
|
+
const result = await webex.cc.setAgentState(expectedPayload);
|
|
1982
|
+
|
|
1983
|
+
// Verify logging calls
|
|
1984
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Setting agent state', {
|
|
1985
|
+
module: CC_FILE,
|
|
1986
|
+
method: 'setAgentState',
|
|
1987
|
+
});
|
|
1988
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
1989
|
+
`Agent state changed successfully to auxCodeId: ${expectedPayload.auxCodeId}`,
|
|
1990
|
+
{
|
|
1991
|
+
module: CC_FILE,
|
|
1992
|
+
method: 'setAgentState',
|
|
1993
|
+
}
|
|
1994
|
+
);
|
|
1995
|
+
|
|
1996
|
+
expect(setAgentStatusMock).toHaveBeenCalledWith({data: expectedPayload});
|
|
1997
|
+
expect(result).toEqual({data: expectedPayload});
|
|
1998
|
+
expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
|
|
1999
|
+
METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_SUCCESS,
|
|
2000
|
+
METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_FAILED,
|
|
2001
|
+
]);
|
|
2002
|
+
});
|
|
2003
|
+
|
|
2004
|
+
it('should set agent status successfully when status is Meeting', async () => {
|
|
2005
|
+
const expectedPayload = {
|
|
2006
|
+
state: 'Meeting',
|
|
2007
|
+
auxCodeId: '12345',
|
|
2008
|
+
agentId: '123',
|
|
2009
|
+
lastStateChangeReason: 'Agent is in meeting',
|
|
990
2010
|
};
|
|
991
2011
|
|
|
992
2012
|
const setAgentStatusMock = jest
|
|
@@ -1071,6 +2091,227 @@ describe('webex.cc', () => {
|
|
|
1071
2091
|
});
|
|
1072
2092
|
});
|
|
1073
2093
|
|
|
2094
|
+
describe('Agent Wellness Break SDK contracts', () => {
|
|
2095
|
+
const wellbeingCode = {
|
|
2096
|
+
id: 'wellbeing-code',
|
|
2097
|
+
name: 'WellbeingBreak',
|
|
2098
|
+
isSystem: true,
|
|
2099
|
+
isDefault: false,
|
|
2100
|
+
};
|
|
2101
|
+
|
|
2102
|
+
beforeEach(() => {
|
|
2103
|
+
webex.cc.agentConfig = {
|
|
2104
|
+
agentId: 'agent-1',
|
|
2105
|
+
isWellnessBreakEnabled: true,
|
|
2106
|
+
} as Profile;
|
|
2107
|
+
webex.cc['updateWellnessSession']('session-1');
|
|
2108
|
+
});
|
|
2109
|
+
|
|
2110
|
+
it('fetches the system idle code once per registration', async () => {
|
|
2111
|
+
const lookup = jest
|
|
2112
|
+
.spyOn(webex.cc.services.config, 'getWellbeingBreakIdleCode')
|
|
2113
|
+
.mockResolvedValue(wellbeingCode);
|
|
2114
|
+
|
|
2115
|
+
await expect(webex.cc.getWellbeingBreakIdleCode()).resolves.toEqual(wellbeingCode);
|
|
2116
|
+
await expect(webex.cc.getWellbeingBreakIdleCode()).resolves.toEqual(wellbeingCode);
|
|
2117
|
+
|
|
2118
|
+
expect(lookup).toHaveBeenCalledTimes(1);
|
|
2119
|
+
expect(lookup).toHaveBeenCalledWith('mockOrgId');
|
|
2120
|
+
});
|
|
2121
|
+
|
|
2122
|
+
it('does not cache an idle-code lookup that completes after a new registration starts', async () => {
|
|
2123
|
+
let resolveLookup: (idleCode: typeof wellbeingCode) => void = () => undefined;
|
|
2124
|
+
jest
|
|
2125
|
+
.spyOn(webex.cc.services.config, 'getWellbeingBreakIdleCode')
|
|
2126
|
+
.mockImplementation(
|
|
2127
|
+
() =>
|
|
2128
|
+
new Promise((resolve) => {
|
|
2129
|
+
resolveLookup = resolve;
|
|
2130
|
+
})
|
|
2131
|
+
);
|
|
2132
|
+
|
|
2133
|
+
const lookupPromise = webex.cc.getWellbeingBreakIdleCode();
|
|
2134
|
+
const staleLookup = expect(lookupPromise).rejects.toThrow(
|
|
2135
|
+
'WELLNESS_BREAK_REGISTRATION_CHANGED'
|
|
2136
|
+
);
|
|
2137
|
+
jest
|
|
2138
|
+
.spyOn(webex.cc, 'connectWebsocket')
|
|
2139
|
+
.mockRejectedValue(new Error('replacement registration failed'));
|
|
2140
|
+
|
|
2141
|
+
await expect(webex.cc.register()).rejects.toThrow('replacement registration failed');
|
|
2142
|
+
resolveLookup(wellbeingCode);
|
|
2143
|
+
|
|
2144
|
+
await staleLookup;
|
|
2145
|
+
expect(webex.cc['wellbeingBreakIdleCode']).toBeUndefined();
|
|
2146
|
+
});
|
|
2147
|
+
|
|
2148
|
+
it('rejects the system idle-code lookup when wellness is disabled', async () => {
|
|
2149
|
+
webex.cc.agentConfig.isWellnessBreakEnabled = false;
|
|
2150
|
+
|
|
2151
|
+
await expect(webex.cc.getWellbeingBreakIdleCode()).rejects.toThrow(
|
|
2152
|
+
'WELLNESS_BREAK_NOT_ENABLED'
|
|
2153
|
+
);
|
|
2154
|
+
expect(webex.cc.services.config.getWellbeingBreakIdleCode).not.toHaveBeenCalled();
|
|
2155
|
+
});
|
|
2156
|
+
|
|
2157
|
+
it('invalidates the wellness session only for a current-session multi-login close', () => {
|
|
2158
|
+
webex.cc['handleWebsocketMessage'](
|
|
2159
|
+
JSON.stringify({
|
|
2160
|
+
type: CC_EVENTS.AGENT_MULTI_LOGIN,
|
|
2161
|
+
data: {
|
|
2162
|
+
type: 'AgentMultiLoginCloseSession',
|
|
2163
|
+
agentSessionId: 'another-session',
|
|
2164
|
+
},
|
|
2165
|
+
})
|
|
2166
|
+
);
|
|
2167
|
+
expect(webex.cc['currentAgentSessionId']).toBe('session-1');
|
|
2168
|
+
|
|
2169
|
+
webex.cc['handleWebsocketMessage'](
|
|
2170
|
+
JSON.stringify({
|
|
2171
|
+
type: CC_EVENTS.AGENT_MULTI_LOGIN,
|
|
2172
|
+
data: {
|
|
2173
|
+
type: 'AgentMultiLoginCloseSession',
|
|
2174
|
+
agentSessionId: 'session-1',
|
|
2175
|
+
},
|
|
2176
|
+
})
|
|
2177
|
+
);
|
|
2178
|
+
expect(webex.cc['currentAgentSessionId']).toBeUndefined();
|
|
2179
|
+
});
|
|
2180
|
+
|
|
2181
|
+
it('emits primary wellness notifications regardless of notification session metadata', () => {
|
|
2182
|
+
const emitSpy = jest.spyOn(webex.cc, 'emit');
|
|
2183
|
+
const payload = {
|
|
2184
|
+
type: 'Wellness_Break_Handler',
|
|
2185
|
+
orgId: 'mockOrgId',
|
|
2186
|
+
trackingId: 'data-notification-tracking',
|
|
2187
|
+
data: {
|
|
2188
|
+
agentId: 'agent-1',
|
|
2189
|
+
orgId: 'mockOrgId',
|
|
2190
|
+
notifType: 'Wellness_Break_Handler',
|
|
2191
|
+
notifDetails: {
|
|
2192
|
+
actionEvent: 'SUGGEST_WELLNESS_BREAK',
|
|
2193
|
+
actionText: 'How about a break?',
|
|
2194
|
+
},
|
|
2195
|
+
data: {
|
|
2196
|
+
orgId: 'mockOrgId',
|
|
2197
|
+
agentSessionId: 'notification-session',
|
|
2198
|
+
interactionId: 'interaction-1',
|
|
2199
|
+
},
|
|
2200
|
+
},
|
|
2201
|
+
};
|
|
2202
|
+
|
|
2203
|
+
webex.cc['handleWebsocketMessage'](JSON.stringify(payload));
|
|
2204
|
+
|
|
2205
|
+
expect(emitSpy).toHaveBeenCalledWith(CC_EVENTS.WELLNESS_BREAK, {
|
|
2206
|
+
agentId: 'agent-1',
|
|
2207
|
+
orgId: 'mockOrgId',
|
|
2208
|
+
agentSessionId: 'notification-session',
|
|
2209
|
+
actionEvent: 'SUGGEST_WELLNESS_BREAK',
|
|
2210
|
+
actionText: 'How about a break?',
|
|
2211
|
+
interactionId: 'interaction-1',
|
|
2212
|
+
trackingId: 'data-notification-tracking',
|
|
2213
|
+
});
|
|
2214
|
+
});
|
|
2215
|
+
|
|
2216
|
+
it.each([
|
|
2217
|
+
['SUGGEST_WELLNESS_BREAK', 'interactionId'],
|
|
2218
|
+
['WELLNESS_BREAK_NOT_ALLOWED', 'InteractionId'],
|
|
2219
|
+
])('normalizes the %s action and either interaction-id spelling', (actionEvent, key) => {
|
|
2220
|
+
const emitSpy = jest.spyOn(webex.cc, 'emit');
|
|
2221
|
+
webex.cc['handleWebsocketMessage'](
|
|
2222
|
+
JSON.stringify({
|
|
2223
|
+
type: 'Wellness_Break_Handler',
|
|
2224
|
+
orgId: 'mockOrgId',
|
|
2225
|
+
data: {
|
|
2226
|
+
agentId: 'agent-1',
|
|
2227
|
+
orgId: 'mockOrgId',
|
|
2228
|
+
notifType: 'Wellness_Break_Handler',
|
|
2229
|
+
notifDetails: {actionEvent},
|
|
2230
|
+
data: {
|
|
2231
|
+
orgId: 'mockOrgId',
|
|
2232
|
+
agentSessionId: 'session-1',
|
|
2233
|
+
[key]: 'interaction-2',
|
|
2234
|
+
},
|
|
2235
|
+
},
|
|
2236
|
+
})
|
|
2237
|
+
);
|
|
2238
|
+
|
|
2239
|
+
expect(emitSpy).toHaveBeenCalledWith(
|
|
2240
|
+
CC_EVENTS.WELLNESS_BREAK,
|
|
2241
|
+
expect.objectContaining({actionEvent, interactionId: 'interaction-2'})
|
|
2242
|
+
);
|
|
2243
|
+
});
|
|
2244
|
+
|
|
2245
|
+
it('does not promote uncontracted notification text fields', () => {
|
|
2246
|
+
const emitSpy = jest.spyOn(webex.cc, 'emit');
|
|
2247
|
+
webex.cc['handleWebsocketMessage'](
|
|
2248
|
+
JSON.stringify({
|
|
2249
|
+
type: 'Wellness_Break_Handler',
|
|
2250
|
+
orgId: 'mockOrgId',
|
|
2251
|
+
trackingId: 'notification-tracking',
|
|
2252
|
+
data: {
|
|
2253
|
+
agentId: 'agent-1',
|
|
2254
|
+
orgId: 'mockOrgId',
|
|
2255
|
+
notifType: 'Wellness_Break_Handler',
|
|
2256
|
+
notifDetails: {
|
|
2257
|
+
actionEvent: 'SUGGEST_WELLNESS_BREAK',
|
|
2258
|
+
actionText2: 'Uncontracted text',
|
|
2259
|
+
},
|
|
2260
|
+
data: {
|
|
2261
|
+
orgId: 'mockOrgId',
|
|
2262
|
+
agentSessionId: 'session-1',
|
|
2263
|
+
InteractionId: 'interaction-qa',
|
|
2264
|
+
},
|
|
2265
|
+
},
|
|
2266
|
+
})
|
|
2267
|
+
);
|
|
2268
|
+
|
|
2269
|
+
expect(emitSpy).toHaveBeenCalledWith(CC_EVENTS.WELLNESS_BREAK, {
|
|
2270
|
+
agentId: 'agent-1',
|
|
2271
|
+
orgId: 'mockOrgId',
|
|
2272
|
+
agentSessionId: 'session-1',
|
|
2273
|
+
actionEvent: 'SUGGEST_WELLNESS_BREAK',
|
|
2274
|
+
interactionId: 'interaction-qa',
|
|
2275
|
+
trackingId: 'notification-tracking',
|
|
2276
|
+
});
|
|
2277
|
+
});
|
|
2278
|
+
|
|
2279
|
+
it('ignores wellness events when the effective flag is disabled or identity is missing', () => {
|
|
2280
|
+
const emitSpy = jest.spyOn(webex.cc, 'emit');
|
|
2281
|
+
const payload = {
|
|
2282
|
+
type: 'Wellness_Break_Handler',
|
|
2283
|
+
orgId: 'mockOrgId',
|
|
2284
|
+
data: {
|
|
2285
|
+
agentId: 'agent-1',
|
|
2286
|
+
orgId: 'mockOrgId',
|
|
2287
|
+
notifType: 'Wellness_Break_Handler',
|
|
2288
|
+
notifDetails: {actionEvent: 'SUGGEST_WELLNESS_BREAK'},
|
|
2289
|
+
data: {orgId: 'mockOrgId', agentSessionId: 'session-1'},
|
|
2290
|
+
},
|
|
2291
|
+
};
|
|
2292
|
+
|
|
2293
|
+
webex.cc.agentConfig.isWellnessBreakEnabled = false;
|
|
2294
|
+
webex.cc['handleWebsocketMessage'](JSON.stringify(payload));
|
|
2295
|
+
webex.cc.agentConfig.isWellnessBreakEnabled = true;
|
|
2296
|
+
const {orgId: omittedOrgId, ...payloadWithoutEnvelopeOrg} = payload;
|
|
2297
|
+
expect(omittedOrgId).toBe('mockOrgId');
|
|
2298
|
+
webex.cc['handleWebsocketMessage'](JSON.stringify(payloadWithoutEnvelopeOrg));
|
|
2299
|
+
|
|
2300
|
+
expect(emitSpy).not.toHaveBeenCalledWith(CC_EVENTS.WELLNESS_BREAK, expect.anything());
|
|
2301
|
+
expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
|
|
2302
|
+
METRIC_EVENT_NAMES.AI_ASSISTANT_WELLNESS_EVENT_INVALID,
|
|
2303
|
+
{reason: 'wellness_disabled'},
|
|
2304
|
+
['operational']
|
|
2305
|
+
);
|
|
2306
|
+
expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
|
|
2307
|
+
METRIC_EVENT_NAMES.AI_ASSISTANT_WELLNESS_EVENT_INVALID,
|
|
2308
|
+
{reason: 'missing_wellness_identity'},
|
|
2309
|
+
['operational']
|
|
2310
|
+
);
|
|
2311
|
+
});
|
|
2312
|
+
|
|
2313
|
+
});
|
|
2314
|
+
|
|
1074
2315
|
describe('getBuddyAgents', () => {
|
|
1075
2316
|
it('should return buddy agents response when successful', async () => {
|
|
1076
2317
|
const data: BuddyAgents = {state: 'Available', mediaType: 'telephony'};
|
|
@@ -1134,6 +2375,41 @@ describe('webex.cc', () => {
|
|
|
1134
2375
|
]);
|
|
1135
2376
|
});
|
|
1136
2377
|
|
|
2378
|
+
it('should map Transfer action to an Available backend state filter', async () => {
|
|
2379
|
+
webex.cc.agentConfig = {agentProfileID: 'test-agent-profile-id'};
|
|
2380
|
+
const buddyAgentsResponse = {data: {agentList: []}};
|
|
2381
|
+
const buddyAgentsSpy = jest
|
|
2382
|
+
.spyOn(webex.cc.services.agent, 'buddyAgents')
|
|
2383
|
+
.mockResolvedValue(buddyAgentsResponse);
|
|
2384
|
+
|
|
2385
|
+
await webex.cc.getBuddyAgents({action: 'Transfer', mediaType: 'chat'});
|
|
2386
|
+
|
|
2387
|
+
expect(buddyAgentsSpy).toHaveBeenCalledWith({
|
|
2388
|
+
data: {
|
|
2389
|
+
agentProfileId: 'test-agent-profile-id',
|
|
2390
|
+
mediaType: 'chat',
|
|
2391
|
+
state: 'Available',
|
|
2392
|
+
},
|
|
2393
|
+
});
|
|
2394
|
+
});
|
|
2395
|
+
|
|
2396
|
+
it('should omit the backend state filter for Consult and default media type to telephony', async () => {
|
|
2397
|
+
webex.cc.agentConfig = {agentProfileID: 'test-agent-profile-id'};
|
|
2398
|
+
const buddyAgentsResponse = {data: {agentList: []}};
|
|
2399
|
+
const buddyAgentsSpy = jest
|
|
2400
|
+
.spyOn(webex.cc.services.agent, 'buddyAgents')
|
|
2401
|
+
.mockResolvedValue(buddyAgentsResponse);
|
|
2402
|
+
|
|
2403
|
+
await webex.cc.getBuddyAgents({action: 'Consult'});
|
|
2404
|
+
|
|
2405
|
+
expect(buddyAgentsSpy).toHaveBeenCalledWith({
|
|
2406
|
+
data: {
|
|
2407
|
+
agentProfileId: 'test-agent-profile-id',
|
|
2408
|
+
mediaType: 'telephony',
|
|
2409
|
+
},
|
|
2410
|
+
});
|
|
2411
|
+
});
|
|
2412
|
+
|
|
1137
2413
|
it('should handle error', async () => {
|
|
1138
2414
|
const data: BuddyAgents = {state: 'Available', mediaType: 'telephony'};
|
|
1139
2415
|
webex.cc.agentConfig = {
|
|
@@ -1210,11 +2486,11 @@ describe('webex.cc', () => {
|
|
|
1210
2486
|
const webSocketManagerOnSpy = jest.spyOn(webex.cc.services.webSocketManager, 'on');
|
|
1211
2487
|
await webex.cc['silentRelogin']();
|
|
1212
2488
|
|
|
1213
|
-
expect(LoggerProxy.
|
|
2489
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
|
|
1214
2490
|
module: CC_FILE,
|
|
1215
2491
|
method: 'silentRelogin',
|
|
1216
2492
|
});
|
|
1217
|
-
expect(LoggerProxy.
|
|
2493
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
1218
2494
|
'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
|
|
1219
2495
|
{module: CC_FILE, method: 'silentRelogin'}
|
|
1220
2496
|
);
|
|
@@ -1259,7 +2535,7 @@ describe('webex.cc', () => {
|
|
|
1259
2535
|
|
|
1260
2536
|
jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
|
|
1261
2537
|
await webex.cc['silentRelogin']();
|
|
1262
|
-
expect(LoggerProxy.
|
|
2538
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
|
|
1263
2539
|
module: CC_FILE,
|
|
1264
2540
|
method: 'silentRelogin',
|
|
1265
2541
|
});
|
|
@@ -1274,7 +2550,7 @@ describe('webex.cc', () => {
|
|
|
1274
2550
|
jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
|
|
1275
2551
|
|
|
1276
2552
|
await expect(webex.cc['silentRelogin']()).rejects.toThrow(error);
|
|
1277
|
-
expect(LoggerProxy.
|
|
2553
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
|
|
1278
2554
|
module: CC_FILE,
|
|
1279
2555
|
method: 'silentRelogin',
|
|
1280
2556
|
});
|
|
@@ -1316,7 +2592,7 @@ describe('webex.cc', () => {
|
|
|
1316
2592
|
|
|
1317
2593
|
await webex.cc['silentRelogin']();
|
|
1318
2594
|
|
|
1319
|
-
expect(LoggerProxy.
|
|
2595
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
|
|
1320
2596
|
module: CC_FILE,
|
|
1321
2597
|
method: 'silentRelogin',
|
|
1322
2598
|
});
|
|
@@ -1361,6 +2637,82 @@ describe('webex.cc', () => {
|
|
|
1361
2637
|
expect(webex.cc.agentConfig.deviceType).toBe(LoginOption.AGENT_DN);
|
|
1362
2638
|
expect(webex.cc.agentConfig.dn).toBe('67890');
|
|
1363
2639
|
});
|
|
2640
|
+
|
|
2641
|
+
it('should publish usersub true on silent relogin when enableWxBetterTogether is true at init', async () => {
|
|
2642
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
2643
|
+
webex.internal.device = {
|
|
2644
|
+
userId: 'user-123',
|
|
2645
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
2646
|
+
registered: true,
|
|
2647
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
2648
|
+
};
|
|
2649
|
+
webex.internal.mercury.connected = true;
|
|
2650
|
+
webex.internal.mercury.connect = jest.fn().mockResolvedValue(undefined);
|
|
2651
|
+
webex.cc.agentConfig = {
|
|
2652
|
+
agentId: 'agentId',
|
|
2653
|
+
agentProfileID: 'test-agent-profile-id',
|
|
2654
|
+
isAgentLoggedIn: false,
|
|
2655
|
+
} as Profile;
|
|
2656
|
+
|
|
2657
|
+
jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
|
|
2658
|
+
data: {
|
|
2659
|
+
auxCodeId: 'auxCodeId',
|
|
2660
|
+
agentId: 'agentId',
|
|
2661
|
+
deviceType: LoginOption.EXTENSION,
|
|
2662
|
+
dn: '1001',
|
|
2663
|
+
teamId: 'teamId',
|
|
2664
|
+
lastStateChangeTimestamp: 1738575135188,
|
|
2665
|
+
lastIdleCodeChangeTimestamp: 1738575135189,
|
|
2666
|
+
},
|
|
2667
|
+
});
|
|
2668
|
+
|
|
2669
|
+
const publishSpy = jest
|
|
2670
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
2671
|
+
.mockResolvedValue(undefined);
|
|
2672
|
+
const subscribeSpy = jest
|
|
2673
|
+
.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe')
|
|
2674
|
+
.mockImplementation(() => {});
|
|
2675
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
2676
|
+
|
|
2677
|
+
await webex.cc['silentRelogin']();
|
|
2678
|
+
|
|
2679
|
+
expect(publishSpy).toHaveBeenCalledWith(true, {userId: 'user-123', trackPublishMetrics: true});
|
|
2680
|
+
expect(subscribeSpy).toHaveBeenCalledWith('agentId', expect.any(Function));
|
|
2681
|
+
});
|
|
2682
|
+
|
|
2683
|
+
it('should force-publish usersub false on silent relogin when enableWxBetterTogether is false at init', async () => {
|
|
2684
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
2685
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
2686
|
+
webex.cc.agentConfig = {
|
|
2687
|
+
agentId: 'agentId',
|
|
2688
|
+
agentProfileID: 'test-agent-profile-id',
|
|
2689
|
+
isAgentLoggedIn: false,
|
|
2690
|
+
} as Profile;
|
|
2691
|
+
|
|
2692
|
+
jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
|
|
2693
|
+
data: {
|
|
2694
|
+
auxCodeId: 'auxCodeId',
|
|
2695
|
+
agentId: 'agentId',
|
|
2696
|
+
deviceType: LoginOption.EXTENSION,
|
|
2697
|
+
dn: '1001',
|
|
2698
|
+
teamId: 'teamId',
|
|
2699
|
+
lastStateChangeTimestamp: 1738575135188,
|
|
2700
|
+
lastIdleCodeChangeTimestamp: 1738575135189,
|
|
2701
|
+
},
|
|
2702
|
+
});
|
|
2703
|
+
|
|
2704
|
+
const publishSpy = jest
|
|
2705
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
2706
|
+
.mockResolvedValue(undefined);
|
|
2707
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
2708
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
2709
|
+
|
|
2710
|
+
await webex.cc['silentRelogin']();
|
|
2711
|
+
|
|
2712
|
+
expect(publishSpy).toHaveBeenCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
2713
|
+
expect(teardownSpy).toHaveBeenCalled();
|
|
2714
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
2715
|
+
});
|
|
1364
2716
|
});
|
|
1365
2717
|
|
|
1366
2718
|
describe('setupEventListeners()', () => {
|
|
@@ -1500,6 +2852,27 @@ describe('webex.cc', () => {
|
|
|
1500
2852
|
});
|
|
1501
2853
|
});
|
|
1502
2854
|
|
|
2855
|
+
describe('getQueues', () => {
|
|
2856
|
+
it('delegates to the queue service when successful', async () => {
|
|
2857
|
+
const mockQueuesResponse = [{queueId: 'queue1', queueName: 'Queue 1'}];
|
|
2858
|
+
const queueSpy = jest
|
|
2859
|
+
.spyOn(webex.cc.queue, 'getQueues')
|
|
2860
|
+
.mockResolvedValue(mockQueuesResponse as any);
|
|
2861
|
+
|
|
2862
|
+
const result = await webex.cc.getQueues({page: 1});
|
|
2863
|
+
|
|
2864
|
+
expect(queueSpy).toHaveBeenCalledWith({page: 1});
|
|
2865
|
+
expect(result).toBe(mockQueuesResponse);
|
|
2866
|
+
});
|
|
2867
|
+
|
|
2868
|
+
it('propagates queue service errors', async () => {
|
|
2869
|
+
const error = new Error('Test error.');
|
|
2870
|
+
jest.spyOn(webex.cc.queue, 'getQueues').mockRejectedValue(error);
|
|
2871
|
+
|
|
2872
|
+
await expect(webex.cc.getQueues()).rejects.toThrow('Test error.');
|
|
2873
|
+
});
|
|
2874
|
+
});
|
|
2875
|
+
|
|
1503
2876
|
describe('uploadLogs', () => {
|
|
1504
2877
|
it('should upload logs successfully', async () => {
|
|
1505
2878
|
const uploadLogsMock = jest.spyOn(webex.cc.webexRequest, 'uploadLogs').mockResolvedValue({
|
|
@@ -1529,6 +2902,7 @@ describe('webex.cc', () => {
|
|
|
1529
2902
|
|
|
1530
2903
|
describe('unregister', () => {
|
|
1531
2904
|
let mockWebSocketManager;
|
|
2905
|
+
let mockRTDWebSocketManager;
|
|
1532
2906
|
let mercuryDisconnectSpy;
|
|
1533
2907
|
let deviceUnregisterSpy;
|
|
1534
2908
|
|
|
@@ -1545,8 +2919,15 @@ describe('webex.cc', () => {
|
|
|
1545
2919
|
off: jest.fn(),
|
|
1546
2920
|
on: jest.fn(),
|
|
1547
2921
|
};
|
|
2922
|
+
mockRTDWebSocketManager = {
|
|
2923
|
+
isSocketClosed: false,
|
|
2924
|
+
close: jest.fn(),
|
|
2925
|
+
off: jest.fn(),
|
|
2926
|
+
on: jest.fn(),
|
|
2927
|
+
};
|
|
1548
2928
|
|
|
1549
2929
|
webex.cc.services.webSocketManager = mockWebSocketManager;
|
|
2930
|
+
webex.cc.services.rtdWebSocketManager = mockRTDWebSocketManager;
|
|
1550
2931
|
|
|
1551
2932
|
webex.internal = webex.internal || {};
|
|
1552
2933
|
webex.internal.mercury = {
|
|
@@ -1562,6 +2943,32 @@ describe('webex.cc', () => {
|
|
|
1562
2943
|
deviceUnregisterSpy = jest.spyOn(webex.internal.device, 'unregister');
|
|
1563
2944
|
});
|
|
1564
2945
|
|
|
2946
|
+
it('invalidates wellness context before deregistration cleanup can fail', async () => {
|
|
2947
|
+
const cleanupError = new Error('cleanup failed');
|
|
2948
|
+
let rejectCleanup: (error: Error) => void = () => undefined;
|
|
2949
|
+
const cleanup = new Promise((resolve, reject) => {
|
|
2950
|
+
rejectCleanup = reject;
|
|
2951
|
+
});
|
|
2952
|
+
webex.cc.agentConfig = {
|
|
2953
|
+
...webex.cc.agentConfig,
|
|
2954
|
+
isWellnessBreakEnabled: true,
|
|
2955
|
+
};
|
|
2956
|
+
webex.cc['updateWellnessSession']('session-1');
|
|
2957
|
+
jest.spyOn(webex.cc as any, 'teardownWxAppLocalState').mockReturnValue(cleanup);
|
|
2958
|
+
|
|
2959
|
+
const deregistration = webex.cc.deregister();
|
|
2960
|
+
const rejection = expect(deregistration).rejects.toThrow(cleanupError);
|
|
2961
|
+
|
|
2962
|
+
expect(webex.cc['currentAgentSessionId']).toBeUndefined();
|
|
2963
|
+
await expect(webex.cc.apiAIAssistant.requestWellnessBreak()).rejects.toThrow(
|
|
2964
|
+
'WELLNESS_BREAK_AGENT_SESSION_REQUIRED'
|
|
2965
|
+
);
|
|
2966
|
+
|
|
2967
|
+
rejectCleanup(cleanupError);
|
|
2968
|
+
await rejection;
|
|
2969
|
+
expect(webex.cc['currentAgentSessionId']).toBeUndefined();
|
|
2970
|
+
});
|
|
2971
|
+
|
|
1565
2972
|
it('should unregister successfully and clean up all resources when webrtc is enabled', async () => {
|
|
1566
2973
|
webex.cc.services.rtdWebSocketManager = {
|
|
1567
2974
|
isSocketClosed: false,
|
|
@@ -1570,6 +2977,18 @@ describe('webex.cc', () => {
|
|
|
1570
2977
|
on: jest.fn(),
|
|
1571
2978
|
} as any;
|
|
1572
2979
|
|
|
2980
|
+
webex.internal.device = {
|
|
2981
|
+
...webex.internal.device,
|
|
2982
|
+
userId: 'user-123',
|
|
2983
|
+
unregister: jest.fn().mockResolvedValue(undefined),
|
|
2984
|
+
};
|
|
2985
|
+
deviceUnregisterSpy = jest.spyOn(webex.internal.device, 'unregister');
|
|
2986
|
+
const publishSpy = jest
|
|
2987
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
2988
|
+
.mockResolvedValue(undefined);
|
|
2989
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
2990
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
2991
|
+
|
|
1573
2992
|
await webex.cc.deregister();
|
|
1574
2993
|
|
|
1575
2994
|
expect(mockTaskManager.off).toHaveBeenCalledWith(
|
|
@@ -1580,6 +2999,10 @@ describe('webex.cc', () => {
|
|
|
1580
2999
|
TASK_EVENTS.TASK_HYDRATE,
|
|
1581
3000
|
expect.any(Function)
|
|
1582
3001
|
);
|
|
3002
|
+
expect(mockTaskManager.off).toHaveBeenCalledWith(
|
|
3003
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
3004
|
+
expect.any(Function)
|
|
3005
|
+
);
|
|
1583
3006
|
expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
|
|
1584
3007
|
expect(webex.cc.services.rtdWebSocketManager.off).toHaveBeenCalledWith(
|
|
1585
3008
|
'message',
|
|
@@ -1636,17 +3059,57 @@ describe('webex.cc', () => {
|
|
|
1636
3059
|
const [, hydrateCallback] = hydrateCalls[0];
|
|
1637
3060
|
expect(hydrateCallback).toBe(webex.cc['handleTaskHydrate']);
|
|
1638
3061
|
|
|
3062
|
+
const multiLoginHydrateCalls = mockTaskManager.off.mock.calls.filter(
|
|
3063
|
+
([evt]) => evt === TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE
|
|
3064
|
+
);
|
|
3065
|
+
expect(multiLoginHydrateCalls).toHaveLength(1);
|
|
3066
|
+
const [, multiLoginHydrateCallback] = multiLoginHydrateCalls[0];
|
|
3067
|
+
expect(multiLoginHydrateCallback).toBe(webex.cc['handleTaskMultiLoginHydrate']);
|
|
3068
|
+
|
|
1639
3069
|
const messageCalls = mockWebSocketManager.off.mock.calls.filter(([evt]) => evt === 'message');
|
|
1640
3070
|
expect(messageCalls).toHaveLength(1);
|
|
1641
3071
|
const [, messageCallback] = messageCalls[0];
|
|
1642
3072
|
expect(messageCallback).toBe(webex.cc['handleWebsocketMessage']);
|
|
1643
3073
|
|
|
1644
|
-
const connectionCalls = webex.cc.services.connectionService.off.mock.calls.filter(
|
|
1645
|
-
([evt]) => evt === 'connectionLost'
|
|
1646
|
-
);
|
|
1647
|
-
expect(connectionCalls).toHaveLength(1);
|
|
1648
|
-
const [, connectionCallback] = connectionCalls[0];
|
|
1649
|
-
expect(connectionCallback).toBe(webex.cc['handleConnectionLost']);
|
|
3074
|
+
const connectionCalls = webex.cc.services.connectionService.off.mock.calls.filter(
|
|
3075
|
+
([evt]) => evt === 'connectionLost'
|
|
3076
|
+
);
|
|
3077
|
+
expect(connectionCalls).toHaveLength(1);
|
|
3078
|
+
const [, connectionCallback] = connectionCalls[0];
|
|
3079
|
+
expect(connectionCallback).toBe(webex.cc['handleConnectionLost']);
|
|
3080
|
+
|
|
3081
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
3082
|
+
expect(teardownSpy).toHaveBeenCalled();
|
|
3083
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
3084
|
+
});
|
|
3085
|
+
|
|
3086
|
+
it('should publish usersub false before device unregister when wxApp is active on deregister', async () => {
|
|
3087
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
3088
|
+
webex.cc['webexCrossClientService'].answerCallsState = true;
|
|
3089
|
+
webex.internal.device = {
|
|
3090
|
+
userId: 'user-123',
|
|
3091
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
3092
|
+
unregister: jest.fn().mockResolvedValue(undefined),
|
|
3093
|
+
};
|
|
3094
|
+
deviceUnregisterSpy = jest.spyOn(webex.internal.device, 'unregister');
|
|
3095
|
+
|
|
3096
|
+
const callOrder: string[] = [];
|
|
3097
|
+
jest
|
|
3098
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
3099
|
+
.mockImplementation(async (enable: boolean) => {
|
|
3100
|
+
if (enable === false && !webex.internal.device.url) {
|
|
3101
|
+
throw new Error('Device URL is unavailable for cross-client publish');
|
|
3102
|
+
}
|
|
3103
|
+
callOrder.push(enable ? 'publish:true' : 'publish:false');
|
|
3104
|
+
});
|
|
3105
|
+
deviceUnregisterSpy.mockImplementation(async () => {
|
|
3106
|
+
callOrder.push('device.unregister');
|
|
3107
|
+
webex.internal.device.url = undefined;
|
|
3108
|
+
});
|
|
3109
|
+
|
|
3110
|
+
await webex.cc.deregister();
|
|
3111
|
+
|
|
3112
|
+
expect(callOrder).toEqual(['publish:false', 'device.unregister']);
|
|
1650
3113
|
});
|
|
1651
3114
|
|
|
1652
3115
|
it('should skip webCallingService and internal cleanup when webrtc is disabled', async () => {
|
|
@@ -1661,6 +3124,10 @@ describe('webex.cc', () => {
|
|
|
1661
3124
|
TASK_EVENTS.TASK_HYDRATE,
|
|
1662
3125
|
expect.any(Function)
|
|
1663
3126
|
);
|
|
3127
|
+
expect(mockTaskManager.off).toHaveBeenCalledWith(
|
|
3128
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
3129
|
+
expect.any(Function)
|
|
3130
|
+
);
|
|
1664
3131
|
expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
|
|
1665
3132
|
expect(webex.cc.services.connectionService.off).toHaveBeenCalledWith(
|
|
1666
3133
|
'connectionLost',
|
|
@@ -1670,6 +3137,7 @@ describe('webex.cc', () => {
|
|
|
1670
3137
|
expect(webex.internal.mercury.off).not.toHaveBeenCalled();
|
|
1671
3138
|
expect(mercuryDisconnectSpy).not.toHaveBeenCalled();
|
|
1672
3139
|
expect(deviceUnregisterSpy).not.toHaveBeenCalled();
|
|
3140
|
+
expect(mockRTDWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
|
|
1673
3141
|
});
|
|
1674
3142
|
|
|
1675
3143
|
it('should skip internal mercury cleanup when loginVoiceOptions does not include BROWSER', async () => {
|
|
@@ -1686,10 +3154,140 @@ describe('webex.cc', () => {
|
|
|
1686
3154
|
expect(mercuryDisconnectSpy).not.toHaveBeenCalled();
|
|
1687
3155
|
expect(deviceUnregisterSpy).not.toHaveBeenCalled();
|
|
1688
3156
|
|
|
3157
|
+
expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
|
|
3158
|
+
expect(mockRTDWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
|
|
3159
|
+
expect(webex.cc.agentConfig).toBeNull();
|
|
3160
|
+
});
|
|
3161
|
+
|
|
3162
|
+
it('should teardown wxApp state on deregister even when usersub publish fails', async () => {
|
|
3163
|
+
webex.cc.agentConfig = {
|
|
3164
|
+
agentId: 'agentId',
|
|
3165
|
+
webRtcEnabled: false,
|
|
3166
|
+
loginVoiceOptions: [LoginOption.EXTENSION],
|
|
3167
|
+
};
|
|
3168
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
3169
|
+
webex.cc['webexCrossClientService'].answerCallsState = true;
|
|
3170
|
+
webex.internal.device = {
|
|
3171
|
+
userId: 'user-123',
|
|
3172
|
+
unregister: jest.fn().mockResolvedValue(undefined),
|
|
3173
|
+
};
|
|
3174
|
+
|
|
3175
|
+
jest
|
|
3176
|
+
.spyOn(webex.cc as never, 'publishAnswerOnWebexCrossClientState' as never)
|
|
3177
|
+
.mockRejectedValue(new Error('usersub publish failed'));
|
|
3178
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
3179
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
3180
|
+
|
|
3181
|
+
await expect(webex.cc.deregister()).rejects.toThrow('usersub publish failed');
|
|
3182
|
+
|
|
3183
|
+
expect(teardownSpy).toHaveBeenCalled();
|
|
3184
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
3185
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
1689
3186
|
expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
|
|
1690
3187
|
expect(webex.cc.agentConfig).toBeNull();
|
|
1691
3188
|
});
|
|
1692
3189
|
|
|
3190
|
+
it('should publish forced false during teardown when enable publish is still in flight', async () => {
|
|
3191
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
3192
|
+
webex.internal.device = {
|
|
3193
|
+
userId: 'user-123',
|
|
3194
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
3195
|
+
};
|
|
3196
|
+
|
|
3197
|
+
let resolveEnable: () => void = () => {};
|
|
3198
|
+
const enableGate = new Promise<void>((resolve) => {
|
|
3199
|
+
resolveEnable = resolve;
|
|
3200
|
+
});
|
|
3201
|
+
|
|
3202
|
+
const publishSpy = jest
|
|
3203
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
3204
|
+
.mockImplementation(async (enable: boolean) => {
|
|
3205
|
+
if (enable) {
|
|
3206
|
+
await enableGate;
|
|
3207
|
+
}
|
|
3208
|
+
});
|
|
3209
|
+
jest
|
|
3210
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
3211
|
+
.mockResolvedValue(undefined);
|
|
3212
|
+
|
|
3213
|
+
expect(webex.cc['webexCrossClientService'].answerCallsState).toBe(false);
|
|
3214
|
+
|
|
3215
|
+
const enableInFlight = (webex.cc as any).publishAnswerOnWebexCrossClientState(true);
|
|
3216
|
+
|
|
3217
|
+
await (webex.cc as any).teardownWxAppLocalState();
|
|
3218
|
+
|
|
3219
|
+
expect(publishSpy).toHaveBeenCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
3220
|
+
|
|
3221
|
+
resolveEnable();
|
|
3222
|
+
await enableInFlight;
|
|
3223
|
+
});
|
|
3224
|
+
|
|
3225
|
+
it('should release wxApp-owned Mercury and device on deregister', async () => {
|
|
3226
|
+
webex.cc.agentConfig = {
|
|
3227
|
+
agentId: 'agentId',
|
|
3228
|
+
webRtcEnabled: false,
|
|
3229
|
+
loginVoiceOptions: [LoginOption.EXTENSION],
|
|
3230
|
+
};
|
|
3231
|
+
webex.cc['wxAppMercuryConnectedByCc'] = true;
|
|
3232
|
+
webex.cc['wxAppDeviceRegisteredByCc'] = true;
|
|
3233
|
+
webex.internal.mercury.connected = true;
|
|
3234
|
+
webex.internal.device.registered = true;
|
|
3235
|
+
|
|
3236
|
+
const mercuryDisconnectSpy = jest
|
|
3237
|
+
.spyOn(webex.internal.mercury, 'disconnect')
|
|
3238
|
+
.mockResolvedValue(undefined);
|
|
3239
|
+
const deviceUnregisterSpy = jest
|
|
3240
|
+
.spyOn(webex.internal.device, 'unregister')
|
|
3241
|
+
.mockResolvedValue(undefined);
|
|
3242
|
+
|
|
3243
|
+
await webex.cc.deregister();
|
|
3244
|
+
|
|
3245
|
+
expect(mercuryDisconnectSpy).toHaveBeenCalled();
|
|
3246
|
+
expect(deviceUnregisterSpy).toHaveBeenCalled();
|
|
3247
|
+
expect(webex.cc['wxAppMercuryConnectedByCc']).toBe(false);
|
|
3248
|
+
expect(webex.cc['wxAppDeviceRegisteredByCc']).toBe(false);
|
|
3249
|
+
});
|
|
3250
|
+
|
|
3251
|
+
it('should retain wxApp Mercury ownership when disconnect fails and retry cleanup', async () => {
|
|
3252
|
+
webex.cc['wxAppMercuryConnectedByCc'] = true;
|
|
3253
|
+
webex.internal.mercury.connected = true;
|
|
3254
|
+
|
|
3255
|
+
const mercuryDisconnectSpy = jest
|
|
3256
|
+
.spyOn(webex.internal.mercury, 'disconnect')
|
|
3257
|
+
.mockRejectedValueOnce(new Error('disconnect failed'))
|
|
3258
|
+
.mockResolvedValueOnce(undefined);
|
|
3259
|
+
|
|
3260
|
+
await webex.cc['releaseWxAppMercuryResources']();
|
|
3261
|
+
|
|
3262
|
+
expect(mercuryDisconnectSpy).toHaveBeenCalledTimes(1);
|
|
3263
|
+
expect(webex.cc['wxAppMercuryConnectedByCc']).toBe(true);
|
|
3264
|
+
|
|
3265
|
+
await webex.cc['releaseWxAppMercuryResources']();
|
|
3266
|
+
|
|
3267
|
+
expect(mercuryDisconnectSpy).toHaveBeenCalledTimes(2);
|
|
3268
|
+
expect(webex.cc['wxAppMercuryConnectedByCc']).toBe(false);
|
|
3269
|
+
});
|
|
3270
|
+
|
|
3271
|
+
it('should retain wxApp device ownership when unregister fails and retry cleanup', async () => {
|
|
3272
|
+
webex.cc['wxAppDeviceRegisteredByCc'] = true;
|
|
3273
|
+
webex.internal.device.registered = true;
|
|
3274
|
+
|
|
3275
|
+
const deviceUnregisterSpy = jest
|
|
3276
|
+
.spyOn(webex.internal.device, 'unregister')
|
|
3277
|
+
.mockRejectedValueOnce(new Error('unregister failed'))
|
|
3278
|
+
.mockResolvedValueOnce(undefined);
|
|
3279
|
+
|
|
3280
|
+
await webex.cc['releaseWxAppMercuryResources']();
|
|
3281
|
+
|
|
3282
|
+
expect(deviceUnregisterSpy).toHaveBeenCalledTimes(1);
|
|
3283
|
+
expect(webex.cc['wxAppDeviceRegisteredByCc']).toBe(true);
|
|
3284
|
+
|
|
3285
|
+
await webex.cc['releaseWxAppMercuryResources']();
|
|
3286
|
+
|
|
3287
|
+
expect(deviceUnregisterSpy).toHaveBeenCalledTimes(2);
|
|
3288
|
+
expect(webex.cc['wxAppDeviceRegisteredByCc']).toBe(false);
|
|
3289
|
+
});
|
|
3290
|
+
|
|
1693
3291
|
it('should handle errors during unregister and track metrics', async () => {
|
|
1694
3292
|
const mockError = new Error('Failed to deregister device');
|
|
1695
3293
|
webex.internal.device.unregister.mockRejectedValue(mockError);
|
|
@@ -1704,6 +3302,10 @@ describe('webex.cc', () => {
|
|
|
1704
3302
|
TASK_EVENTS.TASK_HYDRATE,
|
|
1705
3303
|
expect.any(Function)
|
|
1706
3304
|
);
|
|
3305
|
+
expect(mockTaskManager.off).toHaveBeenCalledWith(
|
|
3306
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
3307
|
+
expect.any(Function)
|
|
3308
|
+
);
|
|
1707
3309
|
|
|
1708
3310
|
expect(LoggerProxy.error).toHaveBeenCalledWith(`Error during deregister: ${mockError}`, {
|
|
1709
3311
|
module: CC_FILE,
|
|
@@ -1726,7 +3328,7 @@ describe('webex.cc', () => {
|
|
|
1726
3328
|
|
|
1727
3329
|
beforeEach(() => {
|
|
1728
3330
|
emitSpy = jest.spyOn(webex.cc, 'emit');
|
|
1729
|
-
messageCallback =
|
|
3331
|
+
messageCallback = webex.cc['handleWebsocketMessage'];
|
|
1730
3332
|
});
|
|
1731
3333
|
|
|
1732
3334
|
it('should emit AGENT_STATION_LOGIN_SUCCESS on CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS with mapped payload', () => {
|
|
@@ -1936,6 +3538,55 @@ describe('webex.cc', () => {
|
|
|
1936
3538
|
expect(result).toEqual(mockResp);
|
|
1937
3539
|
});
|
|
1938
3540
|
|
|
3541
|
+
it('should preserve enableWxBetterTogether init flag across profile update logout/login', async () => {
|
|
3542
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
3543
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
3544
|
+
webex.cc.agentConfig = {
|
|
3545
|
+
...webex.cc.agentConfig,
|
|
3546
|
+
agentId: 'agentId',
|
|
3547
|
+
webRtcEnabled: false,
|
|
3548
|
+
loginVoiceOptions: ['BROWSER', 'EXTENSION'],
|
|
3549
|
+
} as Profile;
|
|
3550
|
+
webex.cc.webCallingService.loginOption = LoginOption.BROWSER;
|
|
3551
|
+
webex.cc.webCallingService.setLoginOption = jest.fn((loginOption: LoginOption) => {
|
|
3552
|
+
webex.cc.webCallingService.loginOption = loginOption;
|
|
3553
|
+
});
|
|
3554
|
+
webex.cc['webexCrossClientService'].answerCallsState = true;
|
|
3555
|
+
|
|
3556
|
+
const publishSpy = jest
|
|
3557
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
3558
|
+
.mockResolvedValue(undefined);
|
|
3559
|
+
jest
|
|
3560
|
+
.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe')
|
|
3561
|
+
.mockImplementation(() => {});
|
|
3562
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
3563
|
+
jest.spyOn(webex.cc as any, 'releaseWxAppMercuryResources').mockResolvedValue(undefined);
|
|
3564
|
+
|
|
3565
|
+
jest.spyOn(webex.cc.services.agent, 'logout').mockResolvedValue({
|
|
3566
|
+
trackingId: 'track-logout',
|
|
3567
|
+
} as StationLogoutResponse);
|
|
3568
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
3569
|
+
data: {
|
|
3570
|
+
agentId: 'agentId',
|
|
3571
|
+
teamId: 'teamId',
|
|
3572
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
3573
|
+
},
|
|
3574
|
+
trackingId: 'track-login',
|
|
3575
|
+
} as StationLoginSuccess);
|
|
3576
|
+
|
|
3577
|
+
webex.internal.mercury.connected = true;
|
|
3578
|
+
|
|
3579
|
+
await webex.cc.updateAgentProfile({
|
|
3580
|
+
teamId: 'teamId',
|
|
3581
|
+
loginOption: LoginOption.EXTENSION,
|
|
3582
|
+
dialNumber: '1001',
|
|
3583
|
+
});
|
|
3584
|
+
|
|
3585
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(true);
|
|
3586
|
+
expect(publishSpy).toHaveBeenCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
3587
|
+
expect(publishSpy).toHaveBeenCalledWith(true, {userId: 'user-123', trackPublishMetrics: true});
|
|
3588
|
+
});
|
|
3589
|
+
|
|
1939
3590
|
it('should use provided teamId if passed in payload', async () => {
|
|
1940
3591
|
const dataWithTeam = {
|
|
1941
3592
|
teamId: 'newTeam',
|
|
@@ -2320,4 +3971,444 @@ describe('webex.cc', () => {
|
|
|
2320
3971
|
expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'acceptPreviewContact', CC_FILE);
|
|
2321
3972
|
});
|
|
2322
3973
|
});
|
|
3974
|
+
|
|
3975
|
+
describe('skipPreviewContact', () => {
|
|
3976
|
+
const previewPayload = {
|
|
3977
|
+
interactionId: 'interaction-123',
|
|
3978
|
+
campaignId: 'campaign-456',
|
|
3979
|
+
};
|
|
3980
|
+
|
|
3981
|
+
it('should skip preview contact successfully', async () => {
|
|
3982
|
+
const mockResponse = {trackingId: 'track-123'} as AgentContact;
|
|
3983
|
+
|
|
3984
|
+
const skipPreviewContactMock = jest
|
|
3985
|
+
.spyOn(webex.cc.services.dialer, 'skipPreviewContact')
|
|
3986
|
+
.mockResolvedValue(mockResponse);
|
|
3987
|
+
|
|
3988
|
+
const result = await webex.cc.skipPreviewContact(previewPayload);
|
|
3989
|
+
|
|
3990
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Skipping campaign preview contact', {
|
|
3991
|
+
module: CC_FILE,
|
|
3992
|
+
method: 'skipPreviewContact',
|
|
3993
|
+
});
|
|
3994
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
3995
|
+
'Campaign preview contact skipped successfully',
|
|
3996
|
+
{
|
|
3997
|
+
module: CC_FILE,
|
|
3998
|
+
method: 'skipPreviewContact',
|
|
3999
|
+
trackingId: 'track-123',
|
|
4000
|
+
interactionId: previewPayload.interactionId,
|
|
4001
|
+
}
|
|
4002
|
+
);
|
|
4003
|
+
|
|
4004
|
+
expect(skipPreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
|
|
4005
|
+
expect(result).toEqual(mockResponse);
|
|
4006
|
+
});
|
|
4007
|
+
|
|
4008
|
+
it('should handle error during skipPreviewContact', async () => {
|
|
4009
|
+
getErrorDetailsSpy.mockRestore();
|
|
4010
|
+
getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
|
|
4011
|
+
|
|
4012
|
+
const error = {
|
|
4013
|
+
details: {
|
|
4014
|
+
trackingId: '1234',
|
|
4015
|
+
data: {
|
|
4016
|
+
reason: 'Error while performing skipPreviewContact',
|
|
4017
|
+
},
|
|
4018
|
+
},
|
|
4019
|
+
};
|
|
4020
|
+
|
|
4021
|
+
jest.spyOn(webex.cc.services.dialer, 'skipPreviewContact').mockRejectedValue(error);
|
|
4022
|
+
|
|
4023
|
+
await expect(webex.cc.skipPreviewContact(previewPayload)).rejects.toThrow(
|
|
4024
|
+
error.details.data.reason
|
|
4025
|
+
);
|
|
4026
|
+
|
|
4027
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Skipping campaign preview contact', {
|
|
4028
|
+
module: CC_FILE,
|
|
4029
|
+
method: 'skipPreviewContact',
|
|
4030
|
+
});
|
|
4031
|
+
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
4032
|
+
`skipPreviewContact failed with reason: ${error.details.data.reason}`,
|
|
4033
|
+
{module: CC_FILE, method: 'skipPreviewContact', trackingId: error.details.trackingId}
|
|
4034
|
+
);
|
|
4035
|
+
expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'skipPreviewContact', CC_FILE);
|
|
4036
|
+
});
|
|
4037
|
+
});
|
|
4038
|
+
|
|
4039
|
+
describe('removePreviewContact', () => {
|
|
4040
|
+
const previewPayload = {
|
|
4041
|
+
interactionId: 'interaction-123',
|
|
4042
|
+
campaignId: 'campaign-456',
|
|
4043
|
+
};
|
|
4044
|
+
|
|
4045
|
+
it('should remove preview contact successfully', async () => {
|
|
4046
|
+
const mockResponse = {trackingId: 'track-123'} as AgentContact;
|
|
4047
|
+
|
|
4048
|
+
const removePreviewContactMock = jest
|
|
4049
|
+
.spyOn(webex.cc.services.dialer, 'removePreviewContact')
|
|
4050
|
+
.mockResolvedValue(mockResponse);
|
|
4051
|
+
|
|
4052
|
+
const result = await webex.cc.removePreviewContact(previewPayload);
|
|
4053
|
+
|
|
4054
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Removing campaign preview contact', {
|
|
4055
|
+
module: CC_FILE,
|
|
4056
|
+
method: 'removePreviewContact',
|
|
4057
|
+
});
|
|
4058
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
4059
|
+
'Campaign preview contact removed successfully',
|
|
4060
|
+
{
|
|
4061
|
+
module: CC_FILE,
|
|
4062
|
+
method: 'removePreviewContact',
|
|
4063
|
+
trackingId: 'track-123',
|
|
4064
|
+
interactionId: previewPayload.interactionId,
|
|
4065
|
+
}
|
|
4066
|
+
);
|
|
4067
|
+
|
|
4068
|
+
expect(removePreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
|
|
4069
|
+
expect(result).toEqual(mockResponse);
|
|
4070
|
+
});
|
|
4071
|
+
|
|
4072
|
+
it('should handle error during removePreviewContact', async () => {
|
|
4073
|
+
getErrorDetailsSpy.mockRestore();
|
|
4074
|
+
getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
|
|
4075
|
+
|
|
4076
|
+
const error = {
|
|
4077
|
+
details: {
|
|
4078
|
+
trackingId: '1234',
|
|
4079
|
+
data: {
|
|
4080
|
+
reason: 'Error while performing removePreviewContact',
|
|
4081
|
+
},
|
|
4082
|
+
},
|
|
4083
|
+
};
|
|
4084
|
+
|
|
4085
|
+
jest.spyOn(webex.cc.services.dialer, 'removePreviewContact').mockRejectedValue(error);
|
|
4086
|
+
|
|
4087
|
+
await expect(webex.cc.removePreviewContact(previewPayload)).rejects.toThrow(
|
|
4088
|
+
error.details.data.reason
|
|
4089
|
+
);
|
|
4090
|
+
|
|
4091
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Removing campaign preview contact', {
|
|
4092
|
+
module: CC_FILE,
|
|
4093
|
+
method: 'removePreviewContact',
|
|
4094
|
+
});
|
|
4095
|
+
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
4096
|
+
`removePreviewContact failed with reason: ${error.details.data.reason}`,
|
|
4097
|
+
{module: CC_FILE, method: 'removePreviewContact', trackingId: error.details.trackingId}
|
|
4098
|
+
);
|
|
4099
|
+
expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'removePreviewContact', CC_FILE);
|
|
4100
|
+
});
|
|
4101
|
+
});
|
|
4102
|
+
|
|
4103
|
+
describe('private setManageWebexCallingInWxcc (runtime toggle impl)', () => {
|
|
4104
|
+
beforeEach(() => {
|
|
4105
|
+
webex.internal.device = {
|
|
4106
|
+
userId: 'user-123',
|
|
4107
|
+
registered: false,
|
|
4108
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
4109
|
+
unregister: jest.fn().mockResolvedValue(undefined),
|
|
4110
|
+
};
|
|
4111
|
+
webex.internal.mercury = {
|
|
4112
|
+
connected: false,
|
|
4113
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
4114
|
+
disconnect: jest.fn().mockResolvedValue(undefined),
|
|
4115
|
+
};
|
|
4116
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe').mockImplementation(() => {});
|
|
4117
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
4118
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
4119
|
+
webex.cc['agentConfig'] = {
|
|
4120
|
+
agentId: 'agent-123',
|
|
4121
|
+
deviceType: LoginOption.EXTENSION,
|
|
4122
|
+
} as Profile;
|
|
4123
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
4124
|
+
webex.cc.webCallingService.setLoginOption = jest.fn((loginOption: LoginOption) => {
|
|
4125
|
+
webex.cc.webCallingService.loginOption = loginOption;
|
|
4126
|
+
});
|
|
4127
|
+
});
|
|
4128
|
+
|
|
4129
|
+
afterEach(() => {
|
|
4130
|
+
jest.restoreAllMocks();
|
|
4131
|
+
});
|
|
4132
|
+
|
|
4133
|
+
it('should update config, task manager, and publish usersub when enabled', async () => {
|
|
4134
|
+
const publishSpy = jest
|
|
4135
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4136
|
+
.mockResolvedValue(undefined);
|
|
4137
|
+
|
|
4138
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(true);
|
|
4139
|
+
|
|
4140
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(true);
|
|
4141
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenCalledWith(true);
|
|
4142
|
+
expect(publishSpy).toHaveBeenCalledWith(true, {userId: 'user-123', trackPublishMetrics: true});
|
|
4143
|
+
});
|
|
4144
|
+
|
|
4145
|
+
it('should publish false and update config when disabled after wxApp was enabled', async () => {
|
|
4146
|
+
webex.internal.device = {
|
|
4147
|
+
userId: 'user-123',
|
|
4148
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
4149
|
+
};
|
|
4150
|
+
webex.request = jest.fn().mockResolvedValue({body: {}});
|
|
4151
|
+
|
|
4152
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(true);
|
|
4153
|
+
|
|
4154
|
+
const publishSpy = jest
|
|
4155
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4156
|
+
.mockResolvedValue(undefined);
|
|
4157
|
+
|
|
4158
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(false);
|
|
4159
|
+
|
|
4160
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
4161
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenCalledWith(false);
|
|
4162
|
+
expect(publishSpy).toHaveBeenCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
4163
|
+
});
|
|
4164
|
+
|
|
4165
|
+
it('should skip usersub publish when disabling without prior wxApp publish', async () => {
|
|
4166
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
4167
|
+
const publishSpy = jest
|
|
4168
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4169
|
+
.mockResolvedValue(undefined);
|
|
4170
|
+
|
|
4171
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(false);
|
|
4172
|
+
|
|
4173
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
4174
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenCalledWith(false);
|
|
4175
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
4176
|
+
});
|
|
4177
|
+
|
|
4178
|
+
it('should throw when enabling before login', async () => {
|
|
4179
|
+
webex.internal.device = undefined;
|
|
4180
|
+
|
|
4181
|
+
await expect((webex.cc as any).setManageWebexCallingInWxcc(true)).rejects.toThrow(
|
|
4182
|
+
'Cannot publish answer-calls-on-wxcc: user is not logged in'
|
|
4183
|
+
);
|
|
4184
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
4185
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).not.toHaveBeenCalled();
|
|
4186
|
+
});
|
|
4187
|
+
|
|
4188
|
+
it('should throw when enabling on BROWSER (Desktop) login', async () => {
|
|
4189
|
+
webex.cc['agentConfig'] = {deviceType: LoginOption.BROWSER} as Profile;
|
|
4190
|
+
webex.cc.webCallingService.loginOption = LoginOption.BROWSER;
|
|
4191
|
+
const publishSpy = jest
|
|
4192
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4193
|
+
.mockResolvedValue(undefined);
|
|
4194
|
+
|
|
4195
|
+
await expect((webex.cc as any).setManageWebexCallingInWxcc(true)).rejects.toThrow(
|
|
4196
|
+
'setManageWebexCallingInWxcc requires EXTENSION or AGENT_DN station login'
|
|
4197
|
+
);
|
|
4198
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
4199
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).not.toHaveBeenCalled();
|
|
4200
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
4201
|
+
});
|
|
4202
|
+
|
|
4203
|
+
it('should throw when enabling before station login', async () => {
|
|
4204
|
+
webex.cc['agentConfig'] = undefined as unknown as Profile;
|
|
4205
|
+
webex.cc.webCallingService.loginOption = undefined as unknown as LoginOption;
|
|
4206
|
+
const publishSpy = jest
|
|
4207
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4208
|
+
.mockResolvedValue(undefined);
|
|
4209
|
+
|
|
4210
|
+
await expect((webex.cc as any).setManageWebexCallingInWxcc(true)).rejects.toThrow(
|
|
4211
|
+
'setManageWebexCallingInWxcc requires EXTENSION or AGENT_DN station login'
|
|
4212
|
+
);
|
|
4213
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
4214
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).not.toHaveBeenCalled();
|
|
4215
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
4216
|
+
});
|
|
4217
|
+
|
|
4218
|
+
it('should throw when webCallingService is BROWSER even if agentConfig deviceType is stale', async () => {
|
|
4219
|
+
webex.cc['agentConfig'] = {deviceType: LoginOption.EXTENSION} as Profile;
|
|
4220
|
+
webex.cc.webCallingService.loginOption = LoginOption.BROWSER;
|
|
4221
|
+
const publishSpy = jest
|
|
4222
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4223
|
+
.mockResolvedValue(undefined);
|
|
4224
|
+
|
|
4225
|
+
await expect((webex.cc as any).setManageWebexCallingInWxcc(true)).rejects.toThrow(
|
|
4226
|
+
'setManageWebexCallingInWxcc requires EXTENSION or AGENT_DN station login'
|
|
4227
|
+
);
|
|
4228
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
4229
|
+
});
|
|
4230
|
+
|
|
4231
|
+
it('should publish usersub when enabling on AGENT_DN login', async () => {
|
|
4232
|
+
webex.cc['agentConfig'] = {
|
|
4233
|
+
agentId: 'agent-123',
|
|
4234
|
+
deviceType: LoginOption.AGENT_DN,
|
|
4235
|
+
} as Profile;
|
|
4236
|
+
const publishSpy = jest
|
|
4237
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4238
|
+
.mockResolvedValue(undefined);
|
|
4239
|
+
|
|
4240
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(true);
|
|
4241
|
+
|
|
4242
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(true);
|
|
4243
|
+
expect(publishSpy).toHaveBeenCalledWith(true, {userId: 'user-123', trackPublishMetrics: true});
|
|
4244
|
+
});
|
|
4245
|
+
|
|
4246
|
+
it('should no-op usersub publish when disabling before login', async () => {
|
|
4247
|
+
webex.internal.device = undefined;
|
|
4248
|
+
const publishSpy = jest
|
|
4249
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4250
|
+
.mockResolvedValue(undefined);
|
|
4251
|
+
|
|
4252
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(false);
|
|
4253
|
+
|
|
4254
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
4255
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenCalledWith(false);
|
|
4256
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
4257
|
+
});
|
|
4258
|
+
|
|
4259
|
+
it('should subscribe to wxApp Mercury mute sync when enabled', async () => {
|
|
4260
|
+
webex.cc['agentConfig'] = {agentId: 'agent-123'} as Profile;
|
|
4261
|
+
webex.internal.device = {
|
|
4262
|
+
...webex.internal.device,
|
|
4263
|
+
registered: true,
|
|
4264
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
4265
|
+
};
|
|
4266
|
+
webex.internal.mercury.connected = true;
|
|
4267
|
+
webex.internal.mercury.connect = jest.fn().mockResolvedValue(undefined);
|
|
4268
|
+
jest
|
|
4269
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4270
|
+
.mockResolvedValue(undefined);
|
|
4271
|
+
const subscribeSpy = jest
|
|
4272
|
+
.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe')
|
|
4273
|
+
.mockImplementation(() => {});
|
|
4274
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
4275
|
+
|
|
4276
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(true);
|
|
4277
|
+
|
|
4278
|
+
expect(subscribeSpy).toHaveBeenCalledWith('agent-123', expect.any(Function));
|
|
4279
|
+
});
|
|
4280
|
+
|
|
4281
|
+
it('should unsubscribe from wxApp Mercury mute sync when disabled', async () => {
|
|
4282
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
4283
|
+
jest
|
|
4284
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4285
|
+
.mockResolvedValue(undefined);
|
|
4286
|
+
const unsubscribeSpy = jest
|
|
4287
|
+
.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe')
|
|
4288
|
+
.mockImplementation(() => {});
|
|
4289
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
4290
|
+
|
|
4291
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(false);
|
|
4292
|
+
|
|
4293
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
4294
|
+
});
|
|
4295
|
+
|
|
4296
|
+
it('should register device and connect mercury when enabling wxApp sync', async () => {
|
|
4297
|
+
webex.cc['agentConfig'] = {agentId: 'agent-123'} as Profile;
|
|
4298
|
+
webex.internal.device = {
|
|
4299
|
+
userId: 'user-123',
|
|
4300
|
+
registered: false,
|
|
4301
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
4302
|
+
};
|
|
4303
|
+
webex.internal.mercury = {
|
|
4304
|
+
connected: false,
|
|
4305
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
4306
|
+
};
|
|
4307
|
+
jest
|
|
4308
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4309
|
+
.mockResolvedValue(undefined);
|
|
4310
|
+
const subscribeSpy = jest
|
|
4311
|
+
.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe')
|
|
4312
|
+
.mockImplementation(() => {});
|
|
4313
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
4314
|
+
|
|
4315
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(true);
|
|
4316
|
+
|
|
4317
|
+
expect(webex.internal.device.register).toHaveBeenCalled();
|
|
4318
|
+
expect(webex.internal.mercury.connect).toHaveBeenCalled();
|
|
4319
|
+
expect(subscribeSpy).toHaveBeenCalledWith('agent-123', expect.any(Function));
|
|
4320
|
+
});
|
|
4321
|
+
|
|
4322
|
+
it('should rollback config and task manager when usersub publish fails', async () => {
|
|
4323
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
4324
|
+
jest
|
|
4325
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4326
|
+
.mockRejectedValue(new Error('usersub failed'));
|
|
4327
|
+
|
|
4328
|
+
await expect((webex.cc as any).setManageWebexCallingInWxcc(true)).rejects.toThrow('usersub failed');
|
|
4329
|
+
|
|
4330
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
4331
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenCalledWith(true);
|
|
4332
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenLastCalledWith(false);
|
|
4333
|
+
});
|
|
4334
|
+
|
|
4335
|
+
it('should rollback config and release Mercury resources when Mercury connect fails', async () => {
|
|
4336
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
4337
|
+
const publishSpy = jest
|
|
4338
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4339
|
+
.mockResolvedValue(undefined);
|
|
4340
|
+
jest
|
|
4341
|
+
.spyOn(webex.cc as any, 'ensureWxAppMercuryConnected')
|
|
4342
|
+
.mockRejectedValue(new Error('mercury connect failed'));
|
|
4343
|
+
const releaseSpy = jest
|
|
4344
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
4345
|
+
.mockResolvedValue(undefined);
|
|
4346
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
4347
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
4348
|
+
|
|
4349
|
+
await expect((webex.cc as any).setManageWebexCallingInWxcc(true)).rejects.toThrow(
|
|
4350
|
+
'mercury connect failed'
|
|
4351
|
+
);
|
|
4352
|
+
|
|
4353
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
4354
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenLastCalledWith(false);
|
|
4355
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
4356
|
+
expect(teardownSpy).not.toHaveBeenCalled();
|
|
4357
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
4358
|
+
expect(releaseSpy).toHaveBeenCalled();
|
|
4359
|
+
});
|
|
4360
|
+
|
|
4361
|
+
it('should schedule compensating false retry when rollback publish fails after enable error', async () => {
|
|
4362
|
+
jest.useFakeTimers();
|
|
4363
|
+
try {
|
|
4364
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
4365
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
4366
|
+
webex.cc.agentConfig = {
|
|
4367
|
+
agentId: 'agent-123',
|
|
4368
|
+
deviceType: LoginOption.EXTENSION,
|
|
4369
|
+
};
|
|
4370
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
4371
|
+
|
|
4372
|
+
const publishSpy = jest
|
|
4373
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4374
|
+
.mockResolvedValueOnce(undefined)
|
|
4375
|
+
.mockRejectedValueOnce(new Error('compensating false failed'))
|
|
4376
|
+
.mockResolvedValue(undefined);
|
|
4377
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe').mockImplementation(() => {});
|
|
4378
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
4379
|
+
jest
|
|
4380
|
+
.spyOn(webex.cc as any, 'ensureWxAppMercuryConnected')
|
|
4381
|
+
.mockResolvedValue(undefined);
|
|
4382
|
+
jest
|
|
4383
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
4384
|
+
.mockResolvedValue(undefined);
|
|
4385
|
+
jest
|
|
4386
|
+
.spyOn(mockTaskManager, 'syncWxAppMuteFromCallDetailsForAllTasks')
|
|
4387
|
+
.mockImplementation(() => {
|
|
4388
|
+
throw new Error('sync failed');
|
|
4389
|
+
});
|
|
4390
|
+
const scheduleRetrySpy = jest.spyOn(
|
|
4391
|
+
webex.cc as any,
|
|
4392
|
+
'scheduleCompensatingFalsePublishRetry'
|
|
4393
|
+
);
|
|
4394
|
+
|
|
4395
|
+
await expect((webex.cc as any).setManageWebexCallingInWxcc(true)).rejects.toThrow(
|
|
4396
|
+
'sync failed'
|
|
4397
|
+
);
|
|
4398
|
+
|
|
4399
|
+
expect(scheduleRetrySpy).toHaveBeenCalled();
|
|
4400
|
+
expect(publishSpy).toHaveBeenCalledTimes(2);
|
|
4401
|
+
expect(publishSpy).toHaveBeenNthCalledWith(1, true, {userId: 'user-123', trackPublishMetrics: true});
|
|
4402
|
+
expect(publishSpy).toHaveBeenNthCalledWith(2, false, {userId: 'user-123', trackPublishMetrics: true});
|
|
4403
|
+
|
|
4404
|
+
jest.advanceTimersByTime(30_000);
|
|
4405
|
+
await publishSpy.mock.results[2]?.value;
|
|
4406
|
+
|
|
4407
|
+
expect(publishSpy).toHaveBeenCalledTimes(3);
|
|
4408
|
+
expect(publishSpy).toHaveBeenLastCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
4409
|
+
} finally {
|
|
4410
|
+
jest.useRealTimers();
|
|
4411
|
+
}
|
|
4412
|
+
});
|
|
4413
|
+
});
|
|
2323
4414
|
});
|