@webex/contact-center 3.8.1 → 3.9.0-multi-llms.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/dist/cc.js +196 -47
  2. package/dist/cc.js.map +1 -1
  3. package/dist/constants.js +1 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/index.js +13 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/logger-proxy.js +24 -1
  8. package/dist/logger-proxy.js.map +1 -1
  9. package/dist/metrics/MetricsManager.js +1 -1
  10. package/dist/metrics/MetricsManager.js.map +1 -1
  11. package/dist/metrics/behavioral-events.js +88 -0
  12. package/dist/metrics/behavioral-events.js.map +1 -1
  13. package/dist/metrics/constants.js +32 -2
  14. package/dist/metrics/constants.js.map +1 -1
  15. package/dist/services/AddressBook.js +271 -0
  16. package/dist/services/AddressBook.js.map +1 -0
  17. package/dist/services/EntryPoint.js +227 -0
  18. package/dist/services/EntryPoint.js.map +1 -0
  19. package/dist/services/Queue.js +261 -0
  20. package/dist/services/Queue.js.map +1 -0
  21. package/dist/services/config/constants.js +36 -2
  22. package/dist/services/config/constants.js.map +1 -1
  23. package/dist/services/config/index.js +29 -21
  24. package/dist/services/config/index.js.map +1 -1
  25. package/dist/services/config/types.js +33 -1
  26. package/dist/services/config/types.js.map +1 -1
  27. package/dist/services/core/GlobalTypes.js.map +1 -1
  28. package/dist/services/core/Utils.js +162 -2
  29. package/dist/services/core/Utils.js.map +1 -1
  30. package/dist/services/core/aqm-reqs.js +0 -4
  31. package/dist/services/core/aqm-reqs.js.map +1 -1
  32. package/dist/services/core/websocket/WebSocketManager.js +0 -4
  33. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  34. package/dist/services/task/TaskManager.js +74 -2
  35. package/dist/services/task/TaskManager.js.map +1 -1
  36. package/dist/services/task/constants.js +7 -1
  37. package/dist/services/task/constants.js.map +1 -1
  38. package/dist/services/task/contact.js +86 -0
  39. package/dist/services/task/contact.js.map +1 -1
  40. package/dist/services/task/index.js +384 -72
  41. package/dist/services/task/index.js.map +1 -1
  42. package/dist/services/task/types.js +14 -0
  43. package/dist/services/task/types.js.map +1 -1
  44. package/dist/types/cc.d.ts +115 -35
  45. package/dist/types/constants.d.ts +1 -0
  46. package/dist/types/index.d.ts +8 -3
  47. package/dist/types/metrics/constants.d.ts +25 -1
  48. package/dist/types/services/AddressBook.d.ts +74 -0
  49. package/dist/types/services/EntryPoint.d.ts +67 -0
  50. package/dist/types/services/Queue.d.ts +76 -0
  51. package/dist/types/services/config/constants.d.ts +35 -1
  52. package/dist/types/services/config/index.d.ts +6 -9
  53. package/dist/types/services/config/types.d.ts +79 -58
  54. package/dist/types/services/core/GlobalTypes.d.ts +25 -0
  55. package/dist/types/services/core/Utils.d.ts +40 -1
  56. package/dist/types/services/task/constants.d.ts +6 -0
  57. package/dist/types/services/task/contact.d.ts +10 -0
  58. package/dist/types/services/task/index.d.ts +44 -2
  59. package/dist/types/services/task/types.d.ts +125 -1
  60. package/dist/types/types.d.ts +162 -0
  61. package/dist/types/utils/PageCache.d.ts +173 -0
  62. package/dist/types.js +17 -0
  63. package/dist/types.js.map +1 -1
  64. package/dist/utils/PageCache.js +192 -0
  65. package/dist/utils/PageCache.js.map +1 -0
  66. package/dist/webex.js +1 -1
  67. package/package.json +11 -10
  68. package/src/cc.ts +221 -52
  69. package/src/constants.ts +1 -0
  70. package/src/index.ts +19 -3
  71. package/src/logger-proxy.ts +24 -1
  72. package/src/metrics/MetricsManager.ts +1 -1
  73. package/src/metrics/behavioral-events.ts +92 -0
  74. package/src/metrics/constants.ts +37 -1
  75. package/src/services/AddressBook.ts +291 -0
  76. package/src/services/EntryPoint.ts +241 -0
  77. package/src/services/Queue.ts +277 -0
  78. package/src/services/config/constants.ts +42 -2
  79. package/src/services/config/index.ts +30 -30
  80. package/src/services/config/types.ts +59 -58
  81. package/src/services/core/GlobalTypes.ts +27 -0
  82. package/src/services/core/Utils.ts +199 -1
  83. package/src/services/core/aqm-reqs.ts +0 -5
  84. package/src/services/core/websocket/WebSocketManager.ts +0 -4
  85. package/src/services/task/TaskManager.ts +79 -3
  86. package/src/services/task/constants.ts +6 -0
  87. package/src/services/task/contact.ts +80 -0
  88. package/src/services/task/index.ts +457 -57
  89. package/src/services/task/types.ts +135 -0
  90. package/src/types.ts +180 -0
  91. package/src/utils/PageCache.ts +252 -0
  92. package/test/unit/spec/cc.ts +77 -84
  93. package/test/unit/spec/metrics/MetricsManager.ts +0 -1
  94. package/test/unit/spec/metrics/behavioral-events.ts +56 -0
  95. package/test/unit/spec/services/AddressBook.ts +332 -0
  96. package/test/unit/spec/services/EntryPoint.ts +259 -0
  97. package/test/unit/spec/services/Queue.ts +323 -0
  98. package/test/unit/spec/services/config/index.ts +279 -65
  99. package/test/unit/spec/services/core/Utils.ts +50 -0
  100. package/test/unit/spec/services/core/aqm-reqs.ts +1 -3
  101. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +0 -4
  102. package/test/unit/spec/services/task/TaskManager.ts +145 -1
  103. package/test/unit/spec/services/task/contact.ts +31 -1
  104. package/test/unit/spec/services/task/index.ts +410 -123
  105. package/umd/contact-center.min.js +2 -2
  106. package/umd/contact-center.min.js.map +1 -1
@@ -7,6 +7,11 @@ import {
7
7
  WebexSDK,
8
8
  } from '../../../src/types';
9
9
  import ContactCenter from '../../../src/cc';
10
+ import EntryPoint from '../../../src/services/EntryPoint';
11
+ import type {EntryPointListResponse} from '../../../src/types';
12
+ import AddressBook from '../../../src/services/AddressBook';
13
+ import Queue from '../../../src/services/Queue';
14
+ import type {ContactServiceQueuesResponse} from '../../../src/types';
10
15
  import MockWebex from '@webex/test-helper-mock-webex';
11
16
  import {StationLoginSuccess, AGENT_EVENTS} from '../../../src/services/agent/types';
12
17
  import {SetStateResponse} from '../../../src/types';
@@ -134,6 +139,7 @@ describe('webex.cc', () => {
134
139
  webSocketManager: mockWebSocketManager,
135
140
  task: undefined,
136
141
  setWrapupData: jest.fn(),
142
+ setAgentId: jest.fn(),
137
143
  registerIncomingCallEvent: jest.fn(),
138
144
  registerTaskListeners: jest.fn(),
139
145
  getTask: jest.fn(),
@@ -1310,7 +1316,7 @@ describe('webex.cc', () => {
1310
1316
  });
1311
1317
 
1312
1318
  describe('startOutdial', () => {
1313
- it('should make outdial call successfully.', async () => {
1319
+ it('should make outdial call successfully without origin.', async () => {
1314
1320
  // Setup outDialEp.
1315
1321
  webex.cc.agentConfig = {
1316
1322
  outDialEp: 'test-entry-point',
@@ -1319,9 +1325,10 @@ describe('webex.cc', () => {
1319
1325
  // destination number required for making outdial call.
1320
1326
  const destination = '1234567890';
1321
1327
 
1322
- // Construct Payload for startOutdial.
1328
+ // Construct Payload for startOutdial without origin.
1323
1329
  const newPayload = {
1324
1330
  destination,
1331
+ origin: undefined,
1325
1332
  entryPointId: 'test-entry-point',
1326
1333
  direction: OUTDIAL_DIRECTION,
1327
1334
  attributes: ATTRIBUTES,
@@ -1351,6 +1358,49 @@ describe('webex.cc', () => {
1351
1358
  expect(result).toEqual(mockResponse);
1352
1359
  });
1353
1360
 
1361
+ it('should make outdial call successfully with origin.', async () => {
1362
+ // Setup outDialEp.
1363
+ webex.cc.agentConfig = {
1364
+ outDialEp: 'test-entry-point',
1365
+ };
1366
+
1367
+ // destination number and origin for making outdial call.
1368
+ const destination = '1234567890';
1369
+ const origin = '+19403016307';
1370
+
1371
+ // Construct Payload for startOutdial with origin.
1372
+ const newPayload = {
1373
+ destination,
1374
+ origin,
1375
+ entryPointId: 'test-entry-point',
1376
+ direction: OUTDIAL_DIRECTION,
1377
+ attributes: ATTRIBUTES,
1378
+ mediaType: OUTDIAL_MEDIA_TYPE,
1379
+ outboundType: OUTBOUND_TYPE,
1380
+ } as const;
1381
+
1382
+ const mockResponse = {} as AgentContact;
1383
+
1384
+ const startOutdialMock = jest
1385
+ .spyOn(webex.cc.services.dialer, 'startOutdial')
1386
+ .mockResolvedValue(mockResponse);
1387
+
1388
+ const result = await webex.cc.startOutdial(destination, origin);
1389
+
1390
+ // Verify logging calls
1391
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Starting outbound dial', {
1392
+ module: CC_FILE,
1393
+ method: 'startOutdial',
1394
+ });
1395
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Outbound dial completed successfully', {
1396
+ module: CC_FILE,
1397
+ method: 'startOutdial',
1398
+ });
1399
+
1400
+ expect(startOutdialMock).toHaveBeenCalledWith({data: newPayload});
1401
+ expect(result).toEqual(mockResponse);
1402
+ });
1403
+
1354
1404
  it('should handle error during startOutdial', async () => {
1355
1405
  // Setup outDialEp.
1356
1406
  webex.cc.agentConfig = {
@@ -1387,88 +1437,6 @@ describe('webex.cc', () => {
1387
1437
  });
1388
1438
  });
1389
1439
 
1390
- describe('getQueues', () => {
1391
- it('should return queues response when successful', async () => {
1392
- const mockQueuesResponse = [
1393
- {
1394
- queueId: 'queue1',
1395
- queueName: 'Queue 1',
1396
- },
1397
- {
1398
- queueId: 'queue2',
1399
- queueName: 'Queue 2',
1400
- },
1401
- ];
1402
-
1403
- webex.cc.services.config.getQueues = jest.fn().mockResolvedValue(mockQueuesResponse);
1404
-
1405
- const result = await webex.cc.getQueues();
1406
-
1407
- // Verify logging calls
1408
- expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching queues', {
1409
- module: CC_FILE,
1410
- method: 'getQueues',
1411
- });
1412
- expect(LoggerProxy.log).toHaveBeenCalledWith(
1413
- `Successfully retrieved ${result.length} queues`,
1414
- {
1415
- module: CC_FILE,
1416
- method: 'getQueues',
1417
- }
1418
- );
1419
-
1420
- expect(webex.cc.services.config.getQueues).toHaveBeenCalledWith(
1421
- 'mockOrgId',
1422
- 0,
1423
- 100,
1424
- undefined,
1425
- undefined
1426
- );
1427
- expect(result).toEqual(mockQueuesResponse);
1428
- });
1429
-
1430
- it('should throw an error if orgId is not present', async () => {
1431
- jest.spyOn(webex.credentials, 'getOrgId').mockResolvedValue(undefined);
1432
- webex.cc.services.config.getQueues = jest.fn();
1433
-
1434
- try {
1435
- await webex.cc.getQueues();
1436
- } catch (error) {
1437
- expect(error).toEqual(new Error('Org ID not found.'));
1438
- expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching queues', {
1439
- module: CC_FILE,
1440
- method: 'getQueues',
1441
- });
1442
- expect(LoggerProxy.error).toHaveBeenCalledWith('Org ID not found.', {
1443
- module: CC_FILE,
1444
- method: 'getQueues',
1445
- });
1446
- expect(webex.cc.services.config.getQueues).not.toHaveBeenCalled();
1447
- }
1448
- });
1449
-
1450
- it('should throw an error if config getQueues throws an error', async () => {
1451
- webex.cc.services.config.getQueues = jest.fn().mockRejectedValue(new Error('Test error.'));
1452
-
1453
- try {
1454
- await webex.cc.getQueues();
1455
- } catch (error) {
1456
- expect(error).toEqual(new Error('Test error.'));
1457
- expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching queues', {
1458
- module: CC_FILE,
1459
- method: 'getQueues',
1460
- });
1461
- expect(webex.cc.services.config.getQueues).toHaveBeenCalledWith(
1462
- 'mockOrgId',
1463
- 0,
1464
- 100,
1465
- undefined,
1466
- undefined
1467
- );
1468
- }
1469
- });
1470
- });
1471
-
1472
1440
  describe('uploadLogs', () => {
1473
1441
  it('should upload logs successfully', async () => {
1474
1442
  const uploadLogsMock = jest.spyOn(webex.cc.webexRequest, 'uploadLogs').mockResolvedValue({
@@ -1796,6 +1764,31 @@ describe('webex.cc', () => {
1796
1764
  });
1797
1765
  });
1798
1766
 
1767
+ describe('API property exposure', () => {
1768
+ it('should provide getEntryPoints wrapper that delegates to EntryPoint', async () => {
1769
+ const spy = jest
1770
+ .spyOn(EntryPoint.prototype, 'getEntryPoints')
1771
+ .mockResolvedValue({} as EntryPointListResponse);
1772
+ await webex.cc.getEntryPoints();
1773
+ expect(spy).toHaveBeenCalled();
1774
+ spy.mockRestore();
1775
+ });
1776
+
1777
+ it('should expose addressBook API', () => {
1778
+ expect(webex.cc.addressBook).toBeDefined();
1779
+ expect(webex.cc.addressBook).toBeInstanceOf(AddressBook);
1780
+ });
1781
+
1782
+ it('should provide getQueues wrapper that delegates to Queue', async () => {
1783
+ const spy = jest
1784
+ .spyOn(Queue.prototype, 'getQueues')
1785
+ .mockResolvedValue({} as ContactServiceQueuesResponse);
1786
+ await webex.cc.getQueues();
1787
+ expect(spy).toHaveBeenCalled();
1788
+ spy.mockRestore();
1789
+ });
1790
+ });
1791
+
1799
1792
  describe('updateAgentProfile', () => {
1800
1793
  beforeEach(() => {
1801
1794
  webex.cc.agentConfig = {
@@ -3,7 +3,6 @@ import MetricsManager from '../../../../src/metrics/MetricsManager';
3
3
  import {METRIC_EVENT_NAMES} from '../../../../src/metrics/constants';
4
4
  import {WebexSDK} from '../../../../src/types';
5
5
  import {EventPayload} from '@webex/internal-plugin-metrics/src/metrics.types';
6
- import LoggerProxy from '../../../../src/logger-proxy';
7
6
 
8
7
  describe('MetricsManagerImplementation', () => {
9
8
  let webex: WebexSDK;
@@ -12,6 +12,20 @@ describe('metrics/behavioral-events', () => {
12
12
  verb: 'set',
13
13
  });
14
14
 
15
+ expect(getEventTaxonomy(METRIC_EVENT_NAMES.AGENT_CONTACT_ASSIGN_FAILED)).toEqual({
16
+ product,
17
+ agent: 'service',
18
+ target: 'agent_contact_assign',
19
+ verb: 'fail',
20
+ });
21
+
22
+ expect(getEventTaxonomy(METRIC_EVENT_NAMES.AGENT_INVITE_FAILED)).toEqual({
23
+ product,
24
+ agent: 'service',
25
+ target: 'agent_invite',
26
+ verb: 'fail',
27
+ });
28
+
15
29
  expect(getEventTaxonomy(METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS)).toEqual({
16
30
  product,
17
31
  agent: 'user',
@@ -96,6 +110,48 @@ describe('metrics/behavioral-events', () => {
96
110
  verb: 'fail',
97
111
  });
98
112
 
113
+ expect(getEventTaxonomy(METRIC_EVENT_NAMES.TASK_CONFERENCE_START_SUCCESS)).toEqual({
114
+ product,
115
+ agent: 'user',
116
+ target: 'task_conference_start',
117
+ verb: 'complete',
118
+ });
119
+
120
+ expect(getEventTaxonomy(METRIC_EVENT_NAMES.TASK_CONFERENCE_START_FAILED)).toEqual({
121
+ product,
122
+ agent: 'user',
123
+ target: 'task_conference_start',
124
+ verb: 'fail',
125
+ });
126
+
127
+ expect(getEventTaxonomy(METRIC_EVENT_NAMES.TASK_CONFERENCE_END_SUCCESS)).toEqual({
128
+ product,
129
+ agent: 'user',
130
+ target: 'task_conference_end',
131
+ verb: 'complete',
132
+ });
133
+
134
+ expect(getEventTaxonomy(METRIC_EVENT_NAMES.TASK_CONFERENCE_END_FAILED)).toEqual({
135
+ product,
136
+ agent: 'user',
137
+ target: 'task_conference_end',
138
+ verb: 'fail',
139
+ });
140
+
141
+ expect(getEventTaxonomy(METRIC_EVENT_NAMES.TASK_CONFERENCE_TRANSFER_SUCCESS)).toEqual({
142
+ product,
143
+ agent: 'user',
144
+ target: 'task_conference_transfer',
145
+ verb: 'complete',
146
+ });
147
+
148
+ expect(getEventTaxonomy(METRIC_EVENT_NAMES.TASK_CONFERENCE_TRANSFER_FAILED)).toEqual({
149
+ product,
150
+ agent: 'user',
151
+ target: 'task_conference_transfer',
152
+ verb: 'fail',
153
+ });
154
+
99
155
  expect(getEventTaxonomy('' as METRIC_EVENT_NAMES)).toEqual(undefined);
100
156
  });
101
157
  });
@@ -0,0 +1,332 @@
1
+ import AddressBook from '../../../../src/services/AddressBook';
2
+ import {HTTP_METHODS, WebexSDK, IHttpResponse} from '../../../../src/types';
3
+ import {METRIC_EVENT_NAMES} from '../../../../src/metrics/constants';
4
+ import WebexRequest from '../../../../src/services/core/WebexRequest';
5
+ import MetricsManager from '../../../../src/metrics/MetricsManager';
6
+ import LoggerProxy from '../../../../src/logger-proxy';
7
+
8
+ jest.mock('../../../../src/metrics/MetricsManager');
9
+ jest.mock('../../../../src/logger-proxy');
10
+
11
+ describe('AddressBook', () => {
12
+ let addressBookAPI: AddressBook;
13
+ let mockWebex: WebexSDK;
14
+ let mockMetricsManager: jest.Mocked<MetricsManager>;
15
+
16
+ const mockGetAddressBookId = jest.fn().mockReturnValue('test-address-book-id');
17
+
18
+ beforeEach(() => {
19
+ jest.clearAllMocks();
20
+ (WebexRequest as any).instance = undefined;
21
+
22
+ mockWebex = {
23
+ credentials: {
24
+ getOrgId: jest.fn().mockReturnValue('test-org-id'),
25
+ },
26
+ request: jest.fn(),
27
+ internal: {
28
+ newMetrics: {
29
+ submitBehavioralEvent: jest.fn(),
30
+ submitOperationalEvent: jest.fn(),
31
+ submitBusinessEvent: jest.fn(),
32
+ },
33
+ },
34
+ ready: true,
35
+ once: jest.fn(),
36
+ } as unknown as WebexSDK;
37
+
38
+ mockMetricsManager = {
39
+ trackEvent: jest.fn(),
40
+ timeEvent: jest.fn(),
41
+ } as unknown as jest.Mocked<MetricsManager>;
42
+ (MetricsManager.getInstance as jest.Mock).mockReturnValue(mockMetricsManager);
43
+
44
+ addressBookAPI = new AddressBook(mockWebex, mockGetAddressBookId);
45
+ });
46
+
47
+ describe('constructor', () => {
48
+ it('should initialize with all required dependencies', () => {
49
+ expect(WebexRequest.getInstance({webex: mockWebex})).toBeDefined();
50
+ expect(MetricsManager.getInstance).toHaveBeenCalledWith({webex: mockWebex});
51
+ });
52
+ });
53
+
54
+ describe('getEntries', () => {
55
+ const mockEntries = [
56
+ {
57
+ id: 'entry1',
58
+ name: 'John Doe',
59
+ number: '+1234567890',
60
+ organizationId: 'test-org-id',
61
+ },
62
+ {
63
+ id: 'entry2',
64
+ name: 'Jane Smith',
65
+ number: '+0987654321',
66
+ organizationId: 'test-org-id',
67
+ },
68
+ ];
69
+
70
+ const mockResponse: IHttpResponse = {
71
+ statusCode: 200,
72
+ method: 'GET',
73
+ url: '/organization/test-org-id/v2/address-book/test-address-book-id/entry',
74
+ headers: {} as any,
75
+ body: {
76
+ data: mockEntries,
77
+ meta: {
78
+ page: 0,
79
+ pageSize: 100,
80
+ totalPages: 1,
81
+ totalRecords: 2,
82
+ orgid: 'test-org-id',
83
+ },
84
+ },
85
+ };
86
+
87
+ beforeEach(() => {
88
+ jest.spyOn(Date, 'now').mockReturnValue(1000);
89
+ });
90
+
91
+ afterEach(() => {
92
+ jest.restoreAllMocks();
93
+ });
94
+
95
+ it('should fetch address book entries successfully with default parameters', async () => {
96
+ (mockWebex.request as jest.Mock).mockResolvedValue(mockResponse);
97
+
98
+ const result = await addressBookAPI.getEntries();
99
+
100
+ expect(mockWebex.request).toHaveBeenCalledWith({
101
+ service: 'wcc-api-gateway',
102
+ resource: '/organization/test-org-id/v2/address-book/test-address-book-id/entry?page=0&pageSize=100',
103
+ method: HTTP_METHODS.GET,
104
+ });
105
+
106
+ expect(result).toEqual(mockResponse.body);
107
+ expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith(METRIC_EVENT_NAMES.ADDRESSBOOK_FETCH_SUCCESS);
108
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
109
+ METRIC_EVENT_NAMES.ADDRESSBOOK_FETCH_SUCCESS,
110
+ {
111
+ orgId: 'test-org-id',
112
+ bookId: 'test-address-book-id',
113
+ statusCode: 200,
114
+ recordCount: 2,
115
+ totalRecords: 2,
116
+ isSearchRequest: false,
117
+ isFirstPage: true,
118
+ },
119
+ ['behavioral', 'operational']
120
+ );
121
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching address book entries', {
122
+ module: 'AddressBook',
123
+ method: 'getEntries',
124
+ data: expect.objectContaining({
125
+ orgId: 'test-org-id',
126
+ bookId: 'test-address-book-id',
127
+ page: 0,
128
+ pageSize: 100,
129
+ isSearchRequest: false,
130
+ }),
131
+ });
132
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Making API request to fetch address book entries', {
133
+ module: 'AddressBook',
134
+ method: 'getEntries',
135
+ data: {
136
+ resource: '/organization/test-org-id/v2/address-book/test-address-book-id/entry?page=0&pageSize=100',
137
+ service: 'wcc-api-gateway',
138
+ },
139
+ });
140
+ });
141
+
142
+ it('should fetch entries with custom parameters', async () => {
143
+ (mockWebex.request as jest.Mock).mockResolvedValue(mockResponse);
144
+
145
+ const params = {
146
+ addressBookId: 'custom-book-id',
147
+ page: 1,
148
+ pageSize: 25,
149
+ search: 'john',
150
+ filter: 'name=="John Doe"',
151
+ attributes: 'id,name,number',
152
+ };
153
+
154
+ const result = await addressBookAPI.getEntries(params);
155
+
156
+ expect(mockWebex.request).toHaveBeenCalledWith({
157
+ service: 'wcc-api-gateway',
158
+ resource: '/organization/test-org-id/v2/address-book/custom-book-id/entry?page=1&pageSize=25&filter=name%3D%3D%22John+Doe%22&attributes=id%2Cname%2Cnumber&search=john',
159
+ method: HTTP_METHODS.GET,
160
+ });
161
+
162
+ expect(result).toEqual(mockResponse.body);
163
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
164
+ METRIC_EVENT_NAMES.ADDRESSBOOK_FETCH_SUCCESS,
165
+ {
166
+ orgId: 'test-org-id',
167
+ bookId: 'custom-book-id',
168
+ statusCode: 200,
169
+ recordCount: 2,
170
+ totalRecords: 2,
171
+ isSearchRequest: true,
172
+ isFirstPage: false,
173
+ },
174
+ ['behavioral', 'operational']
175
+ );
176
+ });
177
+
178
+ it('should handle API errors and track metrics', async () => {
179
+ (mockWebex.request as jest.Mock).mockRejectedValue(new Error('Internal Server Error'));
180
+
181
+ await expect(addressBookAPI.getEntries()).rejects.toThrow('Internal Server Error');
182
+
183
+ expect(mockWebex.request).toHaveBeenCalledWith({
184
+ service: 'wcc-api-gateway',
185
+ resource: '/organization/test-org-id/v2/address-book/test-address-book-id/entry?page=0&pageSize=100',
186
+ method: HTTP_METHODS.GET,
187
+ });
188
+
189
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
190
+ METRIC_EVENT_NAMES.ADDRESSBOOK_FETCH_FAILED,
191
+ expect.objectContaining({
192
+ orgId: 'test-org-id',
193
+ bookId: 'test-address-book-id',
194
+ error: 'Internal Server Error',
195
+ isSearchRequest: false,
196
+ page: 0,
197
+ pageSize: 100,
198
+ }),
199
+ ['behavioral', 'operational']
200
+ );
201
+ expect(LoggerProxy.error).toHaveBeenCalledWith('Failed to fetch address book entries', {
202
+ module: 'AddressBook',
203
+ method: 'getEntries',
204
+ data: expect.objectContaining({
205
+ orgId: 'test-org-id',
206
+ bookId: 'test-address-book-id',
207
+ error: 'Internal Server Error',
208
+ isSearchRequest: false,
209
+ page: 0,
210
+ pageSize: 100,
211
+ }),
212
+ error: expect.any(Error),
213
+ });
214
+ });
215
+
216
+ it('should not track metrics for subsequent pages in simple pagination', async () => {
217
+ const mockResponsePage2: IHttpResponse = {
218
+ statusCode: 200,
219
+ method: 'GET',
220
+ url: '/organization/test-org-id/v2/address-book/test-address-book-id/entry',
221
+ headers: {} as any,
222
+ body: {
223
+ data: mockEntries,
224
+ meta: {
225
+ page: 2,
226
+ pageSize: 100,
227
+ totalPages: 3,
228
+ totalRecords: 2,
229
+ orgid: 'test-org-id',
230
+ },
231
+ },
232
+ };
233
+
234
+ (mockWebex.request as jest.Mock).mockResolvedValueOnce(mockResponsePage2);
235
+
236
+ const result = await addressBookAPI.getEntries({page: 2});
237
+
238
+ expect(result).toEqual(mockResponsePage2.body);
239
+ expect(mockMetricsManager.trackEvent).not.toHaveBeenCalledWith(
240
+ METRIC_EVENT_NAMES.ADDRESSBOOK_FETCH_SUCCESS,
241
+ expect.any(Object),
242
+ expect.any(Array)
243
+ );
244
+ });
245
+
246
+ it('should track metrics for search requests on a valid non-first page', async () => {
247
+ (mockWebex.request as jest.Mock).mockResolvedValue(mockResponse);
248
+
249
+ const result = await addressBookAPI.getEntries({page: 1, search: 'test'});
250
+ expect(result).toEqual(mockResponse.body);
251
+
252
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
253
+ METRIC_EVENT_NAMES.ADDRESSBOOK_FETCH_SUCCESS,
254
+ {
255
+ orgId: 'test-org-id',
256
+ bookId: 'test-address-book-id',
257
+ statusCode: 200,
258
+ recordCount: 2,
259
+ totalRecords: 2,
260
+ isSearchRequest: true,
261
+ isFirstPage: false,
262
+ },
263
+ ['behavioral', 'operational']
264
+ );
265
+ });
266
+
267
+ it('should return cached data for repeat simple pagination calls', async () => {
268
+ (mockWebex.request as jest.Mock).mockResolvedValueOnce(mockResponse);
269
+
270
+ const first = await addressBookAPI.getEntries({page: 0});
271
+ expect(first).toEqual(mockResponse.body);
272
+
273
+ const callsBefore = (mockWebex.request as jest.Mock).mock.calls.length;
274
+ const second = await addressBookAPI.getEntries({page: 0});
275
+ const callsAfter = (mockWebex.request as jest.Mock).mock.calls.length;
276
+
277
+ expect(second.data).toEqual(mockResponse.body.data);
278
+ expect(second.meta).toEqual(
279
+ expect.objectContaining({
280
+ page: 0,
281
+ pageSize: 100,
282
+ totalPages: 1,
283
+ totalRecords: 2,
284
+ })
285
+ );
286
+ expect(callsAfter).toBe(callsBefore);
287
+ });
288
+
289
+ it('should call API when requested page is not cached (cache miss)', async () => {
290
+ (mockWebex.request as jest.Mock).mockResolvedValueOnce(mockResponse);
291
+
292
+ await addressBookAPI.getEntries({page: 0});
293
+
294
+ (mockWebex.request as jest.Mock).mockResolvedValueOnce({
295
+ ...mockResponse,
296
+ body: {
297
+ data: mockEntries,
298
+ meta: {
299
+ page: 1,
300
+ pageSize: 100,
301
+ totalPages: 2,
302
+ totalRecords: 2,
303
+ orgid: 'test-org-id',
304
+ },
305
+ },
306
+ });
307
+
308
+ const callsBefore = (mockWebex.request as jest.Mock).mock.calls.length;
309
+ (LoggerProxy.info as jest.Mock).mockClear();
310
+ (LoggerProxy.log as jest.Mock).mockClear();
311
+
312
+ const result = await addressBookAPI.getEntries({page: 1});
313
+
314
+ const callsAfter = (mockWebex.request as jest.Mock).mock.calls.length;
315
+ expect(callsAfter).toBe(callsBefore + 1);
316
+ expect(result.meta.page).toBe(1);
317
+ expect(mockWebex.request).toHaveBeenCalledWith({
318
+ service: 'wcc-api-gateway',
319
+ resource: '/organization/test-org-id/v2/address-book/test-address-book-id/entry?page=1&pageSize=100',
320
+ method: HTTP_METHODS.GET,
321
+ });
322
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Making API request to fetch address book entries', {
323
+ module: 'AddressBook',
324
+ method: 'getEntries',
325
+ data: {
326
+ resource: '/organization/test-org-id/v2/address-book/test-address-book-id/entry?page=1&pageSize=100',
327
+ service: 'wcc-api-gateway',
328
+ },
329
+ });
330
+ });
331
+ });
332
+ });