@webex/contact-center 3.11.0 → 3.12.0-llmrefactor.2

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 (267) hide show
  1. package/.sdd/manifest.json +882 -0
  2. package/AGENTS.md +94 -0
  3. package/ai-docs/ARCHITECTURE.md +168 -0
  4. package/ai-docs/CONTRACTS.md +46 -0
  5. package/ai-docs/GETTING_STARTED.md +168 -0
  6. package/ai-docs/GLOSSARY.md +43 -0
  7. package/ai-docs/README.md +138 -0
  8. package/ai-docs/REVIEW_CHECKLIST.md +41 -0
  9. package/ai-docs/RULES.md +444 -0
  10. package/ai-docs/SECURITY.md +52 -0
  11. package/ai-docs/SERVICE_STATE.md +48 -0
  12. package/ai-docs/SPEC_INDEX.md +65 -0
  13. package/ai-docs/adr/0001-spec-source-policy.md +55 -0
  14. package/ai-docs/adr/README.md +8 -0
  15. package/ai-docs/adr/_adr-template.md +31 -0
  16. package/ai-docs/contact-center-spec.md +341 -0
  17. package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
  18. package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
  19. package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -0
  20. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  21. package/ai-docs/patterns/testing-patterns.md +480 -0
  22. package/ai-docs/patterns/typescript-patterns.md +365 -0
  23. package/ai-docs/templates/README.md +102 -0
  24. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  25. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  26. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  27. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  28. package/ai-docs/templates/new-method/00-master.md +80 -0
  29. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  30. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  31. package/ai-docs/templates/new-method/03-tests.md +201 -0
  32. package/ai-docs/templates/new-method/04-validation.md +141 -0
  33. package/ai-docs/templates/new-service/00-master.md +109 -0
  34. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  35. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  36. package/ai-docs/templates/new-service/03-integration.md +178 -0
  37. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  38. package/ai-docs/templates/new-service/05-validation.md +145 -0
  39. package/dist/cc.js +379 -50
  40. package/dist/cc.js.map +1 -1
  41. package/dist/config.js +6 -0
  42. package/dist/config.js.map +1 -1
  43. package/dist/constants.js +22 -2
  44. package/dist/constants.js.map +1 -1
  45. package/dist/index.js +27 -5
  46. package/dist/index.js.map +1 -1
  47. package/dist/metrics/behavioral-events.js +114 -0
  48. package/dist/metrics/behavioral-events.js.map +1 -1
  49. package/dist/metrics/constants.js +32 -3
  50. package/dist/metrics/constants.js.map +1 -1
  51. package/dist/services/ApiAiAssistant.js +318 -0
  52. package/dist/services/ApiAiAssistant.js.map +1 -0
  53. package/dist/services/UserPreference.js +427 -0
  54. package/dist/services/UserPreference.js.map +1 -0
  55. package/dist/services/agent/types.js.map +1 -1
  56. package/dist/services/config/Util.js +8 -4
  57. package/dist/services/config/Util.js.map +1 -1
  58. package/dist/services/config/constants.js +35 -2
  59. package/dist/services/config/constants.js.map +1 -1
  60. package/dist/services/config/index.js +41 -2
  61. package/dist/services/config/index.js.map +1 -1
  62. package/dist/services/config/types.js +66 -8
  63. package/dist/services/config/types.js.map +1 -1
  64. package/dist/services/constants.js +27 -1
  65. package/dist/services/constants.js.map +1 -1
  66. package/dist/services/core/Err.js.map +1 -1
  67. package/dist/services/core/Utils.js +122 -25
  68. package/dist/services/core/Utils.js.map +1 -1
  69. package/dist/services/core/aqm-reqs.js +92 -17
  70. package/dist/services/core/aqm-reqs.js.map +1 -1
  71. package/dist/services/core/websocket/WebSocketManager.js +22 -6
  72. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  73. package/dist/services/core/websocket/connection-service.js +3 -1
  74. package/dist/services/core/websocket/connection-service.js.map +1 -1
  75. package/dist/services/core/websocket/types.js.map +1 -1
  76. package/dist/services/index.js +6 -0
  77. package/dist/services/index.js.map +1 -1
  78. package/dist/services/task/Task.js +688 -0
  79. package/dist/services/task/Task.js.map +1 -0
  80. package/dist/services/task/TaskFactory.js +45 -0
  81. package/dist/services/task/TaskFactory.js.map +1 -0
  82. package/dist/services/task/TaskManager.js +751 -457
  83. package/dist/services/task/TaskManager.js.map +1 -1
  84. package/dist/services/task/TaskUtils.js +220 -23
  85. package/dist/services/task/TaskUtils.js.map +1 -1
  86. package/dist/services/task/constants.js +23 -2
  87. package/dist/services/task/constants.js.map +1 -1
  88. package/dist/services/task/dialer.js +129 -0
  89. package/dist/services/task/dialer.js.map +1 -1
  90. package/dist/services/task/digital/Digital.js +77 -0
  91. package/dist/services/task/digital/Digital.js.map +1 -0
  92. package/dist/services/task/state-machine/TaskStateMachine.js +873 -0
  93. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  94. package/dist/services/task/state-machine/actions.js +567 -0
  95. package/dist/services/task/state-machine/actions.js.map +1 -0
  96. package/dist/services/task/state-machine/constants.js +161 -0
  97. package/dist/services/task/state-machine/constants.js.map +1 -0
  98. package/dist/services/task/state-machine/guards.js +382 -0
  99. package/dist/services/task/state-machine/guards.js.map +1 -0
  100. package/dist/services/task/state-machine/index.js +53 -0
  101. package/dist/services/task/state-machine/index.js.map +1 -0
  102. package/dist/services/task/state-machine/types.js +54 -0
  103. package/dist/services/task/state-machine/types.js.map +1 -0
  104. package/dist/services/task/state-machine/uiControlsComputer.js +603 -0
  105. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  106. package/dist/services/task/taskDataNormalizer.js +99 -0
  107. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  108. package/dist/services/task/types.js +227 -4
  109. package/dist/services/task/types.js.map +1 -1
  110. package/dist/services/task/voice/Voice.js +1044 -0
  111. package/dist/services/task/voice/Voice.js.map +1 -0
  112. package/dist/services/task/voice/WebRTC.js +149 -0
  113. package/dist/services/task/voice/WebRTC.js.map +1 -0
  114. package/dist/types/cc.d.ts +894 -0
  115. package/dist/types/config.d.ts +72 -0
  116. package/dist/types/constants.d.ts +66 -0
  117. package/dist/types/index.d.ts +199 -0
  118. package/dist/types/logger-proxy.d.ts +71 -0
  119. package/dist/types/metrics/MetricsManager.d.ts +223 -0
  120. package/dist/types/metrics/behavioral-events.d.ts +29 -0
  121. package/dist/types/metrics/constants.d.ts +181 -0
  122. package/dist/types/services/AddressBook.d.ts +74 -0
  123. package/dist/types/services/ApiAiAssistant.d.ts +49 -0
  124. package/dist/types/services/EntryPoint.d.ts +67 -0
  125. package/dist/types/services/Queue.d.ts +76 -0
  126. package/dist/types/services/UserPreference.d.ts +118 -0
  127. package/dist/types/services/WebCallingService.d.ts +1 -0
  128. package/dist/types/services/agent/index.d.ts +46 -0
  129. package/dist/types/services/agent/types.d.ts +413 -0
  130. package/dist/types/services/config/Util.d.ts +20 -0
  131. package/dist/types/services/config/constants.d.ts +270 -0
  132. package/dist/types/services/config/index.d.ts +177 -0
  133. package/dist/types/services/config/types.d.ts +1368 -0
  134. package/dist/types/services/constants.d.ts +110 -0
  135. package/dist/types/services/core/Err.d.ts +125 -0
  136. package/dist/types/services/core/GlobalTypes.d.ts +58 -0
  137. package/dist/types/services/core/Utils.d.ts +121 -0
  138. package/dist/types/services/core/WebexRequest.d.ts +22 -0
  139. package/dist/types/services/core/aqm-reqs.d.ts +65 -0
  140. package/dist/types/services/core/constants.d.ts +99 -0
  141. package/dist/types/services/core/types.d.ts +47 -0
  142. package/dist/types/services/core/websocket/WebSocketManager.d.ts +36 -0
  143. package/dist/types/services/core/websocket/connection-service.d.ts +27 -0
  144. package/dist/types/services/core/websocket/keepalive.worker.d.ts +2 -0
  145. package/dist/types/services/core/websocket/types.d.ts +37 -0
  146. package/dist/types/services/index.d.ts +54 -0
  147. package/dist/types/services/task/AutoWrapup.d.ts +40 -0
  148. package/dist/types/services/task/Task.d.ts +157 -0
  149. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  150. package/dist/types/services/task/TaskManager.d.ts +1 -0
  151. package/dist/types/services/task/TaskUtils.d.ts +138 -0
  152. package/dist/types/services/task/constants.d.ts +91 -0
  153. package/dist/types/services/task/contact.d.ts +69 -0
  154. package/dist/types/services/task/dialer.d.ts +73 -0
  155. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  156. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1194 -0
  157. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  158. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  159. package/dist/types/services/task/state-machine/guards.d.ts +102 -0
  160. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  161. package/dist/types/services/task/state-machine/types.d.ts +269 -0
  162. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  163. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  164. package/dist/types/services/task/types.d.ts +1856 -0
  165. package/dist/types/services/task/voice/Voice.d.ts +184 -0
  166. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  167. package/dist/types/types.d.ts +778 -0
  168. package/dist/types/utils/PageCache.d.ts +173 -0
  169. package/dist/types/webex-config.d.ts +53 -0
  170. package/dist/types/webex.d.ts +8 -0
  171. package/dist/types.js +130 -1
  172. package/dist/types.js.map +1 -1
  173. package/dist/webex.js +14 -2
  174. package/dist/webex.js.map +1 -1
  175. package/package.json +16 -12
  176. package/src/cc.ts +477 -51
  177. package/src/config.ts +6 -0
  178. package/src/constants.ts +21 -1
  179. package/src/index.ts +24 -5
  180. package/src/metrics/ai-docs/AGENTS.md +350 -0
  181. package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
  182. package/src/metrics/ai-docs/metrics-spec.md +854 -0
  183. package/src/metrics/behavioral-events.ts +120 -0
  184. package/src/metrics/constants.ts +37 -3
  185. package/src/services/ApiAiAssistant.ts +412 -0
  186. package/src/services/UserPreference.ts +509 -0
  187. package/src/services/agent/ai-docs/AGENTS.md +240 -0
  188. package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
  189. package/src/services/agent/ai-docs/agent-spec.md +504 -0
  190. package/src/services/agent/types.ts +1 -1
  191. package/src/services/ai-docs/AGENTS.md +386 -0
  192. package/src/services/ai-docs/services-spec.md +492 -0
  193. package/src/services/config/Util.ts +10 -2
  194. package/src/services/config/ai-docs/AGENTS.md +255 -0
  195. package/src/services/config/ai-docs/ARCHITECTURE.md +426 -0
  196. package/src/services/config/ai-docs/config-spec.md +669 -0
  197. package/src/services/config/constants.ts +37 -1
  198. package/src/services/config/index.ts +45 -1
  199. package/src/services/config/types.ts +241 -11
  200. package/src/services/constants.ts +29 -0
  201. package/src/services/core/Err.ts +3 -0
  202. package/src/services/core/Utils.ts +143 -30
  203. package/src/services/core/ai-docs/AGENTS.md +381 -0
  204. package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
  205. package/src/services/core/ai-docs/core-spec.md +783 -0
  206. package/src/services/core/aqm-reqs.ts +100 -22
  207. package/src/services/core/websocket/WebSocketManager.ts +23 -6
  208. package/src/services/core/websocket/connection-service.ts +5 -1
  209. package/src/services/core/websocket/types.ts +1 -1
  210. package/src/services/index.ts +4 -0
  211. package/src/services/task/Task.ts +837 -0
  212. package/src/services/task/TaskFactory.ts +55 -0
  213. package/src/services/task/TaskManager.ts +793 -521
  214. package/src/services/task/TaskUtils.ts +314 -24
  215. package/src/services/task/ai-docs/AGENTS.md +457 -0
  216. package/src/services/task/ai-docs/ARCHITECTURE.md +594 -0
  217. package/src/services/task/ai-docs/task-spec.md +1319 -0
  218. package/src/services/task/constants.ts +23 -0
  219. package/src/services/task/dialer.ts +136 -1
  220. package/src/services/task/digital/Digital.ts +95 -0
  221. package/src/services/task/state-machine/TaskStateMachine.ts +1166 -0
  222. package/src/services/task/state-machine/actions.ts +738 -0
  223. package/src/services/task/state-machine/ai-docs/AGENTS.md +458 -0
  224. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1137 -0
  225. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2177 -0
  226. package/src/services/task/state-machine/constants.ts +172 -0
  227. package/src/services/task/state-machine/guards.ts +445 -0
  228. package/src/services/task/state-machine/index.ts +28 -0
  229. package/src/services/task/state-machine/types.ts +243 -0
  230. package/src/services/task/state-machine/uiControlsComputer.ts +961 -0
  231. package/src/services/task/taskDataNormalizer.ts +137 -0
  232. package/src/services/task/types.ts +734 -71
  233. package/src/services/task/voice/Voice.ts +1270 -0
  234. package/src/services/task/voice/WebRTC.ts +187 -0
  235. package/src/types.ts +205 -2
  236. package/src/utils/AGENTS.md +278 -0
  237. package/src/utils/ai-docs/utils-spec.md +381 -0
  238. package/src/webex.js +2 -0
  239. package/test/unit/spec/cc.ts +503 -43
  240. package/test/unit/spec/logger-proxy.ts +70 -0
  241. package/test/unit/spec/services/ApiAiAssistant.ts +273 -0
  242. package/test/unit/spec/services/UserPreference.ts +401 -0
  243. package/test/unit/spec/services/WebCallingService.ts +7 -1
  244. package/test/unit/spec/services/config/index.ts +85 -29
  245. package/test/unit/spec/services/core/Utils.ts +481 -2
  246. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +137 -41
  247. package/test/unit/spec/services/core/websocket/connection-service.ts +3 -1
  248. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  249. package/test/unit/spec/services/task/Task.ts +477 -0
  250. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  251. package/test/unit/spec/services/task/TaskManager.ts +1001 -1003
  252. package/test/unit/spec/services/task/TaskUtils.ts +235 -0
  253. package/test/unit/spec/services/task/dialer.ts +372 -96
  254. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  255. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +2651 -0
  256. package/test/unit/spec/services/task/state-machine/guards.ts +637 -0
  257. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  258. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +2663 -0
  259. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  260. package/test/unit/spec/services/task/voice/Voice.ts +649 -0
  261. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  262. package/umd/contact-center.min.js +2 -2
  263. package/umd/contact-center.min.js.map +1 -1
  264. package/dist/services/task/index.js +0 -1525
  265. package/dist/services/task/index.js.map +0 -1
  266. package/src/services/task/index.ts +0 -1801
  267. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -15,7 +15,7 @@ import type {ContactServiceQueuesResponse} from '../../../src/types';
15
15
  import MockWebex from '@webex/test-helper-mock-webex';
16
16
  import {StationLoginSuccess, AGENT_EVENTS} from '../../../src/services/agent/types';
17
17
  import {SetStateResponse} from '../../../src/types';
18
- import {AGENT, WEB_RTC_PREFIX} from '../../../src/services/constants';
18
+ import {AGENT, SUBSCRIBE_API, WEB_RTC_PREFIX} from '../../../src/services/constants';
19
19
  import Services from '../../../src/services';
20
20
  import config from '../../../src/config';
21
21
  import {CC_EVENTS} from '../../../src/services/config/types';
@@ -59,6 +59,7 @@ global.URL.createObjectURL = jest.fn(() => 'blob:http://localhost:3000/12345');
59
59
 
60
60
  describe('webex.cc', () => {
61
61
  let webex;
62
+ let mockApiAIAssistant;
62
63
  let mockContact;
63
64
  let mockTaskManager;
64
65
  let mockMetricsManager;
@@ -88,6 +89,8 @@ describe('webex.cc', () => {
88
89
  initWebSocket: jest.fn(),
89
90
  on: jest.fn(),
90
91
  off: jest.fn(),
92
+ close: jest.fn(),
93
+ isSocketClosed: false,
91
94
  };
92
95
 
93
96
  mockContact = {
@@ -107,6 +110,14 @@ describe('webex.cc', () => {
107
110
  cancelCtq: jest.fn(),
108
111
  };
109
112
 
113
+ mockApiAIAssistant = {
114
+ sendEvent: jest.fn(),
115
+ getRealTimeAssistance: jest.fn(),
116
+ fetchHistoricTranscripts: jest.fn(),
117
+ setAIFeatureFlags: jest.fn(),
118
+ setAgentId: jest.fn(),
119
+ };
120
+
110
121
  // Mock Services instance
111
122
  const mockServicesInstance = {
112
123
  agent: {
@@ -121,6 +132,13 @@ describe('webex.cc', () => {
121
132
  getOutdialAniEntries: jest.fn(),
122
133
  },
123
134
  webSocketManager: mockWebSocketManager,
135
+ rtdWebSocketManager: {
136
+ initWebSocket: jest.fn().mockResolvedValue({}),
137
+ on: jest.fn(),
138
+ off: jest.fn(),
139
+ close: jest.fn(),
140
+ isSocketClosed: false,
141
+ },
124
142
  connectionService: {
125
143
  on: jest.fn(),
126
144
  off: jest.fn(),
@@ -129,16 +147,25 @@ describe('webex.cc', () => {
129
147
 
130
148
  dialer: {
131
149
  startOutdial: jest.fn(),
150
+ acceptPreviewContact: jest.fn(),
151
+ skipPreviewContact: jest.fn(),
152
+ removePreviewContact: jest.fn(),
153
+ },
154
+ apiAIAssistant: {
155
+ sendEvent: jest.fn(),
156
+ fetchHistoricTranscripts: jest.fn(),
132
157
  },
133
158
  };
134
159
 
135
160
  mockTaskManager = {
161
+ apiAIAssistant: mockApiAIAssistant,
136
162
  contact: mockContact,
137
163
  call: undefined,
138
164
  taskCollection: {},
139
165
  webCallingService: undefined,
140
166
  webSocketManager: mockWebSocketManager,
141
167
  task: undefined,
168
+ setConfigFlags: jest.fn(),
142
169
  setWrapupData: jest.fn(),
143
170
  setAgentId: jest.fn(),
144
171
  setWebRtcEnabled: jest.fn(),
@@ -184,6 +211,35 @@ describe('webex.cc', () => {
184
211
  webex.emit('ready');
185
212
  });
186
213
 
214
+ it('should throw when WebRTC registration is disabled without multi-login', () => {
215
+ const invalidWebex = MockWebex({
216
+ children: {
217
+ mercury: Mercury,
218
+ },
219
+ logger: {
220
+ log: jest.fn(),
221
+ error: jest.fn(),
222
+ info: jest.fn(),
223
+ },
224
+ credentials: {
225
+ getOrgId: jest.fn(() => 'mockOrgId'),
226
+ },
227
+ config: {
228
+ ...config,
229
+ cc: {
230
+ ...config.cc,
231
+ allowMultiLogin: false,
232
+ disableWebRTCRegistration: true,
233
+ },
234
+ },
235
+ once: jest.fn((event, callback) => callback()),
236
+ }) as unknown as WebexSDK;
237
+
238
+ expect(() => new ContactCenter({parent: invalidWebex})).toThrow(
239
+ 'Invalid Contact Center configuration: disableWebRTCRegistration cannot be true when allowMultiLogin is false. Enable allowMultiLogin or allow WebRTC registration so an SDK instance can receive Mobius/WebRTC task events.'
240
+ );
241
+ });
242
+
187
243
  describe('cc.getDeviceId', () => {
188
244
  it('should return dialNumber when loginOption is EXTENSION', () => {
189
245
  const loginOption = LoginOption.EXTENSION;
@@ -251,7 +307,7 @@ describe('webex.cc', () => {
251
307
  isAgentAvailableAfterOutdial: false,
252
308
  isCampaignManagementEnabled: false,
253
309
  outDialEp: '',
254
- isEndCallEnabled: false,
310
+ isEndTaskEnabled: false,
255
311
  isEndConsultEnabled: false,
256
312
  agentDbId: '',
257
313
  allowConsultToQueue: false,
@@ -262,6 +318,7 @@ describe('webex.cc', () => {
262
318
  };
263
319
 
264
320
  it('should register successfully and return agent profile', async () => {
321
+ mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: true}} as any;
265
322
  const mercuryConnect = jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
266
323
  const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
267
324
  const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
@@ -283,7 +340,7 @@ describe('webex.cc', () => {
283
340
  const result = await webex.cc.register();
284
341
 
285
342
  // Verify logging calls
286
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting CC SDK registration', {
343
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting CC SDK registration', {
287
344
  module: CC_FILE,
288
345
  method: 'register',
289
346
  });
@@ -305,6 +362,7 @@ describe('webex.cc', () => {
305
362
  clientType: 'WebexCCSDK',
306
363
  allowMultiLogin: false,
307
364
  },
365
+ resource: SUBSCRIBE_API,
308
366
  });
309
367
 
310
368
  // TODO: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-626777 Implement the de-register method and close the listener there
@@ -316,13 +374,37 @@ describe('webex.cc', () => {
316
374
  TASK_EVENTS.TASK_HYDRATE,
317
375
  expect.any(Function)
318
376
  );
377
+ expect(mockTaskManager.on).toHaveBeenCalledWith(
378
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
379
+ expect.any(Function)
380
+ );
319
381
  expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
382
+ expect(webex.cc.services.rtdWebSocketManager.initWebSocket).toHaveBeenCalledWith({
383
+ body: {
384
+ force: true,
385
+ isKeepAliveEnabled: false,
386
+ clientType: 'WebexCCSDK',
387
+ allowMultiLogin: false,
388
+ },
389
+ resource: 'v1/realtime/subscribe',
390
+ });
391
+ expect(webex.cc.services.rtdWebSocketManager.on).toHaveBeenCalledWith(
392
+ 'message',
393
+ expect.any(Function)
394
+ );
320
395
 
321
396
  expect(configSpy).toHaveBeenCalled();
322
397
  expect(LoggerProxy.log).toHaveBeenCalledWith('Agent config is fetched successfully', {
323
398
  module: CC_FILE,
324
399
  method: 'connectWebsocket',
325
400
  });
401
+ expect(mockTaskManager.setConfigFlags).toHaveBeenCalledWith({
402
+ isEndTaskEnabled: mockAgentProfile.isEndTaskEnabled,
403
+ isEndConsultEnabled: mockAgentProfile.isEndConsultEnabled,
404
+ webRtcEnabled: mockAgentProfile.webRtcEnabled,
405
+ autoWrapup: mockAgentProfile.wrapUpData.wrapUpProps.autoWrapup ?? false,
406
+ aiFeature: mockAgentProfile.aiFeature,
407
+ });
326
408
  expect(reloadSpy).toHaveBeenCalled();
327
409
  expect(result).toEqual(mockAgentProfile);
328
410
  expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
@@ -360,6 +442,7 @@ describe('webex.cc', () => {
360
442
  clientType: 'WebexCCSDK',
361
443
  allowMultiLogin: true,
362
444
  },
445
+ resource: SUBSCRIBE_API,
363
446
  });
364
447
  expect(configSpy).toHaveBeenCalled();
365
448
  expect(LoggerProxy.log).toHaveBeenCalledWith('Agent config is fetched successfully', {
@@ -381,7 +464,7 @@ describe('webex.cc', () => {
381
464
 
382
465
  await expect(webex.cc.register()).rejects.toThrow('Error while performing register');
383
466
 
384
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting CC SDK registration', {
467
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting CC SDK registration', {
385
468
  module: CC_FILE,
386
469
  method: 'register',
387
470
  });
@@ -439,6 +522,7 @@ describe('webex.cc', () => {
439
522
  clientType: 'WebexCCSDK',
440
523
  allowMultiLogin: false,
441
524
  },
525
+ resource: SUBSCRIBE_API,
442
526
  });
443
527
 
444
528
  expect(mockTaskManager.on).toHaveBeenCalledWith(
@@ -449,6 +533,10 @@ describe('webex.cc', () => {
449
533
  TASK_EVENTS.TASK_HYDRATE,
450
534
  expect.any(Function)
451
535
  );
536
+ expect(mockTaskManager.on).toHaveBeenCalledWith(
537
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
538
+ expect.any(Function)
539
+ );
452
540
  expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
453
541
 
454
542
  expect(configSpy).toHaveBeenCalled();
@@ -460,8 +548,39 @@ describe('webex.cc', () => {
460
548
  expect(result).toEqual(mockAgentProfile);
461
549
  });
462
550
 
551
+ it('should skip mercury connection when disableWebRTCRegistration is enabled', async () => {
552
+ webex.cc.$config = {
553
+ ...webex.cc.$config,
554
+ allowAutomatedRelogin: false,
555
+ disableWebRTCRegistration: true,
556
+ };
557
+ mockAgentProfile.webRtcEnabled = true;
558
+ const mercurySpy = jest.spyOn(webex.internal.mercury, 'connect');
559
+ const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
560
+ const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
561
+ jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue(mockAgentProfile);
562
+ mockWebSocketManager.initWebSocket.mockResolvedValue({
563
+ agentId: 'agent123',
564
+ });
565
+
566
+ const result = await webex.cc.register();
567
+
568
+ expect(connectWebsocketSpy).toHaveBeenCalled();
569
+ expect(setupEventListenersSpy).toHaveBeenCalled();
570
+ expect(mercurySpy).not.toHaveBeenCalled();
571
+ expect(LoggerProxy.info).toHaveBeenCalledWith(
572
+ 'Skipping Mobius registration because disableWebRTCRegistration is enabled',
573
+ {
574
+ module: CC_FILE,
575
+ method: 'connectWebsocket',
576
+ }
577
+ );
578
+ expect(result).toEqual(mockAgentProfile);
579
+ });
580
+
463
581
  it('should not attempt for mercury connection when webrtc is disabled', async () => {
464
582
  mockAgentProfile.webRtcEnabled = false;
583
+ mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: false}} as any;
465
584
  const mercurySpy = jest.spyOn(webex.internal.mercury, 'connect');
466
585
  const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
467
586
  const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
@@ -491,12 +610,39 @@ describe('webex.cc', () => {
491
610
  clientType: 'WebexCCSDK',
492
611
  allowMultiLogin: false,
493
612
  },
613
+ resource: SUBSCRIBE_API,
494
614
  });
495
615
 
496
616
  expect(configSpy).toHaveBeenCalled();
497
617
  expect(mercurySpy).not.toHaveBeenCalled();
618
+ expect(webex.cc.services.rtdWebSocketManager.initWebSocket).not.toHaveBeenCalled();
498
619
  expect(result).toEqual(mockAgentProfile);
499
620
  });
621
+
622
+ it('should not connect RTD websocket when realtime transcripts feature is disabled', async () => {
623
+ mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: false}} as any;
624
+ jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
625
+ jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
626
+ data: {
627
+ auxCodeId: 'auxCodeId',
628
+ agentId: 'agentId',
629
+ deviceType: LoginOption.EXTENSION,
630
+ dn: '12345',
631
+ },
632
+ });
633
+ jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue(mockAgentProfile);
634
+ mockWebSocketManager.initWebSocket.mockResolvedValue({
635
+ agentId: 'agent123',
636
+ });
637
+
638
+ await webex.cc.register();
639
+
640
+ expect(webex.cc.services.rtdWebSocketManager.initWebSocket).not.toHaveBeenCalled();
641
+ expect(webex.cc.services.rtdWebSocketManager.on).not.toHaveBeenCalledWith(
642
+ 'message',
643
+ expect.any(Function)
644
+ );
645
+ });
500
646
  });
501
647
 
502
648
  describe('stationLogin', () => {
@@ -600,10 +746,8 @@ describe('webex.cc', () => {
600
746
 
601
747
  expect(emitSpy).toHaveBeenCalledTimes(1);
602
748
  expect(emitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_INCOMING, mockTask);
603
- // Verify message event listener
604
- const messageCallback = mockWebSocketManager.on.mock.calls.find(
605
- (call) => call[0] === 'message'
606
- )[1];
749
+ // Verify websocket message handling
750
+ const messageCallback = webex.cc['handleWebsocketMessage'];
607
751
  const agentStateChangeEventData = {
608
752
  type: CC_EVENTS.AGENT_STATE_CHANGE,
609
753
  data: {some: 'data'},
@@ -631,6 +775,59 @@ describe('webex.cc', () => {
631
775
  );
632
776
  });
633
777
 
778
+ it('should skip web calling line registration when disableWebRTCRegistration is enabled', async () => {
779
+ const options = {
780
+ teamId: 'teamId',
781
+ loginOption: LoginOption.BROWSER,
782
+ };
783
+
784
+ webex.cc.$config = {
785
+ ...webex.cc.$config,
786
+ disableWebRTCRegistration: true,
787
+ };
788
+ webex.cc.agentConfig = {
789
+ agentId: 'agentId',
790
+ webRtcEnabled: true,
791
+ loginVoiceOptions: ['BROWSER', 'EXTENSION', 'AGENT_DN'],
792
+ };
793
+
794
+ const registerWebCallingLineSpy = jest.spyOn(
795
+ webex.cc.webCallingService,
796
+ 'registerWebCallingLine'
797
+ );
798
+
799
+ jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
800
+ data: {
801
+ loginOption: LoginOption.BROWSER,
802
+ agentId: 'agentId',
803
+ teamId: 'teamId',
804
+ siteId: 'siteId',
805
+ roles: [AGENT],
806
+ channelsMap: {
807
+ chat: [],
808
+ email: [],
809
+ social: [],
810
+ telephony: [],
811
+ },
812
+ },
813
+ trackingId: 'notifs_52628',
814
+ orgId: 'orgId',
815
+ type: 'StationLoginSuccess',
816
+ eventType: 'STATION_LOGIN',
817
+ } as unknown as StationLoginSuccess);
818
+
819
+ await webex.cc.stationLogin(options);
820
+
821
+ expect(registerWebCallingLineSpy).not.toHaveBeenCalled();
822
+ expect(LoggerProxy.info).toHaveBeenCalledWith(
823
+ 'Skipping web calling line registration because disableWebRTCRegistration is enabled',
824
+ {
825
+ module: CC_FILE,
826
+ method: 'stationLogin',
827
+ }
828
+ );
829
+ });
830
+
634
831
  it('should not attempt mobius registration for LoginOption.BROWSER if webrtc is disabled', async () => {
635
832
  const options = {
636
833
  teamId: 'teamId',
@@ -753,10 +950,13 @@ describe('webex.cc', () => {
753
950
  const result = await webex.cc.stationLogin(options);
754
951
 
755
952
  // Verify logging calls
756
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station login', {
757
- module: CC_FILE,
758
- method: 'stationLogin',
759
- });
953
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
954
+ `Starting agent station login | loginOption: ${options.loginOption} teamId: ${options.teamId}`,
955
+ {
956
+ module: CC_FILE,
957
+ method: 'stationLogin',
958
+ }
959
+ );
760
960
  expect(LoggerProxy.log).toHaveBeenCalledWith(
761
961
  `Agent station login completed successfully agentId: ${mockData.data.agentId} loginOption: ${mockData.data.loginOption} teamId: ${mockData.data.teamId}`,
762
962
  {
@@ -807,10 +1007,13 @@ describe('webex.cc', () => {
807
1007
 
808
1008
  await expect(webex.cc.stationLogin(options)).rejects.toThrow(error.details.data.reason);
809
1009
 
810
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station login', {
811
- module: CC_FILE,
812
- method: 'stationLogin',
813
- });
1010
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
1011
+ `Starting agent station login | loginOption: ${options.loginOption} teamId: ${options.teamId}`,
1012
+ {
1013
+ module: CC_FILE,
1014
+ method: 'stationLogin',
1015
+ }
1016
+ );
814
1017
  expect(LoggerProxy.error).toHaveBeenCalledWith(
815
1018
  `stationLogin failed with reason: ${error.details.data.reason}`,
816
1019
  {module: CC_FILE, method: 'stationLogin', trackingId: error.details.trackingId}
@@ -1149,11 +1352,11 @@ describe('webex.cc', () => {
1149
1352
  const webSocketManagerOnSpy = jest.spyOn(webex.cc.services.webSocketManager, 'on');
1150
1353
  await webex.cc['silentRelogin']();
1151
1354
 
1152
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1355
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1153
1356
  module: CC_FILE,
1154
1357
  method: 'silentRelogin',
1155
1358
  });
1156
- expect(LoggerProxy.info).toHaveBeenCalledWith(
1359
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
1157
1360
  'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
1158
1361
  {module: CC_FILE, method: 'silentRelogin'}
1159
1362
  );
@@ -1198,7 +1401,7 @@ describe('webex.cc', () => {
1198
1401
 
1199
1402
  jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
1200
1403
  await webex.cc['silentRelogin']();
1201
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1404
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1202
1405
  module: CC_FILE,
1203
1406
  method: 'silentRelogin',
1204
1407
  });
@@ -1213,7 +1416,7 @@ describe('webex.cc', () => {
1213
1416
  jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
1214
1417
 
1215
1418
  await expect(webex.cc['silentRelogin']()).rejects.toThrow(error);
1216
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1419
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1217
1420
  module: CC_FILE,
1218
1421
  method: 'silentRelogin',
1219
1422
  });
@@ -1255,7 +1458,7 @@ describe('webex.cc', () => {
1255
1458
 
1256
1459
  await webex.cc['silentRelogin']();
1257
1460
 
1258
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1461
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1259
1462
  module: CC_FILE,
1260
1463
  method: 'silentRelogin',
1261
1464
  });
@@ -1439,6 +1642,27 @@ describe('webex.cc', () => {
1439
1642
  });
1440
1643
  });
1441
1644
 
1645
+ describe('getQueues', () => {
1646
+ it('delegates to the queue service when successful', async () => {
1647
+ const mockQueuesResponse = [{queueId: 'queue1', queueName: 'Queue 1'}];
1648
+ const queueSpy = jest
1649
+ .spyOn(webex.cc.queue, 'getQueues')
1650
+ .mockResolvedValue(mockQueuesResponse as any);
1651
+
1652
+ const result = await webex.cc.getQueues({page: 1});
1653
+
1654
+ expect(queueSpy).toHaveBeenCalledWith({page: 1});
1655
+ expect(result).toBe(mockQueuesResponse);
1656
+ });
1657
+
1658
+ it('propagates queue service errors', async () => {
1659
+ const error = new Error('Test error.');
1660
+ jest.spyOn(webex.cc.queue, 'getQueues').mockRejectedValue(error);
1661
+
1662
+ await expect(webex.cc.getQueues()).rejects.toThrow('Test error.');
1663
+ });
1664
+ });
1665
+
1442
1666
  describe('uploadLogs', () => {
1443
1667
  it('should upload logs successfully', async () => {
1444
1668
  const uploadLogsMock = jest.spyOn(webex.cc.webexRequest, 'uploadLogs').mockResolvedValue({
@@ -1468,6 +1692,7 @@ describe('webex.cc', () => {
1468
1692
 
1469
1693
  describe('unregister', () => {
1470
1694
  let mockWebSocketManager;
1695
+ let mockRTDWebSocketManager;
1471
1696
  let mercuryDisconnectSpy;
1472
1697
  let deviceUnregisterSpy;
1473
1698
 
@@ -1484,8 +1709,15 @@ describe('webex.cc', () => {
1484
1709
  off: jest.fn(),
1485
1710
  on: jest.fn(),
1486
1711
  };
1712
+ mockRTDWebSocketManager = {
1713
+ isSocketClosed: false,
1714
+ close: jest.fn(),
1715
+ off: jest.fn(),
1716
+ on: jest.fn(),
1717
+ };
1487
1718
 
1488
1719
  webex.cc.services.webSocketManager = mockWebSocketManager;
1720
+ webex.cc.services.rtdWebSocketManager = mockRTDWebSocketManager;
1489
1721
 
1490
1722
  webex.internal = webex.internal || {};
1491
1723
  webex.internal.mercury = {
@@ -1502,6 +1734,13 @@ describe('webex.cc', () => {
1502
1734
  });
1503
1735
 
1504
1736
  it('should unregister successfully and clean up all resources when webrtc is enabled', async () => {
1737
+ webex.cc.services.rtdWebSocketManager = {
1738
+ isSocketClosed: false,
1739
+ close: jest.fn(),
1740
+ off: jest.fn(),
1741
+ on: jest.fn(),
1742
+ } as any;
1743
+
1505
1744
  await webex.cc.deregister();
1506
1745
 
1507
1746
  expect(mockTaskManager.off).toHaveBeenCalledWith(
@@ -1512,13 +1751,25 @@ describe('webex.cc', () => {
1512
1751
  TASK_EVENTS.TASK_HYDRATE,
1513
1752
  expect.any(Function)
1514
1753
  );
1754
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1755
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1756
+ expect.any(Function)
1757
+ );
1515
1758
  expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
1759
+ expect(webex.cc.services.rtdWebSocketManager.off).toHaveBeenCalledWith(
1760
+ 'message',
1761
+ expect.any(Function)
1762
+ );
1516
1763
  expect(webex.cc.services.connectionService.off).toHaveBeenCalledWith(
1517
1764
  'connectionLost',
1518
1765
  expect.any(Function)
1519
1766
  );
1520
1767
 
1521
1768
  expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1769
+ expect(webex.cc.services.rtdWebSocketManager.close).toHaveBeenCalledWith(
1770
+ false,
1771
+ 'Unregistering the RTD websocket'
1772
+ );
1522
1773
  expect(webex.cc.agentConfig).toBeNull();
1523
1774
 
1524
1775
  expect(webex.internal.mercury.off).toHaveBeenCalledWith('online');
@@ -1560,6 +1811,13 @@ describe('webex.cc', () => {
1560
1811
  const [, hydrateCallback] = hydrateCalls[0];
1561
1812
  expect(hydrateCallback).toBe(webex.cc['handleTaskHydrate']);
1562
1813
 
1814
+ const multiLoginHydrateCalls = mockTaskManager.off.mock.calls.filter(
1815
+ ([evt]) => evt === TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE
1816
+ );
1817
+ expect(multiLoginHydrateCalls).toHaveLength(1);
1818
+ const [, multiLoginHydrateCallback] = multiLoginHydrateCalls[0];
1819
+ expect(multiLoginHydrateCallback).toBe(webex.cc['handleTaskMultiLoginHydrate']);
1820
+
1563
1821
  const messageCalls = mockWebSocketManager.off.mock.calls.filter(([evt]) => evt === 'message');
1564
1822
  expect(messageCalls).toHaveLength(1);
1565
1823
  const [, messageCallback] = messageCalls[0];
@@ -1585,6 +1843,10 @@ describe('webex.cc', () => {
1585
1843
  TASK_EVENTS.TASK_HYDRATE,
1586
1844
  expect.any(Function)
1587
1845
  );
1846
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1847
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1848
+ expect.any(Function)
1849
+ );
1588
1850
  expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
1589
1851
  expect(webex.cc.services.connectionService.off).toHaveBeenCalledWith(
1590
1852
  'connectionLost',
@@ -1594,6 +1856,7 @@ describe('webex.cc', () => {
1594
1856
  expect(webex.internal.mercury.off).not.toHaveBeenCalled();
1595
1857
  expect(mercuryDisconnectSpy).not.toHaveBeenCalled();
1596
1858
  expect(deviceUnregisterSpy).not.toHaveBeenCalled();
1859
+ expect(mockRTDWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1597
1860
  });
1598
1861
 
1599
1862
  it('should skip internal mercury cleanup when loginVoiceOptions does not include BROWSER', async () => {
@@ -1611,6 +1874,7 @@ describe('webex.cc', () => {
1611
1874
  expect(deviceUnregisterSpy).not.toHaveBeenCalled();
1612
1875
 
1613
1876
  expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1877
+ expect(mockRTDWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1614
1878
  expect(webex.cc.agentConfig).toBeNull();
1615
1879
  });
1616
1880
 
@@ -1628,6 +1892,10 @@ describe('webex.cc', () => {
1628
1892
  TASK_EVENTS.TASK_HYDRATE,
1629
1893
  expect.any(Function)
1630
1894
  );
1895
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1896
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1897
+ expect.any(Function)
1898
+ );
1631
1899
 
1632
1900
  expect(LoggerProxy.error).toHaveBeenCalledWith(`Error during deregister: ${mockError}`, {
1633
1901
  module: CC_FILE,
@@ -1650,7 +1918,7 @@ describe('webex.cc', () => {
1650
1918
 
1651
1919
  beforeEach(() => {
1652
1920
  emitSpy = jest.spyOn(webex.cc, 'emit');
1653
- messageCallback = mockWebSocketManager.on.mock.calls.find((c) => c[0] === 'message')[1];
1921
+ messageCallback = webex.cc['handleWebsocketMessage'];
1654
1922
  });
1655
1923
 
1656
1924
  it('should emit AGENT_STATION_LOGIN_SUCCESS on CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS with mapped payload', () => {
@@ -1850,7 +2118,7 @@ describe('webex.cc', () => {
1850
2118
  );
1851
2119
 
1852
2120
  expect(webex.cc.stationLogout).toHaveBeenCalledWith({
1853
- logoutReason: 'User requested agent device change',
2121
+ logoutReason: 'User requested agent profile update',
1854
2122
  });
1855
2123
  expect(webex.cc.stationLogin).toHaveBeenCalledWith({
1856
2124
  teamId: 'teamId',
@@ -1934,24 +2202,26 @@ describe('webex.cc', () => {
1934
2202
  });
1935
2203
  });
1936
2204
 
1937
- it('should throw with detailed error when loginOption equals current device type', async () => {
1938
- webex.cc.webCallingService.loginOption = LoginOption.BROWSER;
2205
+ it('should allow update when loginOption and teamId are unchanged (e.g. dialNumber or profile refresh)', async () => {
2206
+ webex.cc.webCallingService.loginOption = LoginOption.AGENT_DN;
1939
2207
  const data = {
1940
2208
  teamId: 'teamId',
1941
- loginOption: LoginOption.BROWSER,
1942
- dialNumber: '',
2209
+ loginOption: LoginOption.AGENT_DN,
2210
+ dialNumber: '1234',
1943
2211
  };
1944
- const expectedMessage =
1945
- 'Will not proceed with device update as new Device type is same as current device type and teamId is same as current teamId';
2212
+ const logoutSpy = jest.spyOn(webex.cc, 'stationLogout').mockResolvedValue({});
2213
+ const loginSpy = jest.spyOn(webex.cc, 'stationLogin').mockResolvedValue({
2214
+ type: 'AgentDeviceTypeUpdateSuccess',
2215
+ } as any);
1946
2216
 
1947
- await expect(webex.cc.updateAgentProfile(data)).rejects.toMatchObject({
1948
- message: expectedMessage,
1949
- details: expect.objectContaining({
1950
- data: expect.objectContaining({
1951
- agentId: webex.cc.agentConfig.agentId,
1952
- reason: expectedMessage,
1953
- }),
1954
- }),
2217
+ await expect(webex.cc.updateAgentProfile(data)).resolves.toBeDefined();
2218
+ expect(logoutSpy).toHaveBeenCalledWith({
2219
+ logoutReason: 'User requested agent profile update',
2220
+ });
2221
+ expect(loginSpy).toHaveBeenCalledWith({
2222
+ teamId: data.teamId,
2223
+ loginOption: data.loginOption,
2224
+ dialNumber: data.dialNumber,
1955
2225
  });
1956
2226
  });
1957
2227
 
@@ -2104,7 +2374,9 @@ describe('webex.cc', () => {
2104
2374
  const detailedError = new Error('Detailed service error');
2105
2375
  getErrorDetailsSpy.mockReturnValue({error: detailedError});
2106
2376
 
2107
- await expect(webex.cc.getOutdialAniEntries(mockParams)).rejects.toThrow('Detailed service error');
2377
+ await expect(webex.cc.getOutdialAniEntries(mockParams)).rejects.toThrow(
2378
+ 'Detailed service error'
2379
+ );
2108
2380
 
2109
2381
  // Verify failure metrics are tracked
2110
2382
  expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
@@ -2128,11 +2400,7 @@ describe('webex.cc', () => {
2128
2400
  );
2129
2401
 
2130
2402
  // Verify getErrorDetails was called
2131
- expect(getErrorDetailsSpy).toHaveBeenCalledWith(
2132
- mockError,
2133
- 'getOutdialAniEntries',
2134
- CC_FILE
2135
- );
2403
+ expect(getErrorDetailsSpy).toHaveBeenCalledWith(mockError, 'getOutdialAniEntries', CC_FILE);
2136
2404
  });
2137
2405
 
2138
2406
  it('should throw error when orgId is not found', async () => {
@@ -2180,4 +2448,196 @@ describe('webex.cc', () => {
2180
2448
  );
2181
2449
  });
2182
2450
  });
2451
+
2452
+ describe('acceptPreviewContact', () => {
2453
+ const previewPayload = {
2454
+ interactionId: 'interaction-123',
2455
+ campaignId: 'campaign-456',
2456
+ };
2457
+
2458
+ it('should accept preview contact successfully', async () => {
2459
+ const mockResponse = {trackingId: 'track-123'} as AgentContact;
2460
+
2461
+ const acceptPreviewContactMock = jest
2462
+ .spyOn(webex.cc.services.dialer, 'acceptPreviewContact')
2463
+ .mockResolvedValue(mockResponse);
2464
+
2465
+ const result = await webex.cc.acceptPreviewContact(previewPayload);
2466
+
2467
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Accepting campaign preview contact', {
2468
+ module: CC_FILE,
2469
+ method: 'acceptPreviewContact',
2470
+ });
2471
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
2472
+ 'Campaign preview contact accepted successfully',
2473
+ {
2474
+ module: CC_FILE,
2475
+ method: 'acceptPreviewContact',
2476
+ trackingId: 'track-123',
2477
+ interactionId: previewPayload.interactionId,
2478
+ }
2479
+ );
2480
+
2481
+ expect(acceptPreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
2482
+ expect(result).toEqual(mockResponse);
2483
+ });
2484
+
2485
+ it('should handle error during acceptPreviewContact', async () => {
2486
+ getErrorDetailsSpy.mockRestore();
2487
+ getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
2488
+
2489
+ const error = {
2490
+ details: {
2491
+ trackingId: '1234',
2492
+ data: {
2493
+ reason: 'Error while performing acceptPreviewContact',
2494
+ },
2495
+ },
2496
+ };
2497
+
2498
+ jest.spyOn(webex.cc.services.dialer, 'acceptPreviewContact').mockRejectedValue(error);
2499
+
2500
+ await expect(webex.cc.acceptPreviewContact(previewPayload)).rejects.toThrow(
2501
+ error.details.data.reason
2502
+ );
2503
+
2504
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Accepting campaign preview contact', {
2505
+ module: CC_FILE,
2506
+ method: 'acceptPreviewContact',
2507
+ });
2508
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
2509
+ `acceptPreviewContact failed with reason: ${error.details.data.reason}`,
2510
+ {module: CC_FILE, method: 'acceptPreviewContact', trackingId: error.details.trackingId}
2511
+ );
2512
+ expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'acceptPreviewContact', CC_FILE);
2513
+ });
2514
+ });
2515
+
2516
+ describe('skipPreviewContact', () => {
2517
+ const previewPayload = {
2518
+ interactionId: 'interaction-123',
2519
+ campaignId: 'campaign-456',
2520
+ };
2521
+
2522
+ it('should skip preview contact successfully', async () => {
2523
+ const mockResponse = {trackingId: 'track-123'} as AgentContact;
2524
+
2525
+ const skipPreviewContactMock = jest
2526
+ .spyOn(webex.cc.services.dialer, 'skipPreviewContact')
2527
+ .mockResolvedValue(mockResponse);
2528
+
2529
+ const result = await webex.cc.skipPreviewContact(previewPayload);
2530
+
2531
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Skipping campaign preview contact', {
2532
+ module: CC_FILE,
2533
+ method: 'skipPreviewContact',
2534
+ });
2535
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
2536
+ 'Campaign preview contact skipped successfully',
2537
+ {
2538
+ module: CC_FILE,
2539
+ method: 'skipPreviewContact',
2540
+ trackingId: 'track-123',
2541
+ interactionId: previewPayload.interactionId,
2542
+ }
2543
+ );
2544
+
2545
+ expect(skipPreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
2546
+ expect(result).toEqual(mockResponse);
2547
+ });
2548
+
2549
+ it('should handle error during skipPreviewContact', async () => {
2550
+ getErrorDetailsSpy.mockRestore();
2551
+ getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
2552
+
2553
+ const error = {
2554
+ details: {
2555
+ trackingId: '1234',
2556
+ data: {
2557
+ reason: 'Error while performing skipPreviewContact',
2558
+ },
2559
+ },
2560
+ };
2561
+
2562
+ jest.spyOn(webex.cc.services.dialer, 'skipPreviewContact').mockRejectedValue(error);
2563
+
2564
+ await expect(webex.cc.skipPreviewContact(previewPayload)).rejects.toThrow(
2565
+ error.details.data.reason
2566
+ );
2567
+
2568
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Skipping campaign preview contact', {
2569
+ module: CC_FILE,
2570
+ method: 'skipPreviewContact',
2571
+ });
2572
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
2573
+ `skipPreviewContact failed with reason: ${error.details.data.reason}`,
2574
+ {module: CC_FILE, method: 'skipPreviewContact', trackingId: error.details.trackingId}
2575
+ );
2576
+ expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'skipPreviewContact', CC_FILE);
2577
+ });
2578
+ });
2579
+
2580
+ describe('removePreviewContact', () => {
2581
+ const previewPayload = {
2582
+ interactionId: 'interaction-123',
2583
+ campaignId: 'campaign-456',
2584
+ };
2585
+
2586
+ it('should remove preview contact successfully', async () => {
2587
+ const mockResponse = {trackingId: 'track-123'} as AgentContact;
2588
+
2589
+ const removePreviewContactMock = jest
2590
+ .spyOn(webex.cc.services.dialer, 'removePreviewContact')
2591
+ .mockResolvedValue(mockResponse);
2592
+
2593
+ const result = await webex.cc.removePreviewContact(previewPayload);
2594
+
2595
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Removing campaign preview contact', {
2596
+ module: CC_FILE,
2597
+ method: 'removePreviewContact',
2598
+ });
2599
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
2600
+ 'Campaign preview contact removed successfully',
2601
+ {
2602
+ module: CC_FILE,
2603
+ method: 'removePreviewContact',
2604
+ trackingId: 'track-123',
2605
+ interactionId: previewPayload.interactionId,
2606
+ }
2607
+ );
2608
+
2609
+ expect(removePreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
2610
+ expect(result).toEqual(mockResponse);
2611
+ });
2612
+
2613
+ it('should handle error during removePreviewContact', async () => {
2614
+ getErrorDetailsSpy.mockRestore();
2615
+ getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
2616
+
2617
+ const error = {
2618
+ details: {
2619
+ trackingId: '1234',
2620
+ data: {
2621
+ reason: 'Error while performing removePreviewContact',
2622
+ },
2623
+ },
2624
+ };
2625
+
2626
+ jest.spyOn(webex.cc.services.dialer, 'removePreviewContact').mockRejectedValue(error);
2627
+
2628
+ await expect(webex.cc.removePreviewContact(previewPayload)).rejects.toThrow(
2629
+ error.details.data.reason
2630
+ );
2631
+
2632
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Removing campaign preview contact', {
2633
+ module: CC_FILE,
2634
+ method: 'removePreviewContact',
2635
+ });
2636
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
2637
+ `removePreviewContact failed with reason: ${error.details.data.reason}`,
2638
+ {module: CC_FILE, method: 'removePreviewContact', trackingId: error.details.trackingId}
2639
+ );
2640
+ expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'removePreviewContact', CC_FILE);
2641
+ });
2642
+ });
2183
2643
  });