@webex/contact-center 3.12.0-next.7 → 3.12.0-next.70

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 (63) hide show
  1. package/dist/cc.js +212 -23
  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 +94 -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 +56 -0
  27. package/dist/services/task/types.js.map +1 -1
  28. package/dist/types/cc.d.ts +61 -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 +65 -1
  37. package/dist/types/types.d.ts +2 -0
  38. package/dist/types.js.map +1 -1
  39. package/dist/webex.js +1 -1
  40. package/package.json +9 -9
  41. package/src/cc.ts +270 -24
  42. package/src/constants.ts +2 -0
  43. package/src/metrics/behavioral-events.ts +28 -0
  44. package/src/metrics/constants.ts +4 -0
  45. package/src/services/config/Util.ts +1 -1
  46. package/src/services/config/constants.ts +1 -1
  47. package/src/services/config/types.ts +6 -1
  48. package/src/services/core/Err.ts +2 -0
  49. package/src/services/core/Utils.ts +43 -8
  50. package/src/services/task/TaskManager.ts +106 -22
  51. package/src/services/task/constants.ts +2 -0
  52. package/src/services/task/dialer.ts +80 -0
  53. package/src/services/task/index.ts +7 -2
  54. package/src/services/task/types.ts +70 -0
  55. package/src/types.ts +2 -0
  56. package/test/unit/spec/cc.ts +210 -20
  57. package/test/unit/spec/services/config/index.ts +3 -3
  58. package/test/unit/spec/services/core/Utils.ts +90 -7
  59. package/test/unit/spec/services/task/TaskManager.ts +252 -7
  60. package/test/unit/spec/services/task/dialer.ts +190 -0
  61. package/test/unit/spec/services/task/index.ts +21 -0
  62. package/umd/contact-center.min.js +2 -2
  63. package/umd/contact-center.min.js.map +1 -1
@@ -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(),
@@ -200,6 +202,35 @@ describe('webex.cc', () => {
200
202
  webex.emit('ready');
201
203
  });
202
204
 
205
+ it('should throw when WebRTC registration is disabled without multi-login', () => {
206
+ const invalidWebex = MockWebex({
207
+ children: {
208
+ mercury: Mercury,
209
+ },
210
+ logger: {
211
+ log: jest.fn(),
212
+ error: jest.fn(),
213
+ info: jest.fn(),
214
+ },
215
+ credentials: {
216
+ getOrgId: jest.fn(() => 'mockOrgId'),
217
+ },
218
+ config: {
219
+ ...config,
220
+ cc: {
221
+ ...config.cc,
222
+ allowMultiLogin: false,
223
+ disableWebRTCRegistration: true,
224
+ },
225
+ },
226
+ once: jest.fn((event, callback) => callback()),
227
+ }) as unknown as WebexSDK;
228
+
229
+ expect(() => new ContactCenter({parent: invalidWebex})).toThrow(
230
+ 'Invalid Contact Center configuration: disableWebRTCRegistration cannot be true when allowMultiLogin is false. Enable allowMultiLogin or allow WebRTC registration so an SDK instance can receive Mobius/WebRTC task events.'
231
+ );
232
+ });
233
+
203
234
  describe('cc.getDeviceId', () => {
204
235
  it('should return dialNumber when loginOption is EXTENSION', () => {
205
236
  const loginOption = LoginOption.EXTENSION;
@@ -300,7 +331,7 @@ describe('webex.cc', () => {
300
331
  const result = await webex.cc.register();
301
332
 
302
333
  // Verify logging calls
303
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting CC SDK registration', {
334
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting CC SDK registration', {
304
335
  module: CC_FILE,
305
336
  method: 'register',
306
337
  });
@@ -334,6 +365,10 @@ describe('webex.cc', () => {
334
365
  TASK_EVENTS.TASK_HYDRATE,
335
366
  expect.any(Function)
336
367
  );
368
+ expect(mockTaskManager.on).toHaveBeenCalledWith(
369
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
370
+ expect.any(Function)
371
+ );
337
372
  expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
338
373
  expect(webex.cc.services.rtdWebSocketManager.initWebSocket).toHaveBeenCalledWith({
339
374
  body: {
@@ -413,7 +448,7 @@ describe('webex.cc', () => {
413
448
 
414
449
  await expect(webex.cc.register()).rejects.toThrow('Error while performing register');
415
450
 
416
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting CC SDK registration', {
451
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting CC SDK registration', {
417
452
  module: CC_FILE,
418
453
  method: 'register',
419
454
  });
@@ -482,6 +517,10 @@ describe('webex.cc', () => {
482
517
  TASK_EVENTS.TASK_HYDRATE,
483
518
  expect.any(Function)
484
519
  );
520
+ expect(mockTaskManager.on).toHaveBeenCalledWith(
521
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
522
+ expect.any(Function)
523
+ );
485
524
  expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
486
525
 
487
526
  expect(configSpy).toHaveBeenCalled();
@@ -661,10 +700,8 @@ describe('webex.cc', () => {
661
700
 
662
701
  expect(emitSpy).toHaveBeenCalledTimes(1);
663
702
  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];
703
+ // Verify websocket message handling
704
+ const messageCallback = webex.cc['handleWebsocketMessage'];
668
705
  const agentStateChangeEventData = {
669
706
  type: CC_EVENTS.AGENT_STATE_CHANGE,
670
707
  data: {some: 'data'},
@@ -814,10 +851,13 @@ describe('webex.cc', () => {
814
851
  const result = await webex.cc.stationLogin(options);
815
852
 
816
853
  // Verify logging calls
817
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station login', {
818
- module: CC_FILE,
819
- method: 'stationLogin',
820
- });
854
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
855
+ `Starting agent station login | loginOption: ${options.loginOption} teamId: ${options.teamId}`,
856
+ {
857
+ module: CC_FILE,
858
+ method: 'stationLogin',
859
+ }
860
+ );
821
861
  expect(LoggerProxy.log).toHaveBeenCalledWith(
822
862
  `Agent station login completed successfully agentId: ${mockData.data.agentId} loginOption: ${mockData.data.loginOption} teamId: ${mockData.data.teamId}`,
823
863
  {
@@ -868,10 +908,13 @@ describe('webex.cc', () => {
868
908
 
869
909
  await expect(webex.cc.stationLogin(options)).rejects.toThrow(error.details.data.reason);
870
910
 
871
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station login', {
872
- module: CC_FILE,
873
- method: 'stationLogin',
874
- });
911
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
912
+ `Starting agent station login | loginOption: ${options.loginOption} teamId: ${options.teamId}`,
913
+ {
914
+ module: CC_FILE,
915
+ method: 'stationLogin',
916
+ }
917
+ );
875
918
  expect(LoggerProxy.error).toHaveBeenCalledWith(
876
919
  `stationLogin failed with reason: ${error.details.data.reason}`,
877
920
  {module: CC_FILE, method: 'stationLogin', trackingId: error.details.trackingId}
@@ -1210,11 +1253,11 @@ describe('webex.cc', () => {
1210
1253
  const webSocketManagerOnSpy = jest.spyOn(webex.cc.services.webSocketManager, 'on');
1211
1254
  await webex.cc['silentRelogin']();
1212
1255
 
1213
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1256
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1214
1257
  module: CC_FILE,
1215
1258
  method: 'silentRelogin',
1216
1259
  });
1217
- expect(LoggerProxy.info).toHaveBeenCalledWith(
1260
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
1218
1261
  'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
1219
1262
  {module: CC_FILE, method: 'silentRelogin'}
1220
1263
  );
@@ -1259,7 +1302,7 @@ describe('webex.cc', () => {
1259
1302
 
1260
1303
  jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
1261
1304
  await webex.cc['silentRelogin']();
1262
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1305
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1263
1306
  module: CC_FILE,
1264
1307
  method: 'silentRelogin',
1265
1308
  });
@@ -1274,7 +1317,7 @@ describe('webex.cc', () => {
1274
1317
  jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
1275
1318
 
1276
1319
  await expect(webex.cc['silentRelogin']()).rejects.toThrow(error);
1277
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1320
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1278
1321
  module: CC_FILE,
1279
1322
  method: 'silentRelogin',
1280
1323
  });
@@ -1316,7 +1359,7 @@ describe('webex.cc', () => {
1316
1359
 
1317
1360
  await webex.cc['silentRelogin']();
1318
1361
 
1319
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1362
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1320
1363
  module: CC_FILE,
1321
1364
  method: 'silentRelogin',
1322
1365
  });
@@ -1580,6 +1623,10 @@ describe('webex.cc', () => {
1580
1623
  TASK_EVENTS.TASK_HYDRATE,
1581
1624
  expect.any(Function)
1582
1625
  );
1626
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1627
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1628
+ expect.any(Function)
1629
+ );
1583
1630
  expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
1584
1631
  expect(webex.cc.services.rtdWebSocketManager.off).toHaveBeenCalledWith(
1585
1632
  'message',
@@ -1636,6 +1683,13 @@ describe('webex.cc', () => {
1636
1683
  const [, hydrateCallback] = hydrateCalls[0];
1637
1684
  expect(hydrateCallback).toBe(webex.cc['handleTaskHydrate']);
1638
1685
 
1686
+ const multiLoginHydrateCalls = mockTaskManager.off.mock.calls.filter(
1687
+ ([evt]) => evt === TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE
1688
+ );
1689
+ expect(multiLoginHydrateCalls).toHaveLength(1);
1690
+ const [, multiLoginHydrateCallback] = multiLoginHydrateCalls[0];
1691
+ expect(multiLoginHydrateCallback).toBe(webex.cc['handleTaskMultiLoginHydrate']);
1692
+
1639
1693
  const messageCalls = mockWebSocketManager.off.mock.calls.filter(([evt]) => evt === 'message');
1640
1694
  expect(messageCalls).toHaveLength(1);
1641
1695
  const [, messageCallback] = messageCalls[0];
@@ -1661,6 +1715,10 @@ describe('webex.cc', () => {
1661
1715
  TASK_EVENTS.TASK_HYDRATE,
1662
1716
  expect.any(Function)
1663
1717
  );
1718
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1719
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1720
+ expect.any(Function)
1721
+ );
1664
1722
  expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
1665
1723
  expect(webex.cc.services.connectionService.off).toHaveBeenCalledWith(
1666
1724
  'connectionLost',
@@ -1704,6 +1762,10 @@ describe('webex.cc', () => {
1704
1762
  TASK_EVENTS.TASK_HYDRATE,
1705
1763
  expect.any(Function)
1706
1764
  );
1765
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1766
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1767
+ expect.any(Function)
1768
+ );
1707
1769
 
1708
1770
  expect(LoggerProxy.error).toHaveBeenCalledWith(`Error during deregister: ${mockError}`, {
1709
1771
  module: CC_FILE,
@@ -1726,7 +1788,7 @@ describe('webex.cc', () => {
1726
1788
 
1727
1789
  beforeEach(() => {
1728
1790
  emitSpy = jest.spyOn(webex.cc, 'emit');
1729
- messageCallback = mockWebSocketManager.on.mock.calls.find((c) => c[0] === 'message')[1];
1791
+ messageCallback = webex.cc['handleWebsocketMessage'];
1730
1792
  });
1731
1793
 
1732
1794
  it('should emit AGENT_STATION_LOGIN_SUCCESS on CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS with mapped payload', () => {
@@ -2320,4 +2382,132 @@ describe('webex.cc', () => {
2320
2382
  expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'acceptPreviewContact', CC_FILE);
2321
2383
  });
2322
2384
  });
2385
+
2386
+ describe('skipPreviewContact', () => {
2387
+ const previewPayload = {
2388
+ interactionId: 'interaction-123',
2389
+ campaignId: 'campaign-456',
2390
+ };
2391
+
2392
+ it('should skip preview contact successfully', async () => {
2393
+ const mockResponse = {trackingId: 'track-123'} as AgentContact;
2394
+
2395
+ const skipPreviewContactMock = jest
2396
+ .spyOn(webex.cc.services.dialer, 'skipPreviewContact')
2397
+ .mockResolvedValue(mockResponse);
2398
+
2399
+ const result = await webex.cc.skipPreviewContact(previewPayload);
2400
+
2401
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Skipping campaign preview contact', {
2402
+ module: CC_FILE,
2403
+ method: 'skipPreviewContact',
2404
+ });
2405
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
2406
+ 'Campaign preview contact skipped successfully',
2407
+ {
2408
+ module: CC_FILE,
2409
+ method: 'skipPreviewContact',
2410
+ trackingId: 'track-123',
2411
+ interactionId: previewPayload.interactionId,
2412
+ }
2413
+ );
2414
+
2415
+ expect(skipPreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
2416
+ expect(result).toEqual(mockResponse);
2417
+ });
2418
+
2419
+ it('should handle error during skipPreviewContact', async () => {
2420
+ getErrorDetailsSpy.mockRestore();
2421
+ getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
2422
+
2423
+ const error = {
2424
+ details: {
2425
+ trackingId: '1234',
2426
+ data: {
2427
+ reason: 'Error while performing skipPreviewContact',
2428
+ },
2429
+ },
2430
+ };
2431
+
2432
+ jest.spyOn(webex.cc.services.dialer, 'skipPreviewContact').mockRejectedValue(error);
2433
+
2434
+ await expect(webex.cc.skipPreviewContact(previewPayload)).rejects.toThrow(
2435
+ error.details.data.reason
2436
+ );
2437
+
2438
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Skipping campaign preview contact', {
2439
+ module: CC_FILE,
2440
+ method: 'skipPreviewContact',
2441
+ });
2442
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
2443
+ `skipPreviewContact failed with reason: ${error.details.data.reason}`,
2444
+ {module: CC_FILE, method: 'skipPreviewContact', trackingId: error.details.trackingId}
2445
+ );
2446
+ expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'skipPreviewContact', CC_FILE);
2447
+ });
2448
+ });
2449
+
2450
+ describe('removePreviewContact', () => {
2451
+ const previewPayload = {
2452
+ interactionId: 'interaction-123',
2453
+ campaignId: 'campaign-456',
2454
+ };
2455
+
2456
+ it('should remove preview contact successfully', async () => {
2457
+ const mockResponse = {trackingId: 'track-123'} as AgentContact;
2458
+
2459
+ const removePreviewContactMock = jest
2460
+ .spyOn(webex.cc.services.dialer, 'removePreviewContact')
2461
+ .mockResolvedValue(mockResponse);
2462
+
2463
+ const result = await webex.cc.removePreviewContact(previewPayload);
2464
+
2465
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Removing campaign preview contact', {
2466
+ module: CC_FILE,
2467
+ method: 'removePreviewContact',
2468
+ });
2469
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
2470
+ 'Campaign preview contact removed successfully',
2471
+ {
2472
+ module: CC_FILE,
2473
+ method: 'removePreviewContact',
2474
+ trackingId: 'track-123',
2475
+ interactionId: previewPayload.interactionId,
2476
+ }
2477
+ );
2478
+
2479
+ expect(removePreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
2480
+ expect(result).toEqual(mockResponse);
2481
+ });
2482
+
2483
+ it('should handle error during removePreviewContact', async () => {
2484
+ getErrorDetailsSpy.mockRestore();
2485
+ getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
2486
+
2487
+ const error = {
2488
+ details: {
2489
+ trackingId: '1234',
2490
+ data: {
2491
+ reason: 'Error while performing removePreviewContact',
2492
+ },
2493
+ },
2494
+ };
2495
+
2496
+ jest.spyOn(webex.cc.services.dialer, 'removePreviewContact').mockRejectedValue(error);
2497
+
2498
+ await expect(webex.cc.removePreviewContact(previewPayload)).rejects.toThrow(
2499
+ error.details.data.reason
2500
+ );
2501
+
2502
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Removing campaign preview contact', {
2503
+ module: CC_FILE,
2504
+ method: 'removePreviewContact',
2505
+ });
2506
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
2507
+ `removePreviewContact failed with reason: ${error.details.data.reason}`,
2508
+ {module: CC_FILE, method: 'removePreviewContact', trackingId: error.details.trackingId}
2509
+ );
2510
+ expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'removePreviewContact', CC_FILE);
2511
+ });
2512
+ });
2323
2513
  });
@@ -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
  });