@webex/contact-center 3.12.0-next.8 → 3.12.0-next.81

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 (205) hide show
  1. package/AGENTS.md +438 -0
  2. package/ai-docs/README.md +131 -0
  3. package/ai-docs/RULES.md +455 -0
  4. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  5. package/ai-docs/patterns/testing-patterns.md +480 -0
  6. package/ai-docs/patterns/typescript-patterns.md +365 -0
  7. package/ai-docs/templates/README.md +102 -0
  8. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  9. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  10. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  11. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  12. package/ai-docs/templates/new-method/00-master.md +80 -0
  13. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  14. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  15. package/ai-docs/templates/new-method/03-tests.md +201 -0
  16. package/ai-docs/templates/new-method/04-validation.md +141 -0
  17. package/ai-docs/templates/new-service/00-master.md +109 -0
  18. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  19. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  20. package/ai-docs/templates/new-service/03-integration.md +178 -0
  21. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  22. package/ai-docs/templates/new-service/05-validation.md +145 -0
  23. package/dist/cc.js +265 -29
  24. package/dist/cc.js.map +1 -1
  25. package/dist/config.js +6 -0
  26. package/dist/config.js.map +1 -1
  27. package/dist/constants.js +16 -1
  28. package/dist/constants.js.map +1 -1
  29. package/dist/index.js +20 -5
  30. package/dist/index.js.map +1 -1
  31. package/dist/metrics/behavioral-events.js +101 -0
  32. package/dist/metrics/behavioral-events.js.map +1 -1
  33. package/dist/metrics/constants.js +23 -4
  34. package/dist/metrics/constants.js.map +1 -1
  35. package/dist/services/ApiAiAssistant.js +74 -3
  36. package/dist/services/ApiAiAssistant.js.map +1 -1
  37. package/dist/services/UserPreference.js +427 -0
  38. package/dist/services/UserPreference.js.map +1 -0
  39. package/dist/services/config/Util.js +3 -3
  40. package/dist/services/config/Util.js.map +1 -1
  41. package/dist/services/config/constants.js +23 -2
  42. package/dist/services/config/constants.js.map +1 -1
  43. package/dist/services/config/types.js +49 -9
  44. package/dist/services/config/types.js.map +1 -1
  45. package/dist/services/core/Err.js.map +1 -1
  46. package/dist/services/core/Utils.js +107 -32
  47. package/dist/services/core/Utils.js.map +1 -1
  48. package/dist/services/core/websocket/WebSocketManager.js +2 -1
  49. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  50. package/dist/services/core/websocket/types.js.map +1 -1
  51. package/dist/services/index.js +1 -1
  52. package/dist/services/index.js.map +1 -1
  53. package/dist/services/task/Task.js +688 -0
  54. package/dist/services/task/Task.js.map +1 -0
  55. package/dist/services/task/TaskFactory.js +45 -0
  56. package/dist/services/task/TaskFactory.js.map +1 -0
  57. package/dist/services/task/TaskManager.js +725 -526
  58. package/dist/services/task/TaskManager.js.map +1 -1
  59. package/dist/services/task/TaskUtils.js +162 -26
  60. package/dist/services/task/TaskUtils.js.map +1 -1
  61. package/dist/services/task/constants.js +9 -2
  62. package/dist/services/task/constants.js.map +1 -1
  63. package/dist/services/task/dialer.js +78 -0
  64. package/dist/services/task/dialer.js.map +1 -1
  65. package/dist/services/task/digital/Digital.js +77 -0
  66. package/dist/services/task/digital/Digital.js.map +1 -0
  67. package/dist/services/task/state-machine/TaskStateMachine.js +837 -0
  68. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  69. package/dist/services/task/state-machine/actions.js +543 -0
  70. package/dist/services/task/state-machine/actions.js.map +1 -0
  71. package/dist/services/task/state-machine/constants.js +161 -0
  72. package/dist/services/task/state-machine/constants.js.map +1 -0
  73. package/dist/services/task/state-machine/guards.js +340 -0
  74. package/dist/services/task/state-machine/guards.js.map +1 -0
  75. package/dist/services/task/state-machine/index.js +53 -0
  76. package/dist/services/task/state-machine/index.js.map +1 -0
  77. package/dist/services/task/state-machine/types.js +54 -0
  78. package/dist/services/task/state-machine/types.js.map +1 -0
  79. package/dist/services/task/state-machine/uiControlsComputer.js +553 -0
  80. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  81. package/dist/services/task/taskDataNormalizer.js +99 -0
  82. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  83. package/dist/services/task/types.js +212 -4
  84. package/dist/services/task/types.js.map +1 -1
  85. package/dist/services/task/voice/Voice.js +1042 -0
  86. package/dist/services/task/voice/Voice.js.map +1 -0
  87. package/dist/services/task/voice/WebRTC.js +149 -0
  88. package/dist/services/task/voice/WebRTC.js.map +1 -0
  89. package/dist/types/cc.d.ts +94 -1
  90. package/dist/types/config.d.ts +6 -0
  91. package/dist/types/constants.d.ts +16 -1
  92. package/dist/types/index.d.ts +21 -6
  93. package/dist/types/metrics/constants.d.ts +19 -1
  94. package/dist/types/services/ApiAiAssistant.d.ts +11 -3
  95. package/dist/types/services/UserPreference.d.ts +118 -0
  96. package/dist/types/services/config/constants.d.ts +21 -0
  97. package/dist/types/services/config/types.d.ts +171 -10
  98. package/dist/types/services/core/Err.d.ts +4 -0
  99. package/dist/types/services/core/Utils.d.ts +33 -13
  100. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  101. package/dist/types/services/core/websocket/types.d.ts +1 -1
  102. package/dist/types/services/index.d.ts +1 -1
  103. package/dist/types/services/task/Task.d.ts +157 -0
  104. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  105. package/dist/types/services/task/TaskUtils.d.ts +46 -2
  106. package/dist/types/services/task/constants.d.ts +7 -0
  107. package/dist/types/services/task/dialer.d.ts +30 -0
  108. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  109. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1144 -0
  110. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  111. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  112. package/dist/types/services/task/state-machine/guards.d.ts +90 -0
  113. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  114. package/dist/types/services/task/state-machine/types.d.ts +267 -0
  115. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  116. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  117. package/dist/types/services/task/types.d.ts +603 -66
  118. package/dist/types/services/task/voice/Voice.d.ts +184 -0
  119. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  120. package/dist/types/types.d.ts +94 -0
  121. package/dist/types/webex.d.ts +1 -0
  122. package/dist/types.js +85 -0
  123. package/dist/types.js.map +1 -1
  124. package/dist/webex.js +14 -2
  125. package/dist/webex.js.map +1 -1
  126. package/package.json +15 -12
  127. package/src/cc.ts +329 -30
  128. package/src/config.ts +6 -0
  129. package/src/constants.ts +16 -1
  130. package/src/index.ts +23 -5
  131. package/src/metrics/ai-docs/AGENTS.md +348 -0
  132. package/src/metrics/ai-docs/ARCHITECTURE.md +336 -0
  133. package/src/metrics/behavioral-events.ts +106 -0
  134. package/src/metrics/constants.ts +23 -4
  135. package/src/services/ApiAiAssistant.ts +104 -3
  136. package/src/services/UserPreference.ts +509 -0
  137. package/src/services/agent/ai-docs/AGENTS.md +238 -0
  138. package/src/services/agent/ai-docs/ARCHITECTURE.md +302 -0
  139. package/src/services/ai-docs/AGENTS.md +384 -0
  140. package/src/services/config/Util.ts +3 -3
  141. package/src/services/config/ai-docs/AGENTS.md +253 -0
  142. package/src/services/config/ai-docs/ARCHITECTURE.md +424 -0
  143. package/src/services/config/constants.ts +25 -1
  144. package/src/services/config/types.ts +174 -11
  145. package/src/services/core/Err.ts +2 -0
  146. package/src/services/core/Utils.ts +123 -37
  147. package/src/services/core/ai-docs/AGENTS.md +379 -0
  148. package/src/services/core/ai-docs/ARCHITECTURE.md +696 -0
  149. package/src/services/core/websocket/WebSocketManager.ts +2 -0
  150. package/src/services/core/websocket/types.ts +1 -1
  151. package/src/services/index.ts +1 -1
  152. package/src/services/task/Task.ts +837 -0
  153. package/src/services/task/TaskFactory.ts +55 -0
  154. package/src/services/task/TaskManager.ts +738 -613
  155. package/src/services/task/TaskUtils.ts +205 -25
  156. package/src/services/task/ai-docs/AGENTS.md +455 -0
  157. package/src/services/task/ai-docs/ARCHITECTURE.md +585 -0
  158. package/src/services/task/constants.ts +7 -0
  159. package/src/services/task/dialer.ts +80 -0
  160. package/src/services/task/digital/Digital.ts +95 -0
  161. package/src/services/task/state-machine/TaskStateMachine.ts +1077 -0
  162. package/src/services/task/state-machine/actions.ts +685 -0
  163. package/src/services/task/state-machine/ai-docs/AGENTS.md +495 -0
  164. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1135 -0
  165. package/src/services/task/state-machine/constants.ts +172 -0
  166. package/src/services/task/state-machine/guards.ts +406 -0
  167. package/src/services/task/state-machine/index.ts +28 -0
  168. package/src/services/task/state-machine/types.ts +241 -0
  169. package/src/services/task/state-machine/uiControlsComputer.ts +867 -0
  170. package/src/services/task/taskDataNormalizer.ts +137 -0
  171. package/src/services/task/types.ts +710 -71
  172. package/src/services/task/voice/Voice.ts +1267 -0
  173. package/src/services/task/voice/WebRTC.ts +187 -0
  174. package/src/types.ts +122 -2
  175. package/src/utils/AGENTS.md +276 -0
  176. package/src/webex.js +2 -0
  177. package/test/unit/spec/cc.ts +343 -23
  178. package/test/unit/spec/logger-proxy.ts +70 -0
  179. package/test/unit/spec/services/ApiAiAssistant.ts +122 -17
  180. package/test/unit/spec/services/UserPreference.ts +401 -0
  181. package/test/unit/spec/services/WebCallingService.ts +7 -1
  182. package/test/unit/spec/services/config/index.ts +30 -30
  183. package/test/unit/spec/services/core/Utils.ts +425 -8
  184. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +66 -40
  185. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  186. package/test/unit/spec/services/task/Task.ts +477 -0
  187. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  188. package/test/unit/spec/services/task/TaskManager.ts +832 -1702
  189. package/test/unit/spec/services/task/TaskUtils.ts +206 -0
  190. package/test/unit/spec/services/task/dialer.ts +190 -0
  191. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  192. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +1825 -0
  193. package/test/unit/spec/services/task/state-machine/guards.ts +479 -0
  194. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  195. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +2020 -0
  196. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  197. package/test/unit/spec/services/task/voice/Voice.ts +631 -0
  198. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  199. package/umd/contact-center.min.js +2 -2
  200. package/umd/contact-center.min.js.map +1 -1
  201. package/dist/services/task/index.js +0 -1525
  202. package/dist/services/task/index.js.map +0 -1
  203. package/dist/types/services/task/index.d.ts +0 -650
  204. package/src/services/task/index.ts +0 -1801
  205. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -1,6 +1,7 @@
1
1
  import ApiAIAssistant from '../../../../src/services/ApiAiAssistant';
2
2
  import MetricsManager from '../../../../src/metrics/MetricsManager';
3
3
  import LoggerProxy from '../../../../src/logger-proxy';
4
+ import WebexRequest from '../../../../src/services/core/WebexRequest';
4
5
  import {HTTP_METHODS, WebexSDK} from '../../../../src/types';
5
6
 
6
7
  jest.mock('../../../../src/metrics/MetricsManager');
@@ -13,6 +14,9 @@ describe('ApiAIAssistant', () => {
13
14
 
14
15
  beforeEach(() => {
15
16
  jest.clearAllMocks();
17
+ jest.spyOn(WebexRequest, 'getInstance').mockReturnValue({
18
+ uploadLogs: jest.fn(),
19
+ } as any);
16
20
 
17
21
  mockWebex = {
18
22
  credentials: {
@@ -53,23 +57,18 @@ describe('ApiAIAssistant', () => {
53
57
  'START'
54
58
  );
55
59
 
56
- expect(mockWebex.request).toHaveBeenCalledWith({
57
- uri: 'https://api-ai-assistant.produs1.ciscoccservice.com/event',
58
- method: HTTP_METHODS.POST,
59
- addAuthHeader: true,
60
- body: {
61
- agentId: 'test-agent-id',
62
- orgId: 'test-org-id',
63
- eventType: 'CUSTOM_EVENT',
64
- eventName: 'GET_TRANSCRIPTS',
65
- eventDetails: {
66
- data: expect.objectContaining({
67
- interactionId: 'interaction-1',
68
- action: 'START',
69
- }),
70
- },
71
- },
72
- });
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');
73
72
  expect(result).toEqual({ok: true});
74
73
  });
75
74
 
@@ -93,6 +92,83 @@ describe('ApiAIAssistant', () => {
93
92
  expect(result).toEqual(responseBody as any);
94
93
  });
95
94
 
95
+ it('should request suggested response 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.getSuggestedResponse({
100
+ agentId: 'test-agent-id',
101
+ interactionId: 'interaction-1',
102
+ });
103
+
104
+ expect(sendEventSpy).toHaveBeenCalledTimes(1);
105
+ const [agentId, interactionId, eventType, eventName, action, context, 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(action).toBeUndefined();
113
+ expect(context).toBeUndefined();
114
+ expect(languageCode).toBe('en');
115
+ expect(typeof trackingId).toBe('string');
116
+ expect(trackingId.startsWith('WX_CC_SDK_')).toBe(true);
117
+ expect(result).toEqual({ok: true});
118
+ });
119
+
120
+ it('should request suggested response with extra context using sendEvent', async () => {
121
+ const sendEventSpy = jest.spyOn(apiAIAssistant, 'sendEvent').mockResolvedValue({ok: true});
122
+ apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: true}} as any);
123
+
124
+ const result = await apiAIAssistant.getSuggestedResponse({
125
+ agentId: 'test-agent-id',
126
+ interactionId: 'interaction-1',
127
+ context: 'Need assistance with credit card payment due date',
128
+ });
129
+
130
+ expect(sendEventSpy).toHaveBeenCalledTimes(1);
131
+ const [agentId, interactionId, eventType, eventName, action, context, languageCode, trackingId] =
132
+ sendEventSpy.mock.calls[0];
133
+
134
+ expect(agentId).toBe('test-agent-id');
135
+ expect(interactionId).toBe('interaction-1');
136
+ expect(eventType).toBe('CUSTOM_EVENT');
137
+ expect(eventName).toBe('ADD_SUGGESTIONS_EXTRA_CONTEXT');
138
+ expect(action).toBeUndefined();
139
+ expect(context).toBe('Need assistance with credit card payment due date');
140
+ expect(languageCode).toBe('en');
141
+ expect(typeof trackingId).toBe('string');
142
+ expect(trackingId.startsWith('WX_CC_SDK_')).toBe(true);
143
+ expect(result).toEqual({ok: true});
144
+ });
145
+
146
+ it('should treat whitespace-only context as GET_SUGGESTIONS', async () => {
147
+ const sendEventSpy = jest.spyOn(apiAIAssistant, 'sendEvent').mockResolvedValue({ok: true});
148
+ apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: true}} as any);
149
+
150
+ const result = await apiAIAssistant.getSuggestedResponse({
151
+ agentId: 'test-agent-id',
152
+ interactionId: 'interaction-1',
153
+ context: ' ',
154
+ });
155
+
156
+ expect(sendEventSpy).toHaveBeenCalledTimes(1);
157
+ const [agentId, interactionId, eventType, eventName, action, context, languageCode, trackingId] =
158
+ sendEventSpy.mock.calls[0];
159
+
160
+ expect(agentId).toBe('test-agent-id');
161
+ expect(interactionId).toBe('interaction-1');
162
+ expect(eventType).toBe('CUSTOM_EVENT');
163
+ expect(eventName).toBe('GET_SUGGESTIONS');
164
+ expect(action).toBeUndefined();
165
+ expect(context).toBe('');
166
+ expect(languageCode).toBe('en');
167
+ expect(typeof trackingId).toBe('string');
168
+ expect(trackingId.startsWith('WX_CC_SDK_')).toBe(true);
169
+ expect(result).toEqual({ok: true});
170
+ });
171
+
96
172
  it('should fail when base URL mapping is not available', async () => {
97
173
  (mockWebex.internal.services.get as jest.Mock).mockReturnValue('https://unknown-host.invalid');
98
174
 
@@ -112,4 +188,33 @@ describe('ApiAIAssistant', () => {
112
188
  expect(failed).toBe(true);
113
189
  expect(LoggerProxy.error).toHaveBeenCalled();
114
190
  });
191
+
192
+ it('should fail when realtime transcripts feature is disabled', async () => {
193
+ apiAIAssistant.setAIFeatureFlags({realtimeTranscripts: {enable: false}} as any);
194
+ let errorMessage = '';
195
+
196
+ try {
197
+ await apiAIAssistant.fetchHistoricTranscripts('test-agent-id', 'interaction-1');
198
+ } catch (error) {
199
+ errorMessage = (error as Error)?.message || '';
200
+ }
201
+
202
+ expect(errorMessage).toBe('Error while performing fetchHistoricTranscripts');
203
+ });
204
+
205
+ it('should fail when suggested responses feature is disabled', async () => {
206
+ apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: false}} as any);
207
+ let errorMessage = '';
208
+
209
+ try {
210
+ await apiAIAssistant.getSuggestedResponse({
211
+ agentId: 'test-agent-id',
212
+ interactionId: 'interaction-1',
213
+ });
214
+ } catch (error) {
215
+ errorMessage = (error as Error)?.message || '';
216
+ }
217
+
218
+ expect(errorMessage).toBe('Error while performing getSuggestedResponse');
219
+ });
115
220
  });
@@ -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;