@webex/contact-center 3.12.0-next.14 → 3.12.0-next.140
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 +885 -0
- package/AGENTS.md +94 -0
- package/ai-docs/ARCHITECTURE.md +168 -0
- package/ai-docs/CONTRACTS.md +59 -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 +1004 -43
- 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 +211 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +49 -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 +225 -0
- package/dist/services/WebexCrossClientService.js.map +1 -0
- package/dist/services/WxAppTelephonyMercurySync.js +111 -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 +50 -0
- package/dist/services/task/TaskFactory.js.map +1 -0
- package/dist/services/task/TaskManager.js +1183 -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 +1504 -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/wxAppOfferObservability.js +145 -0
- package/dist/services/task/voice/wxAppOfferObservability.js.map +1 -0
- package/dist/services/task/voice/wxAppVoiceMethods.js +395 -0
- package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
- package/dist/services/wxAppDiagnosticLogging.js +159 -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 +44 -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 +199 -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 +682 -66
- package/dist/types/services/task/voice/Voice.d.ts +240 -0
- package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
- package/dist/types/services/task/voice/wxAppOfferObservability.d.ts +39 -0
- package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +68 -0
- package/dist/types/services/wxAppDiagnosticLogging.d.ts +63 -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 +1330 -117
- 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 +963 -0
- package/src/metrics/behavioral-events.ts +218 -0
- package/src/metrics/constants.ts +53 -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 +298 -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 +199 -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 +61 -0
- package/src/services/task/TaskManager.ts +1430 -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 +1476 -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 +835 -70
- package/src/services/task/voice/Voice.ts +1873 -0
- package/src/services/task/voice/WebRTC.ts +191 -0
- package/src/services/task/voice/wxAppOfferObservability.ts +204 -0
- package/src/services/task/voice/wxAppVoiceMethods.ts +575 -0
- package/src/services/wxAppDiagnosticLogging.ts +286 -0
- package/src/services/wxAppTelephonyUtils.ts +14 -0
- package/src/types.ts +264 -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 +2371 -98
- 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 +509 -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 +2539 -1490
- 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 +2020 -0
- package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
- package/test/unit/spec/services/task/voice/wxAppOfferObservability.ts +361 -0
- package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +719 -0
- package/test/unit/spec/services/wxAppDiagnosticLogging.ts +179 -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 -1530
- 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 -1806
- package/test/unit/spec/services/task/index.ts +0 -2205
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,26 @@ 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
|
-
|
|
173
|
+
setAnswerCallOnWebexService: jest.fn(),
|
|
174
|
+
handleRealtimeWebsocketEvent: jest.fn(),
|
|
161
175
|
registerIncomingCallEvent: jest.fn(),
|
|
162
176
|
registerTaskListeners: jest.fn(),
|
|
163
177
|
getTask: jest.fn(),
|
|
164
178
|
getActiveTasks: jest.fn(),
|
|
179
|
+
applyEnableWxBetterTogether: jest.fn(),
|
|
180
|
+
syncWxAppMuteFromCallDetailsForAllTasks: jest.fn(),
|
|
181
|
+
refreshWxAppOfferObservabilityForAllTasks: jest.fn(),
|
|
165
182
|
on: jest.fn(),
|
|
166
183
|
off: jest.fn(),
|
|
167
184
|
emit: jest.fn(),
|
|
@@ -200,6 +217,35 @@ describe('webex.cc', () => {
|
|
|
200
217
|
webex.emit('ready');
|
|
201
218
|
});
|
|
202
219
|
|
|
220
|
+
it('should throw when WebRTC registration is disabled without multi-login', () => {
|
|
221
|
+
const invalidWebex = MockWebex({
|
|
222
|
+
children: {
|
|
223
|
+
mercury: Mercury,
|
|
224
|
+
},
|
|
225
|
+
logger: {
|
|
226
|
+
log: jest.fn(),
|
|
227
|
+
error: jest.fn(),
|
|
228
|
+
info: jest.fn(),
|
|
229
|
+
},
|
|
230
|
+
credentials: {
|
|
231
|
+
getOrgId: jest.fn(() => 'mockOrgId'),
|
|
232
|
+
},
|
|
233
|
+
config: {
|
|
234
|
+
...config,
|
|
235
|
+
cc: {
|
|
236
|
+
...config.cc,
|
|
237
|
+
allowMultiLogin: false,
|
|
238
|
+
disableWebRTCRegistration: true,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
once: jest.fn((event, callback) => callback()),
|
|
242
|
+
}) as unknown as WebexSDK;
|
|
243
|
+
|
|
244
|
+
expect(() => new ContactCenter({parent: invalidWebex})).toThrow(
|
|
245
|
+
'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.'
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
|
|
203
249
|
describe('cc.getDeviceId', () => {
|
|
204
250
|
it('should return dialNumber when loginOption is EXTENSION', () => {
|
|
205
251
|
const loginOption = LoginOption.EXTENSION;
|
|
@@ -267,14 +313,18 @@ describe('webex.cc', () => {
|
|
|
267
313
|
isAgentAvailableAfterOutdial: false,
|
|
268
314
|
isCampaignManagementEnabled: false,
|
|
269
315
|
outDialEp: '',
|
|
270
|
-
|
|
316
|
+
isEndTaskEnabled: false,
|
|
271
317
|
isEndConsultEnabled: false,
|
|
272
318
|
agentDbId: '',
|
|
273
319
|
allowConsultToQueue: false,
|
|
320
|
+
accessQueue: 'SPECIFIC',
|
|
321
|
+
accessEntryPoint: 'ALL',
|
|
322
|
+
accessBuddyTeam: 'ALL',
|
|
274
323
|
agentPersonalStatsEnabled: false,
|
|
275
324
|
isTimeoutDesktopInactivityEnabled: false,
|
|
276
325
|
webRtcEnabled: true,
|
|
277
326
|
lostConnectionRecoveryTimeout: 0,
|
|
327
|
+
isWellnessBreakEnabled: false,
|
|
278
328
|
};
|
|
279
329
|
|
|
280
330
|
it('should register successfully and return agent profile', async () => {
|
|
@@ -286,6 +336,7 @@ describe('webex.cc', () => {
|
|
|
286
336
|
data: {
|
|
287
337
|
auxCodeId: 'auxCodeId',
|
|
288
338
|
agentId: 'agentId',
|
|
339
|
+
agentSessionId: 'session-after-refresh',
|
|
289
340
|
deviceType: LoginOption.EXTENSION,
|
|
290
341
|
dn: '12345',
|
|
291
342
|
},
|
|
@@ -300,7 +351,7 @@ describe('webex.cc', () => {
|
|
|
300
351
|
const result = await webex.cc.register();
|
|
301
352
|
|
|
302
353
|
// Verify logging calls
|
|
303
|
-
expect(LoggerProxy.
|
|
354
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith('Starting CC SDK registration', {
|
|
304
355
|
module: CC_FILE,
|
|
305
356
|
method: 'register',
|
|
306
357
|
});
|
|
@@ -334,6 +385,10 @@ describe('webex.cc', () => {
|
|
|
334
385
|
TASK_EVENTS.TASK_HYDRATE,
|
|
335
386
|
expect.any(Function)
|
|
336
387
|
);
|
|
388
|
+
expect(mockTaskManager.on).toHaveBeenCalledWith(
|
|
389
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
390
|
+
expect.any(Function)
|
|
391
|
+
);
|
|
337
392
|
expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
|
|
338
393
|
expect(webex.cc.services.rtdWebSocketManager.initWebSocket).toHaveBeenCalledWith({
|
|
339
394
|
body: {
|
|
@@ -354,8 +409,25 @@ describe('webex.cc', () => {
|
|
|
354
409
|
module: CC_FILE,
|
|
355
410
|
method: 'connectWebsocket',
|
|
356
411
|
});
|
|
412
|
+
expect(mockTaskManager.setConfigFlags).toHaveBeenCalledWith(
|
|
413
|
+
expect.objectContaining({
|
|
414
|
+
isEndTaskEnabled: mockAgentProfile.isEndTaskEnabled,
|
|
415
|
+
isEndConsultEnabled: mockAgentProfile.isEndConsultEnabled,
|
|
416
|
+
webRtcEnabled: mockAgentProfile.webRtcEnabled,
|
|
417
|
+
autoWrapup: mockAgentProfile.wrapUpData.wrapUpProps.autoWrapup ?? false,
|
|
418
|
+
aiFeature: mockAgentProfile.aiFeature,
|
|
419
|
+
consultTransfer: {
|
|
420
|
+
allowConsultToQueue: mockAgentProfile.allowConsultToQueue,
|
|
421
|
+
accessQueue: mockAgentProfile.accessQueue,
|
|
422
|
+
accessEntryPoint: mockAgentProfile.accessEntryPoint,
|
|
423
|
+
accessBuddyTeam: mockAgentProfile.accessBuddyTeam,
|
|
424
|
+
},
|
|
425
|
+
enableWxBetterTogether: false,
|
|
426
|
+
getWxAppUsersubPublished: expect.any(Function),
|
|
427
|
+
})
|
|
428
|
+
);
|
|
357
429
|
expect(reloadSpy).toHaveBeenCalled();
|
|
358
|
-
expect(result).toEqual(mockAgentProfile);
|
|
430
|
+
expect(result).toEqual({...mockAgentProfile, agentSessionId: 'session-after-refresh'});
|
|
359
431
|
expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
|
|
360
432
|
METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_SUCCESS,
|
|
361
433
|
METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED,
|
|
@@ -413,7 +485,7 @@ describe('webex.cc', () => {
|
|
|
413
485
|
|
|
414
486
|
await expect(webex.cc.register()).rejects.toThrow('Error while performing register');
|
|
415
487
|
|
|
416
|
-
expect(LoggerProxy.
|
|
488
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith('Starting CC SDK registration', {
|
|
417
489
|
module: CC_FILE,
|
|
418
490
|
method: 'register',
|
|
419
491
|
});
|
|
@@ -432,6 +504,25 @@ describe('webex.cc', () => {
|
|
|
432
504
|
);
|
|
433
505
|
});
|
|
434
506
|
|
|
507
|
+
it('clears wellness session context when registration fails after relogin', async () => {
|
|
508
|
+
const mockError = new Error('Error after silent relogin');
|
|
509
|
+
webex.cc.$config = {...webex.cc.$config, allowAutomatedRelogin: true};
|
|
510
|
+
jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue({
|
|
511
|
+
...mockAgentProfile,
|
|
512
|
+
webRtcEnabled: false,
|
|
513
|
+
aiFeature: undefined,
|
|
514
|
+
} as Profile);
|
|
515
|
+
mockWebSocketManager.initWebSocket.mockResolvedValue({agentId: 'agent123'});
|
|
516
|
+
jest.spyOn(webex.cc as any, 'silentRelogin').mockImplementation(async () => {
|
|
517
|
+
webex.cc['updateWellnessSession']('partial-session');
|
|
518
|
+
throw mockError;
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
await expect(webex.cc.register()).rejects.toThrow(mockError);
|
|
522
|
+
|
|
523
|
+
expect(webex.cc['currentAgentSessionId']).toBeUndefined();
|
|
524
|
+
});
|
|
525
|
+
|
|
435
526
|
it('should log error if mercury connect fails but cc.register() should not fail', async () => {
|
|
436
527
|
const mockError = new Error('Error while performing mercury connect');
|
|
437
528
|
jest.spyOn(webex.internal.mercury, 'connect').mockRejectedValue(mockError);
|
|
@@ -482,6 +573,10 @@ describe('webex.cc', () => {
|
|
|
482
573
|
TASK_EVENTS.TASK_HYDRATE,
|
|
483
574
|
expect.any(Function)
|
|
484
575
|
);
|
|
576
|
+
expect(mockTaskManager.on).toHaveBeenCalledWith(
|
|
577
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
578
|
+
expect.any(Function)
|
|
579
|
+
);
|
|
485
580
|
expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
|
|
486
581
|
|
|
487
582
|
expect(configSpy).toHaveBeenCalled();
|
|
@@ -493,6 +588,36 @@ describe('webex.cc', () => {
|
|
|
493
588
|
expect(result).toEqual(mockAgentProfile);
|
|
494
589
|
});
|
|
495
590
|
|
|
591
|
+
it('should skip mercury connection when disableWebRTCRegistration is enabled', async () => {
|
|
592
|
+
webex.cc.$config = {
|
|
593
|
+
...webex.cc.$config,
|
|
594
|
+
allowAutomatedRelogin: false,
|
|
595
|
+
disableWebRTCRegistration: true,
|
|
596
|
+
};
|
|
597
|
+
mockAgentProfile.webRtcEnabled = true;
|
|
598
|
+
const mercurySpy = jest.spyOn(webex.internal.mercury, 'connect');
|
|
599
|
+
const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
|
|
600
|
+
const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
|
|
601
|
+
jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue(mockAgentProfile);
|
|
602
|
+
mockWebSocketManager.initWebSocket.mockResolvedValue({
|
|
603
|
+
agentId: 'agent123',
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
const result = await webex.cc.register();
|
|
607
|
+
|
|
608
|
+
expect(connectWebsocketSpy).toHaveBeenCalled();
|
|
609
|
+
expect(setupEventListenersSpy).toHaveBeenCalled();
|
|
610
|
+
expect(mercurySpy).not.toHaveBeenCalled();
|
|
611
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith(
|
|
612
|
+
'Skipping Mobius registration because disableWebRTCRegistration is enabled',
|
|
613
|
+
{
|
|
614
|
+
module: CC_FILE,
|
|
615
|
+
method: 'connectWebsocket',
|
|
616
|
+
}
|
|
617
|
+
);
|
|
618
|
+
expect(result).toEqual(mockAgentProfile);
|
|
619
|
+
});
|
|
620
|
+
|
|
496
621
|
it('should not attempt for mercury connection when webrtc is disabled', async () => {
|
|
497
622
|
mockAgentProfile.webRtcEnabled = false;
|
|
498
623
|
mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: false}} as any;
|
|
@@ -534,8 +659,47 @@ describe('webex.cc', () => {
|
|
|
534
659
|
expect(result).toEqual(mockAgentProfile);
|
|
535
660
|
});
|
|
536
661
|
|
|
537
|
-
it('should
|
|
538
|
-
mockAgentProfile.aiFeature = {
|
|
662
|
+
it('should connect RTD websocket when only wellness breaks are enabled', async () => {
|
|
663
|
+
mockAgentProfile.aiFeature = {
|
|
664
|
+
realtimeTranscripts: {enable: false},
|
|
665
|
+
suggestedResponses: {enable: false},
|
|
666
|
+
} as any;
|
|
667
|
+
mockAgentProfile.isWellnessBreakEnabled = true;
|
|
668
|
+
jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
|
|
669
|
+
jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
|
|
670
|
+
data: {
|
|
671
|
+
auxCodeId: 'auxCodeId',
|
|
672
|
+
agentId: 'agentId',
|
|
673
|
+
deviceType: LoginOption.EXTENSION,
|
|
674
|
+
dn: '12345',
|
|
675
|
+
},
|
|
676
|
+
});
|
|
677
|
+
jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue(mockAgentProfile);
|
|
678
|
+
mockWebSocketManager.initWebSocket.mockResolvedValue({agentId: 'agent123'});
|
|
679
|
+
|
|
680
|
+
await webex.cc.register();
|
|
681
|
+
|
|
682
|
+
expect(webex.cc.services.rtdWebSocketManager.initWebSocket).toHaveBeenCalledWith({
|
|
683
|
+
body: {
|
|
684
|
+
force: true,
|
|
685
|
+
isKeepAliveEnabled: false,
|
|
686
|
+
clientType: 'WebexCCSDK',
|
|
687
|
+
allowMultiLogin: false,
|
|
688
|
+
},
|
|
689
|
+
resource: 'v1/realtime/subscribe',
|
|
690
|
+
});
|
|
691
|
+
expect(webex.cc.services.rtdWebSocketManager.on).toHaveBeenCalledWith(
|
|
692
|
+
'message',
|
|
693
|
+
expect.any(Function)
|
|
694
|
+
);
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it('should not connect RTD websocket when transcripts, suggestions, and wellness are disabled', async () => {
|
|
698
|
+
mockAgentProfile.aiFeature = {
|
|
699
|
+
realtimeTranscripts: {enable: false},
|
|
700
|
+
suggestedResponses: {enable: false},
|
|
701
|
+
} as any;
|
|
702
|
+
mockAgentProfile.isWellnessBreakEnabled = false;
|
|
539
703
|
jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
|
|
540
704
|
jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
|
|
541
705
|
data: {
|
|
@@ -631,7 +795,7 @@ describe('webex.cc', () => {
|
|
|
631
795
|
expect(registerWebCallingLineSpy).toHaveBeenCalled();
|
|
632
796
|
expect(stationLoginMock).toHaveBeenCalledWith({
|
|
633
797
|
data: {
|
|
634
|
-
dialNumber:
|
|
798
|
+
dialNumber: `${WEB_RTC_PREFIX}agentId`,
|
|
635
799
|
teamId: 'teamId',
|
|
636
800
|
deviceType: LoginOption.BROWSER,
|
|
637
801
|
isExtension: false,
|
|
@@ -649,6 +813,7 @@ describe('webex.cc', () => {
|
|
|
649
813
|
METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
|
|
650
814
|
]);
|
|
651
815
|
expect(result).toEqual(responseMock);
|
|
816
|
+
expect(webex.cc.agentConfig.deviceType).toBe(LoginOption.BROWSER);
|
|
652
817
|
|
|
653
818
|
const onSpy = jest.spyOn(mockTaskManager, 'on');
|
|
654
819
|
const emitSpy = jest.spyOn(webex.cc, 'trigger');
|
|
@@ -661,10 +826,8 @@ describe('webex.cc', () => {
|
|
|
661
826
|
|
|
662
827
|
expect(emitSpy).toHaveBeenCalledTimes(1);
|
|
663
828
|
expect(emitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_INCOMING, mockTask);
|
|
664
|
-
// Verify message
|
|
665
|
-
const messageCallback =
|
|
666
|
-
(call) => call[0] === 'message'
|
|
667
|
-
)[1];
|
|
829
|
+
// Verify websocket message handling
|
|
830
|
+
const messageCallback = webex.cc['handleWebsocketMessage'];
|
|
668
831
|
const agentStateChangeEventData = {
|
|
669
832
|
type: CC_EVENTS.AGENT_STATE_CHANGE,
|
|
670
833
|
data: {some: 'data'},
|
|
@@ -692,6 +855,59 @@ describe('webex.cc', () => {
|
|
|
692
855
|
);
|
|
693
856
|
});
|
|
694
857
|
|
|
858
|
+
it('should skip web calling line registration when disableWebRTCRegistration is enabled', async () => {
|
|
859
|
+
const options = {
|
|
860
|
+
teamId: 'teamId',
|
|
861
|
+
loginOption: LoginOption.BROWSER,
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
webex.cc.$config = {
|
|
865
|
+
...webex.cc.$config,
|
|
866
|
+
disableWebRTCRegistration: true,
|
|
867
|
+
};
|
|
868
|
+
webex.cc.agentConfig = {
|
|
869
|
+
agentId: 'agentId',
|
|
870
|
+
webRtcEnabled: true,
|
|
871
|
+
loginVoiceOptions: ['BROWSER', 'EXTENSION', 'AGENT_DN'],
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
const registerWebCallingLineSpy = jest.spyOn(
|
|
875
|
+
webex.cc.webCallingService,
|
|
876
|
+
'registerWebCallingLine'
|
|
877
|
+
);
|
|
878
|
+
|
|
879
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
880
|
+
data: {
|
|
881
|
+
loginOption: LoginOption.BROWSER,
|
|
882
|
+
agentId: 'agentId',
|
|
883
|
+
teamId: 'teamId',
|
|
884
|
+
siteId: 'siteId',
|
|
885
|
+
roles: [AGENT],
|
|
886
|
+
channelsMap: {
|
|
887
|
+
chat: [],
|
|
888
|
+
email: [],
|
|
889
|
+
social: [],
|
|
890
|
+
telephony: [],
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
trackingId: 'notifs_52628',
|
|
894
|
+
orgId: 'orgId',
|
|
895
|
+
type: 'StationLoginSuccess',
|
|
896
|
+
eventType: 'STATION_LOGIN',
|
|
897
|
+
} as unknown as StationLoginSuccess);
|
|
898
|
+
|
|
899
|
+
await webex.cc.stationLogin(options);
|
|
900
|
+
|
|
901
|
+
expect(registerWebCallingLineSpy).not.toHaveBeenCalled();
|
|
902
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith(
|
|
903
|
+
'Skipping web calling line registration because disableWebRTCRegistration is enabled',
|
|
904
|
+
{
|
|
905
|
+
module: CC_FILE,
|
|
906
|
+
method: 'stationLogin',
|
|
907
|
+
}
|
|
908
|
+
);
|
|
909
|
+
});
|
|
910
|
+
|
|
695
911
|
it('should not attempt mobius registration for LoginOption.BROWSER if webrtc is disabled', async () => {
|
|
696
912
|
const options = {
|
|
697
913
|
teamId: 'teamId',
|
|
@@ -739,7 +955,7 @@ describe('webex.cc', () => {
|
|
|
739
955
|
expect(registerWebCallingLineSpy).not.toHaveBeenCalled();
|
|
740
956
|
expect(stationLoginSpy).toHaveBeenCalledWith({
|
|
741
957
|
data: {
|
|
742
|
-
dialNumber:
|
|
958
|
+
dialNumber: `${WEB_RTC_PREFIX}agentId`,
|
|
743
959
|
teamId: 'teamId',
|
|
744
960
|
deviceType: LoginOption.BROWSER,
|
|
745
961
|
isExtension: false,
|
|
@@ -814,10 +1030,13 @@ describe('webex.cc', () => {
|
|
|
814
1030
|
const result = await webex.cc.stationLogin(options);
|
|
815
1031
|
|
|
816
1032
|
// Verify logging calls
|
|
817
|
-
expect(LoggerProxy.
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1033
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
1034
|
+
`Starting agent station login | loginOption: ${options.loginOption} teamId: ${options.teamId}`,
|
|
1035
|
+
{
|
|
1036
|
+
module: CC_FILE,
|
|
1037
|
+
method: 'stationLogin',
|
|
1038
|
+
}
|
|
1039
|
+
);
|
|
821
1040
|
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
822
1041
|
`Agent station login completed successfully agentId: ${mockData.data.agentId} loginOption: ${mockData.data.loginOption} teamId: ${mockData.data.teamId}`,
|
|
823
1042
|
{
|
|
@@ -868,97 +1087,1048 @@ describe('webex.cc', () => {
|
|
|
868
1087
|
|
|
869
1088
|
await expect(webex.cc.stationLogin(options)).rejects.toThrow(error.details.data.reason);
|
|
870
1089
|
|
|
871
|
-
expect(LoggerProxy.
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1090
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
1091
|
+
`Starting agent station login | loginOption: ${options.loginOption} teamId: ${options.teamId}`,
|
|
1092
|
+
{
|
|
1093
|
+
module: CC_FILE,
|
|
1094
|
+
method: 'stationLogin',
|
|
1095
|
+
}
|
|
1096
|
+
);
|
|
875
1097
|
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
876
1098
|
`stationLogin failed with reason: ${error.details.data.reason}`,
|
|
877
1099
|
{module: CC_FILE, method: 'stationLogin', trackingId: error.details.trackingId}
|
|
878
1100
|
);
|
|
879
1101
|
});
|
|
880
|
-
});
|
|
881
1102
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1103
|
+
it('should publish usersub and subscribe Mercury on station login when enableWxBetterTogether is true at init', async () => {
|
|
1104
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1105
|
+
webex.internal.device = {
|
|
1106
|
+
userId: 'user-123',
|
|
1107
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
1108
|
+
registered: true,
|
|
1109
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
1110
|
+
};
|
|
1111
|
+
webex.internal.mercury.connected = true;
|
|
1112
|
+
webex.internal.mercury.connect = jest.fn().mockResolvedValue(undefined);
|
|
1113
|
+
webex.cc.agentConfig = {
|
|
1114
|
+
agentId: 'agentId',
|
|
1115
|
+
webRtcEnabled: false,
|
|
1116
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1117
|
+
};
|
|
1118
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
886
1119
|
|
|
887
|
-
const
|
|
888
|
-
.spyOn(webex.cc
|
|
889
|
-
.mockResolvedValue(
|
|
1120
|
+
const publishSpy = jest
|
|
1121
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1122
|
+
.mockResolvedValue(undefined);
|
|
1123
|
+
const subscribeSpy = jest
|
|
1124
|
+
.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe')
|
|
1125
|
+
.mockImplementation(() => {});
|
|
1126
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
890
1127
|
|
|
891
|
-
|
|
1128
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1129
|
+
data: {
|
|
1130
|
+
agentId: 'agentId',
|
|
1131
|
+
teamId: 'teamId',
|
|
1132
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1133
|
+
},
|
|
1134
|
+
trackingId: 'track-1',
|
|
1135
|
+
} as StationLoginSuccess);
|
|
892
1136
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
});
|
|
898
|
-
expect(LoggerProxy.log).toHaveBeenCalledWith('Agent station logout completed successfully', {
|
|
899
|
-
module: CC_FILE,
|
|
900
|
-
method: 'stationLogout',
|
|
1137
|
+
await webex.cc.stationLogin({
|
|
1138
|
+
teamId: 'teamId',
|
|
1139
|
+
loginOption: LoginOption.EXTENSION,
|
|
1140
|
+
dialNumber: '1001',
|
|
901
1141
|
});
|
|
902
1142
|
|
|
903
|
-
expect(
|
|
904
|
-
|
|
905
|
-
|
|
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
|
-
]);
|
|
1143
|
+
expect(publishSpy).toHaveBeenCalledWith(true, {userId: 'user-123', trackPublishMetrics: true});
|
|
1144
|
+
expect(subscribeSpy).toHaveBeenCalledWith('agentId', expect.any(Function));
|
|
1145
|
+
expect(mockTaskManager.refreshWxAppOfferObservabilityForAllTasks).toHaveBeenCalled();
|
|
920
1146
|
});
|
|
921
1147
|
|
|
922
|
-
it('should
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
},
|
|
930
|
-
},
|
|
1148
|
+
it('should rollback wxApp config when post-station-login init fails', async () => {
|
|
1149
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1150
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1151
|
+
webex.cc.agentConfig = {
|
|
1152
|
+
agentId: 'agentId',
|
|
1153
|
+
webRtcEnabled: false,
|
|
1154
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
931
1155
|
};
|
|
1156
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1157
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
932
1158
|
|
|
933
|
-
jest
|
|
1159
|
+
jest
|
|
1160
|
+
.spyOn(webex.cc as never, 'publishAnswerOnWebexCrossClientState' as never)
|
|
1161
|
+
.mockRejectedValue(new Error('usersub publish failed'));
|
|
1162
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
934
1163
|
|
|
935
|
-
|
|
1164
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1165
|
+
data: {
|
|
1166
|
+
agentId: 'agentId',
|
|
1167
|
+
teamId: 'teamId',
|
|
1168
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1169
|
+
},
|
|
1170
|
+
trackingId: 'track-1',
|
|
1171
|
+
} as StationLoginSuccess);
|
|
936
1172
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1173
|
+
await webex.cc.stationLogin({
|
|
1174
|
+
teamId: 'teamId',
|
|
1175
|
+
loginOption: LoginOption.EXTENSION,
|
|
1176
|
+
dialNumber: '1001',
|
|
940
1177
|
});
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
);
|
|
1178
|
+
|
|
1179
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
1180
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
1181
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenCalledWith(false);
|
|
945
1182
|
});
|
|
946
|
-
});
|
|
947
1183
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1184
|
+
it('should reset wxApp config when Mercury fails before usersub publish on station login', async () => {
|
|
1185
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1186
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1187
|
+
webex.cc.agentConfig = {
|
|
1188
|
+
agentId: 'agentId',
|
|
1189
|
+
webRtcEnabled: false,
|
|
1190
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
955
1191
|
};
|
|
1192
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1193
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1194
|
+
|
|
1195
|
+
const publishSpy = jest
|
|
1196
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1197
|
+
.mockResolvedValue(undefined);
|
|
1198
|
+
jest
|
|
1199
|
+
.spyOn(webex.cc as any, 'ensureWxAppMercuryConnected')
|
|
1200
|
+
.mockRejectedValue(new Error('mercury connect failed'));
|
|
1201
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
1202
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
1203
|
+
jest
|
|
1204
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
1205
|
+
.mockResolvedValue(undefined);
|
|
1206
|
+
const metricSpy = jest.spyOn(mockMetricsManager, 'trackEvent');
|
|
956
1207
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1208
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1209
|
+
data: {
|
|
1210
|
+
agentId: 'agentId',
|
|
1211
|
+
teamId: 'teamId',
|
|
1212
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1213
|
+
},
|
|
1214
|
+
trackingId: 'track-1',
|
|
1215
|
+
} as StationLoginSuccess);
|
|
960
1216
|
|
|
961
|
-
|
|
1217
|
+
await webex.cc.stationLogin({
|
|
1218
|
+
teamId: 'teamId',
|
|
1219
|
+
loginOption: LoginOption.EXTENSION,
|
|
1220
|
+
dialNumber: '1001',
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
1224
|
+
expect(teardownSpy).not.toHaveBeenCalled();
|
|
1225
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
1226
|
+
expect(metricSpy).toHaveBeenCalledWith(
|
|
1227
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
|
|
1228
|
+
expect.objectContaining({skipReason: 'mercury_subscribe_failed'}),
|
|
1229
|
+
['operational', 'behavioral']
|
|
1230
|
+
);
|
|
1231
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
1232
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenCalledWith(false);
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
it('should record publish_failed when usersub publish fails after Mercury init', async () => {
|
|
1236
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1237
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1238
|
+
webex.cc.agentConfig = {
|
|
1239
|
+
agentId: 'agentId',
|
|
1240
|
+
webRtcEnabled: false,
|
|
1241
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1242
|
+
};
|
|
1243
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1244
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1245
|
+
|
|
1246
|
+
jest.spyOn(webex.cc as any, 'ensureWxAppMercuryConnected').mockResolvedValue(undefined);
|
|
1247
|
+
jest
|
|
1248
|
+
.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe')
|
|
1249
|
+
.mockImplementation(() => {});
|
|
1250
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
1251
|
+
jest
|
|
1252
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1253
|
+
.mockRejectedValue(new Error('publish failed'));
|
|
1254
|
+
jest
|
|
1255
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
1256
|
+
.mockResolvedValue(undefined);
|
|
1257
|
+
const metricSpy = jest.spyOn(mockMetricsManager, 'trackEvent');
|
|
1258
|
+
|
|
1259
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1260
|
+
data: {
|
|
1261
|
+
agentId: 'agentId',
|
|
1262
|
+
teamId: 'teamId',
|
|
1263
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1264
|
+
},
|
|
1265
|
+
trackingId: 'track-1',
|
|
1266
|
+
} as StationLoginSuccess);
|
|
1267
|
+
|
|
1268
|
+
await webex.cc.stationLogin({
|
|
1269
|
+
teamId: 'teamId',
|
|
1270
|
+
loginOption: LoginOption.EXTENSION,
|
|
1271
|
+
dialNumber: '1001',
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
expect(metricSpy).toHaveBeenCalledWith(
|
|
1275
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
|
|
1276
|
+
expect.objectContaining({skipReason: 'publish_failed', usersubPublished: false}),
|
|
1277
|
+
['operational', 'behavioral']
|
|
1278
|
+
);
|
|
1279
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
it('should report retained usersub state when publish fails during re-init', async () => {
|
|
1283
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1284
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1285
|
+
webex.cc.agentConfig = {
|
|
1286
|
+
agentId: 'agentId',
|
|
1287
|
+
webRtcEnabled: false,
|
|
1288
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1289
|
+
};
|
|
1290
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1291
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1292
|
+
|
|
1293
|
+
jest.spyOn(webex.cc as any, 'ensureWxAppMercuryConnected').mockResolvedValue(undefined);
|
|
1294
|
+
jest
|
|
1295
|
+
.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe')
|
|
1296
|
+
.mockImplementation(() => {});
|
|
1297
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
1298
|
+
jest
|
|
1299
|
+
.spyOn(webex.cc['webexCrossClientService'], 'isAnswerCallsStateActive')
|
|
1300
|
+
.mockReturnValue(true);
|
|
1301
|
+
jest
|
|
1302
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1303
|
+
.mockRejectedValue(new Error('publish failed'));
|
|
1304
|
+
jest
|
|
1305
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
1306
|
+
.mockResolvedValue(undefined);
|
|
1307
|
+
const metricSpy = jest.spyOn(mockMetricsManager, 'trackEvent');
|
|
1308
|
+
|
|
1309
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1310
|
+
data: {
|
|
1311
|
+
agentId: 'agentId',
|
|
1312
|
+
teamId: 'teamId',
|
|
1313
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1314
|
+
},
|
|
1315
|
+
trackingId: 'track-1',
|
|
1316
|
+
} as StationLoginSuccess);
|
|
1317
|
+
|
|
1318
|
+
await webex.cc.stationLogin({
|
|
1319
|
+
teamId: 'teamId',
|
|
1320
|
+
loginOption: LoginOption.EXTENSION,
|
|
1321
|
+
dialNumber: '1001',
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
expect(metricSpy).toHaveBeenCalledWith(
|
|
1325
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
|
|
1326
|
+
expect.objectContaining({skipReason: 'publish_failed', usersubPublished: true}),
|
|
1327
|
+
['operational', 'behavioral']
|
|
1328
|
+
);
|
|
1329
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
it('should register device before publishing usersub true on extension station login', async () => {
|
|
1333
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1334
|
+
webex.internal.device = {
|
|
1335
|
+
userId: 'user-123',
|
|
1336
|
+
registered: false,
|
|
1337
|
+
register: jest.fn().mockImplementation(async function (this: {
|
|
1338
|
+
url?: string;
|
|
1339
|
+
registered?: boolean;
|
|
1340
|
+
}) {
|
|
1341
|
+
this.url = 'https://wdm.example.com/devices/dev-1';
|
|
1342
|
+
this.registered = true;
|
|
1343
|
+
}),
|
|
1344
|
+
};
|
|
1345
|
+
webex.internal.mercury = {
|
|
1346
|
+
connected: false,
|
|
1347
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
1348
|
+
};
|
|
1349
|
+
webex.cc.agentConfig = {
|
|
1350
|
+
agentId: 'agentId',
|
|
1351
|
+
webRtcEnabled: false,
|
|
1352
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1353
|
+
};
|
|
1354
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1355
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe').mockImplementation(() => {});
|
|
1356
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
1357
|
+
|
|
1358
|
+
const callOrder: string[] = [];
|
|
1359
|
+
jest.spyOn(webex.internal.device, 'register').mockImplementation(async function (this: {
|
|
1360
|
+
url?: string;
|
|
1361
|
+
registered?: boolean;
|
|
1362
|
+
}) {
|
|
1363
|
+
callOrder.push('device.register');
|
|
1364
|
+
this.url = 'https://wdm.example.com/devices/dev-1';
|
|
1365
|
+
this.registered = true;
|
|
1366
|
+
});
|
|
1367
|
+
jest
|
|
1368
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1369
|
+
.mockImplementation(async (enable: boolean) => {
|
|
1370
|
+
if (enable === true && !webex.internal.device.url) {
|
|
1371
|
+
throw new Error('Device URL is unavailable for cross-client publish');
|
|
1372
|
+
}
|
|
1373
|
+
callOrder.push(enable ? 'publish:true' : 'publish:false');
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1377
|
+
data: {
|
|
1378
|
+
agentId: 'agentId',
|
|
1379
|
+
teamId: 'teamId',
|
|
1380
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1381
|
+
},
|
|
1382
|
+
trackingId: 'track-1',
|
|
1383
|
+
} as StationLoginSuccess);
|
|
1384
|
+
|
|
1385
|
+
await webex.cc.stationLogin({
|
|
1386
|
+
teamId: 'teamId',
|
|
1387
|
+
loginOption: LoginOption.EXTENSION,
|
|
1388
|
+
dialNumber: '1001',
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1391
|
+
expect(callOrder).toEqual(['device.register', 'publish:true']);
|
|
1392
|
+
});
|
|
1393
|
+
|
|
1394
|
+
it('should register device before publishing forced usersub false when wxApp is disabled at station login', async () => {
|
|
1395
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
1396
|
+
webex.internal.device = {
|
|
1397
|
+
userId: 'user-123',
|
|
1398
|
+
registered: false,
|
|
1399
|
+
register: jest.fn().mockImplementation(async function (this: {
|
|
1400
|
+
url?: string;
|
|
1401
|
+
registered?: boolean;
|
|
1402
|
+
}) {
|
|
1403
|
+
this.url = 'https://wdm.example.com/devices/dev-1';
|
|
1404
|
+
this.registered = true;
|
|
1405
|
+
}),
|
|
1406
|
+
};
|
|
1407
|
+
webex.internal.mercury = {
|
|
1408
|
+
connected: false,
|
|
1409
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
1410
|
+
};
|
|
1411
|
+
webex.cc.agentConfig = {
|
|
1412
|
+
agentId: 'agentId',
|
|
1413
|
+
webRtcEnabled: false,
|
|
1414
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1415
|
+
};
|
|
1416
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1417
|
+
jest
|
|
1418
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
1419
|
+
.mockResolvedValue(undefined);
|
|
1420
|
+
|
|
1421
|
+
const callOrder: string[] = [];
|
|
1422
|
+
jest.spyOn(webex.internal.device, 'register').mockImplementation(async function (this: {
|
|
1423
|
+
url?: string;
|
|
1424
|
+
registered?: boolean;
|
|
1425
|
+
}) {
|
|
1426
|
+
callOrder.push('device.register');
|
|
1427
|
+
this.url = 'https://wdm.example.com/devices/dev-1';
|
|
1428
|
+
this.registered = true;
|
|
1429
|
+
});
|
|
1430
|
+
jest
|
|
1431
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1432
|
+
.mockImplementation(async (enable: boolean) => {
|
|
1433
|
+
if (!webex.internal.device.url) {
|
|
1434
|
+
throw new Error('Device URL is unavailable for cross-client publish');
|
|
1435
|
+
}
|
|
1436
|
+
callOrder.push(enable ? 'publish:true' : 'publish:false');
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1440
|
+
data: {
|
|
1441
|
+
agentId: 'agentId',
|
|
1442
|
+
teamId: 'teamId',
|
|
1443
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1444
|
+
},
|
|
1445
|
+
trackingId: 'track-1',
|
|
1446
|
+
} as StationLoginSuccess);
|
|
1447
|
+
|
|
1448
|
+
await webex.cc.stationLogin({
|
|
1449
|
+
teamId: 'teamId',
|
|
1450
|
+
loginOption: LoginOption.EXTENSION,
|
|
1451
|
+
dialNumber: '1001',
|
|
1452
|
+
});
|
|
1453
|
+
|
|
1454
|
+
expect(callOrder).toEqual(['device.register', 'publish:false']);
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
it('should retry forced false publish when wxApp is disabled at station login', async () => {
|
|
1458
|
+
jest.useFakeTimers();
|
|
1459
|
+
try {
|
|
1460
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
1461
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1462
|
+
webex.cc.agentConfig = {
|
|
1463
|
+
agentId: 'agentId',
|
|
1464
|
+
webRtcEnabled: false,
|
|
1465
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1466
|
+
};
|
|
1467
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1468
|
+
|
|
1469
|
+
const publishSpy = jest
|
|
1470
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1471
|
+
.mockRejectedValueOnce(new Error('usersub publish failed'))
|
|
1472
|
+
.mockResolvedValue(undefined);
|
|
1473
|
+
const scheduleRetrySpy = jest.spyOn(
|
|
1474
|
+
webex.cc as any,
|
|
1475
|
+
'scheduleForcedFalsePublishRetry'
|
|
1476
|
+
);
|
|
1477
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
1478
|
+
jest
|
|
1479
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
1480
|
+
.mockResolvedValue(undefined);
|
|
1481
|
+
|
|
1482
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1483
|
+
data: {
|
|
1484
|
+
agentId: 'agentId',
|
|
1485
|
+
teamId: 'teamId',
|
|
1486
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1487
|
+
},
|
|
1488
|
+
trackingId: 'track-1',
|
|
1489
|
+
} as StationLoginSuccess);
|
|
1490
|
+
|
|
1491
|
+
await webex.cc.stationLogin({
|
|
1492
|
+
teamId: 'teamId',
|
|
1493
|
+
loginOption: LoginOption.EXTENSION,
|
|
1494
|
+
dialNumber: '1001',
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
expect(publishSpy).toHaveBeenCalledTimes(1);
|
|
1498
|
+
expect(scheduleRetrySpy).toHaveBeenCalled();
|
|
1499
|
+
expect(teardownSpy).not.toHaveBeenCalled();
|
|
1500
|
+
|
|
1501
|
+
jest.advanceTimersByTime(30_000);
|
|
1502
|
+
await publishSpy.mock.results[1]?.value;
|
|
1503
|
+
|
|
1504
|
+
expect(publishSpy).toHaveBeenCalledTimes(2);
|
|
1505
|
+
expect(publishSpy).toHaveBeenLastCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
1506
|
+
} finally {
|
|
1507
|
+
jest.useRealTimers();
|
|
1508
|
+
}
|
|
1509
|
+
});
|
|
1510
|
+
|
|
1511
|
+
it('should not publish usersub on BROWSER station login even when enableWxBetterTogether is true at init', async () => {
|
|
1512
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1513
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1514
|
+
webex.cc.agentConfig = {
|
|
1515
|
+
agentId: 'agentId',
|
|
1516
|
+
webRtcEnabled: true,
|
|
1517
|
+
loginVoiceOptions: ['BROWSER'],
|
|
1518
|
+
};
|
|
1519
|
+
webex.cc.webCallingService.loginOption = LoginOption.BROWSER;
|
|
1520
|
+
|
|
1521
|
+
const publishSpy = jest.spyOn(
|
|
1522
|
+
webex.cc['webexCrossClientService'],
|
|
1523
|
+
'setManageWebexCallingInWxcc'
|
|
1524
|
+
);
|
|
1525
|
+
|
|
1526
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1527
|
+
data: {
|
|
1528
|
+
agentId: 'agentId',
|
|
1529
|
+
teamId: 'teamId',
|
|
1530
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1531
|
+
},
|
|
1532
|
+
trackingId: 'track-1',
|
|
1533
|
+
} as StationLoginSuccess);
|
|
1534
|
+
|
|
1535
|
+
await webex.cc.stationLogin({
|
|
1536
|
+
teamId: 'teamId',
|
|
1537
|
+
loginOption: LoginOption.BROWSER,
|
|
1538
|
+
});
|
|
1539
|
+
|
|
1540
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
1541
|
+
});
|
|
1542
|
+
|
|
1543
|
+
it('should force-publish usersub false on station login when enableWxBetterTogether is false at init', async () => {
|
|
1544
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
1545
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1546
|
+
webex.cc.agentConfig = {
|
|
1547
|
+
agentId: 'agentId',
|
|
1548
|
+
webRtcEnabled: false,
|
|
1549
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1550
|
+
};
|
|
1551
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1552
|
+
|
|
1553
|
+
const publishSpy = jest
|
|
1554
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1555
|
+
.mockResolvedValue(undefined);
|
|
1556
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
1557
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
1558
|
+
|
|
1559
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1560
|
+
data: {
|
|
1561
|
+
agentId: 'agentId',
|
|
1562
|
+
teamId: 'teamId',
|
|
1563
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1564
|
+
},
|
|
1565
|
+
trackingId: 'track-1',
|
|
1566
|
+
} as StationLoginSuccess);
|
|
1567
|
+
|
|
1568
|
+
await webex.cc.stationLogin({
|
|
1569
|
+
teamId: 'teamId',
|
|
1570
|
+
loginOption: LoginOption.EXTENSION,
|
|
1571
|
+
dialNumber: '1001',
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1574
|
+
expect(publishSpy).toHaveBeenCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
1575
|
+
expect(teardownSpy).toHaveBeenCalled();
|
|
1576
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
1577
|
+
expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
|
|
1578
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_SKIPPED,
|
|
1579
|
+
expect.objectContaining({
|
|
1580
|
+
enableWxBetterTogether: false,
|
|
1581
|
+
skipReason: 'flag_disabled',
|
|
1582
|
+
}),
|
|
1583
|
+
['operational', 'behavioral']
|
|
1584
|
+
);
|
|
1585
|
+
});
|
|
1586
|
+
|
|
1587
|
+
it('should not emit WXAPP_SESSION_INIT_FAILED when flag-disabled cleanup fails', async () => {
|
|
1588
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
1589
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1590
|
+
webex.cc.agentConfig = {
|
|
1591
|
+
agentId: 'agentId',
|
|
1592
|
+
webRtcEnabled: false,
|
|
1593
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1594
|
+
};
|
|
1595
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1596
|
+
|
|
1597
|
+
jest
|
|
1598
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1599
|
+
.mockRejectedValue(new Error('forced false publish failed'));
|
|
1600
|
+
const metricSpy = jest.spyOn(mockMetricsManager, 'trackEvent');
|
|
1601
|
+
|
|
1602
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1603
|
+
data: {
|
|
1604
|
+
agentId: 'agentId',
|
|
1605
|
+
teamId: 'teamId',
|
|
1606
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1607
|
+
},
|
|
1608
|
+
trackingId: 'track-1',
|
|
1609
|
+
} as StationLoginSuccess);
|
|
1610
|
+
|
|
1611
|
+
await webex.cc.stationLogin({
|
|
1612
|
+
teamId: 'teamId',
|
|
1613
|
+
loginOption: LoginOption.EXTENSION,
|
|
1614
|
+
dialNumber: '1001',
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1617
|
+
expect(metricSpy).not.toHaveBeenCalledWith(
|
|
1618
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
|
|
1619
|
+
expect.anything(),
|
|
1620
|
+
expect.anything()
|
|
1621
|
+
);
|
|
1622
|
+
});
|
|
1623
|
+
|
|
1624
|
+
it('should still publish usersub but record session init failed when Mercury is unavailable', async () => {
|
|
1625
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1626
|
+
webex.internal.device = {
|
|
1627
|
+
userId: 'user-123',
|
|
1628
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
1629
|
+
registered: true,
|
|
1630
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
1631
|
+
};
|
|
1632
|
+
delete webex.internal.mercury;
|
|
1633
|
+
webex.cc.agentConfig = {
|
|
1634
|
+
agentId: 'agentId',
|
|
1635
|
+
webRtcEnabled: false,
|
|
1636
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1637
|
+
};
|
|
1638
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1639
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1640
|
+
|
|
1641
|
+
const publishSpy = jest
|
|
1642
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1643
|
+
.mockResolvedValue(undefined);
|
|
1644
|
+
jest
|
|
1645
|
+
.spyOn(webex.cc['webexCrossClientService'], 'isAnswerCallsStateActive')
|
|
1646
|
+
.mockReturnValue(true);
|
|
1647
|
+
const metricSpy = jest.spyOn(mockMetricsManager, 'trackEvent');
|
|
1648
|
+
|
|
1649
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1650
|
+
data: {
|
|
1651
|
+
agentId: 'agentId',
|
|
1652
|
+
teamId: 'teamId',
|
|
1653
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1654
|
+
},
|
|
1655
|
+
trackingId: 'track-1',
|
|
1656
|
+
} as StationLoginSuccess);
|
|
1657
|
+
|
|
1658
|
+
await webex.cc.stationLogin({
|
|
1659
|
+
teamId: 'teamId',
|
|
1660
|
+
loginOption: LoginOption.EXTENSION,
|
|
1661
|
+
dialNumber: '1001',
|
|
1662
|
+
});
|
|
1663
|
+
|
|
1664
|
+
expect(publishSpy).toHaveBeenCalledWith(true, {userId: 'user-123', trackPublishMetrics: true});
|
|
1665
|
+
expect(metricSpy).toHaveBeenCalledWith(
|
|
1666
|
+
METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_FAILED,
|
|
1667
|
+
expect.objectContaining({error: 'Mercury is unavailable for wxApp mute sync'}),
|
|
1668
|
+
['operational', 'behavioral']
|
|
1669
|
+
);
|
|
1670
|
+
expect(metricSpy).not.toHaveBeenCalledWith(
|
|
1671
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_SUCCESS,
|
|
1672
|
+
expect.anything(),
|
|
1673
|
+
expect.anything()
|
|
1674
|
+
);
|
|
1675
|
+
expect(metricSpy).toHaveBeenCalledWith(
|
|
1676
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
|
|
1677
|
+
expect.objectContaining({skipReason: 'mercury_not_subscribed'}),
|
|
1678
|
+
['operational', 'behavioral']
|
|
1679
|
+
);
|
|
1680
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(true);
|
|
1681
|
+
});
|
|
1682
|
+
|
|
1683
|
+
it('should record session init failed when userId is missing but not emit success', async () => {
|
|
1684
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1685
|
+
webex.internal.device = {
|
|
1686
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
1687
|
+
registered: true,
|
|
1688
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
1689
|
+
};
|
|
1690
|
+
webex.internal.mercury = {
|
|
1691
|
+
connected: true,
|
|
1692
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
1693
|
+
on: jest.fn(),
|
|
1694
|
+
off: jest.fn(),
|
|
1695
|
+
};
|
|
1696
|
+
webex.cc.agentConfig = {
|
|
1697
|
+
agentId: 'agentId',
|
|
1698
|
+
webRtcEnabled: false,
|
|
1699
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1700
|
+
};
|
|
1701
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1702
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1703
|
+
|
|
1704
|
+
const publishSpy = jest
|
|
1705
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1706
|
+
.mockResolvedValue(undefined);
|
|
1707
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
1708
|
+
const metricSpy = jest.spyOn(mockMetricsManager, 'trackEvent');
|
|
1709
|
+
|
|
1710
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1711
|
+
data: {
|
|
1712
|
+
agentId: 'agentId',
|
|
1713
|
+
teamId: 'teamId',
|
|
1714
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1715
|
+
},
|
|
1716
|
+
trackingId: 'track-1',
|
|
1717
|
+
} as StationLoginSuccess);
|
|
1718
|
+
|
|
1719
|
+
await webex.cc.stationLogin({
|
|
1720
|
+
teamId: 'teamId',
|
|
1721
|
+
loginOption: LoginOption.EXTENSION,
|
|
1722
|
+
dialNumber: '1001',
|
|
1723
|
+
});
|
|
1724
|
+
|
|
1725
|
+
expect(publishSpy).not.toHaveBeenCalledWith(true, expect.anything());
|
|
1726
|
+
expect(metricSpy).not.toHaveBeenCalledWith(
|
|
1727
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_SUCCESS,
|
|
1728
|
+
expect.anything(),
|
|
1729
|
+
expect.anything()
|
|
1730
|
+
);
|
|
1731
|
+
expect(metricSpy).toHaveBeenCalledWith(
|
|
1732
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
|
|
1733
|
+
expect.objectContaining({skipReason: 'usersub_not_published'}),
|
|
1734
|
+
['operational', 'behavioral']
|
|
1735
|
+
);
|
|
1736
|
+
expect(metricSpy).toHaveBeenCalledWith(
|
|
1737
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
1738
|
+
expect.objectContaining({skipReason: 'user_id_unavailable', usersubPublished: false}),
|
|
1739
|
+
['operational', 'behavioral']
|
|
1740
|
+
);
|
|
1741
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(true);
|
|
1742
|
+
});
|
|
1743
|
+
|
|
1744
|
+
it('should report retained usersub state when userId is missing during re-init', async () => {
|
|
1745
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1746
|
+
webex.internal.device = {
|
|
1747
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
1748
|
+
registered: true,
|
|
1749
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
1750
|
+
};
|
|
1751
|
+
webex.internal.mercury = {
|
|
1752
|
+
connected: true,
|
|
1753
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
1754
|
+
on: jest.fn(),
|
|
1755
|
+
off: jest.fn(),
|
|
1756
|
+
};
|
|
1757
|
+
webex.cc.agentConfig = {
|
|
1758
|
+
agentId: 'agentId',
|
|
1759
|
+
webRtcEnabled: false,
|
|
1760
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1761
|
+
};
|
|
1762
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1763
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1764
|
+
|
|
1765
|
+
jest
|
|
1766
|
+
.spyOn(webex.cc['webexCrossClientService'], 'isAnswerCallsStateActive')
|
|
1767
|
+
.mockReturnValue(true);
|
|
1768
|
+
const publishSpy = jest
|
|
1769
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1770
|
+
.mockResolvedValue(undefined);
|
|
1771
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
1772
|
+
const metricSpy = jest.spyOn(mockMetricsManager, 'trackEvent');
|
|
1773
|
+
|
|
1774
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1775
|
+
data: {
|
|
1776
|
+
agentId: 'agentId',
|
|
1777
|
+
teamId: 'teamId',
|
|
1778
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1779
|
+
},
|
|
1780
|
+
trackingId: 'track-1',
|
|
1781
|
+
} as StationLoginSuccess);
|
|
1782
|
+
|
|
1783
|
+
await webex.cc.stationLogin({
|
|
1784
|
+
teamId: 'teamId',
|
|
1785
|
+
loginOption: LoginOption.EXTENSION,
|
|
1786
|
+
dialNumber: '1001',
|
|
1787
|
+
});
|
|
1788
|
+
|
|
1789
|
+
expect(publishSpy).not.toHaveBeenCalledWith(true, expect.anything());
|
|
1790
|
+
expect(metricSpy).toHaveBeenCalledWith(
|
|
1791
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
1792
|
+
expect.objectContaining({skipReason: 'user_id_unavailable', usersubPublished: true}),
|
|
1793
|
+
['operational', 'behavioral']
|
|
1794
|
+
);
|
|
1795
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(true);
|
|
1796
|
+
});
|
|
1797
|
+
|
|
1798
|
+
it('should clear stale usersub on login after prior session enabled wxApp then hard refresh', async () => {
|
|
1799
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1800
|
+
webex.cc.agentConfig = {
|
|
1801
|
+
agentId: 'agentId',
|
|
1802
|
+
webRtcEnabled: false,
|
|
1803
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1804
|
+
};
|
|
1805
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1806
|
+
webex.request = jest.fn().mockResolvedValue({body: {}});
|
|
1807
|
+
|
|
1808
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1809
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1810
|
+
data: {
|
|
1811
|
+
agentId: 'agentId',
|
|
1812
|
+
teamId: 'teamId',
|
|
1813
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1814
|
+
},
|
|
1815
|
+
trackingId: 'track-1',
|
|
1816
|
+
} as StationLoginSuccess);
|
|
1817
|
+
|
|
1818
|
+
await webex.cc.stationLogin({
|
|
1819
|
+
teamId: 'teamId',
|
|
1820
|
+
loginOption: LoginOption.EXTENSION,
|
|
1821
|
+
dialNumber: '1001',
|
|
1822
|
+
});
|
|
1823
|
+
|
|
1824
|
+
webex.cc['webexCrossClientService'].teardown();
|
|
1825
|
+
webex.cc['wxAppTelephonyMercurySync'].unsubscribe();
|
|
1826
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
1827
|
+
|
|
1828
|
+
const publishSpy = jest
|
|
1829
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1830
|
+
.mockResolvedValue(undefined);
|
|
1831
|
+
|
|
1832
|
+
await webex.cc.stationLogin({
|
|
1833
|
+
teamId: 'teamId',
|
|
1834
|
+
loginOption: LoginOption.EXTENSION,
|
|
1835
|
+
dialNumber: '1001',
|
|
1836
|
+
});
|
|
1837
|
+
|
|
1838
|
+
expect(publishSpy).toHaveBeenCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
1839
|
+
});
|
|
1840
|
+
});
|
|
1841
|
+
|
|
1842
|
+
describe('stationLogout', () => {
|
|
1843
|
+
it('should logout successfully', async () => {
|
|
1844
|
+
const data = {logoutReason: 'Logout reason'};
|
|
1845
|
+
const response = {};
|
|
1846
|
+
|
|
1847
|
+
const stationLogoutMock = jest
|
|
1848
|
+
.spyOn(webex.cc.services.agent, 'logout')
|
|
1849
|
+
.mockResolvedValue({} as StationLogoutResponse);
|
|
1850
|
+
|
|
1851
|
+
const result = await webex.cc.stationLogout(data);
|
|
1852
|
+
|
|
1853
|
+
// Verify logging calls
|
|
1854
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station logout', {
|
|
1855
|
+
module: CC_FILE,
|
|
1856
|
+
method: 'stationLogout',
|
|
1857
|
+
});
|
|
1858
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith('Agent station logout completed successfully', {
|
|
1859
|
+
module: CC_FILE,
|
|
1860
|
+
method: 'stationLogout',
|
|
1861
|
+
});
|
|
1862
|
+
|
|
1863
|
+
expect(stationLogoutMock).toHaveBeenCalledWith({data: data});
|
|
1864
|
+
// TODO: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-626777 Implement the de-register method and close the listener there
|
|
1865
|
+
// expect(mockTaskManager.unregisterIncomingCallEvent).toHaveBeenCalledWith();
|
|
1866
|
+
// expect(mockTaskManager.off).toHaveBeenCalledWith(
|
|
1867
|
+
// TASK_EVENTS.TASK_INCOMING,
|
|
1868
|
+
// expect.any(Function)
|
|
1869
|
+
// );
|
|
1870
|
+
// expect(mockTaskManager.off).toHaveBeenCalledWith(
|
|
1871
|
+
// TASK_EVENTS.TASK_HYDRATE,
|
|
1872
|
+
// expect.any(Function)
|
|
1873
|
+
// );
|
|
1874
|
+
// expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
|
|
1875
|
+
expect(result).toEqual(response);
|
|
1876
|
+
expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
|
|
1877
|
+
METRIC_EVENT_NAMES.STATION_LOGOUT_SUCCESS,
|
|
1878
|
+
METRIC_EVENT_NAMES.STATION_LOGOUT_FAILED,
|
|
1879
|
+
]);
|
|
1880
|
+
});
|
|
1881
|
+
|
|
1882
|
+
it('should not publish usersub false on logout when wxApp was never enabled', async () => {
|
|
1883
|
+
webex.internal.device = {userId: 'user-123'};
|
|
1884
|
+
const publishSpy = jest
|
|
1885
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1886
|
+
.mockResolvedValue(undefined);
|
|
1887
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
1888
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
1889
|
+
|
|
1890
|
+
jest.spyOn(webex.cc.services.agent, 'logout').mockResolvedValue({
|
|
1891
|
+
trackingId: 'track-1',
|
|
1892
|
+
} as StationLogoutResponse);
|
|
1893
|
+
|
|
1894
|
+
await webex.cc.stationLogout({logoutReason: 'Logout reason'});
|
|
1895
|
+
|
|
1896
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
1897
|
+
expect(teardownSpy).toHaveBeenCalled();
|
|
1898
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
1899
|
+
});
|
|
1900
|
+
|
|
1901
|
+
it('should publish usersub false and teardown wxApp sync on logout when wxApp was enabled', async () => {
|
|
1902
|
+
webex.internal.device = {
|
|
1903
|
+
userId: 'user-123',
|
|
1904
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
1905
|
+
registered: false,
|
|
1906
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
1907
|
+
unregister: jest.fn().mockResolvedValue(undefined),
|
|
1908
|
+
};
|
|
1909
|
+
webex.internal.mercury = {
|
|
1910
|
+
connected: false,
|
|
1911
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
1912
|
+
disconnect: jest.fn().mockResolvedValue(undefined),
|
|
1913
|
+
};
|
|
1914
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe').mockImplementation(() => {});
|
|
1915
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
1916
|
+
webex.request = jest.fn().mockResolvedValue({body: {}});
|
|
1917
|
+
webex.cc['agentConfig'] = {
|
|
1918
|
+
agentId: 'agent-123',
|
|
1919
|
+
deviceType: LoginOption.EXTENSION,
|
|
1920
|
+
} as Profile;
|
|
1921
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1922
|
+
|
|
1923
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(true);
|
|
1924
|
+
|
|
1925
|
+
const publishSpy = jest
|
|
1926
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1927
|
+
.mockResolvedValue(undefined);
|
|
1928
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
1929
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
1930
|
+
|
|
1931
|
+
jest.spyOn(webex.cc.services.agent, 'logout').mockResolvedValue({
|
|
1932
|
+
trackingId: 'track-1',
|
|
1933
|
+
} as StationLogoutResponse);
|
|
1934
|
+
|
|
1935
|
+
await webex.cc.stationLogout({logoutReason: 'Logout reason'});
|
|
1936
|
+
|
|
1937
|
+
expect(publishSpy).toHaveBeenCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
1938
|
+
expect(teardownSpy).toHaveBeenCalled();
|
|
1939
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
1940
|
+
});
|
|
1941
|
+
|
|
1942
|
+
it('should teardown wxApp state on logout even when usersub publish fails', async () => {
|
|
1943
|
+
webex.internal.device = {userId: 'user-123'};
|
|
1944
|
+
webex.cc['agentConfig'] = {
|
|
1945
|
+
agentId: 'agent-123',
|
|
1946
|
+
deviceType: LoginOption.EXTENSION,
|
|
1947
|
+
} as Profile;
|
|
1948
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1949
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1950
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1951
|
+
|
|
1952
|
+
jest.spyOn(webex.cc.services.agent, 'logout').mockResolvedValue({
|
|
1953
|
+
trackingId: 'track-1',
|
|
1954
|
+
} as StationLogoutResponse);
|
|
1955
|
+
jest
|
|
1956
|
+
.spyOn(webex.cc as never, 'publishAnswerOnWebexCrossClientState' as never)
|
|
1957
|
+
.mockRejectedValue(new Error('usersub publish failed'));
|
|
1958
|
+
const scheduleRetrySpy = jest.spyOn(
|
|
1959
|
+
webex.cc as any,
|
|
1960
|
+
'scheduleCompensatingFalsePublishRetry'
|
|
1961
|
+
);
|
|
1962
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
1963
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
1964
|
+
|
|
1965
|
+
const result = await webex.cc.stationLogout({logoutReason: 'Logout reason'});
|
|
1966
|
+
|
|
1967
|
+
expect(result).toEqual({trackingId: 'track-1'});
|
|
1968
|
+
expect(teardownSpy).toHaveBeenCalled();
|
|
1969
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
1970
|
+
expect(scheduleRetrySpy).toHaveBeenCalled();
|
|
1971
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(true);
|
|
1972
|
+
});
|
|
1973
|
+
|
|
1974
|
+
it('should retry compensating false publish when teardown usersub publish fails on logout', async () => {
|
|
1975
|
+
jest.useFakeTimers();
|
|
1976
|
+
try {
|
|
1977
|
+
webex.internal.device = {
|
|
1978
|
+
userId: 'user-123',
|
|
1979
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
1980
|
+
};
|
|
1981
|
+
webex.cc['agentConfig'] = {
|
|
1982
|
+
agentId: 'agent-123',
|
|
1983
|
+
deviceType: LoginOption.EXTENSION,
|
|
1984
|
+
} as Profile;
|
|
1985
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1986
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1987
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1988
|
+
webex.cc['webexCrossClientService'].answerCallsState = true;
|
|
1989
|
+
|
|
1990
|
+
jest.spyOn(webex.cc.services.agent, 'logout').mockResolvedValue({
|
|
1991
|
+
trackingId: 'track-1',
|
|
1992
|
+
} as StationLogoutResponse);
|
|
1993
|
+
|
|
1994
|
+
const publishSpy = jest
|
|
1995
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1996
|
+
.mockRejectedValueOnce(new Error('usersub publish failed'))
|
|
1997
|
+
.mockResolvedValue(undefined);
|
|
1998
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
1999
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
2000
|
+
jest
|
|
2001
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
2002
|
+
.mockResolvedValue(undefined);
|
|
2003
|
+
|
|
2004
|
+
const result = await webex.cc.stationLogout({logoutReason: 'Logout reason'});
|
|
2005
|
+
|
|
2006
|
+
expect(result).toEqual({trackingId: 'track-1'});
|
|
2007
|
+
expect(publishSpy).toHaveBeenCalledTimes(1);
|
|
2008
|
+
expect(publishSpy).toHaveBeenCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
2009
|
+
expect(teardownSpy).toHaveBeenCalled();
|
|
2010
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
2011
|
+
|
|
2012
|
+
jest.advanceTimersByTime(30_000);
|
|
2013
|
+
await publishSpy.mock.results[1]?.value;
|
|
2014
|
+
|
|
2015
|
+
expect(publishSpy).toHaveBeenCalledTimes(2);
|
|
2016
|
+
expect(publishSpy).toHaveBeenLastCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
2017
|
+
} finally {
|
|
2018
|
+
jest.useRealTimers();
|
|
2019
|
+
}
|
|
2020
|
+
});
|
|
2021
|
+
|
|
2022
|
+
it('should cancel compensating retry when station login succeeds before retry fires', async () => {
|
|
2023
|
+
jest.useFakeTimers();
|
|
2024
|
+
try {
|
|
2025
|
+
webex.internal.device = {
|
|
2026
|
+
userId: 'user-123',
|
|
2027
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
2028
|
+
registered: false,
|
|
2029
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
2030
|
+
unregister: jest.fn().mockResolvedValue(undefined),
|
|
2031
|
+
};
|
|
2032
|
+
webex.internal.mercury = {
|
|
2033
|
+
connected: false,
|
|
2034
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
2035
|
+
disconnect: jest.fn().mockResolvedValue(undefined),
|
|
2036
|
+
};
|
|
2037
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe').mockImplementation(() => {});
|
|
2038
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
2039
|
+
webex.cc['agentConfig'] = {
|
|
2040
|
+
agentId: 'agent-123',
|
|
2041
|
+
deviceType: LoginOption.EXTENSION,
|
|
2042
|
+
webRtcEnabled: false,
|
|
2043
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
2044
|
+
} as Profile;
|
|
2045
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
2046
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
2047
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
2048
|
+
webex.cc['webexCrossClientService'].answerCallsState = true;
|
|
2049
|
+
|
|
2050
|
+
jest.spyOn(webex.cc.services.agent, 'logout').mockResolvedValue({
|
|
2051
|
+
trackingId: 'track-1',
|
|
2052
|
+
} as StationLogoutResponse);
|
|
2053
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
2054
|
+
data: {
|
|
2055
|
+
agentId: 'agent-123',
|
|
2056
|
+
teamId: 'team-123',
|
|
2057
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
2058
|
+
},
|
|
2059
|
+
trackingId: 'track-2',
|
|
2060
|
+
} as StationLoginSuccess);
|
|
2061
|
+
|
|
2062
|
+
const publishSpy = jest
|
|
2063
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
2064
|
+
.mockRejectedValueOnce(new Error('usersub publish failed'))
|
|
2065
|
+
.mockResolvedValue(undefined);
|
|
2066
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
2067
|
+
jest
|
|
2068
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
2069
|
+
.mockResolvedValue(undefined);
|
|
2070
|
+
|
|
2071
|
+
await webex.cc.stationLogout({logoutReason: 'Logout reason'});
|
|
2072
|
+
await webex.cc.stationLogin({
|
|
2073
|
+
teamId: 'team-123',
|
|
2074
|
+
loginOption: LoginOption.EXTENSION,
|
|
2075
|
+
dialNumber: '1001',
|
|
2076
|
+
});
|
|
2077
|
+
|
|
2078
|
+
expect(publishSpy).toHaveBeenCalledTimes(2);
|
|
2079
|
+
expect(publishSpy).toHaveBeenNthCalledWith(1, false, {userId: 'user-123', trackPublishMetrics: true});
|
|
2080
|
+
expect(publishSpy).toHaveBeenNthCalledWith(2, true, {userId: 'user-123', trackPublishMetrics: true});
|
|
2081
|
+
expect(teardownSpy).toHaveBeenCalledTimes(1);
|
|
2082
|
+
|
|
2083
|
+
jest.advanceTimersByTime(30_000);
|
|
2084
|
+
|
|
2085
|
+
expect(publishSpy).toHaveBeenCalledTimes(2);
|
|
2086
|
+
expect(teardownSpy).toHaveBeenCalledTimes(1);
|
|
2087
|
+
} finally {
|
|
2088
|
+
jest.useRealTimers();
|
|
2089
|
+
}
|
|
2090
|
+
});
|
|
2091
|
+
|
|
2092
|
+
it('should handle error during stationLogout', async () => {
|
|
2093
|
+
const data = {logoutReason: 'Logout reason'};
|
|
2094
|
+
const error = {
|
|
2095
|
+
details: {
|
|
2096
|
+
trackingId: '1234',
|
|
2097
|
+
data: {
|
|
2098
|
+
reason: 'Error while performing station logout',
|
|
2099
|
+
},
|
|
2100
|
+
},
|
|
2101
|
+
};
|
|
2102
|
+
|
|
2103
|
+
jest.spyOn(webex.cc.services.agent, 'logout').mockRejectedValue(error);
|
|
2104
|
+
|
|
2105
|
+
await expect(webex.cc.stationLogout(data)).rejects.toThrow(error.details.data.reason);
|
|
2106
|
+
|
|
2107
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station logout', {
|
|
2108
|
+
module: CC_FILE,
|
|
2109
|
+
method: 'stationLogout',
|
|
2110
|
+
});
|
|
2111
|
+
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
2112
|
+
`stationLogout failed with reason: ${error.details.data.reason}`,
|
|
2113
|
+
{module: CC_FILE, method: 'stationLogout', trackingId: error.details.trackingId}
|
|
2114
|
+
);
|
|
2115
|
+
});
|
|
2116
|
+
});
|
|
2117
|
+
|
|
2118
|
+
describe('setAgentStatus', () => {
|
|
2119
|
+
it('should set agent status successfully when status is Available', async () => {
|
|
2120
|
+
const expectedPayload = {
|
|
2121
|
+
state: 'Available',
|
|
2122
|
+
auxCodeId: '0',
|
|
2123
|
+
agentId: '123',
|
|
2124
|
+
lastStateChangeReason: 'Agent is available',
|
|
2125
|
+
};
|
|
2126
|
+
|
|
2127
|
+
const setAgentStatusMock = jest
|
|
2128
|
+
.spyOn(webex.cc.services.agent, 'stateChange')
|
|
2129
|
+
.mockResolvedValue({data: expectedPayload});
|
|
2130
|
+
|
|
2131
|
+
const result = await webex.cc.setAgentState(expectedPayload);
|
|
962
2132
|
|
|
963
2133
|
// Verify logging calls
|
|
964
2134
|
expect(LoggerProxy.info).toHaveBeenCalledWith('Setting agent state', {
|
|
@@ -1071,6 +2241,257 @@ describe('webex.cc', () => {
|
|
|
1071
2241
|
});
|
|
1072
2242
|
});
|
|
1073
2243
|
|
|
2244
|
+
describe('Agent Wellness Break SDK contracts', () => {
|
|
2245
|
+
const wellbeingCode = {
|
|
2246
|
+
id: 'wellbeing-code',
|
|
2247
|
+
name: 'WellbeingBreak',
|
|
2248
|
+
isSystem: true,
|
|
2249
|
+
isDefault: false,
|
|
2250
|
+
};
|
|
2251
|
+
|
|
2252
|
+
beforeEach(() => {
|
|
2253
|
+
webex.cc.agentConfig = {
|
|
2254
|
+
agentId: 'agent-1',
|
|
2255
|
+
isWellnessBreakEnabled: true,
|
|
2256
|
+
} as Profile;
|
|
2257
|
+
webex.cc['updateWellnessSession']('session-1');
|
|
2258
|
+
});
|
|
2259
|
+
|
|
2260
|
+
it('fetches the system idle code once per registration', async () => {
|
|
2261
|
+
const lookup = jest
|
|
2262
|
+
.spyOn(webex.cc.services.config, 'getWellbeingBreakIdleCode')
|
|
2263
|
+
.mockResolvedValue(wellbeingCode);
|
|
2264
|
+
|
|
2265
|
+
await expect(webex.cc.getWellbeingBreakIdleCode()).resolves.toEqual(wellbeingCode);
|
|
2266
|
+
await expect(webex.cc.getWellbeingBreakIdleCode()).resolves.toEqual(wellbeingCode);
|
|
2267
|
+
|
|
2268
|
+
expect(lookup).toHaveBeenCalledTimes(1);
|
|
2269
|
+
expect(lookup).toHaveBeenCalledWith('mockOrgId');
|
|
2270
|
+
});
|
|
2271
|
+
|
|
2272
|
+
it('does not cache an idle-code lookup that completes after a new registration starts', async () => {
|
|
2273
|
+
let resolveLookup: (idleCode: typeof wellbeingCode) => void = () => undefined;
|
|
2274
|
+
jest
|
|
2275
|
+
.spyOn(webex.cc.services.config, 'getWellbeingBreakIdleCode')
|
|
2276
|
+
.mockImplementation(
|
|
2277
|
+
() =>
|
|
2278
|
+
new Promise((resolve) => {
|
|
2279
|
+
resolveLookup = resolve;
|
|
2280
|
+
})
|
|
2281
|
+
);
|
|
2282
|
+
|
|
2283
|
+
const lookupPromise = webex.cc.getWellbeingBreakIdleCode();
|
|
2284
|
+
const staleLookup = expect(lookupPromise).rejects.toThrow(
|
|
2285
|
+
'WELLNESS_BREAK_REGISTRATION_CHANGED'
|
|
2286
|
+
);
|
|
2287
|
+
jest
|
|
2288
|
+
.spyOn(webex.cc, 'connectWebsocket')
|
|
2289
|
+
.mockRejectedValue(new Error('replacement registration failed'));
|
|
2290
|
+
|
|
2291
|
+
await expect(webex.cc.register()).rejects.toThrow('replacement registration failed');
|
|
2292
|
+
resolveLookup(wellbeingCode);
|
|
2293
|
+
|
|
2294
|
+
await staleLookup;
|
|
2295
|
+
expect(webex.cc['wellbeingBreakIdleCode']).toBeUndefined();
|
|
2296
|
+
});
|
|
2297
|
+
|
|
2298
|
+
it('rejects the system idle-code lookup when wellness is disabled', async () => {
|
|
2299
|
+
webex.cc.agentConfig.isWellnessBreakEnabled = false;
|
|
2300
|
+
|
|
2301
|
+
await expect(webex.cc.getWellbeingBreakIdleCode()).rejects.toThrow(
|
|
2302
|
+
'WELLNESS_BREAK_NOT_ENABLED'
|
|
2303
|
+
);
|
|
2304
|
+
expect(webex.cc.services.config.getWellbeingBreakIdleCode).not.toHaveBeenCalled();
|
|
2305
|
+
});
|
|
2306
|
+
|
|
2307
|
+
it('invalidates the wellness session only for a current-session multi-login close', () => {
|
|
2308
|
+
webex.cc['handleWebsocketMessage'](
|
|
2309
|
+
JSON.stringify({
|
|
2310
|
+
type: CC_EVENTS.AGENT_MULTI_LOGIN,
|
|
2311
|
+
data: {
|
|
2312
|
+
type: 'AgentMultiLoginCloseSession',
|
|
2313
|
+
agentSessionId: 'another-session',
|
|
2314
|
+
},
|
|
2315
|
+
})
|
|
2316
|
+
);
|
|
2317
|
+
expect(webex.cc['currentAgentSessionId']).toBe('session-1');
|
|
2318
|
+
|
|
2319
|
+
webex.cc['handleWebsocketMessage'](
|
|
2320
|
+
JSON.stringify({
|
|
2321
|
+
type: CC_EVENTS.AGENT_MULTI_LOGIN,
|
|
2322
|
+
data: {
|
|
2323
|
+
type: 'AgentMultiLoginCloseSession',
|
|
2324
|
+
agentSessionId: 'session-1',
|
|
2325
|
+
},
|
|
2326
|
+
})
|
|
2327
|
+
);
|
|
2328
|
+
expect(webex.cc['currentAgentSessionId']).toBeUndefined();
|
|
2329
|
+
});
|
|
2330
|
+
|
|
2331
|
+
it('emits primary wellness notifications regardless of notification session metadata', () => {
|
|
2332
|
+
const emitSpy = jest.spyOn(webex.cc, 'emit');
|
|
2333
|
+
const payload = {
|
|
2334
|
+
type: 'Wellness_Break_Handler',
|
|
2335
|
+
orgId: 'mockOrgId',
|
|
2336
|
+
trackingId: 'data-notification-tracking',
|
|
2337
|
+
data: {
|
|
2338
|
+
agentId: 'agent-1',
|
|
2339
|
+
orgId: 'mockOrgId',
|
|
2340
|
+
notifType: 'Wellness_Break_Handler',
|
|
2341
|
+
notifDetails: {
|
|
2342
|
+
actionEvent: 'SUGGEST_WELLNESS_BREAK',
|
|
2343
|
+
actionText: 'How about a break?',
|
|
2344
|
+
},
|
|
2345
|
+
data: {
|
|
2346
|
+
orgId: 'mockOrgId',
|
|
2347
|
+
agentSessionId: 'notification-session',
|
|
2348
|
+
interactionId: 'interaction-1',
|
|
2349
|
+
},
|
|
2350
|
+
},
|
|
2351
|
+
};
|
|
2352
|
+
|
|
2353
|
+
webex.cc['handleWebsocketMessage'](JSON.stringify(payload));
|
|
2354
|
+
|
|
2355
|
+
expect(emitSpy).toHaveBeenCalledWith(CC_EVENTS.WELLNESS_BREAK, {
|
|
2356
|
+
agentId: 'agent-1',
|
|
2357
|
+
orgId: 'mockOrgId',
|
|
2358
|
+
agentSessionId: 'notification-session',
|
|
2359
|
+
actionEvent: 'SUGGEST_WELLNESS_BREAK',
|
|
2360
|
+
actionText: 'How about a break?',
|
|
2361
|
+
interactionId: 'interaction-1',
|
|
2362
|
+
trackingId: 'data-notification-tracking',
|
|
2363
|
+
});
|
|
2364
|
+
});
|
|
2365
|
+
|
|
2366
|
+
it('routes data-socket wellness notifications without sending them to TaskManager', () => {
|
|
2367
|
+
const emitSpy = jest.spyOn(webex.cc, 'emit');
|
|
2368
|
+
const taskEventSpy = mockTaskManager.handleRealtimeWebsocketEvent;
|
|
2369
|
+
const wellnessMessage = JSON.stringify({
|
|
2370
|
+
type: 'Wellness_Break_Handler',
|
|
2371
|
+
orgId: 'mockOrgId',
|
|
2372
|
+
data: {
|
|
2373
|
+
agentId: 'agent-1',
|
|
2374
|
+
orgId: 'mockOrgId',
|
|
2375
|
+
notifType: 'Wellness_Break_Handler',
|
|
2376
|
+
notifDetails: {actionEvent: 'PROVIDE_WELLNESS_BREAK'},
|
|
2377
|
+
data: {orgId: 'mockOrgId', agentSessionId: 'notification-session'},
|
|
2378
|
+
},
|
|
2379
|
+
});
|
|
2380
|
+
|
|
2381
|
+
webex.cc['handleRTDWebsocketMessage'](wellnessMessage);
|
|
2382
|
+
|
|
2383
|
+
expect(emitSpy).toHaveBeenCalledWith(CC_EVENTS.WELLNESS_BREAK, {
|
|
2384
|
+
agentId: 'agent-1',
|
|
2385
|
+
orgId: 'mockOrgId',
|
|
2386
|
+
agentSessionId: 'notification-session',
|
|
2387
|
+
actionEvent: 'PROVIDE_WELLNESS_BREAK',
|
|
2388
|
+
});
|
|
2389
|
+
expect(taskEventSpy).not.toHaveBeenCalled();
|
|
2390
|
+
|
|
2391
|
+
const transcriptMessage = JSON.stringify({type: CC_EVENTS.REAL_TIME_TRANSCRIPTION});
|
|
2392
|
+
webex.cc['handleRTDWebsocketMessage'](transcriptMessage);
|
|
2393
|
+
expect(taskEventSpy).toHaveBeenCalledWith(transcriptMessage);
|
|
2394
|
+
});
|
|
2395
|
+
|
|
2396
|
+
it.each([
|
|
2397
|
+
['SUGGEST_WELLNESS_BREAK', 'interactionId'],
|
|
2398
|
+
['WELLNESS_BREAK_NOT_ALLOWED', 'InteractionId'],
|
|
2399
|
+
])('normalizes the %s action and either interaction-id spelling', (actionEvent, key) => {
|
|
2400
|
+
const emitSpy = jest.spyOn(webex.cc, 'emit');
|
|
2401
|
+
webex.cc['handleWebsocketMessage'](
|
|
2402
|
+
JSON.stringify({
|
|
2403
|
+
type: 'Wellness_Break_Handler',
|
|
2404
|
+
orgId: 'mockOrgId',
|
|
2405
|
+
data: {
|
|
2406
|
+
agentId: 'agent-1',
|
|
2407
|
+
orgId: 'mockOrgId',
|
|
2408
|
+
notifType: 'Wellness_Break_Handler',
|
|
2409
|
+
notifDetails: {actionEvent},
|
|
2410
|
+
data: {
|
|
2411
|
+
orgId: 'mockOrgId',
|
|
2412
|
+
agentSessionId: 'session-1',
|
|
2413
|
+
[key]: 'interaction-2',
|
|
2414
|
+
},
|
|
2415
|
+
},
|
|
2416
|
+
})
|
|
2417
|
+
);
|
|
2418
|
+
|
|
2419
|
+
expect(emitSpy).toHaveBeenCalledWith(
|
|
2420
|
+
CC_EVENTS.WELLNESS_BREAK,
|
|
2421
|
+
expect.objectContaining({actionEvent, interactionId: 'interaction-2'})
|
|
2422
|
+
);
|
|
2423
|
+
});
|
|
2424
|
+
|
|
2425
|
+
it('does not promote uncontracted notification text fields', () => {
|
|
2426
|
+
const emitSpy = jest.spyOn(webex.cc, 'emit');
|
|
2427
|
+
webex.cc['handleWebsocketMessage'](
|
|
2428
|
+
JSON.stringify({
|
|
2429
|
+
type: 'Wellness_Break_Handler',
|
|
2430
|
+
orgId: 'mockOrgId',
|
|
2431
|
+
trackingId: 'notification-tracking',
|
|
2432
|
+
data: {
|
|
2433
|
+
agentId: 'agent-1',
|
|
2434
|
+
orgId: 'mockOrgId',
|
|
2435
|
+
notifType: 'Wellness_Break_Handler',
|
|
2436
|
+
notifDetails: {
|
|
2437
|
+
actionEvent: 'SUGGEST_WELLNESS_BREAK',
|
|
2438
|
+
actionText2: 'Uncontracted text',
|
|
2439
|
+
},
|
|
2440
|
+
data: {
|
|
2441
|
+
orgId: 'mockOrgId',
|
|
2442
|
+
agentSessionId: 'session-1',
|
|
2443
|
+
InteractionId: 'interaction-qa',
|
|
2444
|
+
},
|
|
2445
|
+
},
|
|
2446
|
+
})
|
|
2447
|
+
);
|
|
2448
|
+
|
|
2449
|
+
expect(emitSpy).toHaveBeenCalledWith(CC_EVENTS.WELLNESS_BREAK, {
|
|
2450
|
+
agentId: 'agent-1',
|
|
2451
|
+
orgId: 'mockOrgId',
|
|
2452
|
+
agentSessionId: 'session-1',
|
|
2453
|
+
actionEvent: 'SUGGEST_WELLNESS_BREAK',
|
|
2454
|
+
interactionId: 'interaction-qa',
|
|
2455
|
+
trackingId: 'notification-tracking',
|
|
2456
|
+
});
|
|
2457
|
+
});
|
|
2458
|
+
|
|
2459
|
+
it('ignores wellness events when the effective flag is disabled or identity is missing', () => {
|
|
2460
|
+
const emitSpy = jest.spyOn(webex.cc, 'emit');
|
|
2461
|
+
const payload = {
|
|
2462
|
+
type: 'Wellness_Break_Handler',
|
|
2463
|
+
orgId: 'mockOrgId',
|
|
2464
|
+
data: {
|
|
2465
|
+
agentId: 'agent-1',
|
|
2466
|
+
orgId: 'mockOrgId',
|
|
2467
|
+
notifType: 'Wellness_Break_Handler',
|
|
2468
|
+
notifDetails: {actionEvent: 'SUGGEST_WELLNESS_BREAK'},
|
|
2469
|
+
data: {orgId: 'mockOrgId', agentSessionId: 'session-1'},
|
|
2470
|
+
},
|
|
2471
|
+
};
|
|
2472
|
+
|
|
2473
|
+
webex.cc.agentConfig.isWellnessBreakEnabled = false;
|
|
2474
|
+
webex.cc['handleWebsocketMessage'](JSON.stringify(payload));
|
|
2475
|
+
webex.cc.agentConfig.isWellnessBreakEnabled = true;
|
|
2476
|
+
const {orgId: omittedOrgId, ...payloadWithoutEnvelopeOrg} = payload;
|
|
2477
|
+
expect(omittedOrgId).toBe('mockOrgId');
|
|
2478
|
+
webex.cc['handleWebsocketMessage'](JSON.stringify(payloadWithoutEnvelopeOrg));
|
|
2479
|
+
|
|
2480
|
+
expect(emitSpy).not.toHaveBeenCalledWith(CC_EVENTS.WELLNESS_BREAK, expect.anything());
|
|
2481
|
+
expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
|
|
2482
|
+
METRIC_EVENT_NAMES.AI_ASSISTANT_WELLNESS_EVENT_INVALID,
|
|
2483
|
+
{reason: 'wellness_disabled'},
|
|
2484
|
+
['operational']
|
|
2485
|
+
);
|
|
2486
|
+
expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
|
|
2487
|
+
METRIC_EVENT_NAMES.AI_ASSISTANT_WELLNESS_EVENT_INVALID,
|
|
2488
|
+
{reason: 'missing_wellness_identity'},
|
|
2489
|
+
['operational']
|
|
2490
|
+
);
|
|
2491
|
+
});
|
|
2492
|
+
|
|
2493
|
+
});
|
|
2494
|
+
|
|
1074
2495
|
describe('getBuddyAgents', () => {
|
|
1075
2496
|
it('should return buddy agents response when successful', async () => {
|
|
1076
2497
|
const data: BuddyAgents = {state: 'Available', mediaType: 'telephony'};
|
|
@@ -1134,6 +2555,41 @@ describe('webex.cc', () => {
|
|
|
1134
2555
|
]);
|
|
1135
2556
|
});
|
|
1136
2557
|
|
|
2558
|
+
it('should map Transfer action to an Available backend state filter', async () => {
|
|
2559
|
+
webex.cc.agentConfig = {agentProfileID: 'test-agent-profile-id'};
|
|
2560
|
+
const buddyAgentsResponse = {data: {agentList: []}};
|
|
2561
|
+
const buddyAgentsSpy = jest
|
|
2562
|
+
.spyOn(webex.cc.services.agent, 'buddyAgents')
|
|
2563
|
+
.mockResolvedValue(buddyAgentsResponse);
|
|
2564
|
+
|
|
2565
|
+
await webex.cc.getBuddyAgents({action: 'Transfer', mediaType: 'chat'});
|
|
2566
|
+
|
|
2567
|
+
expect(buddyAgentsSpy).toHaveBeenCalledWith({
|
|
2568
|
+
data: {
|
|
2569
|
+
agentProfileId: 'test-agent-profile-id',
|
|
2570
|
+
mediaType: 'chat',
|
|
2571
|
+
state: 'Available',
|
|
2572
|
+
},
|
|
2573
|
+
});
|
|
2574
|
+
});
|
|
2575
|
+
|
|
2576
|
+
it('should omit the backend state filter for Consult and default media type to telephony', async () => {
|
|
2577
|
+
webex.cc.agentConfig = {agentProfileID: 'test-agent-profile-id'};
|
|
2578
|
+
const buddyAgentsResponse = {data: {agentList: []}};
|
|
2579
|
+
const buddyAgentsSpy = jest
|
|
2580
|
+
.spyOn(webex.cc.services.agent, 'buddyAgents')
|
|
2581
|
+
.mockResolvedValue(buddyAgentsResponse);
|
|
2582
|
+
|
|
2583
|
+
await webex.cc.getBuddyAgents({action: 'Consult'});
|
|
2584
|
+
|
|
2585
|
+
expect(buddyAgentsSpy).toHaveBeenCalledWith({
|
|
2586
|
+
data: {
|
|
2587
|
+
agentProfileId: 'test-agent-profile-id',
|
|
2588
|
+
mediaType: 'telephony',
|
|
2589
|
+
},
|
|
2590
|
+
});
|
|
2591
|
+
});
|
|
2592
|
+
|
|
1137
2593
|
it('should handle error', async () => {
|
|
1138
2594
|
const data: BuddyAgents = {state: 'Available', mediaType: 'telephony'};
|
|
1139
2595
|
webex.cc.agentConfig = {
|
|
@@ -1210,11 +2666,11 @@ describe('webex.cc', () => {
|
|
|
1210
2666
|
const webSocketManagerOnSpy = jest.spyOn(webex.cc.services.webSocketManager, 'on');
|
|
1211
2667
|
await webex.cc['silentRelogin']();
|
|
1212
2668
|
|
|
1213
|
-
expect(LoggerProxy.
|
|
2669
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
|
|
1214
2670
|
module: CC_FILE,
|
|
1215
2671
|
method: 'silentRelogin',
|
|
1216
2672
|
});
|
|
1217
|
-
expect(LoggerProxy.
|
|
2673
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
1218
2674
|
'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
|
|
1219
2675
|
{module: CC_FILE, method: 'silentRelogin'}
|
|
1220
2676
|
);
|
|
@@ -1259,7 +2715,7 @@ describe('webex.cc', () => {
|
|
|
1259
2715
|
|
|
1260
2716
|
jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
|
|
1261
2717
|
await webex.cc['silentRelogin']();
|
|
1262
|
-
expect(LoggerProxy.
|
|
2718
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
|
|
1263
2719
|
module: CC_FILE,
|
|
1264
2720
|
method: 'silentRelogin',
|
|
1265
2721
|
});
|
|
@@ -1274,7 +2730,7 @@ describe('webex.cc', () => {
|
|
|
1274
2730
|
jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
|
|
1275
2731
|
|
|
1276
2732
|
await expect(webex.cc['silentRelogin']()).rejects.toThrow(error);
|
|
1277
|
-
expect(LoggerProxy.
|
|
2733
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
|
|
1278
2734
|
module: CC_FILE,
|
|
1279
2735
|
method: 'silentRelogin',
|
|
1280
2736
|
});
|
|
@@ -1316,7 +2772,7 @@ describe('webex.cc', () => {
|
|
|
1316
2772
|
|
|
1317
2773
|
await webex.cc['silentRelogin']();
|
|
1318
2774
|
|
|
1319
|
-
expect(LoggerProxy.
|
|
2775
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
|
|
1320
2776
|
module: CC_FILE,
|
|
1321
2777
|
method: 'silentRelogin',
|
|
1322
2778
|
});
|
|
@@ -1361,6 +2817,83 @@ describe('webex.cc', () => {
|
|
|
1361
2817
|
expect(webex.cc.agentConfig.deviceType).toBe(LoginOption.AGENT_DN);
|
|
1362
2818
|
expect(webex.cc.agentConfig.dn).toBe('67890');
|
|
1363
2819
|
});
|
|
2820
|
+
|
|
2821
|
+
it('should publish usersub true on silent relogin when enableWxBetterTogether is true at init', async () => {
|
|
2822
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
2823
|
+
webex.internal.device = {
|
|
2824
|
+
userId: 'user-123',
|
|
2825
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
2826
|
+
registered: true,
|
|
2827
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
2828
|
+
};
|
|
2829
|
+
webex.internal.mercury.connected = true;
|
|
2830
|
+
webex.internal.mercury.connect = jest.fn().mockResolvedValue(undefined);
|
|
2831
|
+
webex.cc.agentConfig = {
|
|
2832
|
+
agentId: 'agentId',
|
|
2833
|
+
agentProfileID: 'test-agent-profile-id',
|
|
2834
|
+
isAgentLoggedIn: false,
|
|
2835
|
+
} as Profile;
|
|
2836
|
+
|
|
2837
|
+
jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
|
|
2838
|
+
data: {
|
|
2839
|
+
auxCodeId: 'auxCodeId',
|
|
2840
|
+
agentId: 'agentId',
|
|
2841
|
+
deviceType: LoginOption.EXTENSION,
|
|
2842
|
+
dn: '1001',
|
|
2843
|
+
teamId: 'teamId',
|
|
2844
|
+
lastStateChangeTimestamp: 1738575135188,
|
|
2845
|
+
lastIdleCodeChangeTimestamp: 1738575135189,
|
|
2846
|
+
},
|
|
2847
|
+
});
|
|
2848
|
+
|
|
2849
|
+
const publishSpy = jest
|
|
2850
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
2851
|
+
.mockResolvedValue(undefined);
|
|
2852
|
+
const subscribeSpy = jest
|
|
2853
|
+
.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe')
|
|
2854
|
+
.mockImplementation(() => {});
|
|
2855
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
2856
|
+
|
|
2857
|
+
await webex.cc['silentRelogin']();
|
|
2858
|
+
|
|
2859
|
+
expect(publishSpy).toHaveBeenCalledWith(true, {userId: 'user-123', trackPublishMetrics: true});
|
|
2860
|
+
expect(subscribeSpy).toHaveBeenCalledWith('agentId', expect.any(Function));
|
|
2861
|
+
expect(mockTaskManager.refreshWxAppOfferObservabilityForAllTasks).toHaveBeenCalled();
|
|
2862
|
+
});
|
|
2863
|
+
|
|
2864
|
+
it('should force-publish usersub false on silent relogin when enableWxBetterTogether is false at init', async () => {
|
|
2865
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
2866
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
2867
|
+
webex.cc.agentConfig = {
|
|
2868
|
+
agentId: 'agentId',
|
|
2869
|
+
agentProfileID: 'test-agent-profile-id',
|
|
2870
|
+
isAgentLoggedIn: false,
|
|
2871
|
+
} as Profile;
|
|
2872
|
+
|
|
2873
|
+
jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
|
|
2874
|
+
data: {
|
|
2875
|
+
auxCodeId: 'auxCodeId',
|
|
2876
|
+
agentId: 'agentId',
|
|
2877
|
+
deviceType: LoginOption.EXTENSION,
|
|
2878
|
+
dn: '1001',
|
|
2879
|
+
teamId: 'teamId',
|
|
2880
|
+
lastStateChangeTimestamp: 1738575135188,
|
|
2881
|
+
lastIdleCodeChangeTimestamp: 1738575135189,
|
|
2882
|
+
},
|
|
2883
|
+
});
|
|
2884
|
+
|
|
2885
|
+
const publishSpy = jest
|
|
2886
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
2887
|
+
.mockResolvedValue(undefined);
|
|
2888
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
2889
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
2890
|
+
|
|
2891
|
+
await webex.cc['silentRelogin']();
|
|
2892
|
+
|
|
2893
|
+
expect(publishSpy).toHaveBeenCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
2894
|
+
expect(teardownSpy).toHaveBeenCalled();
|
|
2895
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
2896
|
+
});
|
|
1364
2897
|
});
|
|
1365
2898
|
|
|
1366
2899
|
describe('setupEventListeners()', () => {
|
|
@@ -1500,6 +3033,27 @@ describe('webex.cc', () => {
|
|
|
1500
3033
|
});
|
|
1501
3034
|
});
|
|
1502
3035
|
|
|
3036
|
+
describe('getQueues', () => {
|
|
3037
|
+
it('delegates to the queue service when successful', async () => {
|
|
3038
|
+
const mockQueuesResponse = [{queueId: 'queue1', queueName: 'Queue 1'}];
|
|
3039
|
+
const queueSpy = jest
|
|
3040
|
+
.spyOn(webex.cc.queue, 'getQueues')
|
|
3041
|
+
.mockResolvedValue(mockQueuesResponse as any);
|
|
3042
|
+
|
|
3043
|
+
const result = await webex.cc.getQueues({page: 1});
|
|
3044
|
+
|
|
3045
|
+
expect(queueSpy).toHaveBeenCalledWith({page: 1});
|
|
3046
|
+
expect(result).toBe(mockQueuesResponse);
|
|
3047
|
+
});
|
|
3048
|
+
|
|
3049
|
+
it('propagates queue service errors', async () => {
|
|
3050
|
+
const error = new Error('Test error.');
|
|
3051
|
+
jest.spyOn(webex.cc.queue, 'getQueues').mockRejectedValue(error);
|
|
3052
|
+
|
|
3053
|
+
await expect(webex.cc.getQueues()).rejects.toThrow('Test error.');
|
|
3054
|
+
});
|
|
3055
|
+
});
|
|
3056
|
+
|
|
1503
3057
|
describe('uploadLogs', () => {
|
|
1504
3058
|
it('should upload logs successfully', async () => {
|
|
1505
3059
|
const uploadLogsMock = jest.spyOn(webex.cc.webexRequest, 'uploadLogs').mockResolvedValue({
|
|
@@ -1529,6 +3083,7 @@ describe('webex.cc', () => {
|
|
|
1529
3083
|
|
|
1530
3084
|
describe('unregister', () => {
|
|
1531
3085
|
let mockWebSocketManager;
|
|
3086
|
+
let mockRTDWebSocketManager;
|
|
1532
3087
|
let mercuryDisconnectSpy;
|
|
1533
3088
|
let deviceUnregisterSpy;
|
|
1534
3089
|
|
|
@@ -1545,8 +3100,15 @@ describe('webex.cc', () => {
|
|
|
1545
3100
|
off: jest.fn(),
|
|
1546
3101
|
on: jest.fn(),
|
|
1547
3102
|
};
|
|
3103
|
+
mockRTDWebSocketManager = {
|
|
3104
|
+
isSocketClosed: false,
|
|
3105
|
+
close: jest.fn(),
|
|
3106
|
+
off: jest.fn(),
|
|
3107
|
+
on: jest.fn(),
|
|
3108
|
+
};
|
|
1548
3109
|
|
|
1549
3110
|
webex.cc.services.webSocketManager = mockWebSocketManager;
|
|
3111
|
+
webex.cc.services.rtdWebSocketManager = mockRTDWebSocketManager;
|
|
1550
3112
|
|
|
1551
3113
|
webex.internal = webex.internal || {};
|
|
1552
3114
|
webex.internal.mercury = {
|
|
@@ -1562,6 +3124,32 @@ describe('webex.cc', () => {
|
|
|
1562
3124
|
deviceUnregisterSpy = jest.spyOn(webex.internal.device, 'unregister');
|
|
1563
3125
|
});
|
|
1564
3126
|
|
|
3127
|
+
it('invalidates wellness context before deregistration cleanup can fail', async () => {
|
|
3128
|
+
const cleanupError = new Error('cleanup failed');
|
|
3129
|
+
let rejectCleanup: (error: Error) => void = () => undefined;
|
|
3130
|
+
const cleanup = new Promise((resolve, reject) => {
|
|
3131
|
+
rejectCleanup = reject;
|
|
3132
|
+
});
|
|
3133
|
+
webex.cc.agentConfig = {
|
|
3134
|
+
...webex.cc.agentConfig,
|
|
3135
|
+
isWellnessBreakEnabled: true,
|
|
3136
|
+
};
|
|
3137
|
+
webex.cc['updateWellnessSession']('session-1');
|
|
3138
|
+
jest.spyOn(webex.cc as any, 'teardownWxAppLocalState').mockReturnValue(cleanup);
|
|
3139
|
+
|
|
3140
|
+
const deregistration = webex.cc.deregister();
|
|
3141
|
+
const rejection = expect(deregistration).rejects.toThrow(cleanupError);
|
|
3142
|
+
|
|
3143
|
+
expect(webex.cc['currentAgentSessionId']).toBeUndefined();
|
|
3144
|
+
await expect(webex.cc.apiAIAssistant.requestWellnessBreak()).rejects.toThrow(
|
|
3145
|
+
'WELLNESS_BREAK_AGENT_SESSION_REQUIRED'
|
|
3146
|
+
);
|
|
3147
|
+
|
|
3148
|
+
rejectCleanup(cleanupError);
|
|
3149
|
+
await rejection;
|
|
3150
|
+
expect(webex.cc['currentAgentSessionId']).toBeUndefined();
|
|
3151
|
+
});
|
|
3152
|
+
|
|
1565
3153
|
it('should unregister successfully and clean up all resources when webrtc is enabled', async () => {
|
|
1566
3154
|
webex.cc.services.rtdWebSocketManager = {
|
|
1567
3155
|
isSocketClosed: false,
|
|
@@ -1570,6 +3158,18 @@ describe('webex.cc', () => {
|
|
|
1570
3158
|
on: jest.fn(),
|
|
1571
3159
|
} as any;
|
|
1572
3160
|
|
|
3161
|
+
webex.internal.device = {
|
|
3162
|
+
...webex.internal.device,
|
|
3163
|
+
userId: 'user-123',
|
|
3164
|
+
unregister: jest.fn().mockResolvedValue(undefined),
|
|
3165
|
+
};
|
|
3166
|
+
deviceUnregisterSpy = jest.spyOn(webex.internal.device, 'unregister');
|
|
3167
|
+
const publishSpy = jest
|
|
3168
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
3169
|
+
.mockResolvedValue(undefined);
|
|
3170
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
3171
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
3172
|
+
|
|
1573
3173
|
await webex.cc.deregister();
|
|
1574
3174
|
|
|
1575
3175
|
expect(mockTaskManager.off).toHaveBeenCalledWith(
|
|
@@ -1580,6 +3180,10 @@ describe('webex.cc', () => {
|
|
|
1580
3180
|
TASK_EVENTS.TASK_HYDRATE,
|
|
1581
3181
|
expect.any(Function)
|
|
1582
3182
|
);
|
|
3183
|
+
expect(mockTaskManager.off).toHaveBeenCalledWith(
|
|
3184
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
3185
|
+
expect.any(Function)
|
|
3186
|
+
);
|
|
1583
3187
|
expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
|
|
1584
3188
|
expect(webex.cc.services.rtdWebSocketManager.off).toHaveBeenCalledWith(
|
|
1585
3189
|
'message',
|
|
@@ -1636,17 +3240,57 @@ describe('webex.cc', () => {
|
|
|
1636
3240
|
const [, hydrateCallback] = hydrateCalls[0];
|
|
1637
3241
|
expect(hydrateCallback).toBe(webex.cc['handleTaskHydrate']);
|
|
1638
3242
|
|
|
3243
|
+
const multiLoginHydrateCalls = mockTaskManager.off.mock.calls.filter(
|
|
3244
|
+
([evt]) => evt === TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE
|
|
3245
|
+
);
|
|
3246
|
+
expect(multiLoginHydrateCalls).toHaveLength(1);
|
|
3247
|
+
const [, multiLoginHydrateCallback] = multiLoginHydrateCalls[0];
|
|
3248
|
+
expect(multiLoginHydrateCallback).toBe(webex.cc['handleTaskMultiLoginHydrate']);
|
|
3249
|
+
|
|
1639
3250
|
const messageCalls = mockWebSocketManager.off.mock.calls.filter(([evt]) => evt === 'message');
|
|
1640
3251
|
expect(messageCalls).toHaveLength(1);
|
|
1641
3252
|
const [, messageCallback] = messageCalls[0];
|
|
1642
3253
|
expect(messageCallback).toBe(webex.cc['handleWebsocketMessage']);
|
|
1643
3254
|
|
|
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']);
|
|
3255
|
+
const connectionCalls = webex.cc.services.connectionService.off.mock.calls.filter(
|
|
3256
|
+
([evt]) => evt === 'connectionLost'
|
|
3257
|
+
);
|
|
3258
|
+
expect(connectionCalls).toHaveLength(1);
|
|
3259
|
+
const [, connectionCallback] = connectionCalls[0];
|
|
3260
|
+
expect(connectionCallback).toBe(webex.cc['handleConnectionLost']);
|
|
3261
|
+
|
|
3262
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
3263
|
+
expect(teardownSpy).toHaveBeenCalled();
|
|
3264
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
3265
|
+
});
|
|
3266
|
+
|
|
3267
|
+
it('should publish usersub false before device unregister when wxApp is active on deregister', async () => {
|
|
3268
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
3269
|
+
webex.cc['webexCrossClientService'].answerCallsState = true;
|
|
3270
|
+
webex.internal.device = {
|
|
3271
|
+
userId: 'user-123',
|
|
3272
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
3273
|
+
unregister: jest.fn().mockResolvedValue(undefined),
|
|
3274
|
+
};
|
|
3275
|
+
deviceUnregisterSpy = jest.spyOn(webex.internal.device, 'unregister');
|
|
3276
|
+
|
|
3277
|
+
const callOrder: string[] = [];
|
|
3278
|
+
jest
|
|
3279
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
3280
|
+
.mockImplementation(async (enable: boolean) => {
|
|
3281
|
+
if (enable === false && !webex.internal.device.url) {
|
|
3282
|
+
throw new Error('Device URL is unavailable for cross-client publish');
|
|
3283
|
+
}
|
|
3284
|
+
callOrder.push(enable ? 'publish:true' : 'publish:false');
|
|
3285
|
+
});
|
|
3286
|
+
deviceUnregisterSpy.mockImplementation(async () => {
|
|
3287
|
+
callOrder.push('device.unregister');
|
|
3288
|
+
webex.internal.device.url = undefined;
|
|
3289
|
+
});
|
|
3290
|
+
|
|
3291
|
+
await webex.cc.deregister();
|
|
3292
|
+
|
|
3293
|
+
expect(callOrder).toEqual(['publish:false', 'device.unregister']);
|
|
1650
3294
|
});
|
|
1651
3295
|
|
|
1652
3296
|
it('should skip webCallingService and internal cleanup when webrtc is disabled', async () => {
|
|
@@ -1661,6 +3305,10 @@ describe('webex.cc', () => {
|
|
|
1661
3305
|
TASK_EVENTS.TASK_HYDRATE,
|
|
1662
3306
|
expect.any(Function)
|
|
1663
3307
|
);
|
|
3308
|
+
expect(mockTaskManager.off).toHaveBeenCalledWith(
|
|
3309
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
3310
|
+
expect.any(Function)
|
|
3311
|
+
);
|
|
1664
3312
|
expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
|
|
1665
3313
|
expect(webex.cc.services.connectionService.off).toHaveBeenCalledWith(
|
|
1666
3314
|
'connectionLost',
|
|
@@ -1670,6 +3318,7 @@ describe('webex.cc', () => {
|
|
|
1670
3318
|
expect(webex.internal.mercury.off).not.toHaveBeenCalled();
|
|
1671
3319
|
expect(mercuryDisconnectSpy).not.toHaveBeenCalled();
|
|
1672
3320
|
expect(deviceUnregisterSpy).not.toHaveBeenCalled();
|
|
3321
|
+
expect(mockRTDWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
|
|
1673
3322
|
});
|
|
1674
3323
|
|
|
1675
3324
|
it('should skip internal mercury cleanup when loginVoiceOptions does not include BROWSER', async () => {
|
|
@@ -1686,10 +3335,140 @@ describe('webex.cc', () => {
|
|
|
1686
3335
|
expect(mercuryDisconnectSpy).not.toHaveBeenCalled();
|
|
1687
3336
|
expect(deviceUnregisterSpy).not.toHaveBeenCalled();
|
|
1688
3337
|
|
|
3338
|
+
expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
|
|
3339
|
+
expect(mockRTDWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
|
|
3340
|
+
expect(webex.cc.agentConfig).toBeNull();
|
|
3341
|
+
});
|
|
3342
|
+
|
|
3343
|
+
it('should teardown wxApp state on deregister even when usersub publish fails', async () => {
|
|
3344
|
+
webex.cc.agentConfig = {
|
|
3345
|
+
agentId: 'agentId',
|
|
3346
|
+
webRtcEnabled: false,
|
|
3347
|
+
loginVoiceOptions: [LoginOption.EXTENSION],
|
|
3348
|
+
};
|
|
3349
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
3350
|
+
webex.cc['webexCrossClientService'].answerCallsState = true;
|
|
3351
|
+
webex.internal.device = {
|
|
3352
|
+
userId: 'user-123',
|
|
3353
|
+
unregister: jest.fn().mockResolvedValue(undefined),
|
|
3354
|
+
};
|
|
3355
|
+
|
|
3356
|
+
jest
|
|
3357
|
+
.spyOn(webex.cc as never, 'publishAnswerOnWebexCrossClientState' as never)
|
|
3358
|
+
.mockRejectedValue(new Error('usersub publish failed'));
|
|
3359
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
3360
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
3361
|
+
|
|
3362
|
+
await expect(webex.cc.deregister()).rejects.toThrow('usersub publish failed');
|
|
3363
|
+
|
|
3364
|
+
expect(teardownSpy).toHaveBeenCalled();
|
|
3365
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
3366
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
1689
3367
|
expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
|
|
1690
3368
|
expect(webex.cc.agentConfig).toBeNull();
|
|
1691
3369
|
});
|
|
1692
3370
|
|
|
3371
|
+
it('should publish forced false during teardown when enable publish is still in flight', async () => {
|
|
3372
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
3373
|
+
webex.internal.device = {
|
|
3374
|
+
userId: 'user-123',
|
|
3375
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
3376
|
+
};
|
|
3377
|
+
|
|
3378
|
+
let resolveEnable: () => void = () => {};
|
|
3379
|
+
const enableGate = new Promise<void>((resolve) => {
|
|
3380
|
+
resolveEnable = resolve;
|
|
3381
|
+
});
|
|
3382
|
+
|
|
3383
|
+
const publishSpy = jest
|
|
3384
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
3385
|
+
.mockImplementation(async (enable: boolean) => {
|
|
3386
|
+
if (enable) {
|
|
3387
|
+
await enableGate;
|
|
3388
|
+
}
|
|
3389
|
+
});
|
|
3390
|
+
jest
|
|
3391
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
3392
|
+
.mockResolvedValue(undefined);
|
|
3393
|
+
|
|
3394
|
+
expect(webex.cc['webexCrossClientService'].answerCallsState).toBe(false);
|
|
3395
|
+
|
|
3396
|
+
const enableInFlight = (webex.cc as any).publishAnswerOnWebexCrossClientState(true);
|
|
3397
|
+
|
|
3398
|
+
await (webex.cc as any).teardownWxAppLocalState();
|
|
3399
|
+
|
|
3400
|
+
expect(publishSpy).toHaveBeenCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
3401
|
+
|
|
3402
|
+
resolveEnable();
|
|
3403
|
+
await enableInFlight;
|
|
3404
|
+
});
|
|
3405
|
+
|
|
3406
|
+
it('should release wxApp-owned Mercury and device on deregister', async () => {
|
|
3407
|
+
webex.cc.agentConfig = {
|
|
3408
|
+
agentId: 'agentId',
|
|
3409
|
+
webRtcEnabled: false,
|
|
3410
|
+
loginVoiceOptions: [LoginOption.EXTENSION],
|
|
3411
|
+
};
|
|
3412
|
+
webex.cc['wxAppMercuryConnectedByCc'] = true;
|
|
3413
|
+
webex.cc['wxAppDeviceRegisteredByCc'] = true;
|
|
3414
|
+
webex.internal.mercury.connected = true;
|
|
3415
|
+
webex.internal.device.registered = true;
|
|
3416
|
+
|
|
3417
|
+
const mercuryDisconnectSpy = jest
|
|
3418
|
+
.spyOn(webex.internal.mercury, 'disconnect')
|
|
3419
|
+
.mockResolvedValue(undefined);
|
|
3420
|
+
const deviceUnregisterSpy = jest
|
|
3421
|
+
.spyOn(webex.internal.device, 'unregister')
|
|
3422
|
+
.mockResolvedValue(undefined);
|
|
3423
|
+
|
|
3424
|
+
await webex.cc.deregister();
|
|
3425
|
+
|
|
3426
|
+
expect(mercuryDisconnectSpy).toHaveBeenCalled();
|
|
3427
|
+
expect(deviceUnregisterSpy).toHaveBeenCalled();
|
|
3428
|
+
expect(webex.cc['wxAppMercuryConnectedByCc']).toBe(false);
|
|
3429
|
+
expect(webex.cc['wxAppDeviceRegisteredByCc']).toBe(false);
|
|
3430
|
+
});
|
|
3431
|
+
|
|
3432
|
+
it('should retain wxApp Mercury ownership when disconnect fails and retry cleanup', async () => {
|
|
3433
|
+
webex.cc['wxAppMercuryConnectedByCc'] = true;
|
|
3434
|
+
webex.internal.mercury.connected = true;
|
|
3435
|
+
|
|
3436
|
+
const mercuryDisconnectSpy = jest
|
|
3437
|
+
.spyOn(webex.internal.mercury, 'disconnect')
|
|
3438
|
+
.mockRejectedValueOnce(new Error('disconnect failed'))
|
|
3439
|
+
.mockResolvedValueOnce(undefined);
|
|
3440
|
+
|
|
3441
|
+
await webex.cc['releaseWxAppMercuryResources']();
|
|
3442
|
+
|
|
3443
|
+
expect(mercuryDisconnectSpy).toHaveBeenCalledTimes(1);
|
|
3444
|
+
expect(webex.cc['wxAppMercuryConnectedByCc']).toBe(true);
|
|
3445
|
+
|
|
3446
|
+
await webex.cc['releaseWxAppMercuryResources']();
|
|
3447
|
+
|
|
3448
|
+
expect(mercuryDisconnectSpy).toHaveBeenCalledTimes(2);
|
|
3449
|
+
expect(webex.cc['wxAppMercuryConnectedByCc']).toBe(false);
|
|
3450
|
+
});
|
|
3451
|
+
|
|
3452
|
+
it('should retain wxApp device ownership when unregister fails and retry cleanup', async () => {
|
|
3453
|
+
webex.cc['wxAppDeviceRegisteredByCc'] = true;
|
|
3454
|
+
webex.internal.device.registered = true;
|
|
3455
|
+
|
|
3456
|
+
const deviceUnregisterSpy = jest
|
|
3457
|
+
.spyOn(webex.internal.device, 'unregister')
|
|
3458
|
+
.mockRejectedValueOnce(new Error('unregister failed'))
|
|
3459
|
+
.mockResolvedValueOnce(undefined);
|
|
3460
|
+
|
|
3461
|
+
await webex.cc['releaseWxAppMercuryResources']();
|
|
3462
|
+
|
|
3463
|
+
expect(deviceUnregisterSpy).toHaveBeenCalledTimes(1);
|
|
3464
|
+
expect(webex.cc['wxAppDeviceRegisteredByCc']).toBe(true);
|
|
3465
|
+
|
|
3466
|
+
await webex.cc['releaseWxAppMercuryResources']();
|
|
3467
|
+
|
|
3468
|
+
expect(deviceUnregisterSpy).toHaveBeenCalledTimes(2);
|
|
3469
|
+
expect(webex.cc['wxAppDeviceRegisteredByCc']).toBe(false);
|
|
3470
|
+
});
|
|
3471
|
+
|
|
1693
3472
|
it('should handle errors during unregister and track metrics', async () => {
|
|
1694
3473
|
const mockError = new Error('Failed to deregister device');
|
|
1695
3474
|
webex.internal.device.unregister.mockRejectedValue(mockError);
|
|
@@ -1704,6 +3483,10 @@ describe('webex.cc', () => {
|
|
|
1704
3483
|
TASK_EVENTS.TASK_HYDRATE,
|
|
1705
3484
|
expect.any(Function)
|
|
1706
3485
|
);
|
|
3486
|
+
expect(mockTaskManager.off).toHaveBeenCalledWith(
|
|
3487
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
3488
|
+
expect.any(Function)
|
|
3489
|
+
);
|
|
1707
3490
|
|
|
1708
3491
|
expect(LoggerProxy.error).toHaveBeenCalledWith(`Error during deregister: ${mockError}`, {
|
|
1709
3492
|
module: CC_FILE,
|
|
@@ -1726,7 +3509,7 @@ describe('webex.cc', () => {
|
|
|
1726
3509
|
|
|
1727
3510
|
beforeEach(() => {
|
|
1728
3511
|
emitSpy = jest.spyOn(webex.cc, 'emit');
|
|
1729
|
-
messageCallback =
|
|
3512
|
+
messageCallback = webex.cc['handleWebsocketMessage'];
|
|
1730
3513
|
});
|
|
1731
3514
|
|
|
1732
3515
|
it('should emit AGENT_STATION_LOGIN_SUCCESS on CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS with mapped payload', () => {
|
|
@@ -1936,6 +3719,55 @@ describe('webex.cc', () => {
|
|
|
1936
3719
|
expect(result).toEqual(mockResp);
|
|
1937
3720
|
});
|
|
1938
3721
|
|
|
3722
|
+
it('should preserve enableWxBetterTogether init flag across profile update logout/login', async () => {
|
|
3723
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
3724
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
3725
|
+
webex.cc.agentConfig = {
|
|
3726
|
+
...webex.cc.agentConfig,
|
|
3727
|
+
agentId: 'agentId',
|
|
3728
|
+
webRtcEnabled: false,
|
|
3729
|
+
loginVoiceOptions: ['BROWSER', 'EXTENSION'],
|
|
3730
|
+
} as Profile;
|
|
3731
|
+
webex.cc.webCallingService.loginOption = LoginOption.BROWSER;
|
|
3732
|
+
webex.cc.webCallingService.setLoginOption = jest.fn((loginOption: LoginOption) => {
|
|
3733
|
+
webex.cc.webCallingService.loginOption = loginOption;
|
|
3734
|
+
});
|
|
3735
|
+
webex.cc['webexCrossClientService'].answerCallsState = true;
|
|
3736
|
+
|
|
3737
|
+
const publishSpy = jest
|
|
3738
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
3739
|
+
.mockResolvedValue(undefined);
|
|
3740
|
+
jest
|
|
3741
|
+
.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe')
|
|
3742
|
+
.mockImplementation(() => {});
|
|
3743
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
3744
|
+
jest.spyOn(webex.cc as any, 'releaseWxAppMercuryResources').mockResolvedValue(undefined);
|
|
3745
|
+
|
|
3746
|
+
jest.spyOn(webex.cc.services.agent, 'logout').mockResolvedValue({
|
|
3747
|
+
trackingId: 'track-logout',
|
|
3748
|
+
} as StationLogoutResponse);
|
|
3749
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
3750
|
+
data: {
|
|
3751
|
+
agentId: 'agentId',
|
|
3752
|
+
teamId: 'teamId',
|
|
3753
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
3754
|
+
},
|
|
3755
|
+
trackingId: 'track-login',
|
|
3756
|
+
} as StationLoginSuccess);
|
|
3757
|
+
|
|
3758
|
+
webex.internal.mercury.connected = true;
|
|
3759
|
+
|
|
3760
|
+
await webex.cc.updateAgentProfile({
|
|
3761
|
+
teamId: 'teamId',
|
|
3762
|
+
loginOption: LoginOption.EXTENSION,
|
|
3763
|
+
dialNumber: '1001',
|
|
3764
|
+
});
|
|
3765
|
+
|
|
3766
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(true);
|
|
3767
|
+
expect(publishSpy).toHaveBeenCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
3768
|
+
expect(publishSpy).toHaveBeenCalledWith(true, {userId: 'user-123', trackPublishMetrics: true});
|
|
3769
|
+
});
|
|
3770
|
+
|
|
1939
3771
|
it('should use provided teamId if passed in payload', async () => {
|
|
1940
3772
|
const dataWithTeam = {
|
|
1941
3773
|
teamId: 'newTeam',
|
|
@@ -2320,4 +4152,445 @@ describe('webex.cc', () => {
|
|
|
2320
4152
|
expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'acceptPreviewContact', CC_FILE);
|
|
2321
4153
|
});
|
|
2322
4154
|
});
|
|
4155
|
+
|
|
4156
|
+
describe('skipPreviewContact', () => {
|
|
4157
|
+
const previewPayload = {
|
|
4158
|
+
interactionId: 'interaction-123',
|
|
4159
|
+
campaignId: 'campaign-456',
|
|
4160
|
+
};
|
|
4161
|
+
|
|
4162
|
+
it('should skip preview contact successfully', async () => {
|
|
4163
|
+
const mockResponse = {trackingId: 'track-123'} as AgentContact;
|
|
4164
|
+
|
|
4165
|
+
const skipPreviewContactMock = jest
|
|
4166
|
+
.spyOn(webex.cc.services.dialer, 'skipPreviewContact')
|
|
4167
|
+
.mockResolvedValue(mockResponse);
|
|
4168
|
+
|
|
4169
|
+
const result = await webex.cc.skipPreviewContact(previewPayload);
|
|
4170
|
+
|
|
4171
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Skipping campaign preview contact', {
|
|
4172
|
+
module: CC_FILE,
|
|
4173
|
+
method: 'skipPreviewContact',
|
|
4174
|
+
});
|
|
4175
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
4176
|
+
'Campaign preview contact skipped successfully',
|
|
4177
|
+
{
|
|
4178
|
+
module: CC_FILE,
|
|
4179
|
+
method: 'skipPreviewContact',
|
|
4180
|
+
trackingId: 'track-123',
|
|
4181
|
+
interactionId: previewPayload.interactionId,
|
|
4182
|
+
}
|
|
4183
|
+
);
|
|
4184
|
+
|
|
4185
|
+
expect(skipPreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
|
|
4186
|
+
expect(result).toEqual(mockResponse);
|
|
4187
|
+
});
|
|
4188
|
+
|
|
4189
|
+
it('should handle error during skipPreviewContact', async () => {
|
|
4190
|
+
getErrorDetailsSpy.mockRestore();
|
|
4191
|
+
getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
|
|
4192
|
+
|
|
4193
|
+
const error = {
|
|
4194
|
+
details: {
|
|
4195
|
+
trackingId: '1234',
|
|
4196
|
+
data: {
|
|
4197
|
+
reason: 'Error while performing skipPreviewContact',
|
|
4198
|
+
},
|
|
4199
|
+
},
|
|
4200
|
+
};
|
|
4201
|
+
|
|
4202
|
+
jest.spyOn(webex.cc.services.dialer, 'skipPreviewContact').mockRejectedValue(error);
|
|
4203
|
+
|
|
4204
|
+
await expect(webex.cc.skipPreviewContact(previewPayload)).rejects.toThrow(
|
|
4205
|
+
error.details.data.reason
|
|
4206
|
+
);
|
|
4207
|
+
|
|
4208
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Skipping campaign preview contact', {
|
|
4209
|
+
module: CC_FILE,
|
|
4210
|
+
method: 'skipPreviewContact',
|
|
4211
|
+
});
|
|
4212
|
+
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
4213
|
+
`skipPreviewContact failed with reason: ${error.details.data.reason}`,
|
|
4214
|
+
{module: CC_FILE, method: 'skipPreviewContact', trackingId: error.details.trackingId}
|
|
4215
|
+
);
|
|
4216
|
+
expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'skipPreviewContact', CC_FILE);
|
|
4217
|
+
});
|
|
4218
|
+
});
|
|
4219
|
+
|
|
4220
|
+
describe('removePreviewContact', () => {
|
|
4221
|
+
const previewPayload = {
|
|
4222
|
+
interactionId: 'interaction-123',
|
|
4223
|
+
campaignId: 'campaign-456',
|
|
4224
|
+
};
|
|
4225
|
+
|
|
4226
|
+
it('should remove preview contact successfully', async () => {
|
|
4227
|
+
const mockResponse = {trackingId: 'track-123'} as AgentContact;
|
|
4228
|
+
|
|
4229
|
+
const removePreviewContactMock = jest
|
|
4230
|
+
.spyOn(webex.cc.services.dialer, 'removePreviewContact')
|
|
4231
|
+
.mockResolvedValue(mockResponse);
|
|
4232
|
+
|
|
4233
|
+
const result = await webex.cc.removePreviewContact(previewPayload);
|
|
4234
|
+
|
|
4235
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Removing campaign preview contact', {
|
|
4236
|
+
module: CC_FILE,
|
|
4237
|
+
method: 'removePreviewContact',
|
|
4238
|
+
});
|
|
4239
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
4240
|
+
'Campaign preview contact removed successfully',
|
|
4241
|
+
{
|
|
4242
|
+
module: CC_FILE,
|
|
4243
|
+
method: 'removePreviewContact',
|
|
4244
|
+
trackingId: 'track-123',
|
|
4245
|
+
interactionId: previewPayload.interactionId,
|
|
4246
|
+
}
|
|
4247
|
+
);
|
|
4248
|
+
|
|
4249
|
+
expect(removePreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
|
|
4250
|
+
expect(result).toEqual(mockResponse);
|
|
4251
|
+
});
|
|
4252
|
+
|
|
4253
|
+
it('should handle error during removePreviewContact', async () => {
|
|
4254
|
+
getErrorDetailsSpy.mockRestore();
|
|
4255
|
+
getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
|
|
4256
|
+
|
|
4257
|
+
const error = {
|
|
4258
|
+
details: {
|
|
4259
|
+
trackingId: '1234',
|
|
4260
|
+
data: {
|
|
4261
|
+
reason: 'Error while performing removePreviewContact',
|
|
4262
|
+
},
|
|
4263
|
+
},
|
|
4264
|
+
};
|
|
4265
|
+
|
|
4266
|
+
jest.spyOn(webex.cc.services.dialer, 'removePreviewContact').mockRejectedValue(error);
|
|
4267
|
+
|
|
4268
|
+
await expect(webex.cc.removePreviewContact(previewPayload)).rejects.toThrow(
|
|
4269
|
+
error.details.data.reason
|
|
4270
|
+
);
|
|
4271
|
+
|
|
4272
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Removing campaign preview contact', {
|
|
4273
|
+
module: CC_FILE,
|
|
4274
|
+
method: 'removePreviewContact',
|
|
4275
|
+
});
|
|
4276
|
+
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
4277
|
+
`removePreviewContact failed with reason: ${error.details.data.reason}`,
|
|
4278
|
+
{module: CC_FILE, method: 'removePreviewContact', trackingId: error.details.trackingId}
|
|
4279
|
+
);
|
|
4280
|
+
expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'removePreviewContact', CC_FILE);
|
|
4281
|
+
});
|
|
4282
|
+
});
|
|
4283
|
+
|
|
4284
|
+
describe('private setManageWebexCallingInWxcc (runtime toggle impl)', () => {
|
|
4285
|
+
beforeEach(() => {
|
|
4286
|
+
webex.internal.device = {
|
|
4287
|
+
userId: 'user-123',
|
|
4288
|
+
registered: false,
|
|
4289
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
4290
|
+
unregister: jest.fn().mockResolvedValue(undefined),
|
|
4291
|
+
};
|
|
4292
|
+
webex.internal.mercury = {
|
|
4293
|
+
connected: false,
|
|
4294
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
4295
|
+
disconnect: jest.fn().mockResolvedValue(undefined),
|
|
4296
|
+
};
|
|
4297
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe').mockImplementation(() => {});
|
|
4298
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
4299
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
4300
|
+
webex.cc['agentConfig'] = {
|
|
4301
|
+
agentId: 'agent-123',
|
|
4302
|
+
deviceType: LoginOption.EXTENSION,
|
|
4303
|
+
} as Profile;
|
|
4304
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
4305
|
+
webex.cc.webCallingService.setLoginOption = jest.fn((loginOption: LoginOption) => {
|
|
4306
|
+
webex.cc.webCallingService.loginOption = loginOption;
|
|
4307
|
+
});
|
|
4308
|
+
});
|
|
4309
|
+
|
|
4310
|
+
afterEach(() => {
|
|
4311
|
+
jest.restoreAllMocks();
|
|
4312
|
+
});
|
|
4313
|
+
|
|
4314
|
+
it('should update config, task manager, and publish usersub when enabled', async () => {
|
|
4315
|
+
const publishSpy = jest
|
|
4316
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4317
|
+
.mockResolvedValue(undefined);
|
|
4318
|
+
|
|
4319
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(true);
|
|
4320
|
+
|
|
4321
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(true);
|
|
4322
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenCalledWith(true);
|
|
4323
|
+
expect(publishSpy).toHaveBeenCalledWith(true, {userId: 'user-123', trackPublishMetrics: true});
|
|
4324
|
+
expect(mockTaskManager.refreshWxAppOfferObservabilityForAllTasks).toHaveBeenCalled();
|
|
4325
|
+
});
|
|
4326
|
+
|
|
4327
|
+
it('should publish false and update config when disabled after wxApp was enabled', async () => {
|
|
4328
|
+
webex.internal.device = {
|
|
4329
|
+
userId: 'user-123',
|
|
4330
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
4331
|
+
};
|
|
4332
|
+
webex.request = jest.fn().mockResolvedValue({body: {}});
|
|
4333
|
+
|
|
4334
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(true);
|
|
4335
|
+
|
|
4336
|
+
const publishSpy = jest
|
|
4337
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4338
|
+
.mockResolvedValue(undefined);
|
|
4339
|
+
|
|
4340
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(false);
|
|
4341
|
+
|
|
4342
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
4343
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenCalledWith(false);
|
|
4344
|
+
expect(publishSpy).toHaveBeenCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
4345
|
+
});
|
|
4346
|
+
|
|
4347
|
+
it('should skip usersub publish when disabling without prior wxApp publish', async () => {
|
|
4348
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
4349
|
+
const publishSpy = jest
|
|
4350
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4351
|
+
.mockResolvedValue(undefined);
|
|
4352
|
+
|
|
4353
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(false);
|
|
4354
|
+
|
|
4355
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
4356
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenCalledWith(false);
|
|
4357
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
4358
|
+
});
|
|
4359
|
+
|
|
4360
|
+
it('should throw when enabling before login', async () => {
|
|
4361
|
+
webex.internal.device = undefined;
|
|
4362
|
+
|
|
4363
|
+
await expect((webex.cc as any).setManageWebexCallingInWxcc(true)).rejects.toThrow(
|
|
4364
|
+
'Cannot publish answer-calls-on-wxcc: user is not logged in'
|
|
4365
|
+
);
|
|
4366
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
4367
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).not.toHaveBeenCalled();
|
|
4368
|
+
});
|
|
4369
|
+
|
|
4370
|
+
it('should throw when enabling on BROWSER (Desktop) login', async () => {
|
|
4371
|
+
webex.cc['agentConfig'] = {deviceType: LoginOption.BROWSER} as Profile;
|
|
4372
|
+
webex.cc.webCallingService.loginOption = LoginOption.BROWSER;
|
|
4373
|
+
const publishSpy = jest
|
|
4374
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4375
|
+
.mockResolvedValue(undefined);
|
|
4376
|
+
|
|
4377
|
+
await expect((webex.cc as any).setManageWebexCallingInWxcc(true)).rejects.toThrow(
|
|
4378
|
+
'setManageWebexCallingInWxcc requires EXTENSION or AGENT_DN station login'
|
|
4379
|
+
);
|
|
4380
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
4381
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).not.toHaveBeenCalled();
|
|
4382
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
4383
|
+
});
|
|
4384
|
+
|
|
4385
|
+
it('should throw when enabling before station login', async () => {
|
|
4386
|
+
webex.cc['agentConfig'] = undefined as unknown as Profile;
|
|
4387
|
+
webex.cc.webCallingService.loginOption = undefined as unknown as LoginOption;
|
|
4388
|
+
const publishSpy = jest
|
|
4389
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4390
|
+
.mockResolvedValue(undefined);
|
|
4391
|
+
|
|
4392
|
+
await expect((webex.cc as any).setManageWebexCallingInWxcc(true)).rejects.toThrow(
|
|
4393
|
+
'setManageWebexCallingInWxcc requires EXTENSION or AGENT_DN station login'
|
|
4394
|
+
);
|
|
4395
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
4396
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).not.toHaveBeenCalled();
|
|
4397
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
4398
|
+
});
|
|
4399
|
+
|
|
4400
|
+
it('should throw when webCallingService is BROWSER even if agentConfig deviceType is stale', async () => {
|
|
4401
|
+
webex.cc['agentConfig'] = {deviceType: LoginOption.EXTENSION} as Profile;
|
|
4402
|
+
webex.cc.webCallingService.loginOption = LoginOption.BROWSER;
|
|
4403
|
+
const publishSpy = jest
|
|
4404
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4405
|
+
.mockResolvedValue(undefined);
|
|
4406
|
+
|
|
4407
|
+
await expect((webex.cc as any).setManageWebexCallingInWxcc(true)).rejects.toThrow(
|
|
4408
|
+
'setManageWebexCallingInWxcc requires EXTENSION or AGENT_DN station login'
|
|
4409
|
+
);
|
|
4410
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
4411
|
+
});
|
|
4412
|
+
|
|
4413
|
+
it('should publish usersub when enabling on AGENT_DN login', async () => {
|
|
4414
|
+
webex.cc['agentConfig'] = {
|
|
4415
|
+
agentId: 'agent-123',
|
|
4416
|
+
deviceType: LoginOption.AGENT_DN,
|
|
4417
|
+
} as Profile;
|
|
4418
|
+
const publishSpy = jest
|
|
4419
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4420
|
+
.mockResolvedValue(undefined);
|
|
4421
|
+
|
|
4422
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(true);
|
|
4423
|
+
|
|
4424
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(true);
|
|
4425
|
+
expect(publishSpy).toHaveBeenCalledWith(true, {userId: 'user-123', trackPublishMetrics: true});
|
|
4426
|
+
});
|
|
4427
|
+
|
|
4428
|
+
it('should no-op usersub publish when disabling before login', async () => {
|
|
4429
|
+
webex.internal.device = undefined;
|
|
4430
|
+
const publishSpy = jest
|
|
4431
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4432
|
+
.mockResolvedValue(undefined);
|
|
4433
|
+
|
|
4434
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(false);
|
|
4435
|
+
|
|
4436
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
4437
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenCalledWith(false);
|
|
4438
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
4439
|
+
});
|
|
4440
|
+
|
|
4441
|
+
it('should subscribe to wxApp Mercury mute sync when enabled', async () => {
|
|
4442
|
+
webex.cc['agentConfig'] = {agentId: 'agent-123'} as Profile;
|
|
4443
|
+
webex.internal.device = {
|
|
4444
|
+
...webex.internal.device,
|
|
4445
|
+
registered: true,
|
|
4446
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
4447
|
+
};
|
|
4448
|
+
webex.internal.mercury.connected = true;
|
|
4449
|
+
webex.internal.mercury.connect = jest.fn().mockResolvedValue(undefined);
|
|
4450
|
+
jest
|
|
4451
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4452
|
+
.mockResolvedValue(undefined);
|
|
4453
|
+
const subscribeSpy = jest
|
|
4454
|
+
.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe')
|
|
4455
|
+
.mockImplementation(() => {});
|
|
4456
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
4457
|
+
|
|
4458
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(true);
|
|
4459
|
+
|
|
4460
|
+
expect(subscribeSpy).toHaveBeenCalledWith('agent-123', expect.any(Function));
|
|
4461
|
+
});
|
|
4462
|
+
|
|
4463
|
+
it('should unsubscribe from wxApp Mercury mute sync when disabled', async () => {
|
|
4464
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
4465
|
+
jest
|
|
4466
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4467
|
+
.mockResolvedValue(undefined);
|
|
4468
|
+
const unsubscribeSpy = jest
|
|
4469
|
+
.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe')
|
|
4470
|
+
.mockImplementation(() => {});
|
|
4471
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
4472
|
+
|
|
4473
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(false);
|
|
4474
|
+
|
|
4475
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
4476
|
+
});
|
|
4477
|
+
|
|
4478
|
+
it('should register device and connect mercury when enabling wxApp sync', async () => {
|
|
4479
|
+
webex.cc['agentConfig'] = {agentId: 'agent-123'} as Profile;
|
|
4480
|
+
webex.internal.device = {
|
|
4481
|
+
userId: 'user-123',
|
|
4482
|
+
registered: false,
|
|
4483
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
4484
|
+
};
|
|
4485
|
+
webex.internal.mercury = {
|
|
4486
|
+
connected: false,
|
|
4487
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
4488
|
+
};
|
|
4489
|
+
jest
|
|
4490
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4491
|
+
.mockResolvedValue(undefined);
|
|
4492
|
+
const subscribeSpy = jest
|
|
4493
|
+
.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe')
|
|
4494
|
+
.mockImplementation(() => {});
|
|
4495
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
4496
|
+
|
|
4497
|
+
await (webex.cc as any).setManageWebexCallingInWxcc(true);
|
|
4498
|
+
|
|
4499
|
+
expect(webex.internal.device.register).toHaveBeenCalled();
|
|
4500
|
+
expect(webex.internal.mercury.connect).toHaveBeenCalled();
|
|
4501
|
+
expect(subscribeSpy).toHaveBeenCalledWith('agent-123', expect.any(Function));
|
|
4502
|
+
});
|
|
4503
|
+
|
|
4504
|
+
it('should rollback config and task manager when usersub publish fails', async () => {
|
|
4505
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
4506
|
+
jest
|
|
4507
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4508
|
+
.mockRejectedValue(new Error('usersub failed'));
|
|
4509
|
+
|
|
4510
|
+
await expect((webex.cc as any).setManageWebexCallingInWxcc(true)).rejects.toThrow('usersub failed');
|
|
4511
|
+
|
|
4512
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
4513
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenCalledWith(true);
|
|
4514
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenLastCalledWith(false);
|
|
4515
|
+
});
|
|
4516
|
+
|
|
4517
|
+
it('should rollback config and release Mercury resources when Mercury connect fails', async () => {
|
|
4518
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: false};
|
|
4519
|
+
const publishSpy = jest
|
|
4520
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4521
|
+
.mockResolvedValue(undefined);
|
|
4522
|
+
jest
|
|
4523
|
+
.spyOn(webex.cc as any, 'ensureWxAppMercuryConnected')
|
|
4524
|
+
.mockRejectedValue(new Error('mercury connect failed'));
|
|
4525
|
+
const releaseSpy = jest
|
|
4526
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
4527
|
+
.mockResolvedValue(undefined);
|
|
4528
|
+
const unsubscribeSpy = jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'unsubscribe');
|
|
4529
|
+
const teardownSpy = jest.spyOn(webex.cc['webexCrossClientService'], 'teardown');
|
|
4530
|
+
|
|
4531
|
+
await expect((webex.cc as any).setManageWebexCallingInWxcc(true)).rejects.toThrow(
|
|
4532
|
+
'mercury connect failed'
|
|
4533
|
+
);
|
|
4534
|
+
|
|
4535
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
4536
|
+
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenLastCalledWith(false);
|
|
4537
|
+
expect(publishSpy).not.toHaveBeenCalled();
|
|
4538
|
+
expect(teardownSpy).not.toHaveBeenCalled();
|
|
4539
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
4540
|
+
expect(releaseSpy).toHaveBeenCalled();
|
|
4541
|
+
});
|
|
4542
|
+
|
|
4543
|
+
it('should schedule compensating false retry when rollback publish fails after enable error', async () => {
|
|
4544
|
+
jest.useFakeTimers();
|
|
4545
|
+
try {
|
|
4546
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
4547
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
4548
|
+
webex.cc.agentConfig = {
|
|
4549
|
+
agentId: 'agent-123',
|
|
4550
|
+
deviceType: LoginOption.EXTENSION,
|
|
4551
|
+
};
|
|
4552
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
4553
|
+
|
|
4554
|
+
const publishSpy = jest
|
|
4555
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
4556
|
+
.mockResolvedValueOnce(undefined)
|
|
4557
|
+
.mockRejectedValueOnce(new Error('compensating false failed'))
|
|
4558
|
+
.mockResolvedValue(undefined);
|
|
4559
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe').mockImplementation(() => {});
|
|
4560
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
4561
|
+
jest
|
|
4562
|
+
.spyOn(webex.cc as any, 'ensureWxAppMercuryConnected')
|
|
4563
|
+
.mockResolvedValue(undefined);
|
|
4564
|
+
jest
|
|
4565
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
4566
|
+
.mockResolvedValue(undefined);
|
|
4567
|
+
jest
|
|
4568
|
+
.spyOn(mockTaskManager, 'syncWxAppMuteFromCallDetailsForAllTasks')
|
|
4569
|
+
.mockImplementation(() => {
|
|
4570
|
+
throw new Error('sync failed');
|
|
4571
|
+
});
|
|
4572
|
+
const scheduleRetrySpy = jest.spyOn(
|
|
4573
|
+
webex.cc as any,
|
|
4574
|
+
'scheduleCompensatingFalsePublishRetry'
|
|
4575
|
+
);
|
|
4576
|
+
|
|
4577
|
+
await expect((webex.cc as any).setManageWebexCallingInWxcc(true)).rejects.toThrow(
|
|
4578
|
+
'sync failed'
|
|
4579
|
+
);
|
|
4580
|
+
|
|
4581
|
+
expect(scheduleRetrySpy).toHaveBeenCalled();
|
|
4582
|
+
expect(publishSpy).toHaveBeenCalledTimes(2);
|
|
4583
|
+
expect(publishSpy).toHaveBeenNthCalledWith(1, true, {userId: 'user-123', trackPublishMetrics: true});
|
|
4584
|
+
expect(publishSpy).toHaveBeenNthCalledWith(2, false, {userId: 'user-123', trackPublishMetrics: true});
|
|
4585
|
+
|
|
4586
|
+
jest.advanceTimersByTime(30_000);
|
|
4587
|
+
await publishSpy.mock.results[2]?.value;
|
|
4588
|
+
|
|
4589
|
+
expect(publishSpy).toHaveBeenCalledTimes(3);
|
|
4590
|
+
expect(publishSpy).toHaveBeenLastCalledWith(false, {userId: 'user-123', trackPublishMetrics: true});
|
|
4591
|
+
} finally {
|
|
4592
|
+
jest.useRealTimers();
|
|
4593
|
+
}
|
|
4594
|
+
});
|
|
4595
|
+
});
|
|
2323
4596
|
});
|