@webex/contact-center 3.11.0 → 3.12.0-auth-prejoin-fetch.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 (325) hide show
  1. package/.sdd/manifest.json +883 -0
  2. package/AGENTS.md +94 -0
  3. package/ai-docs/ARCHITECTURE.md +168 -0
  4. package/ai-docs/CONTRACTS.md +50 -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 +359 -0
  17. package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -0
  18. package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
  19. package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
  20. package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -0
  21. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  22. package/ai-docs/patterns/testing-patterns.md +480 -0
  23. package/ai-docs/patterns/typescript-patterns.md +365 -0
  24. package/ai-docs/templates/README.md +102 -0
  25. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  26. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  27. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  28. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  29. package/ai-docs/templates/new-method/00-master.md +80 -0
  30. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  31. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  32. package/ai-docs/templates/new-method/03-tests.md +201 -0
  33. package/ai-docs/templates/new-method/04-validation.md +141 -0
  34. package/ai-docs/templates/new-service/00-master.md +109 -0
  35. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  36. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  37. package/ai-docs/templates/new-service/03-integration.md +178 -0
  38. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  39. package/ai-docs/templates/new-service/05-validation.md +145 -0
  40. package/dist/cc.js +818 -59
  41. package/dist/cc.js.map +1 -1
  42. package/dist/config.js +13 -0
  43. package/dist/config.js.map +1 -1
  44. package/dist/constants.js +31 -3
  45. package/dist/constants.js.map +1 -1
  46. package/dist/index.js +27 -5
  47. package/dist/index.js.map +1 -1
  48. package/dist/metrics/behavioral-events.js +127 -0
  49. package/dist/metrics/behavioral-events.js.map +1 -1
  50. package/dist/metrics/constants.js +34 -3
  51. package/dist/metrics/constants.js.map +1 -1
  52. package/dist/services/AddressBook.js +18 -15
  53. package/dist/services/AddressBook.js.map +1 -1
  54. package/dist/services/AnswerCallOnWebexService.js +174 -0
  55. package/dist/services/AnswerCallOnWebexService.js.map +1 -0
  56. package/dist/services/ApiAiAssistant.js +318 -0
  57. package/dist/services/ApiAiAssistant.js.map +1 -0
  58. package/dist/services/EntryPoint.js +46 -68
  59. package/dist/services/EntryPoint.js.map +1 -1
  60. package/dist/services/Queue.js +27 -22
  61. package/dist/services/Queue.js.map +1 -1
  62. package/dist/services/UserPreference.js +427 -0
  63. package/dist/services/UserPreference.js.map +1 -0
  64. package/dist/services/WebexCrossClientService.js +171 -0
  65. package/dist/services/WebexCrossClientService.js.map +1 -0
  66. package/dist/services/WxAppTelephonyMercurySync.js +93 -0
  67. package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
  68. package/dist/services/agent/types.js.map +1 -1
  69. package/dist/services/config/Util.js +11 -4
  70. package/dist/services/config/Util.js.map +1 -1
  71. package/dist/services/config/constants.js +45 -8
  72. package/dist/services/config/constants.js.map +1 -1
  73. package/dist/services/config/index.js +41 -2
  74. package/dist/services/config/index.js.map +1 -1
  75. package/dist/services/config/types.js +70 -8
  76. package/dist/services/config/types.js.map +1 -1
  77. package/dist/services/constants.js +27 -1
  78. package/dist/services/constants.js.map +1 -1
  79. package/dist/services/core/Err.js.map +1 -1
  80. package/dist/services/core/Utils.js +122 -25
  81. package/dist/services/core/Utils.js.map +1 -1
  82. package/dist/services/core/WebexRequest.js +6 -2
  83. package/dist/services/core/WebexRequest.js.map +1 -1
  84. package/dist/services/core/aqm-reqs.js +119 -30
  85. package/dist/services/core/aqm-reqs.js.map +1 -1
  86. package/dist/services/core/types.js.map +1 -1
  87. package/dist/services/core/websocket/WebSocketManager.js +22 -6
  88. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  89. package/dist/services/core/websocket/connection-service.js +3 -1
  90. package/dist/services/core/websocket/connection-service.js.map +1 -1
  91. package/dist/services/core/websocket/types.js.map +1 -1
  92. package/dist/services/index.js +6 -0
  93. package/dist/services/index.js.map +1 -1
  94. package/dist/services/task/Task.js +754 -0
  95. package/dist/services/task/Task.js.map +1 -0
  96. package/dist/services/task/TaskFactory.js +49 -0
  97. package/dist/services/task/TaskFactory.js.map +1 -0
  98. package/dist/services/task/TaskManager.js +1073 -447
  99. package/dist/services/task/TaskManager.js.map +1 -1
  100. package/dist/services/task/TaskUtils.js +220 -23
  101. package/dist/services/task/TaskUtils.js.map +1 -1
  102. package/dist/services/task/WebexCallingUtils.js +70 -0
  103. package/dist/services/task/WebexCallingUtils.js.map +1 -0
  104. package/dist/services/task/constants.js +26 -2
  105. package/dist/services/task/constants.js.map +1 -1
  106. package/dist/services/task/contact.js +29 -0
  107. package/dist/services/task/contact.js.map +1 -1
  108. package/dist/services/task/dialer.js +129 -0
  109. package/dist/services/task/dialer.js.map +1 -1
  110. package/dist/services/task/digital/Digital.js +78 -0
  111. package/dist/services/task/digital/Digital.js.map +1 -0
  112. package/dist/services/task/state-machine/TaskStateMachine.js +971 -0
  113. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  114. package/dist/services/task/state-machine/actions.js +572 -0
  115. package/dist/services/task/state-machine/actions.js.map +1 -0
  116. package/dist/services/task/state-machine/constants.js +161 -0
  117. package/dist/services/task/state-machine/constants.js.map +1 -0
  118. package/dist/services/task/state-machine/guards.js +409 -0
  119. package/dist/services/task/state-machine/guards.js.map +1 -0
  120. package/dist/services/task/state-machine/index.js +53 -0
  121. package/dist/services/task/state-machine/index.js.map +1 -0
  122. package/dist/services/task/state-machine/types.js +54 -0
  123. package/dist/services/task/state-machine/types.js.map +1 -0
  124. package/dist/services/task/state-machine/uiControlsComputer.js +703 -0
  125. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  126. package/dist/services/task/taskDataNormalizer.js +99 -0
  127. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  128. package/dist/services/task/types.js +243 -5
  129. package/dist/services/task/types.js.map +1 -1
  130. package/dist/services/task/voice/Voice.js +1380 -0
  131. package/dist/services/task/voice/Voice.js.map +1 -0
  132. package/dist/services/task/voice/WebRTC.js +152 -0
  133. package/dist/services/task/voice/WebRTC.js.map +1 -0
  134. package/dist/services/task/voice/wxAppVoiceMethods.js +201 -0
  135. package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
  136. package/dist/services/wxAppTelephonyUtils.js +19 -0
  137. package/dist/services/wxAppTelephonyUtils.js.map +1 -0
  138. package/dist/types/cc.d.ts +967 -0
  139. package/dist/types/config.d.ts +79 -0
  140. package/dist/types/constants.d.ts +74 -0
  141. package/dist/types/index.d.ts +201 -0
  142. package/dist/types/logger-proxy.d.ts +71 -0
  143. package/dist/types/metrics/MetricsManager.d.ts +223 -0
  144. package/dist/types/metrics/behavioral-events.d.ts +29 -0
  145. package/dist/types/metrics/constants.d.ts +183 -0
  146. package/dist/types/services/AddressBook.d.ts +75 -0
  147. package/dist/types/services/AnswerCallOnWebexService.d.ts +37 -0
  148. package/dist/types/services/ApiAiAssistant.d.ts +49 -0
  149. package/dist/types/services/EntryPoint.d.ts +69 -0
  150. package/dist/types/services/Queue.d.ts +78 -0
  151. package/dist/types/services/UserPreference.d.ts +118 -0
  152. package/dist/types/services/WebCallingService.d.ts +1 -0
  153. package/dist/types/services/WebexCrossClientService.d.ts +28 -0
  154. package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
  155. package/dist/types/services/agent/index.d.ts +46 -0
  156. package/dist/types/services/agent/types.d.ts +413 -0
  157. package/dist/types/services/config/Util.d.ts +20 -0
  158. package/dist/types/services/config/constants.d.ts +273 -0
  159. package/dist/types/services/config/index.d.ts +177 -0
  160. package/dist/types/services/config/types.d.ts +1381 -0
  161. package/dist/types/services/constants.d.ts +110 -0
  162. package/dist/types/services/core/Err.d.ts +127 -0
  163. package/dist/types/services/core/GlobalTypes.d.ts +58 -0
  164. package/dist/types/services/core/Utils.d.ts +121 -0
  165. package/dist/types/services/core/WebexRequest.d.ts +23 -0
  166. package/dist/types/services/core/aqm-reqs.d.ts +65 -0
  167. package/dist/types/services/core/constants.d.ts +99 -0
  168. package/dist/types/services/core/types.d.ts +49 -0
  169. package/dist/types/services/core/websocket/WebSocketManager.d.ts +36 -0
  170. package/dist/types/services/core/websocket/connection-service.d.ts +27 -0
  171. package/dist/types/services/core/websocket/keepalive.worker.d.ts +2 -0
  172. package/dist/types/services/core/websocket/types.d.ts +37 -0
  173. package/dist/types/services/index.d.ts +54 -0
  174. package/dist/types/services/task/AutoWrapup.d.ts +40 -0
  175. package/dist/types/services/task/Task.d.ts +175 -0
  176. package/dist/types/services/task/TaskFactory.d.ts +13 -0
  177. package/dist/types/services/task/TaskManager.d.ts +1 -0
  178. package/dist/types/services/task/TaskUtils.d.ts +138 -0
  179. package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
  180. package/dist/types/services/task/constants.d.ts +94 -0
  181. package/dist/types/services/task/contact.d.ts +73 -0
  182. package/dist/types/services/task/dialer.d.ts +73 -0
  183. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  184. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1398 -0
  185. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  186. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  187. package/dist/types/services/task/state-machine/guards.d.ts +103 -0
  188. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  189. package/dist/types/services/task/state-machine/types.d.ts +277 -0
  190. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  191. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  192. package/dist/types/services/task/types.d.ts +1933 -0
  193. package/dist/types/services/task/voice/Voice.d.ts +223 -0
  194. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  195. package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +52 -0
  196. package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
  197. package/dist/types/types.d.ts +784 -0
  198. package/dist/types/utils/PageCache.d.ts +190 -0
  199. package/dist/types/webex-config.d.ts +53 -0
  200. package/dist/types/webex.d.ts +8 -0
  201. package/dist/types.js +137 -3
  202. package/dist/types.js.map +1 -1
  203. package/dist/utils/PageCache.js +19 -5
  204. package/dist/utils/PageCache.js.map +1 -1
  205. package/dist/webex.js +14 -2
  206. package/dist/webex.js.map +1 -1
  207. package/package.json +16 -12
  208. package/src/cc.ts +983 -60
  209. package/src/config.ts +13 -0
  210. package/src/constants.ts +29 -1
  211. package/src/index.ts +26 -5
  212. package/src/metrics/ai-docs/AGENTS.md +350 -0
  213. package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
  214. package/src/metrics/ai-docs/metrics-spec.md +860 -0
  215. package/src/metrics/behavioral-events.ts +134 -0
  216. package/src/metrics/constants.ts +39 -3
  217. package/src/services/AddressBook.ts +17 -6
  218. package/src/services/AnswerCallOnWebexService.ts +206 -0
  219. package/src/services/ApiAiAssistant.ts +412 -0
  220. package/src/services/EntryPoint.ts +59 -60
  221. package/src/services/Queue.ts +29 -12
  222. package/src/services/UserPreference.ts +509 -0
  223. package/src/services/WebexCrossClientService.ts +212 -0
  224. package/src/services/WxAppTelephonyMercurySync.ts +115 -0
  225. package/src/services/agent/ai-docs/AGENTS.md +240 -0
  226. package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
  227. package/src/services/agent/ai-docs/agent-spec.md +504 -0
  228. package/src/services/agent/types.ts +1 -1
  229. package/src/services/ai-docs/AGENTS.md +386 -0
  230. package/src/services/ai-docs/services-spec.md +497 -0
  231. package/src/services/config/Util.ts +13 -2
  232. package/src/services/config/ai-docs/AGENTS.md +255 -0
  233. package/src/services/config/ai-docs/ARCHITECTURE.md +426 -0
  234. package/src/services/config/ai-docs/config-spec.md +675 -0
  235. package/src/services/config/constants.ts +47 -7
  236. package/src/services/config/index.ts +45 -1
  237. package/src/services/config/types.ts +253 -11
  238. package/src/services/constants.ts +29 -0
  239. package/src/services/core/Err.ts +4 -0
  240. package/src/services/core/Utils.ts +143 -30
  241. package/src/services/core/WebexRequest.ts +3 -1
  242. package/src/services/core/ai-docs/AGENTS.md +381 -0
  243. package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
  244. package/src/services/core/ai-docs/core-spec.md +787 -0
  245. package/src/services/core/aqm-reqs.ts +125 -32
  246. package/src/services/core/types.ts +2 -0
  247. package/src/services/core/websocket/WebSocketManager.ts +23 -6
  248. package/src/services/core/websocket/connection-service.ts +5 -1
  249. package/src/services/core/websocket/types.ts +1 -1
  250. package/src/services/index.ts +4 -0
  251. package/src/services/task/Task.ts +908 -0
  252. package/src/services/task/TaskFactory.ts +60 -0
  253. package/src/services/task/TaskManager.ts +1291 -513
  254. package/src/services/task/TaskUtils.ts +314 -24
  255. package/src/services/task/WebexCallingUtils.ts +136 -0
  256. package/src/services/task/ai-docs/AGENTS.md +457 -0
  257. package/src/services/task/ai-docs/ARCHITECTURE.md +595 -0
  258. package/src/services/task/ai-docs/task-spec.md +1469 -0
  259. package/src/services/task/constants.ts +26 -0
  260. package/src/services/task/contact.ts +30 -0
  261. package/src/services/task/dialer.ts +136 -1
  262. package/src/services/task/digital/Digital.ts +97 -0
  263. package/src/services/task/state-machine/TaskStateMachine.ts +1313 -0
  264. package/src/services/task/state-machine/actions.ts +741 -0
  265. package/src/services/task/state-machine/ai-docs/AGENTS.md +462 -0
  266. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1146 -0
  267. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2209 -0
  268. package/src/services/task/state-machine/constants.ts +172 -0
  269. package/src/services/task/state-machine/guards.ts +498 -0
  270. package/src/services/task/state-machine/index.ts +28 -0
  271. package/src/services/task/state-machine/types.ts +258 -0
  272. package/src/services/task/state-machine/uiControlsComputer.ts +1135 -0
  273. package/src/services/task/taskDataNormalizer.ts +137 -0
  274. package/src/services/task/types.ts +843 -70
  275. package/src/services/task/voice/Voice.ts +1720 -0
  276. package/src/services/task/voice/WebRTC.ts +191 -0
  277. package/src/services/task/voice/wxAppVoiceMethods.ts +307 -0
  278. package/src/services/wxAppTelephonyUtils.ts +14 -0
  279. package/src/types.ts +238 -11
  280. package/src/utils/AGENTS.md +289 -0
  281. package/src/utils/PageCache.ts +38 -5
  282. package/src/utils/ai-docs/utils-spec.md +391 -0
  283. package/src/webex.js +2 -0
  284. package/test/unit/spec/cc.ts +1856 -122
  285. package/test/unit/spec/logger-proxy.ts +70 -0
  286. package/test/unit/spec/metrics/behavioral-events.ts +18 -0
  287. package/test/unit/spec/services/AddressBook.ts +37 -6
  288. package/test/unit/spec/services/AnswerCallOnWebexService.ts +223 -0
  289. package/test/unit/spec/services/ApiAiAssistant.ts +273 -0
  290. package/test/unit/spec/services/EntryPoint.ts +87 -40
  291. package/test/unit/spec/services/Queue.ts +123 -12
  292. package/test/unit/spec/services/UserPreference.ts +401 -0
  293. package/test/unit/spec/services/WebCallingService.ts +7 -1
  294. package/test/unit/spec/services/WebexCrossClientService.ts +261 -0
  295. package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +113 -0
  296. package/test/unit/spec/services/config/Util.ts +85 -0
  297. package/test/unit/spec/services/config/index.ts +85 -29
  298. package/test/unit/spec/services/core/Utils.ts +481 -2
  299. package/test/unit/spec/services/core/WebexRequest.ts +3 -1
  300. package/test/unit/spec/services/core/aqm-reqs.ts +113 -1
  301. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +137 -41
  302. package/test/unit/spec/services/core/websocket/connection-service.ts +3 -1
  303. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  304. package/test/unit/spec/services/task/Task.ts +677 -0
  305. package/test/unit/spec/services/task/TaskFactory.ts +99 -0
  306. package/test/unit/spec/services/task/TaskManager.ts +2209 -918
  307. package/test/unit/spec/services/task/TaskUtils.ts +235 -0
  308. package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
  309. package/test/unit/spec/services/task/contact.ts +33 -0
  310. package/test/unit/spec/services/task/dialer.ts +372 -96
  311. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  312. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +3433 -0
  313. package/test/unit/spec/services/task/state-machine/guards.ts +839 -0
  314. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  315. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +3101 -0
  316. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  317. package/test/unit/spec/services/task/voice/Voice.ts +1523 -0
  318. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  319. package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +459 -0
  320. package/umd/contact-center.min.js +2 -2
  321. package/umd/contact-center.min.js.map +1 -1
  322. package/dist/services/task/index.js +0 -1525
  323. package/dist/services/task/index.js.map +0 -1
  324. package/src/services/task/index.ts +0 -1801
  325. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -0,0 +1,401 @@
1
+ import {expect} from '@jest/globals';
2
+ import UserPreference from '../../../../src/services/UserPreference';
3
+ import {HTTP_METHODS, WebexSDK, IHttpResponse} from '../../../../src/types';
4
+ import {METRIC_EVENT_NAMES} from '../../../../src/metrics/constants';
5
+ import WebexRequest from '../../../../src/services/core/WebexRequest';
6
+ import MetricsManager from '../../../../src/metrics/MetricsManager';
7
+ import LoggerProxy from '../../../../src/logger-proxy';
8
+
9
+ jest.mock('../../../../src/metrics/MetricsManager');
10
+ jest.mock('../../../../src/logger-proxy');
11
+
12
+ describe('UserPreference', () => {
13
+ let userPreferenceAPI: UserPreference;
14
+ let mockWebex: WebexSDK;
15
+ let mockMetricsManager: jest.Mocked<MetricsManager>;
16
+
17
+ const mockGetUserId = jest.fn().mockReturnValue('test-user-id');
18
+
19
+ beforeEach(() => {
20
+ jest.clearAllMocks();
21
+ (WebexRequest as any).instance = undefined;
22
+
23
+ mockWebex = {
24
+ credentials: {
25
+ getOrgId: jest.fn().mockReturnValue('test-org-id'),
26
+ },
27
+ request: jest.fn(),
28
+ internal: {
29
+ newMetrics: {
30
+ submitBehavioralEvent: jest.fn(),
31
+ submitOperationalEvent: jest.fn(),
32
+ submitBusinessEvent: jest.fn(),
33
+ },
34
+ },
35
+ ready: true,
36
+ once: jest.fn(),
37
+ } as unknown as WebexSDK;
38
+
39
+ mockMetricsManager = {
40
+ trackEvent: jest.fn(),
41
+ timeEvent: jest.fn(),
42
+ } as unknown as jest.Mocked<MetricsManager>;
43
+ (MetricsManager.getInstance as jest.Mock).mockReturnValue(mockMetricsManager);
44
+
45
+ userPreferenceAPI = new UserPreference(mockWebex, mockGetUserId);
46
+ });
47
+
48
+ describe('constructor', () => {
49
+ it('should initialize with all required dependencies', () => {
50
+ expect(WebexRequest.getInstance({webex: mockWebex})).toBeDefined();
51
+ expect(MetricsManager.getInstance).toHaveBeenCalledWith({webex: mockWebex});
52
+ });
53
+ });
54
+
55
+ describe('getUserPreference', () => {
56
+ const mockPreference = {
57
+ id: 'pref-123',
58
+ organizationId: 'test-org-id',
59
+ userId: 'test-user-id',
60
+ preferences: {e911Reminder: true},
61
+ createdTime: 1234567890,
62
+ lastUpdatedTime: 1234567890,
63
+ };
64
+
65
+ const mockResponse: IHttpResponse = {
66
+ statusCode: 200,
67
+ method: 'GET',
68
+ url: '/organization/test-org-id/user-preference/test-user-id',
69
+ headers: {} as any,
70
+ body: mockPreference,
71
+ };
72
+
73
+ beforeEach(() => {
74
+ jest.spyOn(Date, 'now').mockReturnValue(1000);
75
+ });
76
+
77
+ afterEach(() => {
78
+ jest.restoreAllMocks();
79
+ });
80
+
81
+ it('should fetch user preferences successfully with default userId', async () => {
82
+ (mockWebex.request as jest.Mock).mockResolvedValue(mockResponse);
83
+
84
+ const result = await userPreferenceAPI.getUserPreference();
85
+
86
+ expect(mockWebex.request).toHaveBeenCalledWith({
87
+ service: 'wcc-api-gateway',
88
+ resource: 'organization/test-org-id/user-preference/test-user-id',
89
+ method: HTTP_METHODS.GET,
90
+ body: undefined,
91
+ });
92
+ expect(result).toEqual(mockPreference);
93
+ expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith(
94
+ METRIC_EVENT_NAMES.USER_PREFERENCE_GET_SUCCESS
95
+ );
96
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
97
+ METRIC_EVENT_NAMES.USER_PREFERENCE_GET_SUCCESS,
98
+ expect.objectContaining({
99
+ orgId: 'test-org-id',
100
+ userId: 'test-user-id',
101
+ statusCode: 200,
102
+ }),
103
+ ['behavioral', 'operational']
104
+ );
105
+ });
106
+
107
+ it('should fetch user preferences for a specific userId', async () => {
108
+ (mockWebex.request as jest.Mock).mockResolvedValue(mockResponse);
109
+
110
+ const result = await userPreferenceAPI.getUserPreference({userId: 'specific-user-id'});
111
+
112
+ expect(mockWebex.request).toHaveBeenCalledWith({
113
+ service: 'wcc-api-gateway',
114
+ resource: 'organization/test-org-id/user-preference/specific-user-id',
115
+ method: HTTP_METHODS.GET,
116
+ body: undefined,
117
+ });
118
+ expect(result).toEqual(mockPreference);
119
+ });
120
+
121
+ it('should fetch user preferences with pagination parameters', async () => {
122
+ (mockWebex.request as jest.Mock).mockResolvedValue(mockResponse);
123
+
124
+ const result = await userPreferenceAPI.getUserPreference({page: 1, pageSize: 50});
125
+
126
+ expect(mockWebex.request).toHaveBeenCalledWith({
127
+ service: 'wcc-api-gateway',
128
+ resource: 'organization/test-org-id/user-preference/test-user-id?page=1&pageSize=50',
129
+ method: HTTP_METHODS.GET,
130
+ body: undefined,
131
+ });
132
+ expect(result).toEqual(mockPreference);
133
+ });
134
+
135
+ it('should throw error when userId is not available', async () => {
136
+ mockGetUserId.mockReturnValueOnce('');
137
+
138
+ await expect(userPreferenceAPI.getUserPreference()).rejects.toThrow(
139
+ 'UserPreference: userId is not available.'
140
+ );
141
+
142
+ expect(mockMetricsManager.trackEvent).not.toHaveBeenCalled();
143
+ });
144
+
145
+ it('should handle API errors', async () => {
146
+ const mockError = new Error('API call failed');
147
+ (mockWebex.request as jest.Mock).mockRejectedValue(mockError);
148
+
149
+ await expect(userPreferenceAPI.getUserPreference()).rejects.toThrow('API call failed');
150
+
151
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
152
+ METRIC_EVENT_NAMES.USER_PREFERENCE_GET_FAILED,
153
+ expect.objectContaining({
154
+ error: 'API call failed',
155
+ }),
156
+ ['behavioral', 'operational']
157
+ );
158
+ });
159
+ });
160
+
161
+ describe('createUserPreference', () => {
162
+ const mockCreateRequest = {
163
+ userId: 'test-user-id',
164
+ desktopPreference: '{}',
165
+ };
166
+
167
+ const mockCreatedPreference = {
168
+ id: 'pref-123',
169
+ organizationId: 'test-org-id',
170
+ userId: 'test-user-id',
171
+ preferences: {e911Reminder: true},
172
+ createdTime: 1234567890,
173
+ lastUpdatedTime: 1234567890,
174
+ };
175
+
176
+ const mockResponse: IHttpResponse = {
177
+ statusCode: 201,
178
+ method: 'POST',
179
+ url: '/organization/test-org-id/user-preference',
180
+ headers: {} as any,
181
+ body: mockCreatedPreference,
182
+ };
183
+
184
+ beforeEach(() => {
185
+ jest.spyOn(Date, 'now').mockReturnValue(1000);
186
+ });
187
+
188
+ afterEach(() => {
189
+ jest.restoreAllMocks();
190
+ });
191
+
192
+ it('should create user preferences successfully', async () => {
193
+ (mockWebex.request as jest.Mock).mockResolvedValue(mockResponse);
194
+
195
+ const result = await userPreferenceAPI.createUserPreference(mockCreateRequest);
196
+
197
+ expect(mockWebex.request).toHaveBeenCalledWith({
198
+ service: 'wcc-api-gateway',
199
+ resource: 'organization/test-org-id/user-preference',
200
+ method: HTTP_METHODS.POST,
201
+ body: mockCreateRequest,
202
+ });
203
+ expect(result).toEqual(mockCreatedPreference);
204
+ expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith(
205
+ METRIC_EVENT_NAMES.USER_PREFERENCE_CREATE_SUCCESS
206
+ );
207
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
208
+ METRIC_EVENT_NAMES.USER_PREFERENCE_CREATE_SUCCESS,
209
+ expect.objectContaining({
210
+ orgId: 'test-org-id',
211
+ userId: 'test-user-id',
212
+ statusCode: 201,
213
+ }),
214
+ ['behavioral', 'operational']
215
+ );
216
+ });
217
+
218
+ it('should throw error when userId is missing in request', async () => {
219
+ const invalidRequest = {
220
+ userId: '',
221
+ desktopPreference: '{}',
222
+ };
223
+
224
+ await expect(userPreferenceAPI.createUserPreference(invalidRequest)).rejects.toThrow(
225
+ 'UserPreference: userId is required to create user preferences.'
226
+ );
227
+
228
+ expect(mockMetricsManager.trackEvent).not.toHaveBeenCalled();
229
+ });
230
+
231
+ it('should handle API errors', async () => {
232
+ const mockError = new Error('API call failed');
233
+ (mockWebex.request as jest.Mock).mockRejectedValue(mockError);
234
+
235
+ await expect(userPreferenceAPI.createUserPreference(mockCreateRequest)).rejects.toThrow(
236
+ 'API call failed'
237
+ );
238
+
239
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
240
+ METRIC_EVENT_NAMES.USER_PREFERENCE_CREATE_FAILED,
241
+ expect.objectContaining({
242
+ error: 'API call failed',
243
+ }),
244
+ ['behavioral', 'operational']
245
+ );
246
+ });
247
+ });
248
+
249
+ describe('updateUserPreference', () => {
250
+ const mockUpdateRequest = {
251
+ desktopPreference: '{}',
252
+ };
253
+
254
+ const mockUpdatedPreference = {
255
+ id: 'pref-123',
256
+ organizationId: 'test-org-id',
257
+ userId: 'test-user-id',
258
+ preferences: {e911Reminder: false},
259
+ createdTime: 1234567890,
260
+ lastUpdatedTime: 1234567899,
261
+ };
262
+
263
+ const mockResponse: IHttpResponse = {
264
+ statusCode: 200,
265
+ method: 'PUT',
266
+ url: '/organization/test-org-id/user-preference/test-user-id',
267
+ headers: {} as any,
268
+ body: mockUpdatedPreference,
269
+ };
270
+
271
+ beforeEach(() => {
272
+ jest.spyOn(Date, 'now').mockReturnValue(1000);
273
+ });
274
+
275
+ afterEach(() => {
276
+ jest.restoreAllMocks();
277
+ });
278
+
279
+ it('should update user preferences successfully', async () => {
280
+ (mockWebex.request as jest.Mock).mockResolvedValue(mockResponse);
281
+
282
+ const result = await userPreferenceAPI.updateUserPreference(
283
+ 'test-user-id',
284
+ mockUpdateRequest
285
+ );
286
+
287
+ expect(mockWebex.request).toHaveBeenCalledWith({
288
+ service: 'wcc-api-gateway',
289
+ resource: 'organization/test-org-id/user-preference/test-user-id',
290
+ method: HTTP_METHODS.PUT,
291
+ body: mockUpdateRequest,
292
+ });
293
+ expect(result).toEqual(mockUpdatedPreference);
294
+ expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith(
295
+ METRIC_EVENT_NAMES.USER_PREFERENCE_UPDATE_SUCCESS
296
+ );
297
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
298
+ METRIC_EVENT_NAMES.USER_PREFERENCE_UPDATE_SUCCESS,
299
+ expect.objectContaining({
300
+ orgId: 'test-org-id',
301
+ userId: 'test-user-id',
302
+ statusCode: 200,
303
+ }),
304
+ ['behavioral', 'operational']
305
+ );
306
+ });
307
+
308
+ it('should throw error when userId is missing', async () => {
309
+ await expect(userPreferenceAPI.updateUserPreference('', mockUpdateRequest)).rejects.toThrow(
310
+ 'UserPreference: userId is required to update user preferences.'
311
+ );
312
+
313
+ expect(mockMetricsManager.trackEvent).not.toHaveBeenCalled();
314
+ });
315
+
316
+ it('should handle API errors', async () => {
317
+ const mockError = new Error('API call failed');
318
+ (mockWebex.request as jest.Mock).mockRejectedValue(mockError);
319
+
320
+ await expect(
321
+ userPreferenceAPI.updateUserPreference('test-user-id', mockUpdateRequest)
322
+ ).rejects.toThrow('API call failed');
323
+
324
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
325
+ METRIC_EVENT_NAMES.USER_PREFERENCE_UPDATE_FAILED,
326
+ expect.objectContaining({
327
+ error: 'API call failed',
328
+ }),
329
+ ['behavioral', 'operational']
330
+ );
331
+ });
332
+ });
333
+
334
+ describe('deleteUserPreference', () => {
335
+ const mockResponse: IHttpResponse = {
336
+ statusCode: 204,
337
+ method: 'DELETE',
338
+ url: '/organization/test-org-id/user-preference/test-user-id',
339
+ headers: {} as any,
340
+ body: null,
341
+ };
342
+
343
+ beforeEach(() => {
344
+ jest.spyOn(Date, 'now').mockReturnValue(1000);
345
+ });
346
+
347
+ afterEach(() => {
348
+ jest.restoreAllMocks();
349
+ });
350
+
351
+ it('should delete user preferences successfully', async () => {
352
+ (mockWebex.request as jest.Mock).mockResolvedValue(mockResponse);
353
+
354
+ await userPreferenceAPI.deleteUserPreference('test-user-id');
355
+
356
+ expect(mockWebex.request).toHaveBeenCalledWith({
357
+ service: 'wcc-api-gateway',
358
+ resource: 'organization/test-org-id/user-preference/test-user-id',
359
+ method: HTTP_METHODS.DELETE,
360
+ body: undefined,
361
+ });
362
+ expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith(
363
+ METRIC_EVENT_NAMES.USER_PREFERENCE_DELETE_SUCCESS
364
+ );
365
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
366
+ METRIC_EVENT_NAMES.USER_PREFERENCE_DELETE_SUCCESS,
367
+ expect.objectContaining({
368
+ orgId: 'test-org-id',
369
+ userId: 'test-user-id',
370
+ statusCode: 204,
371
+ }),
372
+ ['behavioral', 'operational']
373
+ );
374
+ });
375
+
376
+ it('should throw error when userId is missing', async () => {
377
+ await expect(userPreferenceAPI.deleteUserPreference('')).rejects.toThrow(
378
+ 'UserPreference: userId is required to delete user preferences.'
379
+ );
380
+
381
+ expect(mockMetricsManager.trackEvent).not.toHaveBeenCalled();
382
+ });
383
+
384
+ it('should handle API errors', async () => {
385
+ const mockError = new Error('API call failed');
386
+ (mockWebex.request as jest.Mock).mockRejectedValue(mockError);
387
+
388
+ await expect(userPreferenceAPI.deleteUserPreference('test-user-id')).rejects.toThrow(
389
+ 'API call failed'
390
+ );
391
+
392
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
393
+ METRIC_EVENT_NAMES.USER_PREFERENCE_DELETE_FAILED,
394
+ expect.objectContaining({
395
+ error: 'API call failed',
396
+ }),
397
+ ['behavioral', 'operational']
398
+ );
399
+ });
400
+ });
401
+ });
@@ -200,11 +200,17 @@ describe('WebCallingService', () => {
200
200
 
201
201
  it('should reject if registration times out', async () => {
202
202
  line = callingClient.getLines().line1 as ILine;
203
+ jest.spyOn(global, 'setTimeout').mockImplementation(((handler: TimerHandler) => {
204
+ if (typeof handler === 'function') {
205
+ handler();
206
+ }
207
+ return 0 as unknown as NodeJS.Timeout;
208
+ }) as typeof setTimeout);
203
209
 
204
210
  const promise = webRTCCalling.registerWebCallingLine();
205
211
 
206
212
  await expect(promise).rejects.toThrow('WebCallingService Registration timed out');
207
- }, 20003); // Increased timeout to 20 seconds
213
+ });
208
214
 
209
215
  it('should handle incoming calls', async () => {
210
216
  line = callingClient.getLines().line1 as ILine;
@@ -0,0 +1,261 @@
1
+ import WebexCrossClientService from '../../../../src/services/WebexCrossClientService';
2
+
3
+ describe('WebexCrossClientService', () => {
4
+ const userId = 'user-123';
5
+ const deviceUrl = 'https://wdm.example.com/devices/device-abc';
6
+
7
+ let webex: {request: jest.Mock; internal: {device: {userId: string; url: string}}};
8
+ let service: WebexCrossClientService;
9
+
10
+ beforeEach(() => {
11
+ webex = {
12
+ request: jest.fn().mockResolvedValue({body: {}}),
13
+ internal: {
14
+ device: {userId, url: deviceUrl},
15
+ },
16
+ };
17
+ service = new WebexCrossClientService(webex as never);
18
+ });
19
+
20
+ afterEach(() => {
21
+ service.teardown();
22
+ });
23
+
24
+ it('publishes usersub with appName wxcc (Agent Desktop parity)', async () => {
25
+ await service.setManageWebexCallingInWxcc(true);
26
+
27
+ expect(webex.request).toHaveBeenCalledWith(
28
+ expect.objectContaining({
29
+ method: 'POST',
30
+ service: 'usersub',
31
+ resource: 'publish',
32
+ body: expect.objectContaining({
33
+ users: [userId],
34
+ compositions: [
35
+ expect.objectContaining({
36
+ type: 'cross-client-state',
37
+ ttl: 900,
38
+ composition: {
39
+ devices: [
40
+ {
41
+ deviceId: 'device-abc',
42
+ appName: 'wxcc',
43
+ state: {'answer-calls-on-wxcc': true},
44
+ },
45
+ ],
46
+ },
47
+ }),
48
+ ],
49
+ }),
50
+ })
51
+ );
52
+ });
53
+
54
+ it('publishes false when suppression is disabled', async () => {
55
+ await service.setManageWebexCallingInWxcc(false);
56
+
57
+ expect(webex.request).toHaveBeenCalledWith(
58
+ expect.objectContaining({
59
+ body: expect.objectContaining({
60
+ compositions: [
61
+ expect.objectContaining({
62
+ composition: {
63
+ devices: [
64
+ {
65
+ deviceId: 'device-abc',
66
+ appName: 'wxcc',
67
+ state: {'answer-calls-on-wxcc': false},
68
+ },
69
+ ],
70
+ },
71
+ }),
72
+ ],
73
+ }),
74
+ })
75
+ );
76
+ });
77
+
78
+ it('throws when user ID is unavailable', async () => {
79
+ webex.internal.device.userId = undefined as unknown as string;
80
+
81
+ await expect(service.setManageWebexCallingInWxcc(true)).rejects.toThrow(
82
+ 'User ID is unavailable for cross-client publish'
83
+ );
84
+ expect(webex.request).not.toHaveBeenCalled();
85
+ });
86
+
87
+ it('throws when device URL is unavailable', async () => {
88
+ webex.internal.device.url = undefined as unknown as string;
89
+
90
+ await expect(service.setManageWebexCallingInWxcc(true)).rejects.toThrow(
91
+ 'Device URL is unavailable for cross-client publish'
92
+ );
93
+ expect(webex.request).not.toHaveBeenCalled();
94
+ });
95
+
96
+ it('rejects when usersub publish fails', async () => {
97
+ const err = new Error('usersub failed');
98
+ webex.request = jest.fn().mockRejectedValue(err);
99
+
100
+ await expect(service.setManageWebexCallingInWxcc(true)).rejects.toThrow('usersub failed');
101
+ });
102
+
103
+ it('uses custom ttl from options', async () => {
104
+ await service.setManageWebexCallingInWxcc(true, {ttl: 1200});
105
+
106
+ expect(webex.request).toHaveBeenCalledWith(
107
+ expect.objectContaining({
108
+ body: expect.objectContaining({
109
+ compositions: [expect.objectContaining({ttl: 1200})],
110
+ }),
111
+ })
112
+ );
113
+ });
114
+
115
+ it('refreshes publish before ttl expiry while enabled', async () => {
116
+ jest.useFakeTimers();
117
+ try {
118
+ await service.setManageWebexCallingInWxcc(true, {ttl: 900});
119
+ expect(webex.request).toHaveBeenCalledTimes(1);
120
+
121
+ jest.advanceTimersByTime(840_000);
122
+
123
+ await Promise.resolve();
124
+
125
+ expect(webex.request).toHaveBeenCalledTimes(2);
126
+ expect(webex.request).toHaveBeenLastCalledWith(
127
+ expect.objectContaining({
128
+ body: expect.objectContaining({
129
+ compositions: [
130
+ expect.objectContaining({
131
+ composition: {
132
+ devices: [
133
+ {
134
+ deviceId: 'device-abc',
135
+ appName: 'wxcc',
136
+ state: {'answer-calls-on-wxcc': true},
137
+ },
138
+ ],
139
+ },
140
+ }),
141
+ ],
142
+ }),
143
+ })
144
+ );
145
+ } finally {
146
+ jest.useRealTimers();
147
+ }
148
+ });
149
+
150
+ it('retries failed refresh publish before ttl expiry', async () => {
151
+ jest.useFakeTimers();
152
+ try {
153
+ const setManageSpy = jest.spyOn(service, 'setManageWebexCallingInWxcc');
154
+ webex.request = jest
155
+ .fn()
156
+ .mockResolvedValueOnce({body: {}})
157
+ .mockRejectedValueOnce(new Error('refresh failed'))
158
+ .mockResolvedValueOnce({body: {}});
159
+
160
+ await service.setManageWebexCallingInWxcc(true, {ttl: 900});
161
+ expect(webex.request).toHaveBeenCalledTimes(1);
162
+
163
+ jest.advanceTimersByTime(840_000);
164
+ await expect(setManageSpy.mock.results[1].value).rejects.toThrow('refresh failed');
165
+ expect(webex.request).toHaveBeenCalledTimes(2);
166
+
167
+ jest.advanceTimersByTime(30_000);
168
+ await setManageSpy.mock.results[2].value;
169
+
170
+ expect(webex.request).toHaveBeenCalledTimes(3);
171
+ } finally {
172
+ jest.useRealTimers();
173
+ }
174
+ });
175
+
176
+ it('schedules bounded retry when scheduled refresh publish fails', async () => {
177
+ jest.useFakeTimers();
178
+ try {
179
+ const setManageSpy = jest.spyOn(service, 'setManageWebexCallingInWxcc');
180
+ webex.request = jest
181
+ .fn()
182
+ .mockResolvedValueOnce({body: {}})
183
+ .mockRejectedValueOnce(new Error('refresh failed'));
184
+
185
+ await service.setManageWebexCallingInWxcc(true, {ttl: 900});
186
+ const scheduleRefreshRetrySpy = jest.spyOn(service as any, 'scheduleRefreshRetry');
187
+
188
+ jest.advanceTimersByTime(840_000);
189
+ await expect(setManageSpy.mock.results[1].value).rejects.toThrow('refresh failed');
190
+ expect(scheduleRefreshRetrySpy).toHaveBeenCalledWith(userId, 900, 0, 0);
191
+ } finally {
192
+ jest.useRealTimers();
193
+ }
194
+ });
195
+
196
+ it('does not retry refresh after teardown bumps generation', async () => {
197
+ jest.useFakeTimers();
198
+ try {
199
+ const setManageSpy = jest.spyOn(service, 'setManageWebexCallingInWxcc');
200
+ webex.request = jest
201
+ .fn()
202
+ .mockResolvedValueOnce({body: {}})
203
+ .mockRejectedValueOnce(new Error('refresh failed'));
204
+
205
+ await service.setManageWebexCallingInWxcc(true, {ttl: 900});
206
+
207
+ jest.advanceTimersByTime(840_000);
208
+ await expect(setManageSpy.mock.results[1].value).rejects.toThrow('refresh failed');
209
+
210
+ service.teardown();
211
+
212
+ jest.advanceTimersByTime(30_000);
213
+ await Promise.resolve();
214
+
215
+ expect(webex.request).toHaveBeenCalledTimes(2);
216
+ } finally {
217
+ jest.useRealTimers();
218
+ }
219
+ });
220
+
221
+ it('teardown clears refresh timer so publish is not repeated', async () => {
222
+ jest.useFakeTimers();
223
+ try {
224
+ await service.setManageWebexCallingInWxcc(true, {ttl: 900});
225
+ service.teardown();
226
+
227
+ jest.advanceTimersByTime(840_000);
228
+ await Promise.resolve();
229
+
230
+ expect(webex.request).toHaveBeenCalledTimes(1);
231
+ } finally {
232
+ jest.useRealTimers();
233
+ }
234
+ });
235
+
236
+ it('does not schedule refresh when disabled', async () => {
237
+ jest.useFakeTimers();
238
+ try {
239
+ await service.setManageWebexCallingInWxcc(false);
240
+
241
+ jest.advanceTimersByTime(840_000);
242
+ await Promise.resolve();
243
+
244
+ expect(webex.request).toHaveBeenCalledTimes(1);
245
+ } finally {
246
+ jest.useRealTimers();
247
+ }
248
+ });
249
+
250
+ it('reports whether answer-calls-on-wxcc true was published', async () => {
251
+ expect(service.isAnswerCallsStateActive()).toBe(false);
252
+
253
+ await service.setManageWebexCallingInWxcc(true);
254
+
255
+ expect(service.isAnswerCallsStateActive()).toBe(true);
256
+
257
+ await service.setManageWebexCallingInWxcc(false);
258
+
259
+ expect(service.isAnswerCallsStateActive()).toBe(false);
260
+ });
261
+ });