@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,70 @@
1
+ import LoggerProxy from '../../../src/logger-proxy';
2
+
3
+ describe('LoggerProxy', () => {
4
+ beforeEach(() => {
5
+ // Reset between tests
6
+ (LoggerProxy as any).logger = undefined;
7
+ });
8
+
9
+ it('no-ops when logger is not initialized', () => {
10
+ expect(() => {
11
+ LoggerProxy.log('msg');
12
+ LoggerProxy.info('msg');
13
+ LoggerProxy.warn('msg');
14
+ LoggerProxy.trace('msg');
15
+ LoggerProxy.error('msg');
16
+ }).not.toThrow();
17
+ });
18
+
19
+ it('routes calls to the injected logger with formatted context', () => {
20
+ const logger = {
21
+ log: jest.fn(),
22
+ info: jest.fn(),
23
+ warn: jest.fn(),
24
+ trace: jest.fn(),
25
+ error: jest.fn(),
26
+ };
27
+
28
+ LoggerProxy.initialize(logger as any);
29
+
30
+ LoggerProxy.log('hello', {module: 'm', method: 'fn', interactionId: 'i', trackingId: 't'});
31
+ expect(logger.log).toHaveBeenCalled();
32
+ expect(String(logger.log.mock.calls[0][0])).toContain('module:m - method:fn');
33
+ expect(String(logger.log.mock.calls[0][0])).toContain('interactionId:i');
34
+ expect(String(logger.log.mock.calls[0][0])).toContain('trackingId:t');
35
+
36
+ LoggerProxy.info('hello', {module: 'm', method: 'fn'});
37
+ LoggerProxy.warn('hello', {module: 'm', method: 'fn'});
38
+ LoggerProxy.trace('hello', {module: 'm', method: 'fn'});
39
+ LoggerProxy.error('hello', {module: 'm', method: 'fn'});
40
+ expect(logger.info).toHaveBeenCalled();
41
+ expect(logger.warn).toHaveBeenCalled();
42
+ expect(logger.trace).toHaveBeenCalled();
43
+ expect(logger.error).toHaveBeenCalled();
44
+ });
45
+
46
+ it('handles unserializable data and different error shapes', () => {
47
+ const logger = {
48
+ log: jest.fn(),
49
+ info: jest.fn(),
50
+ warn: jest.fn(),
51
+ trace: jest.fn(),
52
+ error: jest.fn(),
53
+ };
54
+ LoggerProxy.initialize(logger as any);
55
+
56
+ const circular: any = {};
57
+ circular.self = circular;
58
+
59
+ LoggerProxy.info('msg', {module: 'm', method: 'fn', data: circular, error: 'oops'});
60
+ expect(String(logger.info.mock.calls[0][0])).toContain('data:[object]');
61
+ expect(String(logger.info.mock.calls[0][0])).toContain('error:oops');
62
+
63
+ const err = new Error('boom');
64
+ LoggerProxy.error('msg', {module: 'm', method: 'fn', error: err});
65
+ const errorLine = String(logger.error.mock.calls[0][0]);
66
+ expect(errorLine).toContain('error:Error:boom');
67
+ expect(errorLine).toContain('stack:');
68
+ });
69
+ });
70
+
@@ -152,6 +152,24 @@ describe('metrics/behavioral-events', () => {
152
152
  verb: 'fail',
153
153
  });
154
154
 
155
+ expect(
156
+ getEventTaxonomy(METRIC_EVENT_NAMES.TASK_CONFERENCE_PARTICIPANT_DROP_SUCCESS)
157
+ ).toEqual({
158
+ product,
159
+ agent: 'user',
160
+ target: 'task_conference_participant_drop',
161
+ verb: 'complete',
162
+ });
163
+
164
+ expect(
165
+ getEventTaxonomy(METRIC_EVENT_NAMES.TASK_CONFERENCE_PARTICIPANT_DROP_FAILED)
166
+ ).toEqual({
167
+ product,
168
+ agent: 'user',
169
+ target: 'task_conference_participant_drop',
170
+ verb: 'fail',
171
+ });
172
+
155
173
  expect(getEventTaxonomy(METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_SUCCESS)).toEqual({
156
174
  product,
157
175
  agent: 'user',
@@ -99,7 +99,8 @@ describe('AddressBook', () => {
99
99
 
100
100
  expect(mockWebex.request).toHaveBeenCalledWith({
101
101
  service: 'wcc-api-gateway',
102
- resource: '/organization/test-org-id/v2/address-book/test-address-book-id/entry?page=0&pageSize=100',
102
+ resource:
103
+ '/organization/test-org-id/v2/address-book/test-address-book-id/entry?page=0&pageSize=100&sort=name%2CASC',
103
104
  method: HTTP_METHODS.GET,
104
105
  });
105
106
 
@@ -133,7 +134,8 @@ describe('AddressBook', () => {
133
134
  module: 'AddressBook',
134
135
  method: 'getEntries',
135
136
  data: {
136
- resource: '/organization/test-org-id/v2/address-book/test-address-book-id/entry?page=0&pageSize=100',
137
+ resource:
138
+ '/organization/test-org-id/v2/address-book/test-address-book-id/entry?page=0&pageSize=100&sort=name%2CASC',
137
139
  service: 'wcc-api-gateway',
138
140
  },
139
141
  });
@@ -155,7 +157,8 @@ describe('AddressBook', () => {
155
157
 
156
158
  expect(mockWebex.request).toHaveBeenCalledWith({
157
159
  service: 'wcc-api-gateway',
158
- resource: '/organization/test-org-id/v2/address-book/custom-book-id/entry?page=1&pageSize=25&filter=name%3D%3D%22John+Doe%22&attributes=id%2Cname%2Cnumber&search=john',
160
+ resource:
161
+ '/organization/test-org-id/v2/address-book/custom-book-id/entry?page=1&pageSize=25&filter=name%3D%3D%22John+Doe%22&attributes=id%2Cname%2Cnumber&search=john&sort=name%2CASC',
159
162
  method: HTTP_METHODS.GET,
160
163
  });
161
164
 
@@ -175,6 +178,31 @@ describe('AddressBook', () => {
175
178
  );
176
179
  });
177
180
 
181
+ it('should allow callers to override the default backend sorting and bypass the default page cache', async () => {
182
+ (mockWebex.request as jest.Mock).mockResolvedValue(mockResponse);
183
+
184
+ await addressBookAPI.getEntries({
185
+ page: 0,
186
+ pageSize: 25,
187
+ sortBy: 'name',
188
+ sortOrder: 'desc',
189
+ });
190
+ await addressBookAPI.getEntries({
191
+ page: 0,
192
+ pageSize: 25,
193
+ sortBy: 'name',
194
+ sortOrder: 'desc',
195
+ });
196
+
197
+ expect(mockWebex.request).toHaveBeenCalledTimes(2);
198
+ expect(mockWebex.request).toHaveBeenLastCalledWith({
199
+ service: 'wcc-api-gateway',
200
+ resource:
201
+ '/organization/test-org-id/v2/address-book/test-address-book-id/entry?page=0&pageSize=25&sort=name%2CDESC',
202
+ method: HTTP_METHODS.GET,
203
+ });
204
+ });
205
+
178
206
  it('should handle API errors and track metrics', async () => {
179
207
  (mockWebex.request as jest.Mock).mockRejectedValue(new Error('Internal Server Error'));
180
208
 
@@ -182,7 +210,8 @@ describe('AddressBook', () => {
182
210
 
183
211
  expect(mockWebex.request).toHaveBeenCalledWith({
184
212
  service: 'wcc-api-gateway',
185
- resource: '/organization/test-org-id/v2/address-book/test-address-book-id/entry?page=0&pageSize=100',
213
+ resource:
214
+ '/organization/test-org-id/v2/address-book/test-address-book-id/entry?page=0&pageSize=100&sort=name%2CASC',
186
215
  method: HTTP_METHODS.GET,
187
216
  });
188
217
 
@@ -316,14 +345,16 @@ describe('AddressBook', () => {
316
345
  expect(result.meta.page).toBe(1);
317
346
  expect(mockWebex.request).toHaveBeenCalledWith({
318
347
  service: 'wcc-api-gateway',
319
- resource: '/organization/test-org-id/v2/address-book/test-address-book-id/entry?page=1&pageSize=100',
348
+ resource:
349
+ '/organization/test-org-id/v2/address-book/test-address-book-id/entry?page=1&pageSize=100&sort=name%2CASC',
320
350
  method: HTTP_METHODS.GET,
321
351
  });
322
352
  expect(LoggerProxy.info).toHaveBeenCalledWith('Making API request to fetch address book entries', {
323
353
  module: 'AddressBook',
324
354
  method: 'getEntries',
325
355
  data: {
326
- resource: '/organization/test-org-id/v2/address-book/test-address-book-id/entry?page=1&pageSize=100',
356
+ resource:
357
+ '/organization/test-org-id/v2/address-book/test-address-book-id/entry?page=1&pageSize=100&sort=name%2CASC',
327
358
  service: 'wcc-api-gateway',
328
359
  },
329
360
  });
@@ -0,0 +1,223 @@
1
+ import {expect} from '@jest/globals';
2
+ import AnswerCallOnWebexService from '../../../../src/services/AnswerCallOnWebexService';
3
+ import {WebexSDK} from '../../../../src/types';
4
+ import LoggerProxy from '../../../../src/logger-proxy';
5
+
6
+ jest.mock('../../../../src/logger-proxy');
7
+ jest.mock('../../../../src/services/core/WebexRequest', () => ({
8
+ __esModule: true,
9
+ default: {
10
+ getInstance: () => ({uploadLogs: jest.fn()}),
11
+ },
12
+ }));
13
+
14
+ const TELEPHONY_BASE = 'https://api.ciscospark.com';
15
+ const CALLS_URL = `${TELEPHONY_BASE}/telephony/calls`;
16
+
17
+ function makeMockWebex(overrides: Partial<{_serviceUrls: Record<string, string>}> = {}): WebexSDK {
18
+ return {
19
+ request: jest.fn().mockResolvedValue({statusCode: 200, body: {}}),
20
+ internal: {
21
+ services: {
22
+ _serviceUrls: {hydra: TELEPHONY_BASE, ...overrides._serviceUrls},
23
+ get: jest.fn(),
24
+ },
25
+ },
26
+ } as unknown as WebexSDK;
27
+ }
28
+
29
+ describe('AnswerCallOnWebexService', () => {
30
+ let webex: WebexSDK;
31
+ let service: AnswerCallOnWebexService;
32
+
33
+ beforeEach(() => {
34
+ jest.clearAllMocks();
35
+ webex = makeMockWebex();
36
+ service = new AnswerCallOnWebexService(webex);
37
+ });
38
+
39
+ describe('getTelephonyBaseUrl resolution', () => {
40
+ it('uses hydra URL from _serviceUrls when available', async () => {
41
+ await service.answerCall({callId: 'c1', endpointId: 'e1'});
42
+
43
+ expect(webex.request).toHaveBeenCalledWith(
44
+ expect.objectContaining({uri: `${CALLS_URL}/answer`})
45
+ );
46
+ });
47
+
48
+ it('falls back to services.get("hydra") when _serviceUrls.hydra is missing', async () => {
49
+ const hydraBase = 'https://api.ciscospark.com/v1';
50
+ const get = jest.fn().mockReturnValue(hydraBase);
51
+ webex = {
52
+ request: jest.fn().mockResolvedValue({statusCode: 200, body: {}}),
53
+ internal: {
54
+ services: {
55
+ _serviceUrls: {},
56
+ get,
57
+ },
58
+ },
59
+ } as unknown as WebexSDK;
60
+ service = new AnswerCallOnWebexService(webex);
61
+
62
+ await service.answerCall({callId: 'c1', endpointId: 'e1'});
63
+
64
+ expect(get).toHaveBeenCalledWith('hydra');
65
+ expect(webex.request).toHaveBeenCalledWith(
66
+ expect.objectContaining({uri: `${hydraBase}/telephony/calls/answer`})
67
+ );
68
+ });
69
+
70
+ it('throws when hydra URL is unavailable', async () => {
71
+ webex = {
72
+ request: jest.fn(),
73
+ internal: {
74
+ services: {
75
+ _serviceUrls: {},
76
+ get: jest.fn().mockReturnValue(''),
77
+ },
78
+ },
79
+ } as unknown as WebexSDK;
80
+ service = new AnswerCallOnWebexService(webex);
81
+
82
+ await expect(service.answerCall({callId: 'c1', endpointId: 'e1'})).rejects.toBeDefined();
83
+ });
84
+ });
85
+
86
+ describe('answerCall', () => {
87
+ it('POST /answer with callId and endpointId', async () => {
88
+ await service.answerCall({callId: 'call-123', endpointId: 'ep-456'});
89
+
90
+ expect(webex.request).toHaveBeenCalledWith({
91
+ uri: `${CALLS_URL}/answer`,
92
+ method: 'POST',
93
+ body: {callId: 'call-123', endpointId: 'ep-456'},
94
+ addAuthHeader: true,
95
+ });
96
+ });
97
+
98
+ it('includes lineOwnerId when provided', async () => {
99
+ await service.answerCall({callId: 'c1', endpointId: 'e1', lineOwnerId: 'lo-1'});
100
+
101
+ expect(webex.request).toHaveBeenCalledWith(
102
+ expect.objectContaining({body: {callId: 'c1', endpointId: 'e1', lineOwnerId: 'lo-1'}})
103
+ );
104
+ });
105
+
106
+ it('omits lineOwnerId when not provided', async () => {
107
+ await service.answerCall({callId: 'c1', endpointId: 'e1'});
108
+
109
+ const call = (webex.request as jest.Mock).mock.calls[0][0];
110
+ expect(call.body).not.toHaveProperty('lineOwnerId');
111
+ });
112
+
113
+ it('rejects and logs on request failure', async () => {
114
+ const err = {
115
+ statusCode: 400,
116
+ details: {
117
+ trackingId: 'track-wxapp-1',
118
+ data: {reason: 'TELEPHONY_ERROR'},
119
+ },
120
+ };
121
+ (webex.request as jest.Mock).mockRejectedValue(err);
122
+
123
+ await expect(service.answerCall({callId: 'c1', endpointId: 'e1'})).rejects.toMatchObject({
124
+ isWxAppTelephonyError: true,
125
+ message: 'TELEPHONY_ERROR',
126
+ trackingId: 'track-wxapp-1',
127
+ status: 400,
128
+ });
129
+ expect(LoggerProxy.error).toHaveBeenCalled();
130
+ });
131
+ });
132
+
133
+ describe('rejectCall', () => {
134
+ it('POST /reject with callId', async () => {
135
+ await service.rejectCall({callId: 'call-123'});
136
+
137
+ expect(webex.request).toHaveBeenCalledWith({
138
+ uri: `${CALLS_URL}/reject`,
139
+ method: 'POST',
140
+ body: {callId: 'call-123'},
141
+ addAuthHeader: true,
142
+ });
143
+ });
144
+ });
145
+
146
+ describe('muteCall', () => {
147
+ it('POST /mute with callId', async () => {
148
+ await service.muteCall({callId: 'call-123'});
149
+
150
+ expect(webex.request).toHaveBeenCalledWith(
151
+ expect.objectContaining({uri: `${CALLS_URL}/mute`, body: {callId: 'call-123'}})
152
+ );
153
+ });
154
+ });
155
+
156
+ describe('unmuteCall', () => {
157
+ it('POST /unmute with callId', async () => {
158
+ await service.unmuteCall({callId: 'call-123'});
159
+
160
+ expect(webex.request).toHaveBeenCalledWith(
161
+ expect.objectContaining({uri: `${CALLS_URL}/unmute`, body: {callId: 'call-123'}})
162
+ );
163
+ });
164
+ });
165
+
166
+ describe('transmitDtmf', () => {
167
+ it('POST /transmitDtmf with callId and dtmf', async () => {
168
+ await service.transmitDtmf({callId: 'call-123', dtmf: '5'});
169
+
170
+ expect(webex.request).toHaveBeenCalledWith({
171
+ uri: `${CALLS_URL}/transmitDtmf`,
172
+ method: 'POST',
173
+ body: {callId: 'call-123', dtmf: '5'},
174
+ addAuthHeader: true,
175
+ });
176
+ });
177
+
178
+ it('includes lineOwnerId in DTMF request when provided', async () => {
179
+ await service.transmitDtmf({callId: 'c1', dtmf: '9', lineOwnerId: 'lo-1'});
180
+
181
+ expect(webex.request).toHaveBeenCalledWith(
182
+ expect.objectContaining({body: {callId: 'c1', dtmf: '9', lineOwnerId: 'lo-1'}})
183
+ );
184
+ });
185
+ });
186
+
187
+ describe('getCallDetails', () => {
188
+ it('GET /telephony/calls/{callId}', async () => {
189
+ (webex.request as jest.Mock).mockResolvedValue({body: {muted: true}});
190
+
191
+ const result = await service.getCallDetails({callId: 'call-123'});
192
+
193
+ expect(webex.request).toHaveBeenCalledWith({
194
+ uri: `${CALLS_URL}/call-123`,
195
+ method: 'GET',
196
+ addAuthHeader: true,
197
+ });
198
+ expect(result).toEqual({muted: true});
199
+ });
200
+
201
+ it('includes lineOwnerId query param when provided', async () => {
202
+ await service.getCallDetails({callId: 'call-123', lineOwnerId: 'lo-1'});
203
+
204
+ expect(webex.request).toHaveBeenCalledWith(
205
+ expect.objectContaining({uri: `${CALLS_URL}/call-123?lineOwnerId=lo-1`})
206
+ );
207
+ });
208
+
209
+ it('rejects and logs on request failure', async () => {
210
+ (webex.request as jest.Mock).mockRejectedValue(new Error('network fail'));
211
+
212
+ await expect(service.getCallDetails({callId: 'c1'})).rejects.toBeDefined();
213
+ expect(LoggerProxy.error).toHaveBeenCalled();
214
+ });
215
+
216
+ it('rejects without error log for expected call-not-found during mute backfill race', async () => {
217
+ (webex.request as jest.Mock).mockRejectedValue({status: 400, message: 'Call not found'});
218
+
219
+ await expect(service.getCallDetails({callId: 'c1'})).rejects.toBeDefined();
220
+ expect(LoggerProxy.error).not.toHaveBeenCalled();
221
+ });
222
+ });
223
+ });
@@ -0,0 +1,273 @@
1
+ import ApiAIAssistant from '../../../../src/services/ApiAiAssistant';
2
+ import MetricsManager from '../../../../src/metrics/MetricsManager';
3
+ import LoggerProxy from '../../../../src/logger-proxy';
4
+ import WebexRequest from '../../../../src/services/core/WebexRequest';
5
+ import {HTTP_METHODS, RealTimeAssistanceUserActionId, WebexSDK} from '../../../../src/types';
6
+
7
+ jest.mock('../../../../src/metrics/MetricsManager');
8
+ jest.mock('../../../../src/logger-proxy');
9
+
10
+ describe('ApiAIAssistant', () => {
11
+ let apiAIAssistant: ApiAIAssistant;
12
+ let mockWebex: WebexSDK;
13
+ let mockMetricsManager: jest.Mocked<MetricsManager>;
14
+
15
+ beforeEach(() => {
16
+ jest.clearAllMocks();
17
+ jest.spyOn(WebexRequest, 'getInstance').mockReturnValue({
18
+ uploadLogs: jest.fn(),
19
+ } as any);
20
+
21
+ mockWebex = {
22
+ credentials: {
23
+ getOrgId: jest.fn().mockReturnValue('test-org-id'),
24
+ },
25
+ request: jest.fn(),
26
+ internal: {
27
+ services: {
28
+ get: jest.fn().mockReturnValue('https://api.wxcc-us1.cisco.com'),
29
+ },
30
+ newMetrics: {
31
+ submitBehavioralEvent: jest.fn(),
32
+ submitOperationalEvent: jest.fn(),
33
+ submitBusinessEvent: jest.fn(),
34
+ },
35
+ },
36
+ ready: true,
37
+ once: jest.fn(),
38
+ } as unknown as WebexSDK;
39
+
40
+ mockMetricsManager = {
41
+ trackEvent: jest.fn(),
42
+ timeEvent: jest.fn(),
43
+ } as unknown as jest.Mocked<MetricsManager>;
44
+ (MetricsManager.getInstance as jest.Mock).mockReturnValue(mockMetricsManager);
45
+
46
+ apiAIAssistant = new ApiAIAssistant(mockWebex);
47
+ });
48
+
49
+ it('should send transcript start event successfully', async () => {
50
+ (mockWebex.request as jest.Mock).mockResolvedValue({body: {ok: true}});
51
+
52
+ const result = await apiAIAssistant.sendEvent(
53
+ 'test-agent-id',
54
+ 'interaction-1',
55
+ 'CUSTOM_EVENT',
56
+ 'GET_TRANSCRIPTS',
57
+ {action: 'START'}
58
+ );
59
+
60
+ expect(mockWebex.request).toHaveBeenCalledTimes(1);
61
+ const requestArgs = (mockWebex.request as jest.Mock).mock.calls[0][0];
62
+
63
+ expect(requestArgs.uri).toBe('https://api-ai-assistant.produs1.ciscoccservice.com/event');
64
+ expect(requestArgs.method).toBe(HTTP_METHODS.POST);
65
+ expect(requestArgs.addAuthHeader).toBe(true);
66
+ expect(requestArgs.body.agentId).toBe('test-agent-id');
67
+ expect(requestArgs.body.orgId).toBe('test-org-id');
68
+ expect(requestArgs.body.eventType).toBe('CUSTOM_EVENT');
69
+ expect(requestArgs.body.eventName).toBe('GET_TRANSCRIPTS');
70
+ expect(requestArgs.body.eventDetails.data.interactionId).toBe('interaction-1');
71
+ expect(requestArgs.body.eventDetails.data.action).toBe('START');
72
+ expect(result).toEqual({ok: true});
73
+ });
74
+
75
+ it('should fetch historic transcripts with mapped base URL', async () => {
76
+ const responseBody = {interactionId: 'interaction-1', data: []};
77
+ (mockWebex.request as jest.Mock).mockResolvedValue({body: responseBody});
78
+ apiAIAssistant.setAIFeatureFlags({realtimeTranscripts: {enable: true}} as any);
79
+
80
+ const result = await apiAIAssistant.fetchHistoricTranscripts('test-agent-id', 'interaction-1');
81
+
82
+ expect(mockWebex.request).toHaveBeenCalledWith({
83
+ uri: 'https://api-ai-assistant.produs1.ciscoccservice.com/transcripts/list',
84
+ method: HTTP_METHODS.POST,
85
+ addAuthHeader: true,
86
+ body: {
87
+ agentId: 'test-agent-id',
88
+ orgId: 'test-org-id',
89
+ interactionId: 'interaction-1',
90
+ },
91
+ });
92
+ expect(result).toEqual(responseBody as any);
93
+ });
94
+
95
+ it('should request real-time assistance without extra context using sendEvent', async () => {
96
+ const sendEventSpy = jest.spyOn(apiAIAssistant, 'sendEvent').mockResolvedValue({ok: true});
97
+ apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: true}} as any);
98
+
99
+ const result = await apiAIAssistant.getRealTimeAssistance({
100
+ agentId: 'test-agent-id',
101
+ interactionId: 'interaction-1',
102
+ });
103
+
104
+ expect(sendEventSpy).toHaveBeenCalledTimes(1);
105
+ const [agentId, interactionId, eventType, eventName, metadata, languageCode, trackingId] =
106
+ sendEventSpy.mock.calls[0];
107
+
108
+ expect(agentId).toBe('test-agent-id');
109
+ expect(interactionId).toBe('interaction-1');
110
+ expect(eventType).toBe('CUSTOM_EVENT');
111
+ expect(eventName).toBe('GET_SUGGESTIONS');
112
+ expect(metadata).toBeUndefined();
113
+ expect(languageCode).toBe('en');
114
+ expect(typeof trackingId).toBe('string');
115
+ expect(trackingId.startsWith('WX_CC_SDK_')).toBe(true);
116
+ expect(result).toEqual({ok: true});
117
+ });
118
+
119
+ it('should request real-time assistance with extra context using sendEvent', async () => {
120
+ const sendEventSpy = jest.spyOn(apiAIAssistant, 'sendEvent').mockResolvedValue({ok: true});
121
+ apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: true}} as any);
122
+
123
+ const result = await apiAIAssistant.getRealTimeAssistance({
124
+ agentId: 'test-agent-id',
125
+ interactionId: 'interaction-1',
126
+ context: 'Need assistance with credit card payment due date',
127
+ });
128
+
129
+ expect(sendEventSpy).toHaveBeenCalledTimes(1);
130
+ const [agentId, interactionId, eventType, eventName, metadata, languageCode, trackingId] =
131
+ sendEventSpy.mock.calls[0];
132
+
133
+ expect(agentId).toBe('test-agent-id');
134
+ expect(interactionId).toBe('interaction-1');
135
+ expect(eventType).toBe('CUSTOM_EVENT');
136
+ expect(eventName).toBe('ADD_SUGGESTIONS_EXTRA_CONTEXT');
137
+ expect(metadata).toEqual({context: 'Need assistance with credit card payment due date'});
138
+ expect(languageCode).toBe('en');
139
+ expect(typeof trackingId).toBe('string');
140
+ expect(trackingId.startsWith('WX_CC_SDK_')).toBe(true);
141
+ expect(result).toEqual({ok: true});
142
+ });
143
+
144
+ it('should treat whitespace-only context as GET_SUGGESTIONS', async () => {
145
+ const sendEventSpy = jest.spyOn(apiAIAssistant, 'sendEvent').mockResolvedValue({ok: true});
146
+ apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: true}} as any);
147
+
148
+ const result = await apiAIAssistant.getRealTimeAssistance({
149
+ agentId: 'test-agent-id',
150
+ interactionId: 'interaction-1',
151
+ context: ' ',
152
+ });
153
+
154
+ expect(sendEventSpy).toHaveBeenCalledTimes(1);
155
+ const [agentId, interactionId, eventType, eventName, metadata, languageCode, trackingId] =
156
+ sendEventSpy.mock.calls[0];
157
+
158
+ expect(agentId).toBe('test-agent-id');
159
+ expect(interactionId).toBe('interaction-1');
160
+ expect(eventType).toBe('CUSTOM_EVENT');
161
+ expect(eventName).toBe('GET_SUGGESTIONS');
162
+ expect(metadata).toEqual({context: ''});
163
+ expect(languageCode).toBe('en');
164
+ expect(typeof trackingId).toBe('string');
165
+ expect(trackingId.startsWith('WX_CC_SDK_')).toBe(true);
166
+ expect(result).toEqual({ok: true});
167
+ });
168
+
169
+ [
170
+ RealTimeAssistanceUserActionId.LIKE,
171
+ RealTimeAssistanceUserActionId.DISLIKE,
172
+ RealTimeAssistanceUserActionId.COPY,
173
+ ].forEach((actionId) => {
174
+ it(`should send real-time assistance user action for ${actionId}`, async () => {
175
+ (mockWebex.request as jest.Mock).mockResolvedValue({body: {ok: true}});
176
+ apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: true}} as any);
177
+
178
+ const result = await apiAIAssistant.sendRealTimeAssistanceUserAction({
179
+ agentId: 'test-agent-id',
180
+ interactionId: 'interaction-1',
181
+ adaptiveCardId: 'adaptive-card-1',
182
+ actionId,
183
+ });
184
+
185
+ expect(mockWebex.request).toHaveBeenCalledTimes(1);
186
+ const requestArgs = (mockWebex.request as jest.Mock).mock.calls[0][0];
187
+ const eventData = requestArgs.body.eventDetails.data;
188
+
189
+ expect(requestArgs.uri).toBe('https://api-ai-assistant.produs1.ciscoccservice.com/event');
190
+ expect(requestArgs.method).toBe(HTTP_METHODS.POST);
191
+ expect(requestArgs.addAuthHeader).toBe(true);
192
+ expect(requestArgs.body.agentId).toBe('test-agent-id');
193
+ expect(requestArgs.body.orgId).toBe('test-org-id');
194
+ expect(requestArgs.body.eventType).toBe('CUSTOM_EVENT');
195
+ expect(requestArgs.body.eventName).toBe('SUGGESTED_RESPONSES_USER_ACTION');
196
+ expect(eventData.interactionId).toBe('interaction-1');
197
+ expect(eventData.adaptiveCardId).toBe('adaptive-card-1');
198
+ expect(eventData.userAction.actionType).toBe('Action.Submit');
199
+ expect(eventData.userAction.actionId).toBe(actionId);
200
+ expect(eventData.languageCode).toBe('en');
201
+ expect(typeof eventData.actionTimeStamp).toBe('string');
202
+ expect(eventData.trackingId.startsWith('WX_CC_SDK_')).toBe(true);
203
+ expect(result).toEqual({ok: true});
204
+ });
205
+ });
206
+
207
+ it('should fail when base URL mapping is not available', async () => {
208
+ (mockWebex.internal.services.get as jest.Mock).mockReturnValue('https://unknown-host.invalid');
209
+
210
+ let failed = false;
211
+ try {
212
+ await apiAIAssistant.sendEvent(
213
+ 'test-agent-id',
214
+ 'interaction-1',
215
+ 'CUSTOM_EVENT',
216
+ 'GET_TRANSCRIPTS',
217
+ {action: 'STOP'}
218
+ );
219
+ } catch (_error) {
220
+ failed = true;
221
+ }
222
+
223
+ expect(failed).toBe(true);
224
+ expect(LoggerProxy.error).toHaveBeenCalled();
225
+ });
226
+
227
+ it('should fail when realtime transcripts feature is disabled', async () => {
228
+ apiAIAssistant.setAIFeatureFlags({realtimeTranscripts: {enable: false}} as any);
229
+ let errorMessage = '';
230
+
231
+ try {
232
+ await apiAIAssistant.fetchHistoricTranscripts('test-agent-id', 'interaction-1');
233
+ } catch (error) {
234
+ errorMessage = (error as Error)?.message || '';
235
+ }
236
+
237
+ expect(errorMessage).toBe('Error while performing fetchHistoricTranscripts');
238
+ });
239
+
240
+ it('should fail when real-time assistance feature is disabled', async () => {
241
+ apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: false}} as any);
242
+ let errorMessage = '';
243
+
244
+ try {
245
+ await apiAIAssistant.getRealTimeAssistance({
246
+ agentId: 'test-agent-id',
247
+ interactionId: 'interaction-1',
248
+ });
249
+ } catch (error) {
250
+ errorMessage = (error as Error)?.message || '';
251
+ }
252
+
253
+ expect(errorMessage).toBe('Error while performing getRealTimeAssistance');
254
+ });
255
+
256
+ it('should fail to send real-time assistance user action when feature is disabled', async () => {
257
+ apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: false}} as any);
258
+ let errorMessage = '';
259
+
260
+ try {
261
+ await apiAIAssistant.sendRealTimeAssistanceUserAction({
262
+ agentId: 'test-agent-id',
263
+ interactionId: 'interaction-1',
264
+ adaptiveCardId: 'adaptive-card-1',
265
+ actionId: RealTimeAssistanceUserActionId.LIKE,
266
+ });
267
+ } catch (error) {
268
+ errorMessage = (error as Error)?.message || '';
269
+ }
270
+
271
+ expect(errorMessage).toBe('Error while performing sendRealTimeAssistanceUserAction');
272
+ });
273
+ });