@webex/contact-center 3.12.0-next.13 → 3.12.0-next.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.sdd/manifest.json +884 -0
- package/AGENTS.md +94 -0
- package/ai-docs/ARCHITECTURE.md +168 -0
- package/ai-docs/CONTRACTS.md +53 -0
- package/ai-docs/GETTING_STARTED.md +168 -0
- package/ai-docs/GLOSSARY.md +43 -0
- package/ai-docs/README.md +138 -0
- package/ai-docs/REVIEW_CHECKLIST.md +41 -0
- package/ai-docs/RULES.md +444 -0
- package/ai-docs/SECURITY.md +52 -0
- package/ai-docs/SERVICE_STATE.md +52 -0
- package/ai-docs/SPEC_INDEX.md +65 -0
- package/ai-docs/adr/0001-spec-source-policy.md +55 -0
- package/ai-docs/adr/README.md +8 -0
- package/ai-docs/adr/_adr-template.md +31 -0
- package/ai-docs/contact-center-spec.md +382 -0
- package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -0
- package/ai-docs/features/contact-owner-change-refactor.md +78 -0
- package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
- package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
- package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -0
- package/ai-docs/patterns/event-driven-patterns.md +485 -0
- package/ai-docs/patterns/testing-patterns.md +480 -0
- package/ai-docs/patterns/typescript-patterns.md +365 -0
- package/ai-docs/templates/README.md +102 -0
- package/ai-docs/templates/documentation/create-agents-md.md +240 -0
- package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
- package/ai-docs/templates/existing-service/bug-fix.md +254 -0
- package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
- package/ai-docs/templates/new-method/00-master.md +80 -0
- package/ai-docs/templates/new-method/01-requirements.md +232 -0
- package/ai-docs/templates/new-method/02-implementation.md +295 -0
- package/ai-docs/templates/new-method/03-tests.md +201 -0
- package/ai-docs/templates/new-method/04-validation.md +141 -0
- package/ai-docs/templates/new-service/00-master.md +109 -0
- package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
- package/ai-docs/templates/new-service/02-code-generation.md +346 -0
- package/ai-docs/templates/new-service/03-integration.md +178 -0
- package/ai-docs/templates/new-service/04-test-generation.md +205 -0
- package/ai-docs/templates/new-service/05-validation.md +145 -0
- package/dist/cc.js +971 -42
- package/dist/cc.js.map +1 -1
- package/dist/config.js +13 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +31 -3
- package/dist/constants.js.map +1 -1
- package/dist/index.js +45 -15
- package/dist/index.js.map +1 -1
- package/dist/metrics/MetricsManager.js +16 -0
- package/dist/metrics/MetricsManager.js.map +1 -1
- package/dist/metrics/behavioral-events.js +205 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +48 -4
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/AddressBook.js +18 -15
- package/dist/services/AddressBook.js.map +1 -1
- package/dist/services/AnswerCallOnWebexService.js +202 -0
- package/dist/services/AnswerCallOnWebexService.js.map +1 -0
- package/dist/services/ApiAiAssistant.js +269 -9
- package/dist/services/ApiAiAssistant.js.map +1 -1
- package/dist/services/EntryPoint.js +46 -68
- package/dist/services/EntryPoint.js.map +1 -1
- package/dist/services/Queue.js +27 -22
- package/dist/services/Queue.js.map +1 -1
- package/dist/services/UserPreference.js +427 -0
- package/dist/services/UserPreference.js.map +1 -0
- package/dist/services/WebexCrossClientService.js +222 -0
- package/dist/services/WebexCrossClientService.js.map +1 -0
- package/dist/services/WxAppTelephonyMercurySync.js +108 -0
- package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
- package/dist/services/config/Util.js +11 -4
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/constants.js +41 -7
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/index.js +51 -0
- package/dist/services/config/index.js.map +1 -1
- package/dist/services/config/types.js +57 -12
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/core/Utils.js +111 -32
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/core/WebexRequest.js +6 -2
- package/dist/services/core/WebexRequest.js.map +1 -1
- package/dist/services/core/aqm-reqs.js +28 -14
- package/dist/services/core/aqm-reqs.js.map +1 -1
- package/dist/services/core/types.js.map +1 -1
- package/dist/services/core/websocket/WebSocketManager.js +2 -1
- package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
- package/dist/services/core/websocket/types.js.map +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/task/Task.js +754 -0
- package/dist/services/task/Task.js.map +1 -0
- package/dist/services/task/TaskFactory.js +49 -0
- package/dist/services/task/TaskFactory.js.map +1 -0
- package/dist/services/task/TaskManager.js +1172 -521
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +211 -27
- package/dist/services/task/TaskUtils.js.map +1 -1
- package/dist/services/task/WebexCallingUtils.js +70 -0
- package/dist/services/task/WebexCallingUtils.js.map +1 -0
- package/dist/services/task/constants.js +12 -2
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/contact.js +29 -0
- package/dist/services/task/contact.js.map +1 -1
- package/dist/services/task/dialer.js +78 -0
- package/dist/services/task/dialer.js.map +1 -1
- package/dist/services/task/digital/Digital.js +78 -0
- package/dist/services/task/digital/Digital.js.map +1 -0
- package/dist/services/task/state-machine/TaskStateMachine.js +982 -0
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
- package/dist/services/task/state-machine/actions.js +572 -0
- package/dist/services/task/state-machine/actions.js.map +1 -0
- package/dist/services/task/state-machine/constants.js +161 -0
- package/dist/services/task/state-machine/constants.js.map +1 -0
- package/dist/services/task/state-machine/guards.js +379 -0
- package/dist/services/task/state-machine/guards.js.map +1 -0
- package/dist/services/task/state-machine/index.js +53 -0
- package/dist/services/task/state-machine/index.js.map +1 -0
- package/dist/services/task/state-machine/types.js +54 -0
- package/dist/services/task/state-machine/types.js.map +1 -0
- package/dist/services/task/state-machine/uiControlsComputer.js +703 -0
- package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
- package/dist/services/task/taskDataNormalizer.js +99 -0
- package/dist/services/task/taskDataNormalizer.js.map +1 -0
- package/dist/services/task/types.js +228 -5
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +1485 -0
- package/dist/services/task/voice/Voice.js.map +1 -0
- package/dist/services/task/voice/WebRTC.js +152 -0
- package/dist/services/task/voice/WebRTC.js.map +1 -0
- package/dist/services/task/voice/wxAppVoiceMethods.js +354 -0
- package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
- package/dist/services/wxAppDiagnosticLogging.js +122 -0
- package/dist/services/wxAppDiagnosticLogging.js.map +1 -0
- package/dist/services/wxAppTelephonyUtils.js +19 -0
- package/dist/services/wxAppTelephonyUtils.js.map +1 -0
- package/dist/types/cc.d.ts +193 -6
- package/dist/types/config.d.ts +13 -0
- package/dist/types/constants.d.ts +29 -1
- package/dist/types/index.d.ts +35 -8
- package/dist/types/metrics/MetricsManager.d.ts +6 -0
- package/dist/types/metrics/constants.d.ts +43 -1
- package/dist/types/services/AddressBook.d.ts +2 -1
- package/dist/types/services/AnswerCallOnWebexService.d.ts +38 -0
- package/dist/types/services/ApiAiAssistant.d.ts +56 -5
- package/dist/types/services/EntryPoint.d.ts +7 -5
- package/dist/types/services/Queue.d.ts +5 -3
- package/dist/types/services/UserPreference.d.ts +118 -0
- package/dist/types/services/WebexCrossClientService.d.ts +32 -0
- package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
- package/dist/types/services/config/constants.d.ts +34 -5
- package/dist/types/services/config/types.d.ts +197 -12
- package/dist/types/services/core/Err.d.ts +6 -0
- package/dist/types/services/core/Utils.d.ts +35 -13
- package/dist/types/services/core/WebexRequest.d.ts +1 -0
- package/dist/types/services/core/types.d.ts +2 -0
- package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
- package/dist/types/services/core/websocket/types.d.ts +1 -1
- package/dist/types/services/index.d.ts +1 -1
- package/dist/types/services/task/Task.d.ts +175 -0
- package/dist/types/services/task/TaskFactory.d.ts +13 -0
- package/dist/types/services/task/TaskUtils.d.ts +48 -2
- package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
- package/dist/types/services/task/constants.d.ts +10 -0
- package/dist/types/services/task/contact.d.ts +4 -0
- package/dist/types/services/task/dialer.d.ts +30 -0
- package/dist/types/services/task/digital/Digital.d.ts +22 -0
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1442 -0
- package/dist/types/services/task/state-machine/actions.d.ts +10 -0
- package/dist/types/services/task/state-machine/constants.d.ts +107 -0
- package/dist/types/services/task/state-machine/guards.d.ts +103 -0
- package/dist/types/services/task/state-machine/index.d.ts +13 -0
- package/dist/types/services/task/state-machine/types.d.ts +277 -0
- package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
- package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
- package/dist/types/services/task/types.d.ts +680 -66
- package/dist/types/services/task/voice/Voice.d.ts +227 -0
- package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
- package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +55 -0
- package/dist/types/services/wxAppDiagnosticLogging.d.ts +51 -0
- package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
- package/dist/types/types.d.ts +205 -9
- package/dist/types/utils/PageCache.d.ts +20 -3
- package/dist/types/webex.d.ts +1 -0
- package/dist/types.js +162 -4
- package/dist/types.js.map +1 -1
- package/dist/utils/PageCache.js +19 -5
- package/dist/utils/PageCache.js.map +1 -1
- package/dist/webex.js +22 -2
- package/dist/webex.js.map +1 -1
- package/package.json +16 -12
- package/src/cc.ts +1289 -112
- package/src/config.ts +13 -0
- package/src/constants.ts +29 -1
- package/src/index.ts +36 -5
- package/src/metrics/MetricsManager.ts +16 -0
- package/src/metrics/ai-docs/AGENTS.md +350 -0
- package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
- package/src/metrics/ai-docs/metrics-spec.md +921 -0
- package/src/metrics/behavioral-events.ts +212 -0
- package/src/metrics/constants.ts +52 -4
- package/src/services/AddressBook.ts +17 -6
- package/src/services/AnswerCallOnWebexService.ts +239 -0
- package/src/services/ApiAiAssistant.ts +356 -9
- package/src/services/EntryPoint.ts +59 -60
- package/src/services/Queue.ts +29 -12
- package/src/services/UserPreference.ts +509 -0
- package/src/services/WebexCrossClientService.ts +293 -0
- package/src/services/WxAppTelephonyMercurySync.ts +142 -0
- package/src/services/agent/ai-docs/AGENTS.md +240 -0
- package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
- package/src/services/agent/ai-docs/agent-spec.md +505 -0
- package/src/services/ai-docs/AGENTS.md +386 -0
- package/src/services/ai-docs/services-spec.md +505 -0
- package/src/services/config/Util.ts +12 -3
- package/src/services/config/ai-docs/AGENTS.md +255 -0
- package/src/services/config/ai-docs/ARCHITECTURE.md +426 -0
- package/src/services/config/ai-docs/config-spec.md +686 -0
- package/src/services/config/constants.ts +46 -6
- package/src/services/config/index.ts +65 -0
- package/src/services/config/types.ts +197 -13
- package/src/services/core/Err.ts +3 -0
- package/src/services/core/Utils.ts +127 -37
- package/src/services/core/WebexRequest.ts +3 -1
- package/src/services/core/ai-docs/AGENTS.md +381 -0
- package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
- package/src/services/core/ai-docs/core-spec.md +787 -0
- package/src/services/core/aqm-reqs.ts +30 -15
- package/src/services/core/types.ts +2 -0
- package/src/services/core/websocket/WebSocketManager.ts +2 -0
- package/src/services/core/websocket/types.ts +1 -1
- package/src/services/index.ts +1 -1
- package/src/services/task/Task.ts +908 -0
- package/src/services/task/TaskFactory.ts +60 -0
- package/src/services/task/TaskManager.ts +1419 -608
- package/src/services/task/TaskUtils.ts +303 -25
- package/src/services/task/WebexCallingUtils.ts +136 -0
- package/src/services/task/ai-docs/AGENTS.md +457 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +595 -0
- package/src/services/task/ai-docs/task-spec.md +1474 -0
- package/src/services/task/constants.ts +10 -0
- package/src/services/task/contact.ts +30 -0
- package/src/services/task/dialer.ts +80 -0
- package/src/services/task/digital/Digital.ts +97 -0
- package/src/services/task/state-machine/TaskStateMachine.ts +1320 -0
- package/src/services/task/state-machine/actions.ts +741 -0
- package/src/services/task/state-machine/ai-docs/AGENTS.md +460 -0
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1156 -0
- package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2247 -0
- package/src/services/task/state-machine/constants.ts +172 -0
- package/src/services/task/state-machine/guards.ts +442 -0
- package/src/services/task/state-machine/index.ts +28 -0
- package/src/services/task/state-machine/types.ts +258 -0
- package/src/services/task/state-machine/uiControlsComputer.ts +1135 -0
- package/src/services/task/taskDataNormalizer.ts +137 -0
- package/src/services/task/types.ts +819 -70
- package/src/services/task/voice/Voice.ts +1851 -0
- package/src/services/task/voice/WebRTC.ts +191 -0
- package/src/services/task/voice/wxAppVoiceMethods.ts +516 -0
- package/src/services/wxAppDiagnosticLogging.ts +174 -0
- package/src/services/wxAppTelephonyUtils.ts +14 -0
- package/src/types.ts +262 -11
- package/src/utils/AGENTS.md +289 -0
- package/src/utils/PageCache.ts +38 -5
- package/src/utils/ai-docs/utils-spec.md +391 -0
- package/src/webex.js +10 -0
- package/test/unit/spec/cc.ts +2210 -119
- package/test/unit/spec/logger-proxy.ts +70 -0
- package/test/unit/spec/metrics/MetricsManager.ts +25 -0
- package/test/unit/spec/metrics/behavioral-events.ts +60 -0
- package/test/unit/spec/services/AddressBook.ts +37 -6
- package/test/unit/spec/services/AnswerCallOnWebexService.ts +264 -0
- package/test/unit/spec/services/ApiAiAssistant.ts +311 -21
- package/test/unit/spec/services/EntryPoint.ts +87 -40
- package/test/unit/spec/services/Queue.ts +123 -12
- package/test/unit/spec/services/UserPreference.ts +401 -0
- package/test/unit/spec/services/WebCallingService.ts +7 -1
- package/test/unit/spec/services/WebexCrossClientService.ts +467 -0
- package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +136 -0
- package/test/unit/spec/services/config/Util.ts +129 -0
- package/test/unit/spec/services/config/index.ts +97 -29
- package/test/unit/spec/services/core/Utils.ts +425 -8
- package/test/unit/spec/services/core/WebexRequest.ts +3 -1
- package/test/unit/spec/services/core/aqm-reqs.ts +113 -1
- package/test/unit/spec/services/core/websocket/WebSocketManager.ts +66 -40
- package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
- package/test/unit/spec/services/task/Task.ts +745 -0
- package/test/unit/spec/services/task/TaskFactory.ts +99 -0
- package/test/unit/spec/services/task/TaskManager.ts +2518 -1494
- package/test/unit/spec/services/task/TaskUtils.ts +235 -0
- package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
- package/test/unit/spec/services/task/contact.ts +33 -0
- package/test/unit/spec/services/task/dialer.ts +190 -0
- package/test/unit/spec/services/task/digital/Digital.ts +105 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +4060 -0
- package/test/unit/spec/services/task/state-machine/guards.ts +839 -0
- package/test/unit/spec/services/task/state-machine/types.ts +18 -0
- package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +3101 -0
- package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
- package/test/unit/spec/services/task/voice/Voice.ts +2061 -0
- package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
- package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +603 -0
- package/test/unit/spec/services/wxAppDiagnosticLogging.ts +143 -0
- package/test/unit/spec/wellnessSampleUtils.ts +288 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
- package/dist/services/task/index.js +0 -1525
- package/dist/services/task/index.js.map +0 -1
- package/dist/types/services/task/index.d.ts +0 -650
- package/src/services/task/index.ts +0 -1801
- package/test/unit/spec/services/task/index.ts +0 -2184
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import WebexCrossClientService from '../../../../src/services/WebexCrossClientService';
|
|
2
|
+
import MetricsManager from '../../../../src/metrics/MetricsManager';
|
|
3
|
+
import {METRIC_EVENT_NAMES} from '../../../../src/metrics/constants';
|
|
4
|
+
|
|
5
|
+
jest.mock('../../../../src/metrics/MetricsManager', () => ({
|
|
6
|
+
__esModule: true,
|
|
7
|
+
default: {
|
|
8
|
+
getInstance: jest.fn(),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe('WebexCrossClientService', () => {
|
|
13
|
+
const userId = 'user-123';
|
|
14
|
+
const deviceUrl = 'https://wdm.example.com/devices/device-abc';
|
|
15
|
+
const trackEvent = jest.fn();
|
|
16
|
+
const timeEvent = jest.fn();
|
|
17
|
+
const cancelTimedEvent = jest.fn();
|
|
18
|
+
|
|
19
|
+
let webex: {request: jest.Mock; internal: {device: {userId: string; url: string}}};
|
|
20
|
+
let service: WebexCrossClientService;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
trackEvent.mockClear();
|
|
24
|
+
timeEvent.mockClear();
|
|
25
|
+
cancelTimedEvent.mockClear();
|
|
26
|
+
(MetricsManager.getInstance as jest.Mock).mockReturnValue({trackEvent, timeEvent, cancelTimedEvent});
|
|
27
|
+
|
|
28
|
+
webex = {
|
|
29
|
+
request: jest.fn().mockResolvedValue({body: {}}),
|
|
30
|
+
internal: {
|
|
31
|
+
device: {userId, url: deviceUrl},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
service = new WebexCrossClientService(webex as never);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
service.teardown();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('publishes usersub with appName wxcc (Agent Desktop parity)', async () => {
|
|
42
|
+
await service.setManageWebexCallingInWxcc(true);
|
|
43
|
+
|
|
44
|
+
expect(webex.request).toHaveBeenCalledWith(
|
|
45
|
+
expect.objectContaining({
|
|
46
|
+
method: 'POST',
|
|
47
|
+
service: 'usersub',
|
|
48
|
+
resource: 'publish',
|
|
49
|
+
body: expect.objectContaining({
|
|
50
|
+
users: [userId],
|
|
51
|
+
compositions: [
|
|
52
|
+
expect.objectContaining({
|
|
53
|
+
type: 'cross-client-state',
|
|
54
|
+
ttl: 900,
|
|
55
|
+
composition: {
|
|
56
|
+
devices: [
|
|
57
|
+
{
|
|
58
|
+
deviceId: 'device-abc',
|
|
59
|
+
appName: 'wxcc',
|
|
60
|
+
state: {'answer-calls-on-wxcc': true},
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
],
|
|
66
|
+
}),
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('publishes false when suppression is disabled', async () => {
|
|
72
|
+
await service.setManageWebexCallingInWxcc(false);
|
|
73
|
+
|
|
74
|
+
expect(webex.request).toHaveBeenCalledWith(
|
|
75
|
+
expect.objectContaining({
|
|
76
|
+
body: expect.objectContaining({
|
|
77
|
+
compositions: [
|
|
78
|
+
expect.objectContaining({
|
|
79
|
+
composition: {
|
|
80
|
+
devices: [
|
|
81
|
+
{
|
|
82
|
+
deviceId: 'device-abc',
|
|
83
|
+
appName: 'wxcc',
|
|
84
|
+
state: {'answer-calls-on-wxcc': false},
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
}),
|
|
89
|
+
],
|
|
90
|
+
}),
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('throws when user ID is unavailable', async () => {
|
|
96
|
+
webex.internal.device.userId = undefined as unknown as string;
|
|
97
|
+
|
|
98
|
+
await expect(service.setManageWebexCallingInWxcc(true)).rejects.toThrow(
|
|
99
|
+
'User ID is unavailable for cross-client publish'
|
|
100
|
+
);
|
|
101
|
+
expect(webex.request).not.toHaveBeenCalled();
|
|
102
|
+
expect(trackEvent).not.toHaveBeenCalled();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('emits usersub publish failed when user ID is unavailable and trackPublishMetrics is true', async () => {
|
|
106
|
+
webex.internal.device.userId = undefined as unknown as string;
|
|
107
|
+
|
|
108
|
+
await expect(
|
|
109
|
+
service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true})
|
|
110
|
+
).rejects.toThrow('User ID is unavailable for cross-client publish');
|
|
111
|
+
|
|
112
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
113
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
114
|
+
expect.objectContaining({
|
|
115
|
+
enableWxBetterTogether: true,
|
|
116
|
+
skipReason: 'user_id_unavailable',
|
|
117
|
+
}),
|
|
118
|
+
['operational', 'behavioral']
|
|
119
|
+
);
|
|
120
|
+
expect(timeEvent).not.toHaveBeenCalled();
|
|
121
|
+
expect(webex.request).not.toHaveBeenCalled();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('throws when device URL is unavailable', async () => {
|
|
125
|
+
webex.internal.device.url = undefined as unknown as string;
|
|
126
|
+
|
|
127
|
+
await expect(service.setManageWebexCallingInWxcc(true)).rejects.toThrow(
|
|
128
|
+
'Device URL is unavailable for cross-client publish'
|
|
129
|
+
);
|
|
130
|
+
expect(webex.request).not.toHaveBeenCalled();
|
|
131
|
+
expect(trackEvent).not.toHaveBeenCalled();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('emits usersub publish failed when device URL is unavailable and trackPublishMetrics is true', async () => {
|
|
135
|
+
webex.internal.device.url = undefined as unknown as string;
|
|
136
|
+
|
|
137
|
+
await expect(
|
|
138
|
+
service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true})
|
|
139
|
+
).rejects.toThrow('Device URL is unavailable for cross-client publish');
|
|
140
|
+
|
|
141
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
142
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
143
|
+
expect.objectContaining({
|
|
144
|
+
enableWxBetterTogether: true,
|
|
145
|
+
skipReason: 'device_url_unavailable',
|
|
146
|
+
}),
|
|
147
|
+
['operational', 'behavioral']
|
|
148
|
+
);
|
|
149
|
+
expect(timeEvent).not.toHaveBeenCalled();
|
|
150
|
+
expect(webex.request).not.toHaveBeenCalled();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('rejects when usersub publish fails', async () => {
|
|
154
|
+
const err = new Error('usersub failed');
|
|
155
|
+
webex.request = jest.fn().mockRejectedValue(err);
|
|
156
|
+
|
|
157
|
+
await expect(
|
|
158
|
+
service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true})
|
|
159
|
+
).rejects.toThrow('usersub failed');
|
|
160
|
+
|
|
161
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
162
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
163
|
+
expect.objectContaining({enableWxBetterTogether: true}),
|
|
164
|
+
['operational', 'behavioral']
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('tracks usersub publish success when trackPublishMetrics is enabled', async () => {
|
|
169
|
+
await service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true});
|
|
170
|
+
|
|
171
|
+
expect(timeEvent).toHaveBeenCalledWith([
|
|
172
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
173
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
174
|
+
]);
|
|
175
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
176
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
177
|
+
{enableWxBetterTogether: true},
|
|
178
|
+
['operational', 'behavioral']
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('cancels usersub publish timer when publish completes after teardown bumps generation', async () => {
|
|
183
|
+
let resolvePublish: (value: {body: Record<string, never>}) => void;
|
|
184
|
+
const publishPromise = new Promise<{body: Record<string, never>}>((resolve) => {
|
|
185
|
+
resolvePublish = resolve;
|
|
186
|
+
});
|
|
187
|
+
webex.request = jest.fn().mockReturnValue(publishPromise);
|
|
188
|
+
|
|
189
|
+
const publishCall = service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true});
|
|
190
|
+
service.teardown();
|
|
191
|
+
resolvePublish!({body: {}});
|
|
192
|
+
await publishCall;
|
|
193
|
+
|
|
194
|
+
expect(cancelTimedEvent).toHaveBeenCalledWith([
|
|
195
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
196
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
197
|
+
]);
|
|
198
|
+
expect(trackEvent).not.toHaveBeenCalled();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('cancels usersub publish timer when publish fails after teardown bumps generation', async () => {
|
|
202
|
+
let rejectPublish: (error: Error) => void;
|
|
203
|
+
const publishPromise = new Promise((_, reject) => {
|
|
204
|
+
rejectPublish = reject;
|
|
205
|
+
});
|
|
206
|
+
webex.request = jest.fn().mockReturnValue(publishPromise);
|
|
207
|
+
|
|
208
|
+
const publishCall = service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true});
|
|
209
|
+
service.teardown();
|
|
210
|
+
rejectPublish!(new Error('usersub failed'));
|
|
211
|
+
await expect(publishCall).rejects.toThrow('usersub failed');
|
|
212
|
+
|
|
213
|
+
expect(cancelTimedEvent).toHaveBeenCalledWith([
|
|
214
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
215
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
216
|
+
]);
|
|
217
|
+
expect(trackEvent).not.toHaveBeenCalled();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('does not cancel newer publish timer when stale publish completes', async () => {
|
|
221
|
+
let resolveFirstPublish: (value: {body: Record<string, never>}) => void;
|
|
222
|
+
const firstPublishPromise = new Promise<{body: Record<string, never>}>((resolve) => {
|
|
223
|
+
resolveFirstPublish = resolve;
|
|
224
|
+
});
|
|
225
|
+
webex.request = jest
|
|
226
|
+
.fn()
|
|
227
|
+
.mockReturnValueOnce(firstPublishPromise)
|
|
228
|
+
.mockResolvedValueOnce({body: {}});
|
|
229
|
+
|
|
230
|
+
const firstPublishCall = service.setManageWebexCallingInWxcc(true, {
|
|
231
|
+
trackPublishMetrics: true,
|
|
232
|
+
});
|
|
233
|
+
const secondPublishCall = service.setManageWebexCallingInWxcc(false, {
|
|
234
|
+
trackPublishMetrics: true,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
await secondPublishCall;
|
|
238
|
+
|
|
239
|
+
service.teardown();
|
|
240
|
+
resolveFirstPublish!({body: {}});
|
|
241
|
+
await firstPublishCall;
|
|
242
|
+
|
|
243
|
+
expect(cancelTimedEvent).not.toHaveBeenCalled();
|
|
244
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
245
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
246
|
+
{enableWxBetterTogether: false},
|
|
247
|
+
['operational', 'behavioral']
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('preserves duration_ms on newer publish when stale publish completes', async () => {
|
|
252
|
+
const {default: RealMetricsManager} = jest.requireActual(
|
|
253
|
+
'../../../../src/metrics/MetricsManager'
|
|
254
|
+
);
|
|
255
|
+
RealMetricsManager.resetInstance();
|
|
256
|
+
const realManager = RealMetricsManager.getInstance();
|
|
257
|
+
jest.spyOn(realManager as any, 'isMetricsDisabled').mockReturnValue(false);
|
|
258
|
+
const trackEventSpy = jest.spyOn(realManager, 'trackEvent');
|
|
259
|
+
(MetricsManager.getInstance as jest.Mock).mockReturnValue(realManager);
|
|
260
|
+
|
|
261
|
+
let resolveFirstPublish: (value: {body: Record<string, never>}) => void;
|
|
262
|
+
const firstPublishPromise = new Promise<{body: Record<string, never>}>((resolve) => {
|
|
263
|
+
resolveFirstPublish = resolve;
|
|
264
|
+
});
|
|
265
|
+
webex.request = jest
|
|
266
|
+
.fn()
|
|
267
|
+
.mockReturnValueOnce(firstPublishPromise)
|
|
268
|
+
.mockResolvedValueOnce({body: {}});
|
|
269
|
+
|
|
270
|
+
const firstPublishCall = service.setManageWebexCallingInWxcc(true, {
|
|
271
|
+
trackPublishMetrics: true,
|
|
272
|
+
});
|
|
273
|
+
const secondPublishCall = service.setManageWebexCallingInWxcc(false, {
|
|
274
|
+
trackPublishMetrics: true,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
await secondPublishCall;
|
|
278
|
+
service.teardown();
|
|
279
|
+
resolveFirstPublish!({body: {}});
|
|
280
|
+
await firstPublishCall;
|
|
281
|
+
|
|
282
|
+
const successCall = trackEventSpy.mock.calls.find(
|
|
283
|
+
([eventName, payload]) =>
|
|
284
|
+
eventName === METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS &&
|
|
285
|
+
(payload as {enableWxBetterTogether?: boolean}).enableWxBetterTogether === false
|
|
286
|
+
);
|
|
287
|
+
expect(successCall?.[1]).toEqual(
|
|
288
|
+
expect.objectContaining({
|
|
289
|
+
enableWxBetterTogether: false,
|
|
290
|
+
duration_ms: expect.any(Number),
|
|
291
|
+
})
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
RealMetricsManager.resetInstance();
|
|
295
|
+
(MetricsManager.getInstance as jest.Mock).mockReturnValue({
|
|
296
|
+
trackEvent,
|
|
297
|
+
timeEvent,
|
|
298
|
+
cancelTimedEvent,
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('does not track usersub metrics by default', async () => {
|
|
303
|
+
await service.setManageWebexCallingInWxcc(true);
|
|
304
|
+
|
|
305
|
+
expect(trackEvent).not.toHaveBeenCalled();
|
|
306
|
+
expect(timeEvent).not.toHaveBeenCalled();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('uses custom ttl from options', async () => {
|
|
310
|
+
await service.setManageWebexCallingInWxcc(true, {ttl: 1200});
|
|
311
|
+
|
|
312
|
+
expect(webex.request).toHaveBeenCalledWith(
|
|
313
|
+
expect.objectContaining({
|
|
314
|
+
body: expect.objectContaining({
|
|
315
|
+
compositions: [expect.objectContaining({ttl: 1200})],
|
|
316
|
+
}),
|
|
317
|
+
})
|
|
318
|
+
);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('refreshes publish before ttl expiry while enabled', async () => {
|
|
322
|
+
jest.useFakeTimers();
|
|
323
|
+
try {
|
|
324
|
+
await service.setManageWebexCallingInWxcc(true, {ttl: 900});
|
|
325
|
+
expect(webex.request).toHaveBeenCalledTimes(1);
|
|
326
|
+
|
|
327
|
+
jest.advanceTimersByTime(840_000);
|
|
328
|
+
|
|
329
|
+
await Promise.resolve();
|
|
330
|
+
|
|
331
|
+
expect(webex.request).toHaveBeenCalledTimes(2);
|
|
332
|
+
expect(webex.request).toHaveBeenLastCalledWith(
|
|
333
|
+
expect.objectContaining({
|
|
334
|
+
body: expect.objectContaining({
|
|
335
|
+
compositions: [
|
|
336
|
+
expect.objectContaining({
|
|
337
|
+
composition: {
|
|
338
|
+
devices: [
|
|
339
|
+
{
|
|
340
|
+
deviceId: 'device-abc',
|
|
341
|
+
appName: 'wxcc',
|
|
342
|
+
state: {'answer-calls-on-wxcc': true},
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
},
|
|
346
|
+
}),
|
|
347
|
+
],
|
|
348
|
+
}),
|
|
349
|
+
})
|
|
350
|
+
);
|
|
351
|
+
} finally {
|
|
352
|
+
jest.useRealTimers();
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('retries failed refresh publish before ttl expiry', async () => {
|
|
357
|
+
jest.useFakeTimers();
|
|
358
|
+
try {
|
|
359
|
+
const setManageSpy = jest.spyOn(service, 'setManageWebexCallingInWxcc');
|
|
360
|
+
webex.request = jest
|
|
361
|
+
.fn()
|
|
362
|
+
.mockResolvedValueOnce({body: {}})
|
|
363
|
+
.mockRejectedValueOnce(new Error('refresh failed'))
|
|
364
|
+
.mockResolvedValueOnce({body: {}});
|
|
365
|
+
|
|
366
|
+
await service.setManageWebexCallingInWxcc(true, {ttl: 900});
|
|
367
|
+
expect(webex.request).toHaveBeenCalledTimes(1);
|
|
368
|
+
|
|
369
|
+
jest.advanceTimersByTime(840_000);
|
|
370
|
+
await expect(setManageSpy.mock.results[1].value).rejects.toThrow('refresh failed');
|
|
371
|
+
expect(webex.request).toHaveBeenCalledTimes(2);
|
|
372
|
+
|
|
373
|
+
jest.advanceTimersByTime(30_000);
|
|
374
|
+
await setManageSpy.mock.results[2].value;
|
|
375
|
+
|
|
376
|
+
expect(webex.request).toHaveBeenCalledTimes(3);
|
|
377
|
+
} finally {
|
|
378
|
+
jest.useRealTimers();
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('schedules bounded retry when scheduled refresh publish fails', async () => {
|
|
383
|
+
jest.useFakeTimers();
|
|
384
|
+
try {
|
|
385
|
+
const setManageSpy = jest.spyOn(service, 'setManageWebexCallingInWxcc');
|
|
386
|
+
webex.request = jest
|
|
387
|
+
.fn()
|
|
388
|
+
.mockResolvedValueOnce({body: {}})
|
|
389
|
+
.mockRejectedValueOnce(new Error('refresh failed'));
|
|
390
|
+
|
|
391
|
+
await service.setManageWebexCallingInWxcc(true, {ttl: 900});
|
|
392
|
+
const scheduleRefreshRetrySpy = jest.spyOn(service as any, 'scheduleRefreshRetry');
|
|
393
|
+
|
|
394
|
+
jest.advanceTimersByTime(840_000);
|
|
395
|
+
await expect(setManageSpy.mock.results[1].value).rejects.toThrow('refresh failed');
|
|
396
|
+
expect(scheduleRefreshRetrySpy).toHaveBeenCalledWith(userId, 900, 0, 0);
|
|
397
|
+
} finally {
|
|
398
|
+
jest.useRealTimers();
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('does not retry refresh after teardown bumps generation', async () => {
|
|
403
|
+
jest.useFakeTimers();
|
|
404
|
+
try {
|
|
405
|
+
const setManageSpy = jest.spyOn(service, 'setManageWebexCallingInWxcc');
|
|
406
|
+
webex.request = jest
|
|
407
|
+
.fn()
|
|
408
|
+
.mockResolvedValueOnce({body: {}})
|
|
409
|
+
.mockRejectedValueOnce(new Error('refresh failed'));
|
|
410
|
+
|
|
411
|
+
await service.setManageWebexCallingInWxcc(true, {ttl: 900});
|
|
412
|
+
|
|
413
|
+
jest.advanceTimersByTime(840_000);
|
|
414
|
+
await expect(setManageSpy.mock.results[1].value).rejects.toThrow('refresh failed');
|
|
415
|
+
|
|
416
|
+
service.teardown();
|
|
417
|
+
|
|
418
|
+
jest.advanceTimersByTime(30_000);
|
|
419
|
+
await Promise.resolve();
|
|
420
|
+
|
|
421
|
+
expect(webex.request).toHaveBeenCalledTimes(2);
|
|
422
|
+
} finally {
|
|
423
|
+
jest.useRealTimers();
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('teardown clears refresh timer so publish is not repeated', async () => {
|
|
428
|
+
jest.useFakeTimers();
|
|
429
|
+
try {
|
|
430
|
+
await service.setManageWebexCallingInWxcc(true, {ttl: 900});
|
|
431
|
+
service.teardown();
|
|
432
|
+
|
|
433
|
+
jest.advanceTimersByTime(840_000);
|
|
434
|
+
await Promise.resolve();
|
|
435
|
+
|
|
436
|
+
expect(webex.request).toHaveBeenCalledTimes(1);
|
|
437
|
+
} finally {
|
|
438
|
+
jest.useRealTimers();
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('does not schedule refresh when disabled', async () => {
|
|
443
|
+
jest.useFakeTimers();
|
|
444
|
+
try {
|
|
445
|
+
await service.setManageWebexCallingInWxcc(false);
|
|
446
|
+
|
|
447
|
+
jest.advanceTimersByTime(840_000);
|
|
448
|
+
await Promise.resolve();
|
|
449
|
+
|
|
450
|
+
expect(webex.request).toHaveBeenCalledTimes(1);
|
|
451
|
+
} finally {
|
|
452
|
+
jest.useRealTimers();
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('reports whether answer-calls-on-wxcc true was published', async () => {
|
|
457
|
+
expect(service.isAnswerCallsStateActive()).toBe(false);
|
|
458
|
+
|
|
459
|
+
await service.setManageWebexCallingInWxcc(true);
|
|
460
|
+
|
|
461
|
+
expect(service.isAnswerCallsStateActive()).toBe(true);
|
|
462
|
+
|
|
463
|
+
await service.setManageWebexCallingInWxcc(false);
|
|
464
|
+
|
|
465
|
+
expect(service.isAnswerCallsStateActive()).toBe(false);
|
|
466
|
+
});
|
|
467
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import WxAppTelephonyMercurySync, {
|
|
2
|
+
TELEPHONY_CALL_MUTED,
|
|
3
|
+
TELEPHONY_CALL_UNMUTED,
|
|
4
|
+
} from '../../../../src/services/WxAppTelephonyMercurySync';
|
|
5
|
+
import MetricsManager from '../../../../src/metrics/MetricsManager';
|
|
6
|
+
import {METRIC_EVENT_NAMES} from '../../../../src/metrics/constants';
|
|
7
|
+
|
|
8
|
+
jest.mock('../../../../src/metrics/MetricsManager', () => ({
|
|
9
|
+
__esModule: true,
|
|
10
|
+
default: {
|
|
11
|
+
getInstance: jest.fn(),
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
describe('WxAppTelephonyMercurySync', () => {
|
|
16
|
+
const agentId = 'agent-123';
|
|
17
|
+
const trackEvent = jest.fn();
|
|
18
|
+
let mercury: {on: jest.Mock; off: jest.Mock};
|
|
19
|
+
let webex: {internal: {mercury?: typeof mercury}};
|
|
20
|
+
let sync: WxAppTelephonyMercurySync;
|
|
21
|
+
let onMuteChange: jest.Mock;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
trackEvent.mockClear();
|
|
25
|
+
(MetricsManager.getInstance as jest.Mock).mockReturnValue({trackEvent});
|
|
26
|
+
|
|
27
|
+
mercury = {on: jest.fn(), off: jest.fn()};
|
|
28
|
+
webex = {internal: {mercury}};
|
|
29
|
+
sync = new WxAppTelephonyMercurySync(webex as never);
|
|
30
|
+
onMuteChange = jest.fn();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
sync.unsubscribe();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('subscribes to muted and unmuted Mercury events', () => {
|
|
38
|
+
sync.subscribe(agentId, onMuteChange);
|
|
39
|
+
|
|
40
|
+
expect(mercury.on).toHaveBeenCalledWith(TELEPHONY_CALL_MUTED, expect.any(Function));
|
|
41
|
+
expect(mercury.on).toHaveBeenCalledWith(TELEPHONY_CALL_UNMUTED, expect.any(Function));
|
|
42
|
+
expect(sync.isSubscribed()).toBe(true);
|
|
43
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
44
|
+
METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_SUCCESS,
|
|
45
|
+
{},
|
|
46
|
+
['operational', 'behavioral']
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('invokes callback with decoded callId and muted when actorId matches agent', () => {
|
|
51
|
+
const callId = 'prefix:call-half-1';
|
|
52
|
+
const encodedCallId = btoa(callId);
|
|
53
|
+
const encodedActorId = btoa(`some-prefix-${agentId}`);
|
|
54
|
+
|
|
55
|
+
sync.subscribe(agentId, onMuteChange);
|
|
56
|
+
const muteHandler = mercury.on.mock.calls.find(([event]) => event === TELEPHONY_CALL_MUTED)![1];
|
|
57
|
+
muteHandler({data: {actorId: encodedActorId, callId: encodedCallId, muted: true}});
|
|
58
|
+
|
|
59
|
+
expect(onMuteChange).toHaveBeenCalledWith(callId, true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('ignores events when decoded actorId does not end with agentId', () => {
|
|
63
|
+
sync.subscribe(agentId, onMuteChange);
|
|
64
|
+
const muteHandler = mercury.on.mock.calls.find(([event]) => event === TELEPHONY_CALL_MUTED)![1];
|
|
65
|
+
muteHandler({data: {actorId: btoa('other-agent'), callId: btoa('call-1'), muted: true}});
|
|
66
|
+
|
|
67
|
+
expect(onMuteChange).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('ignores events with missing payload fields', () => {
|
|
71
|
+
sync.subscribe(agentId, onMuteChange);
|
|
72
|
+
const muteHandler = mercury.on.mock.calls.find(([event]) => event === TELEPHONY_CALL_MUTED)![1];
|
|
73
|
+
muteHandler({data: {actorId: btoa(agentId), callId: btoa('call-1')}});
|
|
74
|
+
|
|
75
|
+
expect(onMuteChange).not.toHaveBeenCalled();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('unsubscribes and removes Mercury listeners', () => {
|
|
79
|
+
sync.subscribe(agentId, onMuteChange);
|
|
80
|
+
const muteHandler = mercury.on.mock.calls.find(([event]) => event === TELEPHONY_CALL_MUTED)![1];
|
|
81
|
+
const unmuteHandler = mercury.on.mock.calls.find(
|
|
82
|
+
([event]) => event === TELEPHONY_CALL_UNMUTED
|
|
83
|
+
)![1];
|
|
84
|
+
|
|
85
|
+
sync.unsubscribe();
|
|
86
|
+
|
|
87
|
+
expect(mercury.off).toHaveBeenCalledWith(TELEPHONY_CALL_MUTED, muteHandler);
|
|
88
|
+
expect(mercury.off).toHaveBeenCalledWith(TELEPHONY_CALL_UNMUTED, unmuteHandler);
|
|
89
|
+
expect(sync.isSubscribed()).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('does not subscribe when mercury is unavailable', () => {
|
|
93
|
+
sync = new WxAppTelephonyMercurySync({internal: {}} as never);
|
|
94
|
+
sync.subscribe(agentId, onMuteChange);
|
|
95
|
+
|
|
96
|
+
expect(sync.isSubscribed()).toBe(false);
|
|
97
|
+
expect(onMuteChange).not.toHaveBeenCalled();
|
|
98
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
99
|
+
METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_FAILED,
|
|
100
|
+
expect.objectContaining({error: expect.stringContaining('Mercury is unavailable')}),
|
|
101
|
+
['operational', 'behavioral']
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('invokes callback with muted false on unmuted Mercury event', () => {
|
|
106
|
+
const callId = 'prefix:call-half-1';
|
|
107
|
+
const encodedCallId = btoa(callId);
|
|
108
|
+
const encodedActorId = btoa(`some-prefix-${agentId}`);
|
|
109
|
+
|
|
110
|
+
sync.subscribe(agentId, onMuteChange);
|
|
111
|
+
const unmuteHandler = mercury.on.mock.calls.find(
|
|
112
|
+
([event]) => event === TELEPHONY_CALL_UNMUTED
|
|
113
|
+
)![1];
|
|
114
|
+
unmuteHandler({data: {actorId: encodedActorId, callId: encodedCallId, muted: false}});
|
|
115
|
+
|
|
116
|
+
expect(onMuteChange).toHaveBeenCalledWith(callId, false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('resubscribe replaces previous Mercury listeners', () => {
|
|
120
|
+
sync.subscribe(agentId, onMuteChange);
|
|
121
|
+
const firstMuteHandler = mercury.on.mock.calls.find(
|
|
122
|
+
([event]) => event === TELEPHONY_CALL_MUTED
|
|
123
|
+
)![1];
|
|
124
|
+
const firstUnmuteHandler = mercury.on.mock.calls.find(
|
|
125
|
+
([event]) => event === TELEPHONY_CALL_UNMUTED
|
|
126
|
+
)![1];
|
|
127
|
+
expect(mercury.on).toHaveBeenCalledTimes(2);
|
|
128
|
+
|
|
129
|
+
const secondHandler = jest.fn();
|
|
130
|
+
sync.subscribe(agentId, secondHandler);
|
|
131
|
+
|
|
132
|
+
expect(mercury.off).toHaveBeenCalledWith(TELEPHONY_CALL_MUTED, firstMuteHandler);
|
|
133
|
+
expect(mercury.off).toHaveBeenCalledWith(TELEPHONY_CALL_UNMUTED, firstUnmuteHandler);
|
|
134
|
+
expect(mercury.on).toHaveBeenCalledTimes(4);
|
|
135
|
+
});
|
|
136
|
+
});
|