@webex/contact-center 0.0.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +81 -0
  2. package/__mocks__/workerMock.js +15 -0
  3. package/babel.config.js +15 -0
  4. package/dist/cc.js +1416 -0
  5. package/dist/cc.js.map +1 -0
  6. package/dist/config.js +72 -0
  7. package/dist/config.js.map +1 -0
  8. package/dist/constants.js +58 -0
  9. package/dist/constants.js.map +1 -0
  10. package/dist/index.js +142 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/logger-proxy.js +115 -0
  13. package/dist/logger-proxy.js.map +1 -0
  14. package/dist/metrics/MetricsManager.js +474 -0
  15. package/dist/metrics/MetricsManager.js.map +1 -0
  16. package/dist/metrics/behavioral-events.js +322 -0
  17. package/dist/metrics/behavioral-events.js.map +1 -0
  18. package/dist/metrics/constants.js +134 -0
  19. package/dist/metrics/constants.js.map +1 -0
  20. package/dist/services/WebCallingService.js +323 -0
  21. package/dist/services/WebCallingService.js.map +1 -0
  22. package/dist/services/agent/index.js +177 -0
  23. package/dist/services/agent/index.js.map +1 -0
  24. package/dist/services/agent/types.js +137 -0
  25. package/dist/services/agent/types.js.map +1 -0
  26. package/dist/services/config/Util.js +203 -0
  27. package/dist/services/config/Util.js.map +1 -0
  28. package/dist/services/config/constants.js +221 -0
  29. package/dist/services/config/constants.js.map +1 -0
  30. package/dist/services/config/index.js +607 -0
  31. package/dist/services/config/index.js.map +1 -0
  32. package/dist/services/config/types.js +334 -0
  33. package/dist/services/config/types.js.map +1 -0
  34. package/dist/services/constants.js +117 -0
  35. package/dist/services/constants.js.map +1 -0
  36. package/dist/services/core/Err.js +43 -0
  37. package/dist/services/core/Err.js.map +1 -0
  38. package/dist/services/core/GlobalTypes.js +6 -0
  39. package/dist/services/core/GlobalTypes.js.map +1 -0
  40. package/dist/services/core/Utils.js +126 -0
  41. package/dist/services/core/Utils.js.map +1 -0
  42. package/dist/services/core/WebexRequest.js +96 -0
  43. package/dist/services/core/WebexRequest.js.map +1 -0
  44. package/dist/services/core/aqm-reqs.js +246 -0
  45. package/dist/services/core/aqm-reqs.js.map +1 -0
  46. package/dist/services/core/constants.js +109 -0
  47. package/dist/services/core/constants.js.map +1 -0
  48. package/dist/services/core/types.js +6 -0
  49. package/dist/services/core/types.js.map +1 -0
  50. package/dist/services/core/websocket/WebSocketManager.js +187 -0
  51. package/dist/services/core/websocket/WebSocketManager.js.map +1 -0
  52. package/dist/services/core/websocket/connection-service.js +111 -0
  53. package/dist/services/core/websocket/connection-service.js.map +1 -0
  54. package/dist/services/core/websocket/keepalive.worker.js +94 -0
  55. package/dist/services/core/websocket/keepalive.worker.js.map +1 -0
  56. package/dist/services/core/websocket/types.js +6 -0
  57. package/dist/services/core/websocket/types.js.map +1 -0
  58. package/dist/services/index.js +78 -0
  59. package/dist/services/index.js.map +1 -0
  60. package/dist/services/task/AutoWrapup.js +88 -0
  61. package/dist/services/task/AutoWrapup.js.map +1 -0
  62. package/dist/services/task/TaskManager.js +369 -0
  63. package/dist/services/task/TaskManager.js.map +1 -0
  64. package/dist/services/task/constants.js +58 -0
  65. package/dist/services/task/constants.js.map +1 -0
  66. package/dist/services/task/contact.js +464 -0
  67. package/dist/services/task/contact.js.map +1 -0
  68. package/dist/services/task/dialer.js +60 -0
  69. package/dist/services/task/dialer.js.map +1 -0
  70. package/dist/services/task/index.js +1188 -0
  71. package/dist/services/task/index.js.map +1 -0
  72. package/dist/services/task/types.js +214 -0
  73. package/dist/services/task/types.js.map +1 -0
  74. package/dist/types/cc.d.ts +676 -0
  75. package/dist/types/config.d.ts +66 -0
  76. package/dist/types/constants.d.ts +45 -0
  77. package/dist/types/index.d.ts +178 -0
  78. package/dist/types/logger-proxy.d.ts +71 -0
  79. package/dist/types/metrics/MetricsManager.d.ts +223 -0
  80. package/dist/types/metrics/behavioral-events.d.ts +29 -0
  81. package/dist/types/metrics/constants.d.ts +127 -0
  82. package/dist/types/services/WebCallingService.d.ts +1 -0
  83. package/dist/types/services/agent/index.d.ts +46 -0
  84. package/dist/types/services/agent/types.d.ts +413 -0
  85. package/dist/types/services/config/Util.d.ts +19 -0
  86. package/dist/types/services/config/constants.d.ts +203 -0
  87. package/dist/types/services/config/index.d.ts +171 -0
  88. package/dist/types/services/config/types.d.ts +1113 -0
  89. package/dist/types/services/constants.d.ts +97 -0
  90. package/dist/types/services/core/Err.d.ts +119 -0
  91. package/dist/types/services/core/GlobalTypes.d.ts +33 -0
  92. package/dist/types/services/core/Utils.d.ts +36 -0
  93. package/dist/types/services/core/WebexRequest.d.ts +22 -0
  94. package/dist/types/services/core/aqm-reqs.d.ts +16 -0
  95. package/dist/types/services/core/constants.d.ts +85 -0
  96. package/dist/types/services/core/types.d.ts +47 -0
  97. package/dist/types/services/core/websocket/WebSocketManager.d.ts +34 -0
  98. package/dist/types/services/core/websocket/connection-service.d.ts +27 -0
  99. package/dist/types/services/core/websocket/keepalive.worker.d.ts +2 -0
  100. package/dist/types/services/core/websocket/types.d.ts +37 -0
  101. package/dist/types/services/index.d.ts +52 -0
  102. package/dist/types/services/task/AutoWrapup.d.ts +40 -0
  103. package/dist/types/services/task/TaskManager.d.ts +1 -0
  104. package/dist/types/services/task/constants.d.ts +46 -0
  105. package/dist/types/services/task/contact.d.ts +59 -0
  106. package/dist/types/services/task/dialer.d.ts +28 -0
  107. package/dist/types/services/task/index.d.ts +569 -0
  108. package/dist/types/services/task/types.d.ts +1041 -0
  109. package/dist/types/types.d.ts +452 -0
  110. package/dist/types/webex-config.d.ts +53 -0
  111. package/dist/types/webex.d.ts +7 -0
  112. package/dist/types.js +292 -0
  113. package/dist/types.js.map +1 -0
  114. package/dist/webex-config.js +60 -0
  115. package/dist/webex-config.js.map +1 -0
  116. package/dist/webex.js +99 -0
  117. package/dist/webex.js.map +1 -0
  118. package/jest.config.js +45 -0
  119. package/package.json +83 -0
  120. package/src/cc.ts +1618 -0
  121. package/src/config.ts +65 -0
  122. package/src/constants.ts +51 -0
  123. package/src/index.ts +220 -0
  124. package/src/logger-proxy.ts +110 -0
  125. package/src/metrics/MetricsManager.ts +512 -0
  126. package/src/metrics/behavioral-events.ts +332 -0
  127. package/src/metrics/constants.ts +135 -0
  128. package/src/services/WebCallingService.ts +351 -0
  129. package/src/services/agent/index.ts +149 -0
  130. package/src/services/agent/types.ts +440 -0
  131. package/src/services/config/Util.ts +261 -0
  132. package/src/services/config/constants.ts +249 -0
  133. package/src/services/config/index.ts +743 -0
  134. package/src/services/config/types.ts +1117 -0
  135. package/src/services/constants.ts +111 -0
  136. package/src/services/core/Err.ts +126 -0
  137. package/src/services/core/GlobalTypes.ts +34 -0
  138. package/src/services/core/Utils.ts +132 -0
  139. package/src/services/core/WebexRequest.ts +103 -0
  140. package/src/services/core/aqm-reqs.ts +272 -0
  141. package/src/services/core/constants.ts +106 -0
  142. package/src/services/core/types.ts +48 -0
  143. package/src/services/core/websocket/WebSocketManager.ts +196 -0
  144. package/src/services/core/websocket/connection-service.ts +142 -0
  145. package/src/services/core/websocket/keepalive.worker.js +88 -0
  146. package/src/services/core/websocket/types.ts +40 -0
  147. package/src/services/index.ts +71 -0
  148. package/src/services/task/AutoWrapup.ts +86 -0
  149. package/src/services/task/TaskManager.ts +420 -0
  150. package/src/services/task/constants.ts +52 -0
  151. package/src/services/task/contact.ts +429 -0
  152. package/src/services/task/dialer.ts +52 -0
  153. package/src/services/task/index.ts +1375 -0
  154. package/src/services/task/types.ts +1113 -0
  155. package/src/types.ts +639 -0
  156. package/src/webex-config.ts +54 -0
  157. package/src/webex.js +96 -0
  158. package/test/unit/spec/cc.ts +1985 -0
  159. package/test/unit/spec/metrics/MetricsManager.ts +491 -0
  160. package/test/unit/spec/metrics/behavioral-events.ts +102 -0
  161. package/test/unit/spec/services/WebCallingService.ts +416 -0
  162. package/test/unit/spec/services/agent/index.ts +65 -0
  163. package/test/unit/spec/services/config/index.ts +1035 -0
  164. package/test/unit/spec/services/core/Utils.ts +279 -0
  165. package/test/unit/spec/services/core/WebexRequest.ts +144 -0
  166. package/test/unit/spec/services/core/aqm-reqs.ts +570 -0
  167. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +378 -0
  168. package/test/unit/spec/services/core/websocket/connection-service.ts +178 -0
  169. package/test/unit/spec/services/task/TaskManager.ts +1351 -0
  170. package/test/unit/spec/services/task/contact.ts +204 -0
  171. package/test/unit/spec/services/task/dialer.ts +157 -0
  172. package/test/unit/spec/services/task/index.ts +1474 -0
  173. package/tsconfig.json +6 -0
  174. package/typedoc.json +37 -0
  175. package/typedoc.md +240 -0
  176. package/umd/contact-center.min.js +3 -0
  177. package/umd/contact-center.min.js.map +1 -0
@@ -0,0 +1,1985 @@
1
+ import 'jsdom-global/register';
2
+ import {
3
+ BuddyAgents,
4
+ BuddyAgentsResponse,
5
+ LoginOption,
6
+ StationLogoutResponse,
7
+ WebexSDK,
8
+ } from '../../../src/types';
9
+ import ContactCenter from '../../../src/cc';
10
+ import MockWebex from '@webex/test-helper-mock-webex';
11
+ import {StationLoginSuccess, AGENT_EVENTS} from '../../../src/services/agent/types';
12
+ import {SetStateResponse} from '../../../src/types';
13
+ import {AGENT, WEB_RTC_PREFIX} from '../../../src/services/constants';
14
+ import Services from '../../../src/services';
15
+ import config from '../../../src/config';
16
+ import {CC_EVENTS} from '../../../src/services/config/types';
17
+ import LoggerProxy from '../../../src/logger-proxy';
18
+ import * as Utils from '../../../src/services/core/Utils';
19
+ import {
20
+ CC_FILE,
21
+ OUTDIAL_DIRECTION,
22
+ OUTBOUND_TYPE,
23
+ ATTRIBUTES,
24
+ OUTDIAL_MEDIA_TYPE,
25
+ } from '../../../src/constants';
26
+
27
+ // Mock the Worker API
28
+ import '../../../__mocks__/workerMock';
29
+ import {Profile} from '../../../src/services/config/types';
30
+ import TaskManager from '../../../src/services/task/TaskManager';
31
+ import {AgentContact, TASK_EVENTS} from '../../../src/services/task/types';
32
+ import MetricsManager from '../../../src/metrics/MetricsManager';
33
+ import {METRIC_EVENT_NAMES} from '../../../src/metrics/constants';
34
+ import Mercury from '@webex/internal-plugin-mercury';
35
+ import WebexRequest from '../../../src/services/core/WebexRequest';
36
+
37
+ jest.mock('../../../src/logger-proxy', () => ({
38
+ __esModule: true,
39
+ default: {
40
+ log: jest.fn(),
41
+ error: jest.fn(),
42
+ info: jest.fn(),
43
+ initialize: jest.fn(),
44
+ },
45
+ }));
46
+
47
+ jest.mock('../../../src/services/config');
48
+ jest.mock('../../../src/services/core/websocket/WebSocketManager');
49
+ jest.mock('../../../src/services/core/websocket/connection-service');
50
+ jest.mock('../../../src/services/WebCallingService');
51
+ jest.mock('uuid', () => ({v4: () => 'mock-tracking-uuid'}));
52
+
53
+ global.URL.createObjectURL = jest.fn(() => 'blob:http://localhost:3000/12345');
54
+
55
+ describe('webex.cc', () => {
56
+ let webex;
57
+ let mockContact;
58
+ let mockTaskManager;
59
+ let mockMetricsManager;
60
+ let mockWebSocketManager;
61
+ let getErrorDetailsSpy;
62
+ let mockWebexRequest;
63
+
64
+ beforeEach(() => {
65
+ webex = MockWebex({
66
+ children: {
67
+ cc: ContactCenter,
68
+ mercury: Mercury,
69
+ },
70
+ logger: {
71
+ log: jest.fn(),
72
+ error: jest.fn(),
73
+ info: jest.fn(),
74
+ },
75
+ credentials: {
76
+ getOrgId: jest.fn(() => 'mockOrgId'),
77
+ },
78
+ config: config,
79
+ once: jest.fn((event, callback) => callback()),
80
+ }) as unknown as WebexSDK;
81
+
82
+ mockWebSocketManager = {
83
+ initWebSocket: jest.fn(),
84
+ on: jest.fn(),
85
+ off: jest.fn(),
86
+ };
87
+
88
+ mockContact = {
89
+ accept: jest.fn(),
90
+ hold: jest.fn(),
91
+ unHold: jest.fn(),
92
+ pauseRecording: jest.fn(),
93
+ resumeRecording: jest.fn(),
94
+ consult: jest.fn(),
95
+ consultAccept: jest.fn(),
96
+ blindTransfer: jest.fn(),
97
+ vteamTransfer: jest.fn(),
98
+ consultTransfer: jest.fn(),
99
+ end: jest.fn(),
100
+ wrapup: jest.fn(),
101
+ cancelTask: jest.fn(),
102
+ cancelCtq: jest.fn(),
103
+ };
104
+
105
+ // Mock Services instance
106
+ const mockServicesInstance = {
107
+ agent: {
108
+ stationLogin: jest.fn(),
109
+ logout: jest.fn(),
110
+ reload: jest.fn(),
111
+ stateChange: jest.fn(),
112
+ buddyAgents: jest.fn(),
113
+ },
114
+ config: {
115
+ getAgentConfig: jest.fn(),
116
+ },
117
+ webSocketManager: mockWebSocketManager,
118
+ connectionService: {
119
+ on: jest.fn(),
120
+ off: jest.fn(),
121
+ },
122
+ contact: mockContact,
123
+
124
+ dialer: {
125
+ startOutdial: jest.fn(),
126
+ },
127
+ };
128
+
129
+ mockTaskManager = {
130
+ contact: mockContact,
131
+ call: undefined,
132
+ taskCollection: {},
133
+ webCallingService: undefined,
134
+ webSocketManager: mockWebSocketManager,
135
+ task: undefined,
136
+ setWrapupData: jest.fn(),
137
+ registerIncomingCallEvent: jest.fn(),
138
+ registerTaskListeners: jest.fn(),
139
+ getTask: jest.fn(),
140
+ getActiveTasks: jest.fn(),
141
+ on: jest.fn(),
142
+ off: jest.fn(),
143
+ emit: jest.fn(),
144
+ unregisterIncomingCallEvent: jest.fn(),
145
+ };
146
+
147
+ mockMetricsManager = {
148
+ trackEvent: jest.fn(),
149
+ timeEvent: jest.fn(),
150
+ };
151
+
152
+ mockWebexRequest = {
153
+ request: jest.fn(),
154
+ uploadLogs: jest.fn(),
155
+ };
156
+
157
+ jest.spyOn(MetricsManager, 'getInstance').mockReturnValue(mockMetricsManager);
158
+ jest.spyOn(Services, 'getInstance').mockReturnValue(mockServicesInstance);
159
+ jest.spyOn(TaskManager, 'getTaskManager').mockReturnValue(mockTaskManager);
160
+ jest.spyOn(WebexRequest, 'getInstance').mockReturnValue(mockWebexRequest);
161
+ // Instantiate ContactCenter to ensure it's fully initialized
162
+ webex.cc = new ContactCenter({parent: webex});
163
+ getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
164
+ });
165
+
166
+ afterEach(() => {
167
+ jest.clearAllMocks();
168
+ });
169
+
170
+ it('should initialize services and logger proxy on ready event', () => {
171
+ webex.once('ready', () => {
172
+ expect(Services.getInstance).toHaveBeenCalled();
173
+ expect(LoggerProxy.initialize).toHaveBeenCalledWith(webex.logger);
174
+ });
175
+
176
+ webex.emit('ready');
177
+ });
178
+
179
+ describe('cc.getDeviceId', () => {
180
+ it('should return dialNumber when loginOption is EXTENSION', () => {
181
+ const loginOption = LoginOption.EXTENSION;
182
+ const dialNumber = '12345';
183
+ const result = webex.cc['getDeviceId'](loginOption, dialNumber);
184
+ expect(result).toBe(dialNumber);
185
+ });
186
+
187
+ it('should return dialNumber when loginOption is AGENT_DN', () => {
188
+ const loginOption = LoginOption.AGENT_DN;
189
+ const dialNumber = '12345';
190
+ const result = webex.cc['getDeviceId'](loginOption, dialNumber);
191
+ expect(result).toBe(dialNumber);
192
+ });
193
+
194
+ it('should return prefix + agentId for other loginOptions', () => {
195
+ const loginOption = 'OTHER_OPTION';
196
+ webex.cc.agentConfig = {
197
+ agentId: 'agentId',
198
+ };
199
+ const result = webex.cc['getDeviceId'](loginOption, '');
200
+ expect(result).toBe(WEB_RTC_PREFIX + 'agentId');
201
+ });
202
+ });
203
+
204
+ describe('register', () => {
205
+ const mockAgentProfile: Profile = {
206
+ agentId: 'agent123',
207
+ agentMailId: '',
208
+ agentName: 'John',
209
+ teams: [],
210
+ agentProfileID: '',
211
+ loginVoiceOptions: ['BROWSER', 'EXTENSION'],
212
+ idleCodes: [],
213
+ wrapupCodes: [],
214
+ defaultDn: '',
215
+ forceDefaultDn: false,
216
+ forceDefaultDnForAgent: false,
217
+ regexUS: '',
218
+ regexOther: '',
219
+ dialPlan: {
220
+ type: '',
221
+ dialPlanEntity: [],
222
+ },
223
+ skillProfileId: '',
224
+ siteId: '',
225
+ enterpriseId: '',
226
+ privacyShieldVisible: true,
227
+ defaultWrapupCode: '',
228
+ wrapUpData: {
229
+ wrapUpProps: {
230
+ autoWrapup: undefined,
231
+ autoWrapupInterval: undefined,
232
+ lastAgentRoute: undefined,
233
+ wrapUpReasonList: [],
234
+ wrapUpCodesList: undefined,
235
+ idleCodesAccess: undefined,
236
+ interactionId: undefined,
237
+ allowCancelAutoWrapup: undefined,
238
+ },
239
+ },
240
+ isOutboundEnabledForTenant: false,
241
+ isOutboundEnabledForAgent: false,
242
+ isAdhocDialingEnabled: false,
243
+ isAgentAvailableAfterOutdial: false,
244
+ isCampaignManagementEnabled: false,
245
+ outDialEp: '',
246
+ isEndCallEnabled: false,
247
+ isEndConsultEnabled: false,
248
+ agentDbId: '',
249
+ allowConsultToQueue: false,
250
+ agentPersonalStatsEnabled: false,
251
+ isTimeoutDesktopInactivityEnabled: false,
252
+ webRtcEnabled: true,
253
+ lostConnectionRecoveryTimeout: 0,
254
+ };
255
+
256
+ it('should register successfully and return agent profile', async () => {
257
+ const mercuryConnect = jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
258
+ const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
259
+ const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
260
+ const reloadSpy = jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
261
+ data: {
262
+ auxCodeId: 'auxCodeId',
263
+ agentId: 'agentId',
264
+ deviceType: LoginOption.EXTENSION,
265
+ dn: '12345',
266
+ },
267
+ });
268
+ const configSpy = jest
269
+ .spyOn(webex.cc.services.config, 'getAgentConfig')
270
+ .mockResolvedValue(mockAgentProfile);
271
+ mockWebSocketManager.initWebSocket.mockResolvedValue({
272
+ agentId: 'agent123',
273
+ });
274
+
275
+ const result = await webex.cc.register();
276
+
277
+ // Verify logging calls
278
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Starting CC SDK registration', {
279
+ module: CC_FILE,
280
+ method: 'register',
281
+ });
282
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
283
+ `CC SDK registration completed successfully with agentId: ${result.agentId}`,
284
+ {
285
+ module: CC_FILE,
286
+ method: 'register',
287
+ }
288
+ );
289
+
290
+ expect(mercuryConnect).toHaveBeenCalled();
291
+ expect(connectWebsocketSpy).toHaveBeenCalled();
292
+ expect(setupEventListenersSpy).toHaveBeenCalled();
293
+ expect(mockWebSocketManager.initWebSocket).toHaveBeenCalledWith({
294
+ body: {
295
+ force: true,
296
+ isKeepAliveEnabled: false,
297
+ clientType: 'WebexCCSDK',
298
+ allowMultiLogin: false,
299
+ },
300
+ });
301
+
302
+ // TODO: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-626777 Implement the de-register method and close the listener there
303
+ expect(mockTaskManager.on).toHaveBeenCalledWith(
304
+ TASK_EVENTS.TASK_INCOMING,
305
+ expect.any(Function)
306
+ );
307
+ expect(mockTaskManager.on).toHaveBeenCalledWith(
308
+ TASK_EVENTS.TASK_HYDRATE,
309
+ expect.any(Function)
310
+ );
311
+ expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
312
+
313
+ expect(configSpy).toHaveBeenCalled();
314
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Agent config is fetched successfully', {
315
+ module: CC_FILE,
316
+ method: 'connectWebsocket',
317
+ });
318
+ expect(reloadSpy).toHaveBeenCalled();
319
+ expect(result).toEqual(mockAgentProfile);
320
+ expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
321
+ METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_SUCCESS,
322
+ METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED,
323
+ ]);
324
+ });
325
+
326
+ it('should not register when config is undefined', async () => {
327
+ webex.cc.$config = undefined;
328
+ jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
329
+ const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
330
+ const reloadSpy = jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
331
+ data: {
332
+ auxCodeId: 'auxCodeId',
333
+ agentId: 'agentId',
334
+ },
335
+ });
336
+
337
+ const configSpy = jest
338
+ .spyOn(webex.cc.services.config, 'getAgentConfig')
339
+ .mockResolvedValue(mockAgentProfile);
340
+
341
+ mockWebSocketManager.initWebSocket.mockResolvedValue({
342
+ agentId: 'agent123',
343
+ });
344
+
345
+ const result = await webex.cc.register();
346
+
347
+ expect(connectWebsocketSpy).toHaveBeenCalled();
348
+ expect(mockWebSocketManager.initWebSocket).toHaveBeenCalledWith({
349
+ body: {
350
+ force: true,
351
+ isKeepAliveEnabled: false,
352
+ clientType: 'WebexCCSDK',
353
+ allowMultiLogin: true,
354
+ },
355
+ });
356
+ expect(configSpy).toHaveBeenCalled();
357
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Agent config is fetched successfully', {
358
+ module: CC_FILE,
359
+ method: 'connectWebsocket',
360
+ });
361
+ expect(reloadSpy).not.toHaveBeenCalled();
362
+ expect(result).toEqual(mockAgentProfile);
363
+ expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
364
+ METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_SUCCESS,
365
+ METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED,
366
+ ]);
367
+ });
368
+
369
+ it('should log error and reject if registration fails', async () => {
370
+ jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
371
+ const mockError = new Error('Error while performing register');
372
+ mockWebSocketManager.initWebSocket.mockRejectedValue(mockError);
373
+
374
+ await expect(webex.cc.register()).rejects.toThrow('Error while performing register');
375
+
376
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Starting CC SDK registration', {
377
+ module: CC_FILE,
378
+ method: 'register',
379
+ });
380
+ expect(LoggerProxy.error).toHaveBeenCalledWith(`Error during register: ${mockError}`, {
381
+ module: CC_FILE,
382
+ method: 'register',
383
+ });
384
+
385
+ // Verify metrics tracking
386
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
387
+ METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED,
388
+ {
389
+ orgId: undefined,
390
+ },
391
+ ['operational']
392
+ );
393
+ });
394
+
395
+ it('should log error if mercury connect fails but cc.register() should not fail', async () => {
396
+ const mockError = new Error('Error while performing mercury connect');
397
+ jest.spyOn(webex.internal.mercury, 'connect').mockRejectedValue(mockError);
398
+
399
+ const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
400
+ const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
401
+ const reloadSpy = jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
402
+ data: {
403
+ auxCodeId: 'auxCodeId',
404
+ agentId: 'agentId',
405
+ deviceType: LoginOption.EXTENSION,
406
+ dn: '12345',
407
+ },
408
+ });
409
+ const configSpy = jest
410
+ .spyOn(webex.cc.services.config, 'getAgentConfig')
411
+ .mockResolvedValue(mockAgentProfile);
412
+ mockWebSocketManager.initWebSocket.mockResolvedValue({
413
+ agentId: 'agent123',
414
+ });
415
+
416
+ const result = await webex.cc.register();
417
+
418
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
419
+ `Error occurred during mercury.connect() ${mockError}`,
420
+ {
421
+ module: CC_FILE,
422
+ method: 'connectWebsocket',
423
+ }
424
+ );
425
+ expect(connectWebsocketSpy).toHaveBeenCalled();
426
+ expect(setupEventListenersSpy).toHaveBeenCalled();
427
+ expect(mockWebSocketManager.initWebSocket).toHaveBeenCalledWith({
428
+ body: {
429
+ force: true,
430
+ isKeepAliveEnabled: false,
431
+ clientType: 'WebexCCSDK',
432
+ allowMultiLogin: false,
433
+ },
434
+ });
435
+
436
+ expect(mockTaskManager.on).toHaveBeenCalledWith(
437
+ TASK_EVENTS.TASK_INCOMING,
438
+ expect.any(Function)
439
+ );
440
+ expect(mockTaskManager.on).toHaveBeenCalledWith(
441
+ TASK_EVENTS.TASK_HYDRATE,
442
+ expect.any(Function)
443
+ );
444
+ expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
445
+
446
+ expect(configSpy).toHaveBeenCalled();
447
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Agent config is fetched successfully', {
448
+ module: CC_FILE,
449
+ method: 'connectWebsocket',
450
+ });
451
+ expect(reloadSpy).toHaveBeenCalled();
452
+ expect(result).toEqual(mockAgentProfile);
453
+ });
454
+
455
+ it('should not attempt for mercury connection when webrtc is disabled', async () => {
456
+ mockAgentProfile.webRtcEnabled = false;
457
+ const mercurySpy = jest.spyOn(webex.internal.mercury, 'connect');
458
+ const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
459
+ const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
460
+ const reloadSpy = jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
461
+ data: {
462
+ auxCodeId: 'auxCodeId',
463
+ agentId: 'agentId',
464
+ deviceType: LoginOption.EXTENSION,
465
+ dn: '12345',
466
+ },
467
+ });
468
+ const configSpy = jest
469
+ .spyOn(webex.cc.services.config, 'getAgentConfig')
470
+ .mockResolvedValue(mockAgentProfile);
471
+ mockWebSocketManager.initWebSocket.mockResolvedValue({
472
+ agentId: 'agent123',
473
+ });
474
+
475
+ const result = await webex.cc.register();
476
+
477
+ expect(connectWebsocketSpy).toHaveBeenCalled();
478
+ expect(setupEventListenersSpy).toHaveBeenCalled();
479
+ expect(mockWebSocketManager.initWebSocket).toHaveBeenCalledWith({
480
+ body: {
481
+ force: true,
482
+ isKeepAliveEnabled: false,
483
+ clientType: 'WebexCCSDK',
484
+ allowMultiLogin: false,
485
+ },
486
+ });
487
+
488
+ expect(configSpy).toHaveBeenCalled();
489
+ expect(mercurySpy).not.toHaveBeenCalled();
490
+ expect(result).toEqual(mockAgentProfile);
491
+ });
492
+ });
493
+
494
+ describe('stationLogin', () => {
495
+ it('should login successfully with LoginOption.BROWSER and webrtc enabled', async () => {
496
+ const mockTask = {};
497
+ const options = {
498
+ teamId: 'teamId',
499
+ loginOption: LoginOption.BROWSER,
500
+ };
501
+
502
+ webex.cc.agentConfig = {
503
+ agentId: 'agentId',
504
+ webRtcEnabled: true,
505
+ loginVoiceOptions: ['BROWSER', 'EXTENSION', 'AGENT_DN'],
506
+ };
507
+
508
+ const registerWebCallingLineSpy = jest.spyOn(
509
+ webex.cc.webCallingService,
510
+ 'registerWebCallingLine'
511
+ );
512
+
513
+ const mockData = {
514
+ data: {
515
+ loginOption: LoginOption.BROWSER,
516
+ agentId: 'agentId',
517
+ teamId: 'teamId',
518
+ siteId: 'siteId',
519
+ roles: [AGENT],
520
+ trackingId: '1234',
521
+ eventType: 'DESKTOP_MESSAGE',
522
+ channelsMap: {
523
+ chat: ['25d8ggg7-4821-7de7-b626-36437adec509', '14e7fff7-7de7-4821-a919-36437adec509'],
524
+ email: [
525
+ '14e7fff7-7de7-4821-a919-36437adec509',
526
+ '14e7fff7-7de7-4821-a919-36437adec509',
527
+ '14e7fff7-7de7-4821-a919-36437adec509',
528
+ ],
529
+ social: [],
530
+ telephony: ['14e7fff7-7de7-4821-a919-36437adec509'],
531
+ },
532
+ },
533
+ trackingId: 'notifs_52628',
534
+ orgId: 'orgId',
535
+ type: 'StationLoginSuccess',
536
+ eventType: 'STATION_LOGIN',
537
+ };
538
+
539
+ const responseMock = {
540
+ loginOption: LoginOption.BROWSER,
541
+ agentId: 'agentId',
542
+ teamId: 'teamId',
543
+ siteId: 'siteId',
544
+ roles: [AGENT],
545
+ trackingId: '1234',
546
+ eventType: 'DESKTOP_MESSAGE',
547
+ mmProfile: {
548
+ chat: 2,
549
+ email: 3,
550
+ social: 0,
551
+ telephony: 1,
552
+ },
553
+ notifsTrackingId: 'notifs_52628',
554
+ };
555
+
556
+ const stationLoginMock = jest
557
+ .spyOn(webex.cc.services.agent, 'stationLogin')
558
+ .mockResolvedValue(mockData as unknown as StationLoginSuccess);
559
+
560
+ const result = await webex.cc.stationLogin(options);
561
+
562
+ expect(registerWebCallingLineSpy).toHaveBeenCalled();
563
+ expect(stationLoginMock).toHaveBeenCalledWith({
564
+ data: {
565
+ dialNumber: 'agentId',
566
+ teamId: 'teamId',
567
+ deviceType: LoginOption.BROWSER,
568
+ isExtension: false,
569
+ deviceId: `${WEB_RTC_PREFIX}agentId`,
570
+ roles: [AGENT],
571
+ teamName: '',
572
+ siteId: '',
573
+ usesOtherDN: false,
574
+ auxCodeId: '',
575
+ },
576
+ });
577
+
578
+ expect(mockMetricsManager.timeEvent).toBeCalledWith([
579
+ METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
580
+ METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
581
+ ]);
582
+ expect(result).toEqual(responseMock);
583
+
584
+ const onSpy = jest.spyOn(mockTaskManager, 'on');
585
+ const emitSpy = jest.spyOn(webex.cc, 'trigger');
586
+ const ccEmitSpy = jest.spyOn(webex.cc, 'emit');
587
+ const incomingCallCb = onSpy.mock.calls[0][1];
588
+
589
+ expect(onSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_INCOMING, incomingCallCb);
590
+
591
+ incomingCallCb(mockTask);
592
+
593
+ expect(emitSpy).toHaveBeenCalledTimes(1);
594
+ expect(emitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_INCOMING, mockTask);
595
+ // Verify message event listener
596
+ const messageCallback = mockWebSocketManager.on.mock.calls.find(
597
+ (call) => call[0] === 'message'
598
+ )[1];
599
+ const agentStateChangeEventData = {
600
+ type: CC_EVENTS.AGENT_STATE_CHANGE,
601
+ data: {some: 'data'},
602
+ };
603
+
604
+ const agentMultiLoginEventData = {
605
+ type: CC_EVENTS.AGENT_MULTI_LOGIN,
606
+ data: {some: 'data'},
607
+ };
608
+
609
+ // Simulate receiving a message event
610
+ messageCallback(JSON.stringify(agentStateChangeEventData));
611
+
612
+ expect(ccEmitSpy).toHaveBeenCalledWith(
613
+ AGENT_EVENTS.AGENT_STATE_CHANGE,
614
+ agentStateChangeEventData.data
615
+ );
616
+
617
+ // Simulate receiving a message event
618
+ messageCallback(JSON.stringify(agentMultiLoginEventData));
619
+
620
+ expect(ccEmitSpy).toHaveBeenCalledWith(
621
+ AGENT_EVENTS.AGENT_MULTI_LOGIN,
622
+ agentMultiLoginEventData.data
623
+ );
624
+ });
625
+
626
+ it('should not attempt mobius registration for LoginOption.BROWSER if webrtc is disabled', async () => {
627
+ const options = {
628
+ teamId: 'teamId',
629
+ loginOption: LoginOption.BROWSER,
630
+ };
631
+
632
+ webex.cc.agentConfig = {
633
+ agentId: 'agentId',
634
+ webRtcEnabled: false,
635
+ };
636
+
637
+ const mockData = {
638
+ data: {
639
+ loginOption: LoginOption.BROWSER,
640
+ agentId: 'agentId',
641
+ teamId: 'teamId',
642
+ siteId: 'siteId',
643
+ roles: [AGENT],
644
+ trackingId: '1234',
645
+ eventType: 'DESKTOP_MESSAGE',
646
+ channelsMap: {
647
+ chat: ['25d8ggg7-4821-7de7-b626-36437adec509', '14e7fff7-7de7-4821-a919-36437adec509'],
648
+ email: [],
649
+ social: [],
650
+ telephony: ['14e7fff7-7de7-4821-a919-36437adec509'],
651
+ },
652
+ },
653
+ trackingId: '1234',
654
+ orgId: 'orgId',
655
+ type: 'StationLoginSuccess',
656
+ eventType: 'STATION_LOGIN',
657
+ };
658
+
659
+ const registerWebCallingLineSpy = jest.spyOn(
660
+ webex.cc.webCallingService,
661
+ 'registerWebCallingLine'
662
+ );
663
+
664
+ const stationLoginSpy = jest
665
+ .spyOn(webex.cc.services.agent, 'stationLogin')
666
+ .mockResolvedValue(mockData as unknown as StationLoginSuccess);
667
+
668
+ await webex.cc.stationLogin(options);
669
+
670
+ expect(registerWebCallingLineSpy).not.toHaveBeenCalled();
671
+ expect(stationLoginSpy).toHaveBeenCalledWith({
672
+ data: {
673
+ dialNumber: 'agentId',
674
+ teamId: 'teamId',
675
+ deviceType: LoginOption.BROWSER,
676
+ isExtension: false,
677
+ deviceId: `${WEB_RTC_PREFIX}agentId`,
678
+ roles: [AGENT],
679
+ teamName: '',
680
+ siteId: '',
681
+ usesOtherDN: false,
682
+ auxCodeId: '',
683
+ },
684
+ });
685
+ });
686
+
687
+ it('should login successfully with other LoginOption', async () => {
688
+ webex.cc.agentConfig = {
689
+ webRtcEnabled: true,
690
+ };
691
+
692
+ const options = {
693
+ teamId: 'teamId',
694
+ loginOption: LoginOption.AGENT_DN,
695
+ dialNumber: '12345678901',
696
+ };
697
+
698
+ const mockData = {
699
+ data: {
700
+ loginOption: LoginOption.AGENT_DN,
701
+ agentId: 'agentId',
702
+ teamId: 'teamId',
703
+ siteId: 'siteId',
704
+ roles: [AGENT],
705
+ trackingId: '1234',
706
+ eventType: 'DESKTOP_MESSAGE',
707
+ channelsMap: {
708
+ chat: ['25d8ggg7-4821-7de7-b626-36437adec509', '14e7fff7-7de7-4821-a919-36437adec509'],
709
+ email: [
710
+ '14e7fff7-7de7-4821-a919-36437adec509',
711
+ '14e7fff7-7de7-4821-a919-36437adec509',
712
+ '14e7fff7-7de7-4821-a919-36437adec509',
713
+ ],
714
+ social: [],
715
+ telephony: ['14e7fff7-7de7-4821-a919-36437adec509'],
716
+ },
717
+ },
718
+ trackingId: 'notifs_52628',
719
+ orgId: 'orgId',
720
+ type: 'StationLoginSuccess',
721
+ eventType: 'STATION_LOGIN',
722
+ };
723
+
724
+ const responseMock = {
725
+ loginOption: LoginOption.AGENT_DN,
726
+ agentId: 'agentId',
727
+ teamId: 'teamId',
728
+ siteId: 'siteId',
729
+ roles: [AGENT],
730
+ trackingId: '1234',
731
+ eventType: 'DESKTOP_MESSAGE',
732
+ mmProfile: {
733
+ chat: 2,
734
+ email: 3,
735
+ social: 0,
736
+ telephony: 1,
737
+ },
738
+ notifsTrackingId: 'notifs_52628',
739
+ };
740
+
741
+ const stationLoginMock = jest
742
+ .spyOn(webex.cc.services.agent, 'stationLogin')
743
+ .mockResolvedValue(mockData as unknown as StationLoginSuccess);
744
+
745
+ const result = await webex.cc.stationLogin(options);
746
+
747
+ // Verify logging calls
748
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station login', {
749
+ module: CC_FILE,
750
+ method: 'stationLogin',
751
+ });
752
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
753
+ `Agent station login completed successfully agentId: ${mockData.data.agentId} loginOption: ${mockData.data.loginOption} teamId: ${mockData.data.teamId}`,
754
+ {
755
+ module: CC_FILE,
756
+ method: 'stationLogin',
757
+ trackingId: mockData.trackingId,
758
+ }
759
+ );
760
+
761
+ expect(stationLoginMock).toHaveBeenCalledWith({
762
+ data: {
763
+ dialNumber: '12345678901',
764
+ teamId: 'teamId',
765
+ deviceType: LoginOption.AGENT_DN,
766
+ isExtension: false,
767
+ deviceId: '12345678901',
768
+ roles: [AGENT],
769
+ teamName: '',
770
+ siteId: '',
771
+ usesOtherDN: false,
772
+ auxCodeId: '',
773
+ },
774
+ });
775
+ expect(result).toEqual(responseMock);
776
+ });
777
+
778
+ it('should handle error during stationLogin', async () => {
779
+ webex.cc.agentConfig = {
780
+ webRtcEnabled: true,
781
+ };
782
+
783
+ const options = {
784
+ teamId: 'teamId',
785
+ loginOption: LoginOption.EXTENSION,
786
+ dialNumber: '1234567890',
787
+ };
788
+
789
+ const error = {
790
+ details: {
791
+ trackingId: '1234',
792
+ data: {
793
+ reason: 'Error while performing stationLogin',
794
+ },
795
+ },
796
+ };
797
+
798
+ jest.spyOn(webex.cc.services.agent, 'stationLogin').mockRejectedValue(error);
799
+
800
+ await expect(webex.cc.stationLogin(options)).rejects.toThrow(error.details.data.reason);
801
+
802
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station login', {
803
+ module: CC_FILE,
804
+ method: 'stationLogin',
805
+ });
806
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
807
+ `stationLogin failed with reason: ${error.details.data.reason}`,
808
+ {module: CC_FILE, method: 'stationLogin', trackingId: error.details.trackingId}
809
+ );
810
+ });
811
+ });
812
+
813
+ describe('stationLogout', () => {
814
+ it('should logout successfully', async () => {
815
+ const data = {logoutReason: 'Logout reason'};
816
+ const response = {};
817
+
818
+ const stationLogoutMock = jest
819
+ .spyOn(webex.cc.services.agent, 'logout')
820
+ .mockResolvedValue({} as StationLogoutResponse);
821
+
822
+ const result = await webex.cc.stationLogout(data);
823
+
824
+ // Verify logging calls
825
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station logout', {
826
+ module: CC_FILE,
827
+ method: 'stationLogout',
828
+ });
829
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Agent station logout completed successfully', {
830
+ module: CC_FILE,
831
+ method: 'stationLogout',
832
+ });
833
+
834
+ expect(stationLogoutMock).toHaveBeenCalledWith({data: data});
835
+ // TODO: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-626777 Implement the de-register method and close the listener there
836
+ // expect(mockTaskManager.unregisterIncomingCallEvent).toHaveBeenCalledWith();
837
+ // expect(mockTaskManager.off).toHaveBeenCalledWith(
838
+ // TASK_EVENTS.TASK_INCOMING,
839
+ // expect.any(Function)
840
+ // );
841
+ // expect(mockTaskManager.off).toHaveBeenCalledWith(
842
+ // TASK_EVENTS.TASK_HYDRATE,
843
+ // expect.any(Function)
844
+ // );
845
+ // expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
846
+ expect(result).toEqual(response);
847
+ expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
848
+ METRIC_EVENT_NAMES.STATION_LOGOUT_SUCCESS,
849
+ METRIC_EVENT_NAMES.STATION_LOGOUT_FAILED,
850
+ ]);
851
+ });
852
+
853
+ it('should handle error during stationLogout', async () => {
854
+ const data = {logoutReason: 'Logout reason'};
855
+ const error = {
856
+ details: {
857
+ trackingId: '1234',
858
+ data: {
859
+ reason: 'Error while performing station logout',
860
+ },
861
+ },
862
+ };
863
+
864
+ jest.spyOn(webex.cc.services.agent, 'logout').mockRejectedValue(error);
865
+
866
+ await expect(webex.cc.stationLogout(data)).rejects.toThrow(error.details.data.reason);
867
+
868
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station logout', {
869
+ module: CC_FILE,
870
+ method: 'stationLogout',
871
+ });
872
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
873
+ `stationLogout failed with reason: ${error.details.data.reason}`,
874
+ {module: CC_FILE, method: 'stationLogout', trackingId: error.details.trackingId}
875
+ );
876
+ });
877
+ });
878
+
879
+ describe('setAgentStatus', () => {
880
+ it('should set agent status successfully when status is Available', async () => {
881
+ const expectedPayload = {
882
+ state: 'Available',
883
+ auxCodeId: '0',
884
+ agentId: '123',
885
+ lastStateChangeReason: 'Agent is available',
886
+ };
887
+
888
+ const setAgentStatusMock = jest
889
+ .spyOn(webex.cc.services.agent, 'stateChange')
890
+ .mockResolvedValue({data: expectedPayload});
891
+
892
+ const result = await webex.cc.setAgentState(expectedPayload);
893
+
894
+ // Verify logging calls
895
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Setting agent state', {
896
+ module: CC_FILE,
897
+ method: 'setAgentState',
898
+ });
899
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
900
+ `Agent state changed successfully to auxCodeId: ${expectedPayload.auxCodeId}`,
901
+ {
902
+ module: CC_FILE,
903
+ method: 'setAgentState',
904
+ }
905
+ );
906
+
907
+ expect(setAgentStatusMock).toHaveBeenCalledWith({data: expectedPayload});
908
+ expect(result).toEqual({data: expectedPayload});
909
+ expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
910
+ METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_SUCCESS,
911
+ METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_FAILED,
912
+ ]);
913
+ });
914
+
915
+ it('should set agent status successfully when status is Meeting', async () => {
916
+ const expectedPayload = {
917
+ state: 'Meeting',
918
+ auxCodeId: '12345',
919
+ agentId: '123',
920
+ lastStateChangeReason: 'Agent is in meeting',
921
+ };
922
+
923
+ const setAgentStatusMock = jest
924
+ .spyOn(webex.cc.services.agent, 'stateChange')
925
+ .mockResolvedValue({data: expectedPayload});
926
+
927
+ const result = await webex.cc.setAgentState(expectedPayload);
928
+
929
+ expect(setAgentStatusMock).toHaveBeenCalledWith({data: expectedPayload});
930
+ expect(result).toEqual({data: expectedPayload});
931
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
932
+ `Agent state changed successfully to auxCodeId: ${expectedPayload.auxCodeId}`,
933
+ {
934
+ module: CC_FILE,
935
+ method: 'setAgentState',
936
+ }
937
+ );
938
+ expect(setAgentStatusMock).toHaveBeenCalledWith({data: expectedPayload});
939
+ expect(result).toEqual({data: expectedPayload});
940
+ expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
941
+ METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_SUCCESS,
942
+ METRIC_EVENT_NAMES.AGENT_STATE_CHANGE_FAILED,
943
+ ]);
944
+ });
945
+
946
+ it('should handle error during setAgentStatus when status is Meeting', async () => {
947
+ const expectedPayload = {
948
+ state: 'Meeting',
949
+ auxCodeId: '12345',
950
+ agentId: '123',
951
+ lastStateChangeReason: 'Agent is in meeting',
952
+ };
953
+
954
+ const error = {
955
+ details: {
956
+ trackingId: '1234',
957
+ data: {
958
+ reason: 'missing status',
959
+ },
960
+ },
961
+ };
962
+ jest.spyOn(webex.cc.services.agent, 'stateChange').mockRejectedValue(error);
963
+
964
+ await expect(webex.cc.setAgentState(expectedPayload)).rejects.toThrow(
965
+ error.details.data.reason
966
+ );
967
+
968
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Setting agent state', {
969
+ module: CC_FILE,
970
+ method: 'setAgentState',
971
+ });
972
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
973
+ `setAgentState failed with reason: ${error.details.data.reason}`,
974
+ {module: CC_FILE, method: 'setAgentState', trackingId: error.details.trackingId}
975
+ );
976
+ });
977
+
978
+ it('should handle invalid status', async () => {
979
+ const invalidPayload = {
980
+ state: 'invalid',
981
+ auxCodeId: '12345',
982
+ agentId: '123',
983
+ lastStateChangeReason: 'invalid',
984
+ };
985
+ const error = {
986
+ details: {
987
+ trackingId: '1234',
988
+ data: {
989
+ reason: 'Invalid status',
990
+ },
991
+ },
992
+ };
993
+ jest.spyOn(webex.cc.services.agent, 'stateChange').mockRejectedValue(error);
994
+
995
+ await expect(webex.cc.setAgentState(invalidPayload)).rejects.toThrow(
996
+ error.details.data.reason
997
+ );
998
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
999
+ `setAgentState failed with reason: ${error.details.data.reason}`,
1000
+ {module: CC_FILE, method: 'setAgentState', trackingId: error.details.trackingId}
1001
+ );
1002
+ });
1003
+ });
1004
+
1005
+ describe('getBuddyAgents', () => {
1006
+ it('should return buddy agents response when successful', async () => {
1007
+ const data: BuddyAgents = {state: 'Available', mediaType: 'telephony'};
1008
+ webex.cc.agentConfig = {
1009
+ agentId: 'agentId',
1010
+ agentProfileID: 'test-agent-profile-id',
1011
+ };
1012
+
1013
+ const buddyAgentsResponse = {
1014
+ type: 'BuddyAgentsSuccess',
1015
+ orgId: '',
1016
+ trackingId: '1234',
1017
+ data: {
1018
+ eventType: 'BuddyAgents',
1019
+ agentId: 'agentId',
1020
+ trackingId: '1234',
1021
+ orgId: '',
1022
+ type: '',
1023
+ agentSessionId: 'session123',
1024
+ agentList: [
1025
+ {
1026
+ agentId: 'agentId',
1027
+ state: 'Available',
1028
+ teamId: 'teamId',
1029
+ dn: '1234567890',
1030
+ agentName: 'John',
1031
+ siteId: 'siteId',
1032
+ },
1033
+ ],
1034
+ },
1035
+ };
1036
+
1037
+ const buddyAgentsSpy = jest
1038
+ .spyOn(webex.cc.services.agent, 'buddyAgents')
1039
+ .mockResolvedValue(buddyAgentsResponse);
1040
+
1041
+ const result = await webex.cc.getBuddyAgents(data);
1042
+
1043
+ // Verify logging calls
1044
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching buddy agents', {
1045
+ module: CC_FILE,
1046
+ method: 'getBuddyAgents',
1047
+ });
1048
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
1049
+ `Successfully retrieved ${buddyAgentsResponse.data.agentList.length} buddy agents`,
1050
+ {
1051
+ module: CC_FILE,
1052
+ method: 'getBuddyAgents',
1053
+ trackingId: buddyAgentsResponse.trackingId,
1054
+ }
1055
+ );
1056
+
1057
+ expect(buddyAgentsSpy).toHaveBeenCalledWith({
1058
+ data: {agentProfileId: 'test-agent-profile-id', ...data},
1059
+ });
1060
+
1061
+ expect(result).toEqual(buddyAgentsResponse);
1062
+ expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
1063
+ METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_SUCCESS,
1064
+ METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_FAILED,
1065
+ ]);
1066
+ });
1067
+
1068
+ it('should handle error', async () => {
1069
+ const data: BuddyAgents = {state: 'Available', mediaType: 'telephony'};
1070
+ webex.cc.agentConfig = {
1071
+ agentId: 'f520d6b5-28ad-4f2f-b83e-781bb64af617',
1072
+ agentProfileID: 'test-agent-profile-id',
1073
+ };
1074
+
1075
+ const error = {
1076
+ details: {
1077
+ data: {
1078
+ agentId: 'f520d6b5-28ad-4f2f-b83e-781bb64af617',
1079
+ eventTime: 1731402794534,
1080
+ eventType: 'AgentDesktopMessage',
1081
+ orgId: 'e7924666-777d-40d4-a504-01aa1e62dd2f',
1082
+ reason: 'AGENT_NOT_FOUND',
1083
+ reasonCode: 1038,
1084
+ trackingId: '5d2ddfaf-9b8a-491f-9c3f-3bb8ba60d595',
1085
+ type: 'BuddyAgentsRetrieveFailed',
1086
+ },
1087
+ orgId: 'e7924666-777d-40d4-a504-01aa1e62dd2f',
1088
+ trackingId: 'notifs_a7727d9e-7651-4c60-90a7-ff3de47b784d',
1089
+ type: 'BuddyAgents',
1090
+ },
1091
+ };
1092
+
1093
+ jest.spyOn(webex.cc.services.agent, 'buddyAgents').mockRejectedValue(error);
1094
+
1095
+ await expect(webex.cc.getBuddyAgents(data)).rejects.toThrow(error.details.data.reason);
1096
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching buddy agents', {
1097
+ module: CC_FILE,
1098
+ method: 'getBuddyAgents',
1099
+ });
1100
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
1101
+ `getBuddyAgents failed with reason: ${error.details.data.reason}`,
1102
+ {module: CC_FILE, method: 'getBuddyAgents', trackingId: error.details.trackingId}
1103
+ );
1104
+ });
1105
+ });
1106
+
1107
+ describe('silentRelogin', () => {
1108
+ it('should perform silent relogin and set agent state to available', async () => {
1109
+ const mockReLoginResponse = {
1110
+ data: {
1111
+ auxCodeId: 'auxCodeId',
1112
+ agentId: 'agentId',
1113
+ lastStateChangeReason: 'agent-wss-disconnect',
1114
+ lastStateChangeTimestamp: 1738575135188,
1115
+ lastIdleCodeChangeTimestamp: 1738575135189,
1116
+ deviceType: LoginOption.BROWSER,
1117
+ dn: '12345',
1118
+ },
1119
+ };
1120
+
1121
+ // Mock the agentConfig
1122
+ webex.cc.agentConfig = {
1123
+ agentId: 'agentId',
1124
+ agentProfileID: 'test-agent-profile-id',
1125
+ isAgentLoggedIn: false,
1126
+ } as Profile;
1127
+
1128
+ const date = new Date();
1129
+ const setAgentStateSpy = jest.spyOn(webex.cc, 'setAgentState').mockResolvedValue({
1130
+ data: {lastStateChangeTimestamp: 1234, lastIdleCodeChangeTimestamp: 12345},
1131
+ } as unknown as SetStateResponse);
1132
+ jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue(mockReLoginResponse);
1133
+
1134
+ const registerWebCallingLineSpy = jest.spyOn(
1135
+ webex.cc.webCallingService,
1136
+ 'registerWebCallingLine'
1137
+ );
1138
+
1139
+ const setLoginOptionSpy = jest.spyOn(webex.cc.webCallingService, 'setLoginOption');
1140
+ const incomingTaskListenerSpy = jest.spyOn(webex.cc, 'incomingTaskListener');
1141
+ const webSocketManagerOnSpy = jest.spyOn(webex.cc.services.webSocketManager, 'on');
1142
+ await webex.cc['silentRelogin']();
1143
+
1144
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1145
+ module: CC_FILE,
1146
+ method: 'silentRelogin',
1147
+ });
1148
+ expect(LoggerProxy.info).toHaveBeenCalledWith(
1149
+ 'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
1150
+ {module: CC_FILE, method: 'silentRelogin'}
1151
+ );
1152
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
1153
+ `Silent relogin process completed successfully with login Option: ${mockReLoginResponse.data.deviceType} teamId: ${mockReLoginResponse.data.teamId}`,
1154
+ {
1155
+ module: CC_FILE,
1156
+ method: 'silentRelogin',
1157
+ }
1158
+ );
1159
+ expect(setAgentStateSpy).toHaveBeenCalledWith({
1160
+ state: 'Available',
1161
+ auxCodeId: '0', // even if get auxcodeId from relogin response, it should be 0 for available state
1162
+ lastStateChangeReason: 'agent-wss-disconnect',
1163
+ agentId: 'agentId',
1164
+ });
1165
+ expect(webex.cc.agentConfig.isAgentLoggedIn).toBe(true);
1166
+ expect(webex.cc.agentConfig.lastStateAuxCodeId).toBe('0');
1167
+ expect(webex.cc.agentConfig.lastStateChangeTimestamp).toStrictEqual(1234); // it should be updated with the new timestamp of setAgentState response
1168
+ expect(webex.cc.agentConfig.lastIdleCodeChangeTimestamp).toStrictEqual(12345);
1169
+ expect(webex.cc.agentConfig.deviceType).toBe(LoginOption.BROWSER);
1170
+ expect(registerWebCallingLineSpy).toHaveBeenCalled();
1171
+ expect(setLoginOptionSpy).toHaveBeenCalledWith(LoginOption.BROWSER);
1172
+ // TODO: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-626777 Implement the de-register method and close the listener there
1173
+ // expect(incomingTaskListenerSpy).toHaveBeenCalled();
1174
+ // expect(webSocketManagerOnSpy).toHaveBeenCalledWith('message', expect.any(Function));
1175
+ // expect(mockTaskManager.on).toHaveBeenCalledWith(
1176
+ // TASK_EVENTS.TASK_HYDRATE,
1177
+ // expect.any(Function)
1178
+ // );
1179
+ });
1180
+
1181
+ it('should handle AGENT_NOT_FOUND error silently', async () => {
1182
+ const error = {
1183
+ details: {
1184
+ trackingId: '1234',
1185
+ data: {
1186
+ reason: 'AGENT_NOT_FOUND',
1187
+ },
1188
+ },
1189
+ };
1190
+
1191
+ jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
1192
+ await webex.cc['silentRelogin']();
1193
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1194
+ module: CC_FILE,
1195
+ method: 'silentRelogin',
1196
+ });
1197
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
1198
+ 'Agent not found during relogin, handling silently',
1199
+ {module: CC_FILE, method: 'silentRelogin'}
1200
+ );
1201
+ });
1202
+
1203
+ it('should handle errors during silent relogin', async () => {
1204
+ const error = new Error('Error while performing silentRelogin');
1205
+ jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
1206
+
1207
+ await expect(webex.cc['silentRelogin']()).rejects.toThrow(error);
1208
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1209
+ module: CC_FILE,
1210
+ method: 'silentRelogin',
1211
+ });
1212
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
1213
+ `silentRelogin failed with reason: Error while performing silentRelogin`,
1214
+ {
1215
+ module: CC_FILE,
1216
+ method: 'silentRelogin',
1217
+ trackingId: undefined,
1218
+ }
1219
+ );
1220
+ });
1221
+
1222
+ it('should update agentConfig with deviceType during silent relogin for EXTENSION', async () => {
1223
+ const mockReLoginResponse = {
1224
+ data: {
1225
+ auxCodeId: 'auxCodeId',
1226
+ agentId: 'agentId',
1227
+ deviceType: LoginOption.EXTENSION,
1228
+ dn: '12345',
1229
+ lastStateChangeTimestamp: 1738575135188,
1230
+ lastIdleCodeChangeTimestamp: 1738575135189,
1231
+ teamId: 'teamId',
1232
+ },
1233
+ };
1234
+
1235
+ // Mock the agentConfig
1236
+ webex.cc.agentConfig = {
1237
+ agentId: 'agentId',
1238
+ agentProfileID: 'test-agent-profile-id',
1239
+ isAgentLoggedIn: false,
1240
+ } as Profile;
1241
+
1242
+ const registerWebCallingLineSpy = jest.spyOn(
1243
+ webex.cc.webCallingService,
1244
+ 'registerWebCallingLine'
1245
+ );
1246
+ jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue(mockReLoginResponse);
1247
+
1248
+ await webex.cc['silentRelogin']();
1249
+
1250
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1251
+ module: CC_FILE,
1252
+ method: 'silentRelogin',
1253
+ });
1254
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
1255
+ `Silent relogin process completed successfully with login Option: ${mockReLoginResponse.data.deviceType} teamId: ${mockReLoginResponse.data.teamId}`,
1256
+ {
1257
+ module: CC_FILE,
1258
+ method: 'silentRelogin',
1259
+ }
1260
+ );
1261
+
1262
+ expect(webex.cc.agentConfig.deviceType).toBe(LoginOption.EXTENSION);
1263
+ expect(webex.cc.agentConfig.dn).toBe('12345');
1264
+ expect(webex.cc.agentConfig.lastStateAuxCodeId).toBe('auxCodeId');
1265
+ expect(webex.cc.agentConfig.lastStateChangeTimestamp).toStrictEqual(1738575135188);
1266
+ expect(webex.cc.agentConfig.lastIdleCodeChangeTimestamp).toStrictEqual(1738575135189);
1267
+ });
1268
+
1269
+ it('should update agentConfig with deviceType during silent relogin for AGENT_DN', async () => {
1270
+ const mockReLoginResponse = {
1271
+ data: {
1272
+ auxCodeId: 'auxCodeId',
1273
+ agentId: 'agentId',
1274
+ lastStateChangeReason: 'agent-wss-disconnect',
1275
+ deviceType: LoginOption.AGENT_DN,
1276
+ dn: '67890',
1277
+ subStatus: 'subStatusValue',
1278
+ },
1279
+ };
1280
+
1281
+ // Mock the agentConfig
1282
+ webex.cc.agentConfig = {
1283
+ agentId: 'agentId',
1284
+ agentProfileID: 'test-agent-profile-id',
1285
+ isAgentLoggedIn: false,
1286
+ } as Profile;
1287
+
1288
+ jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue(mockReLoginResponse);
1289
+
1290
+ await webex.cc['silentRelogin']();
1291
+
1292
+ expect(webex.cc.agentConfig.deviceType).toBe(LoginOption.AGENT_DN);
1293
+ expect(webex.cc.agentConfig.dn).toBe('67890');
1294
+ });
1295
+ });
1296
+
1297
+ describe('setupEventListeners()', () => {
1298
+ let connectionServiceOnSpy, cCEmitSpy;
1299
+
1300
+ beforeEach(() => {
1301
+ connectionServiceOnSpy = jest.spyOn(webex.cc.services.connectionService, 'on');
1302
+ cCEmitSpy = jest.spyOn(webex.cc, 'emit');
1303
+ });
1304
+
1305
+ it('should set up connectionLost and message event listener', () => {
1306
+ webex.cc.setupEventListeners();
1307
+
1308
+ expect(connectionServiceOnSpy).toHaveBeenCalledWith('connectionLost', expect.any(Function));
1309
+ });
1310
+ });
1311
+
1312
+ describe('startOutdial', () => {
1313
+ it('should make outdial call successfully.', async () => {
1314
+ // Setup outDialEp.
1315
+ webex.cc.agentConfig = {
1316
+ outDialEp: 'test-entry-point',
1317
+ };
1318
+
1319
+ // destination number required for making outdial call.
1320
+ const destination = '1234567890';
1321
+
1322
+ // Construct Payload for startOutdial.
1323
+ const newPayload = {
1324
+ destination,
1325
+ entryPointId: 'test-entry-point',
1326
+ direction: OUTDIAL_DIRECTION,
1327
+ attributes: ATTRIBUTES,
1328
+ mediaType: OUTDIAL_MEDIA_TYPE,
1329
+ outboundType: OUTBOUND_TYPE,
1330
+ } as const;
1331
+
1332
+ const mockResponse = {} as AgentContact;
1333
+
1334
+ const startOutdialMock = jest
1335
+ .spyOn(webex.cc.services.dialer, 'startOutdial')
1336
+ .mockResolvedValue(mockResponse);
1337
+
1338
+ const result = await webex.cc.startOutdial(destination);
1339
+
1340
+ // Verify logging calls
1341
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Starting outbound dial', {
1342
+ module: CC_FILE,
1343
+ method: 'startOutdial',
1344
+ });
1345
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Outbound dial completed successfully', {
1346
+ module: CC_FILE,
1347
+ method: 'startOutdial',
1348
+ });
1349
+
1350
+ expect(startOutdialMock).toHaveBeenCalledWith({data: newPayload});
1351
+ expect(result).toEqual(mockResponse);
1352
+ });
1353
+
1354
+ it('should handle error during startOutdial', async () => {
1355
+ // Setup outDialEp.
1356
+ webex.cc.agentConfig = {
1357
+ outDialEp: 'test-entry-point',
1358
+ };
1359
+
1360
+ // destination number required for making outdial call.
1361
+ const invalidDestination = '12345';
1362
+
1363
+ const error = {
1364
+ details: {
1365
+ trackingId: '1234',
1366
+ data: {
1367
+ reason: 'Error while performing startOutdial',
1368
+ },
1369
+ },
1370
+ };
1371
+
1372
+ jest.spyOn(webex.cc.services.dialer, 'startOutdial').mockRejectedValue(error);
1373
+
1374
+ await expect(webex.cc.startOutdial(invalidDestination)).rejects.toThrow(
1375
+ error.details.data.reason
1376
+ );
1377
+
1378
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Starting outbound dial', {
1379
+ module: CC_FILE,
1380
+ method: 'startOutdial',
1381
+ });
1382
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
1383
+ `startOutdial failed with reason: ${error.details.data.reason}`,
1384
+ {module: CC_FILE, method: `startOutdial`, trackingId: error.details.trackingId}
1385
+ );
1386
+ expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'startOutdial', CC_FILE);
1387
+ });
1388
+ });
1389
+
1390
+ describe('getQueues', () => {
1391
+ it('should return queues response when successful', async () => {
1392
+ const mockQueuesResponse = [
1393
+ {
1394
+ queueId: 'queue1',
1395
+ queueName: 'Queue 1',
1396
+ },
1397
+ {
1398
+ queueId: 'queue2',
1399
+ queueName: 'Queue 2',
1400
+ },
1401
+ ];
1402
+
1403
+ webex.cc.services.config.getQueues = jest.fn().mockResolvedValue(mockQueuesResponse);
1404
+
1405
+ const result = await webex.cc.getQueues();
1406
+
1407
+ // Verify logging calls
1408
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching queues', {
1409
+ module: CC_FILE,
1410
+ method: 'getQueues',
1411
+ });
1412
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
1413
+ `Successfully retrieved ${result.length} queues`,
1414
+ {
1415
+ module: CC_FILE,
1416
+ method: 'getQueues',
1417
+ }
1418
+ );
1419
+
1420
+ expect(webex.cc.services.config.getQueues).toHaveBeenCalledWith(
1421
+ 'mockOrgId',
1422
+ 0,
1423
+ 100,
1424
+ undefined,
1425
+ undefined
1426
+ );
1427
+ expect(result).toEqual(mockQueuesResponse);
1428
+ });
1429
+
1430
+ it('should throw an error if orgId is not present', async () => {
1431
+ jest.spyOn(webex.credentials, 'getOrgId').mockResolvedValue(undefined);
1432
+ webex.cc.services.config.getQueues = jest.fn();
1433
+
1434
+ try {
1435
+ await webex.cc.getQueues();
1436
+ } catch (error) {
1437
+ expect(error).toEqual(new Error('Org ID not found.'));
1438
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching queues', {
1439
+ module: CC_FILE,
1440
+ method: 'getQueues',
1441
+ });
1442
+ expect(LoggerProxy.error).toHaveBeenCalledWith('Org ID not found.', {
1443
+ module: CC_FILE,
1444
+ method: 'getQueues',
1445
+ });
1446
+ expect(webex.cc.services.config.getQueues).not.toHaveBeenCalled();
1447
+ }
1448
+ });
1449
+
1450
+ it('should throw an error if config getQueues throws an error', async () => {
1451
+ webex.cc.services.config.getQueues = jest.fn().mockRejectedValue(new Error('Test error.'));
1452
+
1453
+ try {
1454
+ await webex.cc.getQueues();
1455
+ } catch (error) {
1456
+ expect(error).toEqual(new Error('Test error.'));
1457
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching queues', {
1458
+ module: CC_FILE,
1459
+ method: 'getQueues',
1460
+ });
1461
+ expect(webex.cc.services.config.getQueues).toHaveBeenCalledWith(
1462
+ 'mockOrgId',
1463
+ 0,
1464
+ 100,
1465
+ undefined,
1466
+ undefined
1467
+ );
1468
+ }
1469
+ });
1470
+ });
1471
+
1472
+ describe('uploadLogs', () => {
1473
+ it('should upload logs successfully', async () => {
1474
+ const uploadLogsMock = jest.spyOn(webex.cc.webexRequest, 'uploadLogs').mockResolvedValue({
1475
+ trackingId: '1234',
1476
+ feedbackId: '12345',
1477
+ });
1478
+
1479
+ const result = await webex.cc.uploadLogs('12345');
1480
+
1481
+ expect(uploadLogsMock).toHaveBeenCalled();
1482
+
1483
+ expect(result).toEqual({
1484
+ trackingId: '1234',
1485
+ feedbackId: '12345',
1486
+ });
1487
+ });
1488
+
1489
+ it('should handle error during uploadLogs', async () => {
1490
+ const error = new Error('Error while performing uploadLogs');
1491
+ error.stack = 'My stack';
1492
+
1493
+ jest.spyOn(webex.cc.webexRequest, 'uploadLogs').mockRejectedValue(error);
1494
+
1495
+ await expect(webex.cc.uploadLogs('12345')).rejects.toThrow(error);
1496
+ });
1497
+ });
1498
+
1499
+ describe('unregister', () => {
1500
+ let mockWebSocketManager;
1501
+ let mercuryDisconnectSpy;
1502
+ let deviceUnregisterSpy;
1503
+
1504
+ beforeEach(() => {
1505
+ webex.cc.agentConfig = {
1506
+ agentId: 'agentId',
1507
+ webRtcEnabled: true,
1508
+ loginVoiceOptions: [LoginOption.BROWSER],
1509
+ };
1510
+
1511
+ mockWebSocketManager = {
1512
+ isSocketClosed: false,
1513
+ close: jest.fn(),
1514
+ off: jest.fn(),
1515
+ on: jest.fn(),
1516
+ };
1517
+
1518
+ webex.cc.services.webSocketManager = mockWebSocketManager;
1519
+
1520
+ webex.internal = webex.internal || {};
1521
+ webex.internal.mercury = {
1522
+ connected: true,
1523
+ disconnect: jest.fn().mockResolvedValue(),
1524
+ off: jest.fn(),
1525
+ };
1526
+ webex.internal.device = {
1527
+ unregister: jest.fn().mockResolvedValue(),
1528
+ };
1529
+
1530
+ mercuryDisconnectSpy = jest.spyOn(webex.internal.mercury, 'disconnect');
1531
+ deviceUnregisterSpy = jest.spyOn(webex.internal.device, 'unregister');
1532
+ });
1533
+
1534
+ it('should unregister successfully and clean up all resources when webrtc is enabled', async () => {
1535
+ await webex.cc.deregister();
1536
+
1537
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1538
+ TASK_EVENTS.TASK_INCOMING,
1539
+ expect.any(Function)
1540
+ );
1541
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1542
+ TASK_EVENTS.TASK_HYDRATE,
1543
+ expect.any(Function)
1544
+ );
1545
+ expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
1546
+ expect(webex.cc.services.connectionService.off).toHaveBeenCalledWith(
1547
+ 'connectionLost',
1548
+ expect.any(Function)
1549
+ );
1550
+
1551
+ expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1552
+ expect(webex.cc.agentConfig).toBeNull();
1553
+
1554
+ expect(webex.internal.mercury.off).toHaveBeenCalledWith('online');
1555
+ expect(webex.internal.mercury.off).toHaveBeenCalledWith('offline');
1556
+ expect(mercuryDisconnectSpy).toHaveBeenCalled();
1557
+ expect(deviceUnregisterSpy).toHaveBeenCalled();
1558
+
1559
+ expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
1560
+ METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_SUCCESS,
1561
+ METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_FAIL,
1562
+ ]);
1563
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
1564
+ METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_SUCCESS,
1565
+ {},
1566
+ ['operational']
1567
+ );
1568
+
1569
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Mercury disconnected successfully', {
1570
+ module: CC_FILE,
1571
+ method: 'deregister',
1572
+ });
1573
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Deregistered successfully', {
1574
+ module: CC_FILE,
1575
+ method: 'deregister',
1576
+ });
1577
+
1578
+ // verify listeners removed with correct callbacks
1579
+ const incomingCalls = mockTaskManager.off.mock.calls.filter(
1580
+ ([evt]) => evt === TASK_EVENTS.TASK_INCOMING
1581
+ );
1582
+ expect(incomingCalls).toHaveLength(1);
1583
+ const [, incomingCallback] = incomingCalls[0];
1584
+ expect(incomingCallback).toBe(webex.cc['handleIncomingTask']);
1585
+
1586
+ const hydrateCalls = mockTaskManager.off.mock.calls.filter(
1587
+ ([evt]) => evt === TASK_EVENTS.TASK_HYDRATE
1588
+ );
1589
+ expect(hydrateCalls).toHaveLength(1);
1590
+ const [, hydrateCallback] = hydrateCalls[0];
1591
+ expect(hydrateCallback).toBe(webex.cc['handleTaskHydrate']);
1592
+
1593
+ const messageCalls = mockWebSocketManager.off.mock.calls.filter(([evt]) => evt === 'message');
1594
+ expect(messageCalls).toHaveLength(1);
1595
+ const [, messageCallback] = messageCalls[0];
1596
+ expect(messageCallback).toBe(webex.cc['handleWebsocketMessage']);
1597
+
1598
+ const connectionCalls = webex.cc.services.connectionService.off.mock.calls.filter(
1599
+ ([evt]) => evt === 'connectionLost'
1600
+ );
1601
+ expect(connectionCalls).toHaveLength(1);
1602
+ const [, connectionCallback] = connectionCalls[0];
1603
+ expect(connectionCallback).toBe(webex.cc['handleConnectionLost']);
1604
+ });
1605
+
1606
+ it('should skip webCallingService and internal cleanup when webrtc is disabled', async () => {
1607
+ webex.cc.agentConfig.webRtcEnabled = false;
1608
+ await webex.cc.deregister();
1609
+
1610
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1611
+ TASK_EVENTS.TASK_INCOMING,
1612
+ expect.any(Function)
1613
+ );
1614
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1615
+ TASK_EVENTS.TASK_HYDRATE,
1616
+ expect.any(Function)
1617
+ );
1618
+ expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
1619
+ expect(webex.cc.services.connectionService.off).toHaveBeenCalledWith(
1620
+ 'connectionLost',
1621
+ expect.any(Function)
1622
+ );
1623
+
1624
+ expect(webex.internal.mercury.off).not.toHaveBeenCalled();
1625
+ expect(mercuryDisconnectSpy).not.toHaveBeenCalled();
1626
+ expect(deviceUnregisterSpy).not.toHaveBeenCalled();
1627
+ });
1628
+
1629
+ it('should skip internal mercury cleanup when loginVoiceOptions does not include BROWSER', async () => {
1630
+ webex.cc.agentConfig = {
1631
+ agentId: 'agentId',
1632
+ webRtcEnabled: true,
1633
+ loginVoiceOptions: ['EXTENSION'],
1634
+ };
1635
+
1636
+ await webex.cc.deregister();
1637
+
1638
+ // mercury listeners & disconnect should not run
1639
+ expect(webex.internal.mercury.off).not.toHaveBeenCalled();
1640
+ expect(mercuryDisconnectSpy).not.toHaveBeenCalled();
1641
+ expect(deviceUnregisterSpy).not.toHaveBeenCalled();
1642
+
1643
+ expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1644
+ expect(webex.cc.agentConfig).toBeNull();
1645
+ });
1646
+
1647
+ it('should handle errors during unregister and track metrics', async () => {
1648
+ const mockError = new Error('Failed to deregister device');
1649
+ webex.internal.device.unregister.mockRejectedValue(mockError);
1650
+
1651
+ await expect(webex.cc.deregister()).rejects.toThrow('Failed to deregister device');
1652
+
1653
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1654
+ TASK_EVENTS.TASK_INCOMING,
1655
+ expect.any(Function)
1656
+ );
1657
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1658
+ TASK_EVENTS.TASK_HYDRATE,
1659
+ expect.any(Function)
1660
+ );
1661
+
1662
+ expect(LoggerProxy.error).toHaveBeenCalledWith(`Error during deregister: ${mockError}`, {
1663
+ module: CC_FILE,
1664
+ method: 'deregister',
1665
+ });
1666
+
1667
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
1668
+ METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_FAIL,
1669
+ {
1670
+ error: 'Failed to deregister device',
1671
+ },
1672
+ ['operational']
1673
+ );
1674
+ });
1675
+ });
1676
+
1677
+ describe('handleWebsocketMessage events', () => {
1678
+ let messageCallback;
1679
+ let emitSpy;
1680
+
1681
+ beforeEach(() => {
1682
+ emitSpy = jest.spyOn(webex.cc, 'emit');
1683
+ messageCallback = mockWebSocketManager.on.mock.calls.find((c) => c[0] === 'message')[1];
1684
+ });
1685
+
1686
+ it('should emit AGENT_STATION_LOGIN_SUCCESS on CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS with mapped payload', () => {
1687
+ const channelsMap = {chat: ['c1', 'c2'], email: [], social: ['s1'], telephony: []};
1688
+ const payload = {
1689
+ trackingId: 'track-123',
1690
+ data: {
1691
+ agentId: 'agent-id',
1692
+ teamId: 'team-id',
1693
+ siteId: 'site-id',
1694
+ roles: ['role1', 'role2'],
1695
+ channelsMap,
1696
+ type: CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS,
1697
+ },
1698
+ type: CC_EVENTS.AGENT_STATION_LOGIN,
1699
+ };
1700
+
1701
+ messageCallback(JSON.stringify(payload));
1702
+
1703
+ expect(emitSpy).toHaveBeenNthCalledWith(2, AGENT_EVENTS.AGENT_STATION_LOGIN_SUCCESS, {
1704
+ agentId: 'agent-id',
1705
+ teamId: 'team-id',
1706
+ siteId: 'site-id',
1707
+ roles: ['role1', 'role2'],
1708
+ mmProfile: {
1709
+ chat: 2,
1710
+ email: 0,
1711
+ social: 1,
1712
+ telephony: 0,
1713
+ },
1714
+ notifsTrackingId: 'track-123',
1715
+ type: CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS,
1716
+ });
1717
+ });
1718
+
1719
+ it('should emit AGENT_RELOGIN_SUCCESS on CC_EVENTS.AGENT_RELOGIN_SUCCESS with mapped payload', () => {
1720
+ const channelsMap = {chat: ['a', 'b'], email: [], social: ['x'], telephony: ['y', 'z']};
1721
+ const payload = {
1722
+ trackingId: 'trk-relogin',
1723
+ data: {
1724
+ agentId: 'agent-re',
1725
+ teamId: 'team-re',
1726
+ siteId: 'site-re',
1727
+ roles: ['r1', 'r2'],
1728
+ channelsMap,
1729
+ type: CC_EVENTS.AGENT_RELOGIN_SUCCESS,
1730
+ },
1731
+ type: CC_EVENTS.AGENT_RELOGIN_SUCCESS,
1732
+ };
1733
+
1734
+ messageCallback(JSON.stringify(payload));
1735
+
1736
+ expect(emitSpy).toHaveBeenNthCalledWith(2, AGENT_EVENTS.AGENT_RELOGIN_SUCCESS, {
1737
+ agentId: 'agent-re',
1738
+ teamId: 'team-re',
1739
+ siteId: 'site-re',
1740
+ roles: ['r1', 'r2'],
1741
+ mmProfile: {
1742
+ chat: 2,
1743
+ email: 0,
1744
+ social: 1,
1745
+ telephony: 2,
1746
+ },
1747
+ notifsTrackingId: 'trk-relogin',
1748
+ type: CC_EVENTS.AGENT_RELOGIN_SUCCESS,
1749
+ });
1750
+ });
1751
+
1752
+ [
1753
+ {
1754
+ ccEvent: CC_EVENTS.AGENT_STATION_LOGIN_FAILED,
1755
+ constant: AGENT_EVENTS.AGENT_STATION_LOGIN_FAILED,
1756
+ },
1757
+ {ccEvent: CC_EVENTS.AGENT_LOGOUT_SUCCESS, constant: AGENT_EVENTS.AGENT_LOGOUT_SUCCESS},
1758
+ {ccEvent: CC_EVENTS.AGENT_LOGOUT_FAILED, constant: AGENT_EVENTS.AGENT_LOGOUT_FAILED},
1759
+ {ccEvent: CC_EVENTS.AGENT_DN_REGISTERED, constant: AGENT_EVENTS.AGENT_DN_REGISTERED},
1760
+ {
1761
+ ccEvent: CC_EVENTS.AGENT_STATE_CHANGE_SUCCESS,
1762
+ constant: AGENT_EVENTS.AGENT_STATE_CHANGE_SUCCESS,
1763
+ },
1764
+ {
1765
+ ccEvent: CC_EVENTS.AGENT_STATE_CHANGE_FAILED,
1766
+ constant: AGENT_EVENTS.AGENT_STATE_CHANGE_FAILED,
1767
+ },
1768
+ ].forEach(({ccEvent, constant}) => {
1769
+ it(`should emit ${constant} on ${ccEvent}`, () => {
1770
+ const sample = {foo: 'bar', type: ccEvent};
1771
+ messageCallback(JSON.stringify({type: ccEvent, data: sample}));
1772
+ expect(emitSpy).toHaveBeenCalledWith(constant, sample);
1773
+ });
1774
+ });
1775
+
1776
+ it('should call webCallingService.setLoginOption with correct deviceType on AGENT_STATION_LOGIN_SUCCESS', () => {
1777
+ const setLoginOptionSpy = jest.spyOn(webex.cc.webCallingService, 'setLoginOption');
1778
+ const deviceType = LoginOption.EXTENSION;
1779
+ const payload = {
1780
+ trackingId: 'track-123',
1781
+ data: {
1782
+ agentId: 'agent-id',
1783
+ teamId: 'team-id',
1784
+ siteId: 'site-id',
1785
+ roles: ['role1', 'role2'],
1786
+ channelsMap: {chat: [], email: [], social: [], telephony: []},
1787
+ deviceType,
1788
+ type: CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS,
1789
+ },
1790
+ type: CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS,
1791
+ };
1792
+
1793
+ messageCallback(JSON.stringify(payload));
1794
+
1795
+ expect(setLoginOptionSpy).toHaveBeenCalledWith(deviceType);
1796
+ });
1797
+ });
1798
+
1799
+ describe('updateAgentProfile', () => {
1800
+ beforeEach(() => {
1801
+ webex.cc.agentConfig = {
1802
+ ...webex.cc.agentConfig,
1803
+ currentTeamId: 'teamId',
1804
+ agentId: 'agent123',
1805
+ } as any;
1806
+ });
1807
+
1808
+ it('should logout then login and return AgentDeviceTypeUpdateSuccess type', async () => {
1809
+ const data = {
1810
+ teamId: 'teamId',
1811
+ loginOption: LoginOption.EXTENSION,
1812
+ dialNumber: '98765',
1813
+ };
1814
+ const mockResp = {
1815
+ eventType: 'AgentDesktopMessage',
1816
+ agentId: 'agentId',
1817
+ trackingId: 'track-1',
1818
+ auxCodeId: 'aux-1',
1819
+ teamId: 'teamId',
1820
+ agentSessionId: 'sessId',
1821
+ orgId: 'org-1',
1822
+ interactionIds: ['i1'],
1823
+ status: 'LoggedIn',
1824
+ subStatus: 'Available',
1825
+ siteId: 'site-1',
1826
+ lastIdleCodeChangeTimestamp: 1,
1827
+ lastStateChangeTimestamp: 2,
1828
+ profileType: 'type',
1829
+ mmProfile: {chat: 0, email: 0, social: 0, telephony: 0},
1830
+ dialNumber: '98765',
1831
+ roles: ['role'],
1832
+ supervisorSessionId: undefined,
1833
+ notifsTrackingId: 'notif-1',
1834
+ type: 'AgentDeviceTypeUpdateSuccess',
1835
+ };
1836
+
1837
+ jest.spyOn(webex.cc, 'stationLogout').mockResolvedValue({});
1838
+ jest.spyOn(webex.cc, 'stationLogin').mockResolvedValue(mockResp as any);
1839
+
1840
+ const result = await webex.cc.updateAgentProfile(data);
1841
+
1842
+ // Verify logging calls
1843
+ expect(LoggerProxy.info).toHaveBeenCalledWith(`starting profile update`, {
1844
+ module: CC_FILE,
1845
+ method: 'updateAgentProfile',
1846
+ trackingId: 'WX_CC_SDK_mock-tracking-uuid',
1847
+ });
1848
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
1849
+ `profile updated successfully with ${data.loginOption} teamId: ${data.teamId}`,
1850
+ {
1851
+ module: CC_FILE,
1852
+ method: 'updateAgentProfile',
1853
+ trackingId: 'WX_CC_SDK_mock-tracking-uuid',
1854
+ }
1855
+ );
1856
+
1857
+ expect(webex.cc.stationLogout).toHaveBeenCalledWith({
1858
+ logoutReason: 'User requested agent device change',
1859
+ });
1860
+ expect(webex.cc.stationLogin).toHaveBeenCalledWith({
1861
+ teamId: 'teamId',
1862
+ loginOption: data.loginOption,
1863
+ dialNumber: data.dialNumber,
1864
+ });
1865
+ expect(result).toEqual(mockResp);
1866
+ });
1867
+
1868
+ it('should use provided teamId if passed in payload', async () => {
1869
+ const dataWithTeam = {
1870
+ teamId: 'newTeam',
1871
+ loginOption: LoginOption.EXTENSION,
1872
+ dialNumber: '0000',
1873
+ };
1874
+ const mockResp = {
1875
+ ...({} as any),
1876
+ type: 'AgentDeviceTypeUpdateSuccess',
1877
+ };
1878
+ jest.spyOn(webex.cc, 'stationLogout').mockResolvedValue({});
1879
+ const loginSpy = jest.spyOn(webex.cc, 'stationLogin').mockResolvedValue(mockResp);
1880
+
1881
+ const result = await webex.cc.updateAgentProfile(dataWithTeam);
1882
+
1883
+ expect(loginSpy).toHaveBeenCalledWith({
1884
+ teamId: 'newTeam',
1885
+ loginOption: dataWithTeam.loginOption,
1886
+ dialNumber: dataWithTeam.dialNumber,
1887
+ });
1888
+ expect(result).toEqual(mockResp);
1889
+ });
1890
+
1891
+ it('should track failure and throw when stationLogout fails', async () => {
1892
+ const data = {
1893
+ teamId: 'teamId',
1894
+ loginOption: LoginOption.EXTENSION,
1895
+ dialNumber: '98765',
1896
+ };
1897
+ const err = new Error('logout failure');
1898
+ jest.spyOn(webex.cc, 'stationLogout').mockRejectedValue(err);
1899
+ const metricSpy = jest.spyOn(mockMetricsManager, 'trackEvent');
1900
+ const logSpy = jest.spyOn(LoggerProxy, 'error');
1901
+
1902
+ await expect(webex.cc.updateAgentProfile(data)).rejects.toThrow(err);
1903
+
1904
+ expect(metricSpy).toHaveBeenCalledWith(
1905
+ METRIC_EVENT_NAMES.AGENT_DEVICE_TYPE_UPDATE_FAILED,
1906
+ expect.objectContaining({loginType: data.loginOption}),
1907
+ ['behavioral', 'business', 'operational']
1908
+ );
1909
+ expect(logSpy).toHaveBeenCalledWith(`error updating profile: ${err}`, {
1910
+ module: CC_FILE,
1911
+ method: 'updateAgentProfile',
1912
+ trackingId: 'WX_CC_SDK_mock-tracking-uuid',
1913
+ });
1914
+ });
1915
+
1916
+ it('should track failure and throw when stationLogin fails', async () => {
1917
+ const data = {
1918
+ teamId: 'teamId',
1919
+ loginOption: LoginOption.EXTENSION,
1920
+ dialNumber: '98765',
1921
+ };
1922
+ jest.spyOn(webex.cc, 'stationLogout').mockResolvedValue({});
1923
+ const loginErr = new Error('login failure');
1924
+ jest.spyOn(webex.cc, 'stationLogin').mockRejectedValue(loginErr);
1925
+ const metricSpy = jest.spyOn(mockMetricsManager, 'trackEvent');
1926
+ const logSpy = jest.spyOn(LoggerProxy, 'error');
1927
+
1928
+ await expect(webex.cc.updateAgentProfile(data)).rejects.toThrow(loginErr);
1929
+
1930
+ expect(metricSpy).toHaveBeenCalledWith(
1931
+ METRIC_EVENT_NAMES.AGENT_DEVICE_TYPE_UPDATE_FAILED,
1932
+ expect.objectContaining({loginType: data.loginOption}),
1933
+ ['behavioral', 'business', 'operational']
1934
+ );
1935
+ expect(logSpy).toHaveBeenCalledWith(`error updating profile: ${loginErr}`, {
1936
+ module: CC_FILE,
1937
+ method: 'updateAgentProfile',
1938
+ trackingId: 'WX_CC_SDK_mock-tracking-uuid',
1939
+ });
1940
+ });
1941
+
1942
+ it('should throw with detailed error when loginOption equals current device type', async () => {
1943
+ webex.cc.webCallingService.loginOption = LoginOption.BROWSER;
1944
+ const data = {
1945
+ teamId: 'teamId',
1946
+ loginOption: LoginOption.BROWSER,
1947
+ dialNumber: '',
1948
+ };
1949
+ const expectedMessage =
1950
+ 'Will not proceed with device update as new Device type is same as current device type and teamId is same as current teamId';
1951
+
1952
+ await expect(webex.cc.updateAgentProfile(data)).rejects.toMatchObject({
1953
+ message: expectedMessage,
1954
+ details: expect.objectContaining({
1955
+ data: expect.objectContaining({
1956
+ agentId: webex.cc.agentConfig.agentId,
1957
+ reason: expectedMessage,
1958
+ }),
1959
+ }),
1960
+ });
1961
+ });
1962
+
1963
+ it('should allow update when same device type but different teamId', async () => {
1964
+ webex.cc.agentConfig.currentTeamId = 'team1';
1965
+ webex.cc.webCallingService.loginOption = LoginOption.BROWSER;
1966
+
1967
+ const data = {
1968
+ teamId: 'team2',
1969
+ loginOption: LoginOption.BROWSER,
1970
+ dialNumber: '1234',
1971
+ };
1972
+ jest.spyOn(webex.cc, 'stationLogout').mockResolvedValue({});
1973
+ const loginSpy = jest.spyOn(webex.cc, 'stationLogin').mockResolvedValue({
1974
+ type: 'AgentDeviceTypeUpdateSuccess',
1975
+ } as any);
1976
+
1977
+ await expect(webex.cc.updateAgentProfile(data)).resolves.toBeDefined();
1978
+ expect(loginSpy).toHaveBeenCalledWith({
1979
+ teamId: 'team2',
1980
+ loginOption: data.loginOption,
1981
+ dialNumber: data.dialNumber,
1982
+ });
1983
+ });
1984
+ });
1985
+ });