@webex/contact-center 3.12.0-task-refactor.10 → 3.12.0-task-refactor.12

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 (110) hide show
  1. package/dist/cc.js +345 -82
  2. package/dist/cc.js.map +1 -1
  3. package/dist/config.js +6 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/constants.js +3 -0
  6. package/dist/constants.js.map +1 -1
  7. package/dist/metrics/behavioral-events.js +39 -0
  8. package/dist/metrics/behavioral-events.js.map +1 -1
  9. package/dist/metrics/constants.js +8 -1
  10. package/dist/metrics/constants.js.map +1 -1
  11. package/dist/services/ApiAiAssistant.js +3 -0
  12. package/dist/services/ApiAiAssistant.js.map +1 -1
  13. package/dist/services/config/Util.js +1 -1
  14. package/dist/services/config/Util.js.map +1 -1
  15. package/dist/services/config/types.js +10 -0
  16. package/dist/services/config/types.js.map +1 -1
  17. package/dist/services/constants.js +1 -0
  18. package/dist/services/constants.js.map +1 -1
  19. package/dist/services/core/Err.js.map +1 -1
  20. package/dist/services/core/Utils.js +57 -6
  21. package/dist/services/core/Utils.js.map +1 -1
  22. package/dist/services/core/aqm-reqs.js +92 -17
  23. package/dist/services/core/aqm-reqs.js.map +1 -1
  24. package/dist/services/core/websocket/WebSocketManager.js +24 -3
  25. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  26. package/dist/services/index.js +1 -0
  27. package/dist/services/index.js.map +1 -1
  28. package/dist/services/task/Task.js +16 -4
  29. package/dist/services/task/Task.js.map +1 -1
  30. package/dist/services/task/TaskManager.js +158 -6
  31. package/dist/services/task/TaskManager.js.map +1 -1
  32. package/dist/services/task/TaskUtils.js +39 -11
  33. package/dist/services/task/TaskUtils.js.map +1 -1
  34. package/dist/services/task/constants.js +7 -1
  35. package/dist/services/task/constants.js.map +1 -1
  36. package/dist/services/task/dialer.js +129 -0
  37. package/dist/services/task/dialer.js.map +1 -1
  38. package/dist/services/task/state-machine/TaskStateMachine.js +30 -3
  39. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
  40. package/dist/services/task/state-machine/actions.js +4 -0
  41. package/dist/services/task/state-machine/actions.js.map +1 -1
  42. package/dist/services/task/state-machine/constants.js +3 -0
  43. package/dist/services/task/state-machine/constants.js.map +1 -1
  44. package/dist/services/task/state-machine/guards.js +23 -0
  45. package/dist/services/task/state-machine/guards.js.map +1 -1
  46. package/dist/services/task/state-machine/types.js.map +1 -1
  47. package/dist/services/task/state-machine/uiControlsComputer.js +1 -1
  48. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -1
  49. package/dist/services/task/types.js +69 -0
  50. package/dist/services/task/types.js.map +1 -1
  51. package/dist/types/cc.d.ts +93 -2
  52. package/dist/types/config.d.ts +6 -0
  53. package/dist/types/constants.d.ts +3 -0
  54. package/dist/types/metrics/constants.d.ts +6 -0
  55. package/dist/types/services/config/types.d.ts +22 -1
  56. package/dist/types/services/core/Err.d.ts +6 -0
  57. package/dist/types/services/core/Utils.d.ts +20 -2
  58. package/dist/types/services/core/aqm-reqs.d.ts +49 -0
  59. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  60. package/dist/types/services/core/websocket/connection-service.d.ts +1 -0
  61. package/dist/types/services/task/Task.d.ts +1 -0
  62. package/dist/types/services/task/TaskUtils.d.ts +13 -0
  63. package/dist/types/services/task/constants.d.ts +6 -0
  64. package/dist/types/services/task/dialer.d.ts +45 -0
  65. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +36 -8
  66. package/dist/types/services/task/state-machine/constants.d.ts +3 -0
  67. package/dist/types/services/task/state-machine/guards.d.ts +2 -0
  68. package/dist/types/services/task/state-machine/types.d.ts +11 -0
  69. package/dist/types/services/task/types.d.ts +85 -1
  70. package/dist/types/types.d.ts +2 -0
  71. package/dist/types.js.map +1 -1
  72. package/dist/webex.js +1 -1
  73. package/package.json +2 -2
  74. package/src/cc.ts +452 -102
  75. package/src/config.ts +6 -0
  76. package/src/constants.ts +3 -0
  77. package/src/metrics/behavioral-events.ts +42 -0
  78. package/src/metrics/constants.ts +9 -1
  79. package/src/services/ApiAiAssistant.ts +3 -0
  80. package/src/services/config/Util.ts +1 -1
  81. package/src/services/config/types.ts +12 -1
  82. package/src/services/constants.ts +1 -0
  83. package/src/services/core/Err.ts +3 -0
  84. package/src/services/core/Utils.ts +68 -5
  85. package/src/services/core/aqm-reqs.ts +100 -22
  86. package/src/services/core/websocket/WebSocketManager.ts +23 -2
  87. package/src/services/index.ts +1 -0
  88. package/src/services/task/Task.ts +22 -4
  89. package/src/services/task/TaskManager.ts +175 -7
  90. package/src/services/task/TaskUtils.ts +33 -1
  91. package/src/services/task/constants.ts +6 -0
  92. package/src/services/task/dialer.ts +136 -1
  93. package/src/services/task/state-machine/TaskStateMachine.ts +37 -5
  94. package/src/services/task/state-machine/actions.ts +4 -0
  95. package/src/services/task/state-machine/constants.ts +3 -0
  96. package/src/services/task/state-machine/guards.ts +30 -0
  97. package/src/services/task/state-machine/types.ts +14 -1
  98. package/src/services/task/state-machine/uiControlsComputer.ts +1 -1
  99. package/src/services/task/types.ts +91 -0
  100. package/src/types.ts +6 -1
  101. package/test/unit/spec/cc.ts +432 -26
  102. package/test/unit/spec/services/config/index.ts +30 -2
  103. package/test/unit/spec/services/core/Utils.ts +367 -1
  104. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +90 -1
  105. package/test/unit/spec/services/core/websocket/connection-service.ts +4 -5
  106. package/test/unit/spec/services/task/TaskManager.ts +2334 -145
  107. package/test/unit/spec/services/task/dialer.ts +372 -96
  108. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +47 -0
  109. package/umd/contact-center.min.js +2 -2
  110. package/umd/contact-center.min.js.map +1 -1
@@ -1,157 +1,433 @@
1
- import AqmReqs from "../../../../../src/services/core/aqm-reqs";
2
- import aqmDialer from "../../../../../src/services/task/dialer";
1
+ import AqmReqs from '../../../../../src/services/core/aqm-reqs';
2
+ import aqmDialer from '../../../../../src/services/task/dialer';
3
3
 
4
4
  jest.mock('../../../../../src/services/core/Utils', () => ({
5
-
6
5
  createErrDetailsObject: jest.fn(),
7
6
  getRoutingHost: jest.fn(),
8
-
9
7
  }));
10
8
 
11
9
  jest.mock('../../../../../src/services/core/aqm-reqs');
12
10
 
13
11
  describe('AQM routing dialer', () => {
14
-
15
12
  let fakeAqm: jest.Mocked<AqmReqs>;
16
13
 
17
14
  beforeEach(() => {
18
-
19
15
  jest.clearAllMocks();
20
16
 
21
17
  fakeAqm = new AqmReqs() as jest.Mocked<AqmReqs>;
22
18
  fakeAqm.reqEmpty = jest.fn().mockImplementation((fn) => fn);
23
19
  fakeAqm.req = jest.fn().mockImplementation((fn) => fn);
20
+ });
21
+
22
+ describe('Routing outbound dial', () => {
23
+ it('should call the startdial api', () => {
24
+ const fakeAqm = {
25
+ req: () =>
26
+ jest.fn().mockResolvedValue(() => {
27
+ Promise.resolve({data: 'outdial success'});
28
+ }),
29
+ evt: jest.fn(),
30
+ };
31
+
32
+ const dialer = aqmDialer(fakeAqm as any);
33
+
34
+ dialer
35
+ .startOutdial({
36
+ data: {
37
+ entryPointId: '1212312',
38
+ destination: '+142356',
39
+ direction: 'OUTBOUND',
40
+ attributes: {},
41
+ mediaType: 'telephony',
42
+ outboundType: 'OUTDIAL',
43
+ },
44
+ })
45
+ .then((response) => {
46
+ expect(response.data).toBe('outdial success');
47
+ })
48
+ .catch(() => {
49
+ expect(true).toBe(true);
50
+ });
51
+
52
+ expect(dialer.startOutdial).toHaveBeenCalled();
53
+ });
54
+
55
+ it('should handle network errors', () => {
56
+ const fakeAqm = {
57
+ req: () => jest.fn().mockRejectedValue(new Error('Network Error')),
58
+ evt: jest.fn(),
59
+ };
60
+
61
+ const dialer = aqmDialer(fakeAqm as any);
24
62
 
63
+ return expect(
64
+ dialer.startOutdial({
65
+ data: {
66
+ entryPointId: '1212312',
67
+ destination: '+142356',
68
+ direction: 'OUTBOUND',
69
+ attributes: {},
70
+ mediaType: 'telephony',
71
+ outboundType: 'OUTDIAL',
72
+ },
73
+ })
74
+ ).rejects.toThrow('Network Error');
75
+ });
76
+
77
+ it('should handle invalid payload', () => {
78
+ const fakeAqm = {
79
+ req: () => jest.fn().mockRejectedValue(new Error('Invalid Payload in request')),
80
+ evt: jest.fn(),
81
+ };
82
+
83
+ const dialer = aqmDialer(fakeAqm as any);
84
+
85
+ return expect(
86
+ dialer.startOutdial({
87
+ data: {
88
+ entryPointId: '',
89
+ destination: '',
90
+ direction: 'OUTBOUND',
91
+ attributes: {},
92
+ mediaType: 'telephony',
93
+ outboundType: 'OUTDIAL',
94
+ },
95
+ })
96
+ ).rejects.toThrow('Invalid Payload in request');
97
+ });
98
+
99
+ it('should handle servers errors', () => {
100
+ const fakeAqm = {
101
+ req: () => jest.fn().mockRejectedValue(new Error('Server Error')),
102
+ evt: jest.fn(),
103
+ };
104
+
105
+ const dialer = aqmDialer(fakeAqm as any);
106
+ return expect(
107
+ dialer.startOutdial({
108
+ data: {
109
+ entryPointId: '123456',
110
+ destination: '+142356',
111
+ direction: 'OUTBOUND',
112
+ attributes: {},
113
+ mediaType: 'telephony',
114
+ outboundType: 'OUTDIAL',
115
+ },
116
+ })
117
+ ).rejects.toThrow('Server Error');
118
+ });
119
+
120
+ it('should handle Timeout scenarios', () => {
121
+ const fakeAqm = {
122
+ req: () => jest.fn().mockRejectedValue(new Error('Request Timeout')),
123
+ evt: jest.fn(),
124
+ };
125
+
126
+ const dialer = aqmDialer(fakeAqm as any);
127
+ return expect(
128
+ dialer.startOutdial({
129
+ data: {
130
+ entryPointId: '12345',
131
+ destination: '+123456',
132
+ direction: 'OUTBOUND',
133
+ attributes: {},
134
+ mediaType: 'telephony',
135
+ outboundType: 'OUTDIAL',
136
+ },
137
+ })
138
+ ).rejects.toThrow('Request Timeout');
139
+ });
25
140
  });
26
141
 
27
- describe("Routing outbound dial", () => {
142
+ describe('Campaign preview contact operations', () => {
143
+ const previewPayload = {
144
+ interactionId: 'interaction-123',
145
+ campaignId: 'TestCampaignPreview',
146
+ };
28
147
 
29
- it("should call the startdial api", () => {
148
+ describe('acceptPreviewContact', () => {
149
+ it('should construct the correct URL with campaignId and interactionId', () => {
150
+ const dialer = aqmDialer(fakeAqm as any);
151
+ const config = dialer.acceptPreviewContact({data: previewPayload}) as any;
30
152
 
153
+ expect(config.url).toBe(
154
+ `/v1/dialer/campaign/${previewPayload.campaignId}/preview-task/${previewPayload.interactionId}/accept`
155
+ );
156
+ });
157
+
158
+ it('should URL-encode campaignId when it contains reserved characters', () => {
159
+ const dialer = aqmDialer(fakeAqm as any);
160
+ const payloadWithSpecialChars = {
161
+ interactionId: 'interaction-456',
162
+ campaignId: 'My Campaign/Test #1',
163
+ };
164
+ const config = dialer.acceptPreviewContact({data: payloadWithSpecialChars}) as any;
165
+
166
+ expect(config.url).toBe(
167
+ `/v1/dialer/campaign/${encodeURIComponent(
168
+ payloadWithSpecialChars.campaignId
169
+ )}/preview-task/${payloadWithSpecialChars.interactionId}/accept`
170
+ );
171
+ expect(config.url).toContain('My%20Campaign%2FTest%20%231');
172
+ });
173
+
174
+ it('should call the acceptPreviewContact api', () => {
31
175
  const fakeAqm = {
32
176
  req: () =>
33
177
  jest.fn().mockResolvedValue(() => {
34
- Promise.resolve({ data: "outdial success" });
178
+ Promise.resolve({data: 'accept preview success'});
35
179
  }),
36
- evt: jest.fn()
180
+ evt: jest.fn(),
37
181
  };
38
-
182
+
39
183
  const dialer = aqmDialer(fakeAqm as any);
40
184
 
41
185
  dialer
42
- .startOutdial({
43
- data: {
44
- entryPointId: "1212312",
45
- destination: "+142356",
46
- direction: "OUTBOUND",
47
- attributes: {},
48
- mediaType: "telephony",
49
- outboundType: "OUTDIAL"
50
- }
186
+ .acceptPreviewContact({data: previewPayload})
187
+ .then((response) => {
188
+ expect(response.data).toBe('accept preview success');
189
+ })
190
+ .catch(() => {
191
+ expect(true).toBe(true);
192
+ });
193
+
194
+ expect(dialer.acceptPreviewContact).toHaveBeenCalled();
195
+ });
196
+
197
+ it('should handle network errors', () => {
198
+ const fakeAqm = {
199
+ req: () => jest.fn().mockRejectedValue(new Error('Network Error')),
200
+ evt: jest.fn(),
201
+ };
202
+
203
+ const dialer = aqmDialer(fakeAqm as any);
204
+
205
+ return expect(
206
+ dialer.acceptPreviewContact({
207
+ data: previewPayload,
51
208
  })
52
- .then(response => {
53
- expect(response.data).toBe("outdial success");
209
+ ).rejects.toThrow('Network Error');
210
+ });
211
+
212
+ it('should handle server errors', () => {
213
+ const fakeAqm = {
214
+ req: () => jest.fn().mockRejectedValue(new Error('Server Error')),
215
+ evt: jest.fn(),
216
+ };
217
+
218
+ const dialer = aqmDialer(fakeAqm as any);
219
+
220
+ return expect(
221
+ dialer.acceptPreviewContact({
222
+ data: previewPayload,
223
+ })
224
+ ).rejects.toThrow('Server Error');
225
+ });
226
+
227
+ it('should handle timeout scenarios', () => {
228
+ const fakeAqm = {
229
+ req: () => jest.fn().mockRejectedValue(new Error('Request Timeout')),
230
+ evt: jest.fn(),
231
+ };
232
+
233
+ const dialer = aqmDialer(fakeAqm as any);
234
+
235
+ return expect(
236
+ dialer.acceptPreviewContact({
237
+ data: previewPayload,
238
+ })
239
+ ).rejects.toThrow('Request Timeout');
240
+ });
241
+ });
242
+
243
+ describe('skipPreviewContact', () => {
244
+ it('should construct the correct URL with campaignId and interactionId', () => {
245
+ const dialer = aqmDialer(fakeAqm as any);
246
+ const config = dialer.skipPreviewContact({data: previewPayload}) as any;
247
+
248
+ expect(config.url).toBe(
249
+ `/v1/dialer/campaign/${previewPayload.campaignId}/preview-task/${previewPayload.interactionId}/skip`
250
+ );
251
+ });
252
+
253
+ it('should URL-encode campaignId when it contains reserved characters', () => {
254
+ const dialer = aqmDialer(fakeAqm as any);
255
+ const payloadWithSpecialChars = {
256
+ interactionId: 'interaction-456',
257
+ campaignId: 'My Campaign/Test #1',
258
+ };
259
+ const config = dialer.skipPreviewContact({data: payloadWithSpecialChars}) as any;
260
+
261
+ expect(config.url).toBe(
262
+ `/v1/dialer/campaign/${encodeURIComponent(
263
+ payloadWithSpecialChars.campaignId
264
+ )}/preview-task/${payloadWithSpecialChars.interactionId}/skip`
265
+ );
266
+ expect(config.url).toContain('My%20Campaign%2FTest%20%231');
267
+ });
268
+
269
+ it('should call the skipPreviewContact api', () => {
270
+ const fakeAqm = {
271
+ req: () =>
272
+ jest.fn().mockResolvedValue(() => {
273
+ Promise.resolve({data: 'skip preview success'});
274
+ }),
275
+ evt: jest.fn(),
276
+ };
277
+
278
+ const dialer = aqmDialer(fakeAqm as any);
279
+
280
+ dialer
281
+ .skipPreviewContact({data: previewPayload})
282
+ .then((response) => {
283
+ expect(response.data).toBe('skip preview success');
54
284
  })
55
285
  .catch(() => {
56
286
  expect(true).toBe(true);
57
287
  });
58
-
59
- expect(dialer.startOutdial).toHaveBeenCalled();
60
-
288
+
289
+ expect(dialer.skipPreviewContact).toHaveBeenCalled();
61
290
  });
62
291
 
63
- it("should handle network errors", () => {
292
+ it('should handle network errors', () => {
293
+ const fakeAqm = {
294
+ req: () => jest.fn().mockRejectedValue(new Error('Network Error')),
295
+ evt: jest.fn(),
296
+ };
64
297
 
65
- const fakeAqm = {
66
- req: () => jest.fn().mockRejectedValue(new Error("Network Error")),
67
- evt: jest.fn()
68
- };
298
+ const dialer = aqmDialer(fakeAqm as any);
69
299
 
70
- const dialer = aqmDialer(fakeAqm as any);
300
+ return expect(
301
+ dialer.skipPreviewContact({
302
+ data: previewPayload,
303
+ })
304
+ ).rejects.toThrow('Network Error');
305
+ });
71
306
 
72
- return expect(dialer.startOutdial({
73
-
74
- data: {
75
- entryPointId: "1212312",
76
- destination: "+142356",
77
- direction: "OUTBOUND",
78
- attributes: {},
79
- mediaType: "telephony",
80
- outboundType: "OUTDIAL"
81
- }
307
+ it('should handle server errors', () => {
308
+ const fakeAqm = {
309
+ req: () => jest.fn().mockRejectedValue(new Error('Server Error')),
310
+ evt: jest.fn(),
311
+ };
82
312
 
83
- })).rejects.toThrow("Network Error");
84
- });
85
-
86
- it("should handle invalid payload", () => {
313
+ const dialer = aqmDialer(fakeAqm as any);
87
314
 
88
- const fakeAqm = {
315
+ return expect(
316
+ dialer.skipPreviewContact({
317
+ data: previewPayload,
318
+ })
319
+ ).rejects.toThrow('Server Error');
320
+ });
89
321
 
90
- req: () => jest.fn().mockRejectedValue(new Error("Invalid Payload in request")),
91
- evt: jest.fn()
322
+ it('should handle timeout scenarios', () => {
323
+ const fakeAqm = {
324
+ req: () => jest.fn().mockRejectedValue(new Error('Request Timeout')),
325
+ evt: jest.fn(),
326
+ };
92
327
 
93
- };
328
+ const dialer = aqmDialer(fakeAqm as any);
94
329
 
95
- const dialer = aqmDialer(fakeAqm as any);
330
+ return expect(
331
+ dialer.skipPreviewContact({
332
+ data: previewPayload,
333
+ })
334
+ ).rejects.toThrow('Request Timeout');
335
+ });
336
+ });
96
337
 
97
- return expect(dialer.startOutdial({
338
+ describe('removePreviewContact', () => {
339
+ it('should construct the correct URL with campaignId and interactionId', () => {
340
+ const dialer = aqmDialer(fakeAqm as any);
341
+ const config = dialer.removePreviewContact({data: previewPayload}) as any;
98
342
 
99
- data: {
100
- entryPointId: "",
101
- destination: "",
102
- direction: "OUTBOUND",
103
- attributes: {},
104
- mediaType: "telephony",
105
- outboundType: "OUTDIAL"
106
- }
343
+ expect(config.url).toBe(
344
+ `/v1/dialer/campaign/${previewPayload.campaignId}/preview-task/${previewPayload.interactionId}/remove`
345
+ );
346
+ });
107
347
 
108
- })).rejects.toThrow("Invalid Payload in request");
109
- });
348
+ it('should URL-encode campaignId when it contains reserved characters', () => {
349
+ const dialer = aqmDialer(fakeAqm as any);
350
+ const payloadWithSpecialChars = {
351
+ interactionId: 'interaction-456',
352
+ campaignId: 'My Campaign/Test #1',
353
+ };
354
+ const config = dialer.removePreviewContact({data: payloadWithSpecialChars}) as any;
355
+
356
+ expect(config.url).toBe(
357
+ `/v1/dialer/campaign/${encodeURIComponent(
358
+ payloadWithSpecialChars.campaignId
359
+ )}/preview-task/${payloadWithSpecialChars.interactionId}/remove`
360
+ );
361
+ expect(config.url).toContain('My%20Campaign%2FTest%20%231');
362
+ });
110
363
 
364
+ it('should call the removePreviewContact api', () => {
365
+ const fakeAqm = {
366
+ req: () =>
367
+ jest.fn().mockResolvedValue(() => {
368
+ Promise.resolve({data: 'remove preview success'});
369
+ }),
370
+ evt: jest.fn(),
371
+ };
111
372
 
112
- it("should handle servers errors", () => {
373
+ const dialer = aqmDialer(fakeAqm as any);
113
374
 
114
- const fakeAqm = {
115
- req: () => jest.fn().mockRejectedValue(new Error("Server Error")),
116
- evt: jest.fn()
117
- };
375
+ dialer
376
+ .removePreviewContact({data: previewPayload})
377
+ .then((response) => {
378
+ expect(response.data).toBe('remove preview success');
379
+ })
380
+ .catch(() => {
381
+ expect(true).toBe(true);
382
+ });
118
383
 
119
- const dialer = aqmDialer(fakeAqm as any);
120
- return expect(dialer.startOutdial({
384
+ expect(dialer.removePreviewContact).toHaveBeenCalled();
385
+ });
121
386
 
122
- data: {
123
- entryPointId: "123456",
124
- destination: "+142356",
125
- direction: "OUTBOUND",
126
- attributes: {},
127
- mediaType: "telephony",
128
- outboundType: "OUTDIAL"
129
- }
387
+ it('should handle network errors', () => {
388
+ const fakeAqm = {
389
+ req: () => jest.fn().mockRejectedValue(new Error('Network Error')),
390
+ evt: jest.fn(),
391
+ };
130
392
 
131
- })).rejects.toThrow("Server Error");
393
+ const dialer = aqmDialer(fakeAqm as any);
132
394
 
133
- });
395
+ return expect(
396
+ dialer.removePreviewContact({
397
+ data: previewPayload,
398
+ })
399
+ ).rejects.toThrow('Network Error');
400
+ });
134
401
 
135
- it("should handle Timeout scenarios", () => {
402
+ it('should handle server errors', () => {
403
+ const fakeAqm = {
404
+ req: () => jest.fn().mockRejectedValue(new Error('Server Error')),
405
+ evt: jest.fn(),
406
+ };
136
407
 
137
- const fakeAqm = {
138
- req: () => jest.fn().mockRejectedValue(new Error("Request Timeout")),
139
- evt: jest.fn()
140
- };
408
+ const dialer = aqmDialer(fakeAqm as any);
141
409
 
142
- const dialer = aqmDialer(fakeAqm as any);
143
- return expect(dialer.startOutdial({
410
+ return expect(
411
+ dialer.removePreviewContact({
412
+ data: previewPayload,
413
+ })
414
+ ).rejects.toThrow('Server Error');
415
+ });
144
416
 
145
- data: {
146
- entryPointId: "12345",
147
- destination: "+123456",
148
- direction: "OUTBOUND",
149
- attributes: {},
150
- mediaType: "telephony",
151
- outboundType: "OUTDIAL"
152
- }
417
+ it('should handle timeout scenarios', () => {
418
+ const fakeAqm = {
419
+ req: () => jest.fn().mockRejectedValue(new Error('Request Timeout')),
420
+ evt: jest.fn(),
421
+ };
153
422
 
154
- })).rejects.toThrow("Request Timeout");
155
- });
156
- });
423
+ const dialer = aqmDialer(fakeAqm as any);
424
+
425
+ return expect(
426
+ dialer.removePreviewContact({
427
+ data: previewPayload,
428
+ })
429
+ ).rejects.toThrow('Request Timeout');
430
+ });
431
+ });
432
+ });
157
433
  });
@@ -1784,6 +1784,53 @@ describe('uiControlsComputer outdial accept/decline controls', () => {
1784
1784
  });
1785
1785
  });
1786
1786
 
1787
+ describe('uiControlsComputer WebRTC call-control end button', () => {
1788
+ function createConnectedContext(voiceVariant: 'webrtc' | 'pstn'): TaskContext {
1789
+ const taskData = createTaskData({
1790
+ interaction: {
1791
+ state: 'connected',
1792
+ } as any,
1793
+ });
1794
+
1795
+ return {
1796
+ taskData,
1797
+ consultInitiator: false,
1798
+ exitingConference: false,
1799
+ consultFromConference: false,
1800
+ transferConferenceRequested: false,
1801
+ consultDestinationType: null,
1802
+ consultDestinationAgentId: null,
1803
+ consultDestinationAgentJoined: false,
1804
+ consultCallHeld: false,
1805
+ recordingControlsAvailable: true,
1806
+ recordingInProgress: true,
1807
+ uiControlConfig: {
1808
+ isEndTaskEnabled: false,
1809
+ isEndConsultEnabled: true,
1810
+ channelType: TASK_CHANNEL_TYPE.VOICE,
1811
+ isRecordingEnabled: true,
1812
+ agentId: taskData.agentId,
1813
+ voiceVariant,
1814
+ },
1815
+ uiControls: getDefaultUIControls(),
1816
+ };
1817
+ }
1818
+
1819
+ it('shows main end for connected WebRTC call-control tasks even when end task is disabled', () => {
1820
+ const context = createConnectedContext('webrtc');
1821
+ const uiControls = computeUIControls(TaskState.CONNECTED, context, context.taskData);
1822
+
1823
+ expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: true});
1824
+ });
1825
+
1826
+ it('keeps main end hidden for non-WebRTC voice tasks when end task is disabled', () => {
1827
+ const context = createConnectedContext('pstn');
1828
+ const uiControls = computeUIControls(TaskState.CONNECTED, context, context.taskData);
1829
+
1830
+ expect(uiControls.main.end).toEqual({isVisible: false, isEnabled: false});
1831
+ });
1832
+ });
1833
+
1787
1834
  describe('uiControlsComputer conference controls', () => {
1788
1835
  function createConferenceTaskData(participantCount: number) {
1789
1836
  const participants: Record<string, any> = {