@webex/contact-center 3.12.0-next.6 → 3.12.0-next.60

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 (60) hide show
  1. package/dist/cc.js +161 -21
  2. package/dist/cc.js.map +1 -1
  3. package/dist/constants.js +2 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/metrics/behavioral-events.js +26 -0
  6. package/dist/metrics/behavioral-events.js.map +1 -1
  7. package/dist/metrics/constants.js +4 -0
  8. package/dist/metrics/constants.js.map +1 -1
  9. package/dist/services/config/Util.js +1 -1
  10. package/dist/services/config/Util.js.map +1 -1
  11. package/dist/services/config/constants.js +1 -1
  12. package/dist/services/config/constants.js.map +1 -1
  13. package/dist/services/config/types.js +4 -0
  14. package/dist/services/config/types.js.map +1 -1
  15. package/dist/services/core/Err.js.map +1 -1
  16. package/dist/services/core/Utils.js +37 -9
  17. package/dist/services/core/Utils.js.map +1 -1
  18. package/dist/services/task/TaskManager.js +90 -8
  19. package/dist/services/task/TaskManager.js.map +1 -1
  20. package/dist/services/task/constants.js +3 -1
  21. package/dist/services/task/constants.js.map +1 -1
  22. package/dist/services/task/dialer.js +78 -0
  23. package/dist/services/task/dialer.js.map +1 -1
  24. package/dist/services/task/index.js +7 -2
  25. package/dist/services/task/index.js.map +1 -1
  26. package/dist/services/task/types.js +44 -0
  27. package/dist/services/task/types.js.map +1 -1
  28. package/dist/types/cc.d.ts +44 -0
  29. package/dist/types/constants.d.ts +2 -0
  30. package/dist/types/metrics/constants.d.ts +4 -0
  31. package/dist/types/services/config/types.d.ts +10 -1
  32. package/dist/types/services/core/Err.d.ts +4 -0
  33. package/dist/types/services/core/Utils.d.ts +10 -3
  34. package/dist/types/services/task/constants.d.ts +2 -0
  35. package/dist/types/services/task/dialer.d.ts +30 -0
  36. package/dist/types/services/task/types.d.ts +53 -1
  37. package/dist/webex.js +1 -1
  38. package/package.json +9 -9
  39. package/src/cc.ts +196 -22
  40. package/src/constants.ts +2 -0
  41. package/src/metrics/behavioral-events.ts +28 -0
  42. package/src/metrics/constants.ts +4 -0
  43. package/src/services/config/Util.ts +1 -1
  44. package/src/services/config/constants.ts +1 -1
  45. package/src/services/config/types.ts +6 -1
  46. package/src/services/core/Err.ts +2 -0
  47. package/src/services/core/Utils.ts +43 -8
  48. package/src/services/task/TaskManager.ts +102 -22
  49. package/src/services/task/constants.ts +2 -0
  50. package/src/services/task/dialer.ts +80 -0
  51. package/src/services/task/index.ts +7 -2
  52. package/src/services/task/types.ts +56 -0
  53. package/test/unit/spec/cc.ts +154 -20
  54. package/test/unit/spec/services/config/index.ts +3 -3
  55. package/test/unit/spec/services/core/Utils.ts +90 -7
  56. package/test/unit/spec/services/task/TaskManager.ts +238 -7
  57. package/test/unit/spec/services/task/dialer.ts +190 -0
  58. package/test/unit/spec/services/task/index.ts +21 -0
  59. package/umd/contact-center.min.js +2 -2
  60. package/umd/contact-center.min.js.map +1 -1
@@ -7,6 +7,8 @@ import {
7
7
  TASK_API,
8
8
  DIALER_API,
9
9
  CAMPAIGN_PREVIEW_ACCEPT,
10
+ CAMPAIGN_PREVIEW_SKIP,
11
+ CAMPAIGN_PREVIEW_REMOVE,
10
12
  TIMEOUT_PREVIEW_ACCEPT,
11
13
  } from './constants';
12
14
  import * as Contact from './types';
@@ -103,5 +105,83 @@ export default function aqmDialer(aqm: AqmReqs) {
103
105
  errId: 'Service.aqm.dialer.acceptPreviewContact',
104
106
  },
105
107
  })),
108
+
109
+ /**
110
+ * Skips a campaign preview contact, requesting the next contact from the campaign.
111
+ *
112
+ * @param {Object} p - Parameters object.
113
+ * @param {Contact.PreviewContactPayload} p.data - Payload containing interactionId and campaignId.
114
+ * @returns {Promise<Contact.AgentContact>} A promise that resolves with agent contact on success.
115
+ *
116
+ * Emits:
117
+ * - `CC_EVENTS.CAMPAIGN_CONTACT_UPDATED` or `CC_EVENTS.CONTACT_ENDED` on success
118
+ * - `CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED` on failure
119
+ * @ignore
120
+ */
121
+ skipPreviewContact: aqm.req((p: {data: Contact.PreviewContactPayload}) => ({
122
+ url: `${DIALER_API}/campaign/${encodeURIComponent(p.data.campaignId)}/preview-task/${
123
+ p.data.interactionId
124
+ }${CAMPAIGN_PREVIEW_SKIP}`,
125
+ host: WCC_API_GATEWAY,
126
+ data: {},
127
+ method: HTTP_METHODS.POST,
128
+ err,
129
+ notifSuccess: {
130
+ bind: {
131
+ type: TASK_MESSAGE_TYPE,
132
+ data: {
133
+ type: [CC_EVENTS.CAMPAIGN_CONTACT_UPDATED, CC_EVENTS.CONTACT_ENDED],
134
+ interactionId: p.data.interactionId,
135
+ },
136
+ },
137
+ msg: {} as Contact.AgentContact,
138
+ },
139
+ notifFail: {
140
+ bind: {
141
+ type: TASK_MESSAGE_TYPE,
142
+ data: {type: CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED, campaignId: p.data.campaignId},
143
+ },
144
+ errId: 'Service.aqm.dialer.skipPreviewContact',
145
+ },
146
+ })),
147
+
148
+ /**
149
+ * Removes a campaign preview contact from the campaign list entirely.
150
+ *
151
+ * @param {Object} p - Parameters object.
152
+ * @param {Contact.PreviewContactPayload} p.data - Payload containing interactionId and campaignId.
153
+ * @returns {Promise<Contact.AgentContact>} A promise that resolves with agent contact on success.
154
+ *
155
+ * Emits:
156
+ * - `CC_EVENTS.CAMPAIGN_CONTACT_UPDATED` or `CC_EVENTS.CONTACT_ENDED` on success
157
+ * - `CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED` on failure
158
+ * @ignore
159
+ */
160
+ removePreviewContact: aqm.req((p: {data: Contact.PreviewContactPayload}) => ({
161
+ url: `${DIALER_API}/campaign/${encodeURIComponent(p.data.campaignId)}/preview-task/${
162
+ p.data.interactionId
163
+ }${CAMPAIGN_PREVIEW_REMOVE}`,
164
+ host: WCC_API_GATEWAY,
165
+ data: {},
166
+ method: HTTP_METHODS.POST,
167
+ err,
168
+ notifSuccess: {
169
+ bind: {
170
+ type: TASK_MESSAGE_TYPE,
171
+ data: {
172
+ type: [CC_EVENTS.CAMPAIGN_CONTACT_UPDATED, CC_EVENTS.CONTACT_ENDED],
173
+ interactionId: p.data.interactionId,
174
+ },
175
+ },
176
+ msg: {} as Contact.AgentContact,
177
+ },
178
+ notifFail: {
179
+ bind: {
180
+ type: TASK_MESSAGE_TYPE,
181
+ data: {type: CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED, campaignId: p.data.campaignId},
182
+ },
183
+ errId: 'Service.aqm.dialer.removePreviewContact',
184
+ },
185
+ })),
106
186
  };
107
187
  }
@@ -565,7 +565,10 @@ export default class Task extends EventEmitter implements ITask {
565
565
  METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
566
566
  ]);
567
567
 
568
- const effectiveMediaResourceId = mediaResourceId ?? this.data.mediaResourceId;
568
+ const {mainInteractionId} = this.data.interaction;
569
+ const defaultMediaResourceId =
570
+ this.data.interaction.media[mainInteractionId]?.mediaResourceId;
571
+ const effectiveMediaResourceId = mediaResourceId ?? defaultMediaResourceId;
569
572
 
570
573
  const response = await this.contact.hold({
571
574
  interactionId: this.data.interactionId,
@@ -599,7 +602,9 @@ export default class Task extends EventEmitter implements ITask {
599
602
  errorData: err.data?.errorData,
600
603
  reasonCode: err.data?.reasonCode,
601
604
  };
602
- const effectiveMediaResourceId = mediaResourceId ?? this.data.mediaResourceId;
605
+ const defaultMediaResourceId =
606
+ this.data.interaction.media[this.data.interaction.mainInteractionId]?.mediaResourceId;
607
+ const effectiveMediaResourceId = mediaResourceId ?? defaultMediaResourceId;
603
608
 
604
609
  this.metricsManager.trackEvent(
605
610
  METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
@@ -554,6 +554,54 @@ export enum TASK_EVENTS {
554
554
  * ```
555
555
  */
556
556
  TASK_CAMPAIGN_PREVIEW_RESERVATION = 'task:campaignPreviewReservation',
557
+
558
+ /**
559
+ * Triggered when accepting a campaign preview contact fails
560
+ * @example
561
+ * ```typescript
562
+ * task.on(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED, (task: ITask) => {
563
+ * console.log('Campaign preview accept failed:', task.data.interactionId);
564
+ * // Handle accept failure
565
+ * });
566
+ * ```
567
+ */
568
+ TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED = 'task:campaignPreviewAcceptFailed',
569
+
570
+ /**
571
+ * Triggered when skipping a campaign preview contact fails
572
+ * @example
573
+ * ```typescript
574
+ * task.on(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_SKIP_FAILED, (task: ITask) => {
575
+ * console.log('Campaign preview skip failed:', task.data.interactionId);
576
+ * // Handle skip failure
577
+ * });
578
+ * ```
579
+ */
580
+ TASK_CAMPAIGN_PREVIEW_SKIP_FAILED = 'task:campaignPreviewSkipFailed',
581
+
582
+ /**
583
+ * Triggered when removing a campaign preview contact fails
584
+ * @example
585
+ * ```typescript
586
+ * task.on(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED, (task: ITask) => {
587
+ * console.log('Campaign preview remove failed:', task.data.interactionId);
588
+ * // Handle remove failure
589
+ * });
590
+ * ```
591
+ */
592
+ TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED = 'task:campaignPreviewRemoveFailed',
593
+
594
+ /**
595
+ * Triggered when a campaign contact is updated (e.g., after skip or remove, when the next contact is offered)
596
+ * @example
597
+ * ```typescript
598
+ * task.on(TASK_EVENTS.TASK_CAMPAIGN_CONTACT_UPDATED, (task: ITask) => {
599
+ * console.log('Campaign contact updated:', task.data.interactionId);
600
+ * // Handle updated campaign contact (e.g., display next contact)
601
+ * });
602
+ * ```
603
+ */
604
+ TASK_CAMPAIGN_CONTACT_UPDATED = 'task:campaignContactUpdated',
557
605
  }
558
606
 
559
607
  /**
@@ -702,6 +750,14 @@ export type Interaction = {
702
750
  fcDesktopView?: string;
703
751
  /** Agent ID who initiated the outdial call */
704
752
  outdialAgentId?: string;
753
+ /** Indicates if the skip action is disabled for campaign preview contacts */
754
+ campaignPreviewSkipDisabled?: string;
755
+ /** Indicates if the remove action is disabled for campaign preview contacts */
756
+ campaignPreviewRemoveDisabled?: string;
757
+ /** Auto-action to perform when campaign preview offer times out (ACCEPT, SKIP, REMOVE) */
758
+ campaignPreviewAutoAction?: string;
759
+ /** Timestamp (ms) when the campaign preview offer expires */
760
+ campaignPreviewOfferTimeout?: string;
705
761
  };
706
762
  /** Main interaction identifier for related interactions */
707
763
  mainInteractionId?: string;
@@ -139,6 +139,8 @@ describe('webex.cc', () => {
139
139
  dialer: {
140
140
  startOutdial: jest.fn(),
141
141
  acceptPreviewContact: jest.fn(),
142
+ skipPreviewContact: jest.fn(),
143
+ removePreviewContact: jest.fn(),
142
144
  },
143
145
  apiAIAssistant: {
144
146
  sendEvent: jest.fn(),
@@ -300,7 +302,7 @@ describe('webex.cc', () => {
300
302
  const result = await webex.cc.register();
301
303
 
302
304
  // Verify logging calls
303
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting CC SDK registration', {
305
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting CC SDK registration', {
304
306
  module: CC_FILE,
305
307
  method: 'register',
306
308
  });
@@ -413,7 +415,7 @@ describe('webex.cc', () => {
413
415
 
414
416
  await expect(webex.cc.register()).rejects.toThrow('Error while performing register');
415
417
 
416
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting CC SDK registration', {
418
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting CC SDK registration', {
417
419
  module: CC_FILE,
418
420
  method: 'register',
419
421
  });
@@ -661,10 +663,8 @@ describe('webex.cc', () => {
661
663
 
662
664
  expect(emitSpy).toHaveBeenCalledTimes(1);
663
665
  expect(emitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_INCOMING, mockTask);
664
- // Verify message event listener
665
- const messageCallback = mockWebSocketManager.on.mock.calls.find(
666
- (call) => call[0] === 'message'
667
- )[1];
666
+ // Verify websocket message handling
667
+ const messageCallback = webex.cc['handleWebsocketMessage'];
668
668
  const agentStateChangeEventData = {
669
669
  type: CC_EVENTS.AGENT_STATE_CHANGE,
670
670
  data: {some: 'data'},
@@ -814,10 +814,13 @@ describe('webex.cc', () => {
814
814
  const result = await webex.cc.stationLogin(options);
815
815
 
816
816
  // Verify logging calls
817
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station login', {
818
- module: CC_FILE,
819
- method: 'stationLogin',
820
- });
817
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
818
+ `Starting agent station login | loginOption: ${options.loginOption} teamId: ${options.teamId}`,
819
+ {
820
+ module: CC_FILE,
821
+ method: 'stationLogin',
822
+ }
823
+ );
821
824
  expect(LoggerProxy.log).toHaveBeenCalledWith(
822
825
  `Agent station login completed successfully agentId: ${mockData.data.agentId} loginOption: ${mockData.data.loginOption} teamId: ${mockData.data.teamId}`,
823
826
  {
@@ -868,10 +871,13 @@ describe('webex.cc', () => {
868
871
 
869
872
  await expect(webex.cc.stationLogin(options)).rejects.toThrow(error.details.data.reason);
870
873
 
871
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station login', {
872
- module: CC_FILE,
873
- method: 'stationLogin',
874
- });
874
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
875
+ `Starting agent station login | loginOption: ${options.loginOption} teamId: ${options.teamId}`,
876
+ {
877
+ module: CC_FILE,
878
+ method: 'stationLogin',
879
+ }
880
+ );
875
881
  expect(LoggerProxy.error).toHaveBeenCalledWith(
876
882
  `stationLogin failed with reason: ${error.details.data.reason}`,
877
883
  {module: CC_FILE, method: 'stationLogin', trackingId: error.details.trackingId}
@@ -1210,11 +1216,11 @@ describe('webex.cc', () => {
1210
1216
  const webSocketManagerOnSpy = jest.spyOn(webex.cc.services.webSocketManager, 'on');
1211
1217
  await webex.cc['silentRelogin']();
1212
1218
 
1213
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1219
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1214
1220
  module: CC_FILE,
1215
1221
  method: 'silentRelogin',
1216
1222
  });
1217
- expect(LoggerProxy.info).toHaveBeenCalledWith(
1223
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
1218
1224
  'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
1219
1225
  {module: CC_FILE, method: 'silentRelogin'}
1220
1226
  );
@@ -1259,7 +1265,7 @@ describe('webex.cc', () => {
1259
1265
 
1260
1266
  jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
1261
1267
  await webex.cc['silentRelogin']();
1262
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1268
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1263
1269
  module: CC_FILE,
1264
1270
  method: 'silentRelogin',
1265
1271
  });
@@ -1274,7 +1280,7 @@ describe('webex.cc', () => {
1274
1280
  jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
1275
1281
 
1276
1282
  await expect(webex.cc['silentRelogin']()).rejects.toThrow(error);
1277
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1283
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1278
1284
  module: CC_FILE,
1279
1285
  method: 'silentRelogin',
1280
1286
  });
@@ -1316,7 +1322,7 @@ describe('webex.cc', () => {
1316
1322
 
1317
1323
  await webex.cc['silentRelogin']();
1318
1324
 
1319
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1325
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1320
1326
  module: CC_FILE,
1321
1327
  method: 'silentRelogin',
1322
1328
  });
@@ -1726,7 +1732,7 @@ describe('webex.cc', () => {
1726
1732
 
1727
1733
  beforeEach(() => {
1728
1734
  emitSpy = jest.spyOn(webex.cc, 'emit');
1729
- messageCallback = mockWebSocketManager.on.mock.calls.find((c) => c[0] === 'message')[1];
1735
+ messageCallback = webex.cc['handleWebsocketMessage'];
1730
1736
  });
1731
1737
 
1732
1738
  it('should emit AGENT_STATION_LOGIN_SUCCESS on CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS with mapped payload', () => {
@@ -2320,4 +2326,132 @@ describe('webex.cc', () => {
2320
2326
  expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'acceptPreviewContact', CC_FILE);
2321
2327
  });
2322
2328
  });
2329
+
2330
+ describe('skipPreviewContact', () => {
2331
+ const previewPayload = {
2332
+ interactionId: 'interaction-123',
2333
+ campaignId: 'campaign-456',
2334
+ };
2335
+
2336
+ it('should skip preview contact successfully', async () => {
2337
+ const mockResponse = {trackingId: 'track-123'} as AgentContact;
2338
+
2339
+ const skipPreviewContactMock = jest
2340
+ .spyOn(webex.cc.services.dialer, 'skipPreviewContact')
2341
+ .mockResolvedValue(mockResponse);
2342
+
2343
+ const result = await webex.cc.skipPreviewContact(previewPayload);
2344
+
2345
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Skipping campaign preview contact', {
2346
+ module: CC_FILE,
2347
+ method: 'skipPreviewContact',
2348
+ });
2349
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
2350
+ 'Campaign preview contact skipped successfully',
2351
+ {
2352
+ module: CC_FILE,
2353
+ method: 'skipPreviewContact',
2354
+ trackingId: 'track-123',
2355
+ interactionId: previewPayload.interactionId,
2356
+ }
2357
+ );
2358
+
2359
+ expect(skipPreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
2360
+ expect(result).toEqual(mockResponse);
2361
+ });
2362
+
2363
+ it('should handle error during skipPreviewContact', async () => {
2364
+ getErrorDetailsSpy.mockRestore();
2365
+ getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
2366
+
2367
+ const error = {
2368
+ details: {
2369
+ trackingId: '1234',
2370
+ data: {
2371
+ reason: 'Error while performing skipPreviewContact',
2372
+ },
2373
+ },
2374
+ };
2375
+
2376
+ jest.spyOn(webex.cc.services.dialer, 'skipPreviewContact').mockRejectedValue(error);
2377
+
2378
+ await expect(webex.cc.skipPreviewContact(previewPayload)).rejects.toThrow(
2379
+ error.details.data.reason
2380
+ );
2381
+
2382
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Skipping campaign preview contact', {
2383
+ module: CC_FILE,
2384
+ method: 'skipPreviewContact',
2385
+ });
2386
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
2387
+ `skipPreviewContact failed with reason: ${error.details.data.reason}`,
2388
+ {module: CC_FILE, method: 'skipPreviewContact', trackingId: error.details.trackingId}
2389
+ );
2390
+ expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'skipPreviewContact', CC_FILE);
2391
+ });
2392
+ });
2393
+
2394
+ describe('removePreviewContact', () => {
2395
+ const previewPayload = {
2396
+ interactionId: 'interaction-123',
2397
+ campaignId: 'campaign-456',
2398
+ };
2399
+
2400
+ it('should remove preview contact successfully', async () => {
2401
+ const mockResponse = {trackingId: 'track-123'} as AgentContact;
2402
+
2403
+ const removePreviewContactMock = jest
2404
+ .spyOn(webex.cc.services.dialer, 'removePreviewContact')
2405
+ .mockResolvedValue(mockResponse);
2406
+
2407
+ const result = await webex.cc.removePreviewContact(previewPayload);
2408
+
2409
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Removing campaign preview contact', {
2410
+ module: CC_FILE,
2411
+ method: 'removePreviewContact',
2412
+ });
2413
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
2414
+ 'Campaign preview contact removed successfully',
2415
+ {
2416
+ module: CC_FILE,
2417
+ method: 'removePreviewContact',
2418
+ trackingId: 'track-123',
2419
+ interactionId: previewPayload.interactionId,
2420
+ }
2421
+ );
2422
+
2423
+ expect(removePreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
2424
+ expect(result).toEqual(mockResponse);
2425
+ });
2426
+
2427
+ it('should handle error during removePreviewContact', async () => {
2428
+ getErrorDetailsSpy.mockRestore();
2429
+ getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
2430
+
2431
+ const error = {
2432
+ details: {
2433
+ trackingId: '1234',
2434
+ data: {
2435
+ reason: 'Error while performing removePreviewContact',
2436
+ },
2437
+ },
2438
+ };
2439
+
2440
+ jest.spyOn(webex.cc.services.dialer, 'removePreviewContact').mockRejectedValue(error);
2441
+
2442
+ await expect(webex.cc.removePreviewContact(previewPayload)).rejects.toThrow(
2443
+ error.details.data.reason
2444
+ );
2445
+
2446
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Removing campaign preview contact', {
2447
+ module: CC_FILE,
2448
+ method: 'removePreviewContact',
2449
+ });
2450
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
2451
+ `removePreviewContact failed with reason: ${error.details.data.reason}`,
2452
+ {module: CC_FILE, method: 'removePreviewContact', trackingId: error.details.trackingId}
2453
+ );
2454
+ expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'removePreviewContact', CC_FILE);
2455
+ });
2456
+ });
2323
2457
  });
@@ -263,7 +263,7 @@ describe('AgentConfigService', () => {
263
263
 
264
264
  expect(mockWebexRequest.request).toHaveBeenCalledWith({
265
265
  service: mockWccAPIURL,
266
- resource: `organization/${mockOrgId}/v2/auxiliary-code?page=${page}&pageSize=${pageSize}&filter=id=in=(${filter})&attributes=${attributes}`,
266
+ resource: `organization/${mockOrgId}/v2/auxiliary-code?page=${page}&pageSize=${pageSize}&filter=id=in=(${filter})&attributes=${attributes}&desktopProfileFilter=true`,
267
267
  method: 'GET',
268
268
  });
269
269
  expect(result).toEqual(mockResponse.body);
@@ -724,7 +724,7 @@ describe('AgentConfigService', () => {
724
724
  agentProfileId: 'profile123',
725
725
  siteId: 'site789',
726
726
  dbId: 'db123',
727
- defaultDialledNumber: '1234567890',
727
+ deafultDialledNumber: '1234567890',
728
728
  id: 'user001',
729
729
  teamIds: ['team1', 'team2'],
730
730
  };
@@ -864,7 +864,7 @@ describe('AgentConfigService', () => {
864
864
  skillProfileId: 'skillProfile456',
865
865
  siteId: 'site789',
866
866
  dbId: 'db123',
867
- defaultDialledNumber: '1234567890',
867
+ deafultDialledNumber: '1234567890',
868
868
  id: 'user001',
869
869
  teamIds: ['team1', 'team2'],
870
870
  };
@@ -1,5 +1,4 @@
1
1
  import * as Utils from '../../../../../src/services/core/Utils';
2
- import {FALLBACK_DIAL_NUMBER_REGEX} from '../../../../../src/services/core/Utils';
3
2
  import LoggerProxy from '../../../../../src/logger-proxy';
4
3
  import WebexRequest from '../../../../../src/services/core/WebexRequest';
5
4
  import {LoginOption, WebexRequestPayload} from '../../../../../src/types';
@@ -12,6 +11,7 @@ jest.mock('../../../../../src/logger-proxy', () => ({
12
11
  log: jest.fn(),
13
12
  error: jest.fn(),
14
13
  info: jest.fn(),
14
+ warn: jest.fn(),
15
15
  initialize: jest.fn(),
16
16
  },
17
17
  }));
@@ -569,7 +569,7 @@ describe('Utils', () => {
569
569
  const usOnlyEntry = {
570
570
  name: 'US',
571
571
  prefix: '1',
572
- regex: FALLBACK_DIAL_NUMBER_REGEX.source,
572
+ regex: '1[0-9]{3}[2-9][0-9]{6}([,]{1,10}[0-9]+){0,1}',
573
573
  strippedChars: '( )-',
574
574
  };
575
575
 
@@ -585,6 +585,11 @@ describe('Utils', () => {
585
585
  const result = Utils.isValidDialNumber('+442030484377', dialPlanEntries);
586
586
  expect(result).toBe(true);
587
587
  });
588
+
589
+ it('should return true for a European number', () => {
590
+ const result = Utils.isValidDialNumber('6955577166', dialPlanEntries);
591
+ expect(result).toBe(true);
592
+ });
588
593
  });
589
594
 
590
595
  describe('with US-only dial plan entry', () => {
@@ -606,17 +611,95 @@ describe('Utils', () => {
606
611
  });
607
612
  });
608
613
 
609
- describe('with empty dial plan entries (fallback to US regex)', () => {
610
- it('should return true for a valid US phone number', () => {
611
- const result = Utils.isValidDialNumber('12223334567', []);
614
+ describe('with empty dial plan entries (defers to server)', () => {
615
+ it('should return true for any dial number', () => {
616
+ expect(Utils.isValidDialNumber('12223334567', [])).toBe(true);
617
+ });
618
+
619
+ it('should return true for a UK phone number', () => {
620
+ expect(Utils.isValidDialNumber('+442030484377', [])).toBe(true);
621
+ });
622
+
623
+ it('should return true for a European number', () => {
624
+ expect(Utils.isValidDialNumber('6955577166', [])).toBe(true);
625
+ });
626
+ });
627
+
628
+ describe('strippedChars handling', () => {
629
+ it('should strip characters before regex matching', () => {
630
+ const strictEntry = {
631
+ name: 'Digits Only',
632
+ prefix: '',
633
+ regex: '^[0-9]{10,15}$',
634
+ strippedChars: '( )-+',
635
+ };
636
+ const result = Utils.isValidDialNumber('+44 (203) 048-4377', [strictEntry]);
612
637
  expect(result).toBe(true);
613
638
  });
614
639
 
615
- it('should return false for a UK phone number', () => {
616
- const result = Utils.isValidDialNumber('+442030484377', []);
640
+ it('should handle entries with no strippedChars', () => {
641
+ const noStripEntry = {
642
+ name: 'No Strip',
643
+ prefix: '',
644
+ regex: '^[0-9]+$',
645
+ strippedChars: '',
646
+ };
647
+ expect(Utils.isValidDialNumber('12345', [noStripEntry])).toBe(true);
648
+ expect(Utils.isValidDialNumber('+12345', [noStripEntry])).toBe(false);
649
+ });
650
+ });
651
+
652
+ describe('empty or undefined dial number', () => {
653
+ it('should return false and log warning for undefined dial number', () => {
654
+ const result = Utils.isValidDialNumber(undefined as any, [anyFormatEntry]);
655
+ expect(result).toBe(false);
656
+ expect(LoggerProxy.warn).toHaveBeenCalledWith(
657
+ 'Dial number is empty or undefined.',
658
+ expect.objectContaining({module: 'Utils', method: 'isValidDialNumber'})
659
+ );
660
+ });
661
+
662
+ it('should return false and log warning for empty string dial number', () => {
663
+ const result = Utils.isValidDialNumber('', [anyFormatEntry]);
664
+ expect(result).toBe(false);
665
+ expect(LoggerProxy.warn).toHaveBeenCalledWith(
666
+ 'Dial number is empty or undefined.',
667
+ expect.objectContaining({module: 'Utils', method: 'isValidDialNumber'})
668
+ );
669
+ });
670
+ });
671
+
672
+ describe('invalid regex handling', () => {
673
+ it('should return false and log warning for invalid regex pattern', () => {
674
+ const badEntry = {
675
+ name: 'Bad Regex',
676
+ prefix: '',
677
+ regex: '[invalid(',
678
+ strippedChars: '',
679
+ };
680
+ const result = Utils.isValidDialNumber('12345', [badEntry]);
617
681
  expect(result).toBe(false);
682
+ expect(LoggerProxy.warn).toHaveBeenCalledWith(
683
+ expect.stringContaining('Failed to validate dial number against entry "Bad Regex"'),
684
+ expect.objectContaining({module: 'Utils', method: 'isValidDialNumber'})
685
+ );
618
686
  });
619
687
  });
620
688
  });
621
689
 
690
+ describe('stripDialPlanChars', () => {
691
+ it('should remove specified characters from input', () => {
692
+ expect(Utils.stripDialPlanChars('+44 (203) 048-4377', '( )-+')).toBe('442030484377');
693
+ });
694
+
695
+ it('should return input unchanged when strippedChars is empty', () => {
696
+ expect(Utils.stripDialPlanChars('+442030484377', '')).toBe('+442030484377');
697
+ });
698
+
699
+ it('should return input unchanged when strippedChars is null/undefined', () => {
700
+ expect(Utils.stripDialPlanChars('12345', null as any)).toBe('12345');
701
+ expect(Utils.stripDialPlanChars('12345', undefined as any)).toBe('12345');
702
+ });
703
+ });
704
+
622
705
  });