@webex/contact-center 3.11.0 → 3.12.0-mobius-socket.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.
- package/dist/cc.js +121 -28
- package/dist/cc.js.map +1 -1
- package/dist/constants.js +5 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/metrics/behavioral-events.js +13 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +9 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/ApiAiAssistant.js +173 -0
- package/dist/services/ApiAiAssistant.js.map +1 -0
- package/dist/services/agent/types.js.map +1 -1
- package/dist/services/config/Util.js +6 -2
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/constants.js +12 -0
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/index.js +41 -2
- package/dist/services/config/index.js.map +1 -1
- package/dist/services/config/types.js +19 -1
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/constants.js +27 -1
- package/dist/services/constants.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/core/Utils.js +28 -6
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/core/aqm-reqs.js +92 -17
- package/dist/services/core/aqm-reqs.js.map +1 -1
- package/dist/services/core/websocket/WebSocketManager.js +20 -5
- package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
- package/dist/services/core/websocket/connection-service.js +3 -1
- package/dist/services/core/websocket/connection-service.js.map +1 -1
- package/dist/services/index.js +6 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/task/TaskManager.js +117 -24
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +16 -3
- package/dist/services/task/TaskUtils.js.map +1 -1
- package/dist/services/task/constants.js +15 -1
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/dialer.js +51 -0
- package/dist/services/task/dialer.js.map +1 -1
- package/dist/services/task/types.js +15 -0
- package/dist/services/task/types.js.map +1 -1
- package/dist/types/cc.d.ts +801 -0
- package/dist/types/config.d.ts +66 -0
- package/dist/types/constants.d.ts +50 -0
- package/dist/types/index.d.ts +184 -0
- package/dist/types/logger-proxy.d.ts +71 -0
- package/dist/types/metrics/MetricsManager.d.ts +223 -0
- package/dist/types/metrics/behavioral-events.d.ts +29 -0
- package/dist/types/metrics/constants.d.ts +161 -0
- package/dist/types/services/AddressBook.d.ts +74 -0
- package/dist/types/services/ApiAiAssistant.d.ts +31 -0
- package/dist/types/services/EntryPoint.d.ts +67 -0
- package/dist/types/services/Queue.d.ts +76 -0
- package/dist/types/services/WebCallingService.d.ts +1 -0
- package/dist/types/services/agent/index.d.ts +46 -0
- package/dist/types/services/agent/types.d.ts +413 -0
- package/dist/types/services/config/Util.d.ts +20 -0
- package/dist/types/services/config/constants.d.ts +249 -0
- package/dist/types/services/config/index.d.ts +177 -0
- package/dist/types/services/config/types.d.ts +1207 -0
- package/dist/types/services/constants.d.ts +110 -0
- package/dist/types/services/core/Err.d.ts +121 -0
- package/dist/types/services/core/GlobalTypes.d.ts +58 -0
- package/dist/types/services/core/Utils.d.ts +101 -0
- package/dist/types/services/core/WebexRequest.d.ts +22 -0
- package/dist/types/services/core/aqm-reqs.d.ts +65 -0
- package/dist/types/services/core/constants.d.ts +99 -0
- package/dist/types/services/core/types.d.ts +47 -0
- package/dist/types/services/core/websocket/WebSocketManager.d.ts +35 -0
- package/dist/types/services/core/websocket/connection-service.d.ts +27 -0
- package/dist/types/services/core/websocket/keepalive.worker.d.ts +2 -0
- package/dist/types/services/core/websocket/types.d.ts +37 -0
- package/dist/types/services/index.d.ts +54 -0
- package/dist/types/services/task/AutoWrapup.d.ts +40 -0
- package/dist/types/services/task/TaskManager.d.ts +1 -0
- package/dist/types/services/task/TaskUtils.d.ts +92 -0
- package/dist/types/services/task/constants.d.ts +84 -0
- package/dist/types/services/task/contact.d.ts +69 -0
- package/dist/types/services/task/dialer.d.ts +43 -0
- package/dist/types/services/task/index.d.ts +650 -0
- package/dist/types/services/task/types.d.ts +1319 -0
- package/dist/types/types.d.ts +643 -0
- package/dist/types/utils/PageCache.d.ts +173 -0
- package/dist/types/webex-config.d.ts +53 -0
- package/dist/types/webex.d.ts +7 -0
- package/dist/types.js +14 -1
- package/dist/types.js.map +1 -1
- package/dist/webex.js +1 -1
- package/package.json +9 -9
- package/src/cc.ts +157 -30
- package/src/constants.ts +4 -0
- package/src/index.ts +1 -0
- package/src/metrics/behavioral-events.ts +14 -0
- package/src/metrics/constants.ts +11 -0
- package/src/services/ApiAiAssistant.ts +217 -0
- package/src/services/agent/types.ts +1 -1
- package/src/services/config/Util.ts +8 -0
- package/src/services/config/constants.ts +12 -0
- package/src/services/config/index.ts +45 -1
- package/src/services/config/types.ts +67 -0
- package/src/services/constants.ts +29 -0
- package/src/services/core/Err.ts +1 -0
- package/src/services/core/Utils.ts +32 -5
- package/src/services/core/aqm-reqs.ts +100 -22
- package/src/services/core/websocket/WebSocketManager.ts +21 -6
- package/src/services/core/websocket/connection-service.ts +5 -1
- package/src/services/index.ts +4 -0
- package/src/services/task/TaskManager.ts +174 -27
- package/src/services/task/TaskUtils.ts +12 -0
- package/src/services/task/constants.ts +16 -0
- package/src/services/task/dialer.ts +56 -1
- package/src/services/task/types.ts +24 -0
- package/src/types.ts +40 -1
- package/test/unit/spec/cc.ts +163 -23
- package/test/unit/spec/services/ApiAiAssistant.ts +115 -0
- package/test/unit/spec/services/config/index.ts +56 -0
- package/test/unit/spec/services/core/Utils.ts +63 -1
- package/test/unit/spec/services/core/websocket/WebSocketManager.ts +82 -12
- package/test/unit/spec/services/core/websocket/connection-service.ts +3 -1
- package/test/unit/spec/services/task/TaskManager.ts +1119 -251
- package/test/unit/spec/services/task/dialer.ts +198 -112
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
package/test/unit/spec/cc.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type {ContactServiceQueuesResponse} from '../../../src/types';
|
|
|
15
15
|
import MockWebex from '@webex/test-helper-mock-webex';
|
|
16
16
|
import {StationLoginSuccess, AGENT_EVENTS} from '../../../src/services/agent/types';
|
|
17
17
|
import {SetStateResponse} from '../../../src/types';
|
|
18
|
-
import {AGENT, WEB_RTC_PREFIX} from '../../../src/services/constants';
|
|
18
|
+
import {AGENT, SUBSCRIBE_API, WEB_RTC_PREFIX} from '../../../src/services/constants';
|
|
19
19
|
import Services from '../../../src/services';
|
|
20
20
|
import config from '../../../src/config';
|
|
21
21
|
import {CC_EVENTS} from '../../../src/services/config/types';
|
|
@@ -88,6 +88,8 @@ describe('webex.cc', () => {
|
|
|
88
88
|
initWebSocket: jest.fn(),
|
|
89
89
|
on: jest.fn(),
|
|
90
90
|
off: jest.fn(),
|
|
91
|
+
close: jest.fn(),
|
|
92
|
+
isSocketClosed: false,
|
|
91
93
|
};
|
|
92
94
|
|
|
93
95
|
mockContact = {
|
|
@@ -121,6 +123,13 @@ describe('webex.cc', () => {
|
|
|
121
123
|
getOutdialAniEntries: jest.fn(),
|
|
122
124
|
},
|
|
123
125
|
webSocketManager: mockWebSocketManager,
|
|
126
|
+
rtdWebSocketManager: {
|
|
127
|
+
initWebSocket: jest.fn().mockResolvedValue({}),
|
|
128
|
+
on: jest.fn(),
|
|
129
|
+
off: jest.fn(),
|
|
130
|
+
close: jest.fn(),
|
|
131
|
+
isSocketClosed: false,
|
|
132
|
+
},
|
|
124
133
|
connectionService: {
|
|
125
134
|
on: jest.fn(),
|
|
126
135
|
off: jest.fn(),
|
|
@@ -129,6 +138,11 @@ describe('webex.cc', () => {
|
|
|
129
138
|
|
|
130
139
|
dialer: {
|
|
131
140
|
startOutdial: jest.fn(),
|
|
141
|
+
acceptPreviewContact: jest.fn(),
|
|
142
|
+
},
|
|
143
|
+
apiAIAssistant: {
|
|
144
|
+
sendEvent: jest.fn(),
|
|
145
|
+
fetchHistoricTranscripts: jest.fn(),
|
|
132
146
|
},
|
|
133
147
|
};
|
|
134
148
|
|
|
@@ -142,6 +156,8 @@ describe('webex.cc', () => {
|
|
|
142
156
|
setWrapupData: jest.fn(),
|
|
143
157
|
setAgentId: jest.fn(),
|
|
144
158
|
setWebRtcEnabled: jest.fn(),
|
|
159
|
+
handleRealtimeTranscriptEvent: jest.fn(),
|
|
160
|
+
setApiAIAssistant: jest.fn(),
|
|
145
161
|
registerIncomingCallEvent: jest.fn(),
|
|
146
162
|
registerTaskListeners: jest.fn(),
|
|
147
163
|
getTask: jest.fn(),
|
|
@@ -262,6 +278,7 @@ describe('webex.cc', () => {
|
|
|
262
278
|
};
|
|
263
279
|
|
|
264
280
|
it('should register successfully and return agent profile', async () => {
|
|
281
|
+
mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: true}} as any;
|
|
265
282
|
const mercuryConnect = jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
|
|
266
283
|
const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
|
|
267
284
|
const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
|
|
@@ -305,6 +322,7 @@ describe('webex.cc', () => {
|
|
|
305
322
|
clientType: 'WebexCCSDK',
|
|
306
323
|
allowMultiLogin: false,
|
|
307
324
|
},
|
|
325
|
+
resource: SUBSCRIBE_API,
|
|
308
326
|
});
|
|
309
327
|
|
|
310
328
|
// TODO: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-626777 Implement the de-register method and close the listener there
|
|
@@ -317,6 +335,19 @@ describe('webex.cc', () => {
|
|
|
317
335
|
expect.any(Function)
|
|
318
336
|
);
|
|
319
337
|
expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
|
|
338
|
+
expect(webex.cc.services.rtdWebSocketManager.initWebSocket).toHaveBeenCalledWith({
|
|
339
|
+
body: {
|
|
340
|
+
force: true,
|
|
341
|
+
isKeepAliveEnabled: false,
|
|
342
|
+
clientType: 'WebexCCSDK',
|
|
343
|
+
allowMultiLogin: false,
|
|
344
|
+
},
|
|
345
|
+
resource: 'v1/realtime/subscribe',
|
|
346
|
+
});
|
|
347
|
+
expect(webex.cc.services.rtdWebSocketManager.on).toHaveBeenCalledWith(
|
|
348
|
+
'message',
|
|
349
|
+
expect.any(Function)
|
|
350
|
+
);
|
|
320
351
|
|
|
321
352
|
expect(configSpy).toHaveBeenCalled();
|
|
322
353
|
expect(LoggerProxy.log).toHaveBeenCalledWith('Agent config is fetched successfully', {
|
|
@@ -360,6 +391,7 @@ describe('webex.cc', () => {
|
|
|
360
391
|
clientType: 'WebexCCSDK',
|
|
361
392
|
allowMultiLogin: true,
|
|
362
393
|
},
|
|
394
|
+
resource: SUBSCRIBE_API,
|
|
363
395
|
});
|
|
364
396
|
expect(configSpy).toHaveBeenCalled();
|
|
365
397
|
expect(LoggerProxy.log).toHaveBeenCalledWith('Agent config is fetched successfully', {
|
|
@@ -439,6 +471,7 @@ describe('webex.cc', () => {
|
|
|
439
471
|
clientType: 'WebexCCSDK',
|
|
440
472
|
allowMultiLogin: false,
|
|
441
473
|
},
|
|
474
|
+
resource: SUBSCRIBE_API,
|
|
442
475
|
});
|
|
443
476
|
|
|
444
477
|
expect(mockTaskManager.on).toHaveBeenCalledWith(
|
|
@@ -462,6 +495,7 @@ describe('webex.cc', () => {
|
|
|
462
495
|
|
|
463
496
|
it('should not attempt for mercury connection when webrtc is disabled', async () => {
|
|
464
497
|
mockAgentProfile.webRtcEnabled = false;
|
|
498
|
+
mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: false}} as any;
|
|
465
499
|
const mercurySpy = jest.spyOn(webex.internal.mercury, 'connect');
|
|
466
500
|
const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
|
|
467
501
|
const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
|
|
@@ -491,12 +525,39 @@ describe('webex.cc', () => {
|
|
|
491
525
|
clientType: 'WebexCCSDK',
|
|
492
526
|
allowMultiLogin: false,
|
|
493
527
|
},
|
|
528
|
+
resource: SUBSCRIBE_API,
|
|
494
529
|
});
|
|
495
530
|
|
|
496
531
|
expect(configSpy).toHaveBeenCalled();
|
|
497
532
|
expect(mercurySpy).not.toHaveBeenCalled();
|
|
533
|
+
expect(webex.cc.services.rtdWebSocketManager.initWebSocket).not.toHaveBeenCalled();
|
|
498
534
|
expect(result).toEqual(mockAgentProfile);
|
|
499
535
|
});
|
|
536
|
+
|
|
537
|
+
it('should not connect RTD websocket when realtime transcripts feature is disabled', async () => {
|
|
538
|
+
mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: false}} as any;
|
|
539
|
+
jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
|
|
540
|
+
jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
|
|
541
|
+
data: {
|
|
542
|
+
auxCodeId: 'auxCodeId',
|
|
543
|
+
agentId: 'agentId',
|
|
544
|
+
deviceType: LoginOption.EXTENSION,
|
|
545
|
+
dn: '12345',
|
|
546
|
+
},
|
|
547
|
+
});
|
|
548
|
+
jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue(mockAgentProfile);
|
|
549
|
+
mockWebSocketManager.initWebSocket.mockResolvedValue({
|
|
550
|
+
agentId: 'agent123',
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
await webex.cc.register();
|
|
554
|
+
|
|
555
|
+
expect(webex.cc.services.rtdWebSocketManager.initWebSocket).not.toHaveBeenCalled();
|
|
556
|
+
expect(webex.cc.services.rtdWebSocketManager.on).not.toHaveBeenCalledWith(
|
|
557
|
+
'message',
|
|
558
|
+
expect.any(Function)
|
|
559
|
+
);
|
|
560
|
+
});
|
|
500
561
|
});
|
|
501
562
|
|
|
502
563
|
describe('stationLogin', () => {
|
|
@@ -1502,6 +1563,13 @@ describe('webex.cc', () => {
|
|
|
1502
1563
|
});
|
|
1503
1564
|
|
|
1504
1565
|
it('should unregister successfully and clean up all resources when webrtc is enabled', async () => {
|
|
1566
|
+
webex.cc.services.rtdWebSocketManager = {
|
|
1567
|
+
isSocketClosed: false,
|
|
1568
|
+
close: jest.fn(),
|
|
1569
|
+
off: jest.fn(),
|
|
1570
|
+
on: jest.fn(),
|
|
1571
|
+
} as any;
|
|
1572
|
+
|
|
1505
1573
|
await webex.cc.deregister();
|
|
1506
1574
|
|
|
1507
1575
|
expect(mockTaskManager.off).toHaveBeenCalledWith(
|
|
@@ -1513,12 +1581,20 @@ describe('webex.cc', () => {
|
|
|
1513
1581
|
expect.any(Function)
|
|
1514
1582
|
);
|
|
1515
1583
|
expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
|
|
1584
|
+
expect(webex.cc.services.rtdWebSocketManager.off).toHaveBeenCalledWith(
|
|
1585
|
+
'message',
|
|
1586
|
+
expect.any(Function)
|
|
1587
|
+
);
|
|
1516
1588
|
expect(webex.cc.services.connectionService.off).toHaveBeenCalledWith(
|
|
1517
1589
|
'connectionLost',
|
|
1518
1590
|
expect.any(Function)
|
|
1519
1591
|
);
|
|
1520
1592
|
|
|
1521
1593
|
expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
|
|
1594
|
+
expect(webex.cc.services.rtdWebSocketManager.close).toHaveBeenCalledWith(
|
|
1595
|
+
false,
|
|
1596
|
+
'Unregistering the RTD websocket'
|
|
1597
|
+
);
|
|
1522
1598
|
expect(webex.cc.agentConfig).toBeNull();
|
|
1523
1599
|
|
|
1524
1600
|
expect(webex.internal.mercury.off).toHaveBeenCalledWith('online');
|
|
@@ -1850,7 +1926,7 @@ describe('webex.cc', () => {
|
|
|
1850
1926
|
);
|
|
1851
1927
|
|
|
1852
1928
|
expect(webex.cc.stationLogout).toHaveBeenCalledWith({
|
|
1853
|
-
logoutReason: 'User requested agent
|
|
1929
|
+
logoutReason: 'User requested agent profile update',
|
|
1854
1930
|
});
|
|
1855
1931
|
expect(webex.cc.stationLogin).toHaveBeenCalledWith({
|
|
1856
1932
|
teamId: 'teamId',
|
|
@@ -1934,24 +2010,26 @@ describe('webex.cc', () => {
|
|
|
1934
2010
|
});
|
|
1935
2011
|
});
|
|
1936
2012
|
|
|
1937
|
-
it('should
|
|
1938
|
-
webex.cc.webCallingService.loginOption = LoginOption.
|
|
2013
|
+
it('should allow update when loginOption and teamId are unchanged (e.g. dialNumber or profile refresh)', async () => {
|
|
2014
|
+
webex.cc.webCallingService.loginOption = LoginOption.AGENT_DN;
|
|
1939
2015
|
const data = {
|
|
1940
2016
|
teamId: 'teamId',
|
|
1941
|
-
loginOption: LoginOption.
|
|
1942
|
-
dialNumber: '',
|
|
2017
|
+
loginOption: LoginOption.AGENT_DN,
|
|
2018
|
+
dialNumber: '1234',
|
|
1943
2019
|
};
|
|
1944
|
-
const
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
2020
|
+
const logoutSpy = jest.spyOn(webex.cc, 'stationLogout').mockResolvedValue({});
|
|
2021
|
+
const loginSpy = jest.spyOn(webex.cc, 'stationLogin').mockResolvedValue({
|
|
2022
|
+
type: 'AgentDeviceTypeUpdateSuccess',
|
|
2023
|
+
} as any);
|
|
2024
|
+
|
|
2025
|
+
await expect(webex.cc.updateAgentProfile(data)).resolves.toBeDefined();
|
|
2026
|
+
expect(logoutSpy).toHaveBeenCalledWith({
|
|
2027
|
+
logoutReason: 'User requested agent profile update',
|
|
2028
|
+
});
|
|
2029
|
+
expect(loginSpy).toHaveBeenCalledWith({
|
|
2030
|
+
teamId: data.teamId,
|
|
2031
|
+
loginOption: data.loginOption,
|
|
2032
|
+
dialNumber: data.dialNumber,
|
|
1955
2033
|
});
|
|
1956
2034
|
});
|
|
1957
2035
|
|
|
@@ -2104,7 +2182,9 @@ describe('webex.cc', () => {
|
|
|
2104
2182
|
const detailedError = new Error('Detailed service error');
|
|
2105
2183
|
getErrorDetailsSpy.mockReturnValue({error: detailedError});
|
|
2106
2184
|
|
|
2107
|
-
await expect(webex.cc.getOutdialAniEntries(mockParams)).rejects.toThrow(
|
|
2185
|
+
await expect(webex.cc.getOutdialAniEntries(mockParams)).rejects.toThrow(
|
|
2186
|
+
'Detailed service error'
|
|
2187
|
+
);
|
|
2108
2188
|
|
|
2109
2189
|
// Verify failure metrics are tracked
|
|
2110
2190
|
expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
|
|
@@ -2128,11 +2208,7 @@ describe('webex.cc', () => {
|
|
|
2128
2208
|
);
|
|
2129
2209
|
|
|
2130
2210
|
// Verify getErrorDetails was called
|
|
2131
|
-
expect(getErrorDetailsSpy).toHaveBeenCalledWith(
|
|
2132
|
-
mockError,
|
|
2133
|
-
'getOutdialAniEntries',
|
|
2134
|
-
CC_FILE
|
|
2135
|
-
);
|
|
2211
|
+
expect(getErrorDetailsSpy).toHaveBeenCalledWith(mockError, 'getOutdialAniEntries', CC_FILE);
|
|
2136
2212
|
});
|
|
2137
2213
|
|
|
2138
2214
|
it('should throw error when orgId is not found', async () => {
|
|
@@ -2180,4 +2256,68 @@ describe('webex.cc', () => {
|
|
|
2180
2256
|
);
|
|
2181
2257
|
});
|
|
2182
2258
|
});
|
|
2259
|
+
|
|
2260
|
+
describe('acceptPreviewContact', () => {
|
|
2261
|
+
const previewPayload = {
|
|
2262
|
+
interactionId: 'interaction-123',
|
|
2263
|
+
campaignId: 'campaign-456',
|
|
2264
|
+
};
|
|
2265
|
+
|
|
2266
|
+
it('should accept preview contact successfully', async () => {
|
|
2267
|
+
const mockResponse = {trackingId: 'track-123'} as AgentContact;
|
|
2268
|
+
|
|
2269
|
+
const acceptPreviewContactMock = jest
|
|
2270
|
+
.spyOn(webex.cc.services.dialer, 'acceptPreviewContact')
|
|
2271
|
+
.mockResolvedValue(mockResponse);
|
|
2272
|
+
|
|
2273
|
+
const result = await webex.cc.acceptPreviewContact(previewPayload);
|
|
2274
|
+
|
|
2275
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Accepting campaign preview contact', {
|
|
2276
|
+
module: CC_FILE,
|
|
2277
|
+
method: 'acceptPreviewContact',
|
|
2278
|
+
});
|
|
2279
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith(
|
|
2280
|
+
'Campaign preview contact accepted successfully',
|
|
2281
|
+
{
|
|
2282
|
+
module: CC_FILE,
|
|
2283
|
+
method: 'acceptPreviewContact',
|
|
2284
|
+
trackingId: 'track-123',
|
|
2285
|
+
interactionId: previewPayload.interactionId,
|
|
2286
|
+
}
|
|
2287
|
+
);
|
|
2288
|
+
|
|
2289
|
+
expect(acceptPreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
|
|
2290
|
+
expect(result).toEqual(mockResponse);
|
|
2291
|
+
});
|
|
2292
|
+
|
|
2293
|
+
it('should handle error during acceptPreviewContact', async () => {
|
|
2294
|
+
getErrorDetailsSpy.mockRestore();
|
|
2295
|
+
getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
|
|
2296
|
+
|
|
2297
|
+
const error = {
|
|
2298
|
+
details: {
|
|
2299
|
+
trackingId: '1234',
|
|
2300
|
+
data: {
|
|
2301
|
+
reason: 'Error while performing acceptPreviewContact',
|
|
2302
|
+
},
|
|
2303
|
+
},
|
|
2304
|
+
};
|
|
2305
|
+
|
|
2306
|
+
jest.spyOn(webex.cc.services.dialer, 'acceptPreviewContact').mockRejectedValue(error);
|
|
2307
|
+
|
|
2308
|
+
await expect(webex.cc.acceptPreviewContact(previewPayload)).rejects.toThrow(
|
|
2309
|
+
error.details.data.reason
|
|
2310
|
+
);
|
|
2311
|
+
|
|
2312
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith('Accepting campaign preview contact', {
|
|
2313
|
+
module: CC_FILE,
|
|
2314
|
+
method: 'acceptPreviewContact',
|
|
2315
|
+
});
|
|
2316
|
+
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
2317
|
+
`acceptPreviewContact failed with reason: ${error.details.data.reason}`,
|
|
2318
|
+
{module: CC_FILE, method: 'acceptPreviewContact', trackingId: error.details.trackingId}
|
|
2319
|
+
);
|
|
2320
|
+
expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'acceptPreviewContact', CC_FILE);
|
|
2321
|
+
});
|
|
2322
|
+
});
|
|
2183
2323
|
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import ApiAIAssistant from '../../../../src/services/ApiAiAssistant';
|
|
2
|
+
import MetricsManager from '../../../../src/metrics/MetricsManager';
|
|
3
|
+
import LoggerProxy from '../../../../src/logger-proxy';
|
|
4
|
+
import {HTTP_METHODS, WebexSDK} from '../../../../src/types';
|
|
5
|
+
|
|
6
|
+
jest.mock('../../../../src/metrics/MetricsManager');
|
|
7
|
+
jest.mock('../../../../src/logger-proxy');
|
|
8
|
+
|
|
9
|
+
describe('ApiAIAssistant', () => {
|
|
10
|
+
let apiAIAssistant: ApiAIAssistant;
|
|
11
|
+
let mockWebex: WebexSDK;
|
|
12
|
+
let mockMetricsManager: jest.Mocked<MetricsManager>;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
|
|
17
|
+
mockWebex = {
|
|
18
|
+
credentials: {
|
|
19
|
+
getOrgId: jest.fn().mockReturnValue('test-org-id'),
|
|
20
|
+
},
|
|
21
|
+
request: jest.fn(),
|
|
22
|
+
internal: {
|
|
23
|
+
services: {
|
|
24
|
+
get: jest.fn().mockReturnValue('https://api.wxcc-us1.cisco.com'),
|
|
25
|
+
},
|
|
26
|
+
newMetrics: {
|
|
27
|
+
submitBehavioralEvent: jest.fn(),
|
|
28
|
+
submitOperationalEvent: jest.fn(),
|
|
29
|
+
submitBusinessEvent: jest.fn(),
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
ready: true,
|
|
33
|
+
once: jest.fn(),
|
|
34
|
+
} as unknown as WebexSDK;
|
|
35
|
+
|
|
36
|
+
mockMetricsManager = {
|
|
37
|
+
trackEvent: jest.fn(),
|
|
38
|
+
timeEvent: jest.fn(),
|
|
39
|
+
} as unknown as jest.Mocked<MetricsManager>;
|
|
40
|
+
(MetricsManager.getInstance as jest.Mock).mockReturnValue(mockMetricsManager);
|
|
41
|
+
|
|
42
|
+
apiAIAssistant = new ApiAIAssistant(mockWebex);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should send transcript start event successfully', async () => {
|
|
46
|
+
(mockWebex.request as jest.Mock).mockResolvedValue({body: {ok: true}});
|
|
47
|
+
|
|
48
|
+
const result = await apiAIAssistant.sendEvent(
|
|
49
|
+
'test-agent-id',
|
|
50
|
+
'interaction-1',
|
|
51
|
+
'CUSTOM_EVENT',
|
|
52
|
+
'GET_TRANSCRIPTS',
|
|
53
|
+
'START'
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
expect(mockWebex.request).toHaveBeenCalledWith({
|
|
57
|
+
uri: 'https://api-ai-assistant.produs1.ciscoccservice.com/event',
|
|
58
|
+
method: HTTP_METHODS.POST,
|
|
59
|
+
addAuthHeader: true,
|
|
60
|
+
body: {
|
|
61
|
+
agentId: 'test-agent-id',
|
|
62
|
+
orgId: 'test-org-id',
|
|
63
|
+
eventType: 'CUSTOM_EVENT',
|
|
64
|
+
eventName: 'GET_TRANSCRIPTS',
|
|
65
|
+
eventDetails: {
|
|
66
|
+
data: expect.objectContaining({
|
|
67
|
+
interactionId: 'interaction-1',
|
|
68
|
+
action: 'START',
|
|
69
|
+
}),
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
expect(result).toEqual({ok: true});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should fetch historic transcripts with mapped base URL', async () => {
|
|
77
|
+
const responseBody = {interactionId: 'interaction-1', data: []};
|
|
78
|
+
(mockWebex.request as jest.Mock).mockResolvedValue({body: responseBody});
|
|
79
|
+
apiAIAssistant.setAIFeatureFlags({realtimeTranscripts: {enable: true}} as any);
|
|
80
|
+
|
|
81
|
+
const result = await apiAIAssistant.fetchHistoricTranscripts('test-agent-id', 'interaction-1');
|
|
82
|
+
|
|
83
|
+
expect(mockWebex.request).toHaveBeenCalledWith({
|
|
84
|
+
uri: 'https://api-ai-assistant.produs1.ciscoccservice.com/transcripts/list',
|
|
85
|
+
method: HTTP_METHODS.POST,
|
|
86
|
+
addAuthHeader: true,
|
|
87
|
+
body: {
|
|
88
|
+
agentId: 'test-agent-id',
|
|
89
|
+
orgId: 'test-org-id',
|
|
90
|
+
interactionId: 'interaction-1',
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
expect(result).toEqual(responseBody as any);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should fail when base URL mapping is not available', async () => {
|
|
97
|
+
(mockWebex.internal.services.get as jest.Mock).mockReturnValue('https://unknown-host.invalid');
|
|
98
|
+
|
|
99
|
+
let failed = false;
|
|
100
|
+
try {
|
|
101
|
+
await apiAIAssistant.sendEvent(
|
|
102
|
+
'test-agent-id',
|
|
103
|
+
'interaction-1',
|
|
104
|
+
'CUSTOM_EVENT',
|
|
105
|
+
'GET_TRANSCRIPTS',
|
|
106
|
+
'STOP'
|
|
107
|
+
);
|
|
108
|
+
} catch (_error) {
|
|
109
|
+
failed = true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
expect(failed).toBe(true);
|
|
113
|
+
expect(LoggerProxy.error).toHaveBeenCalled();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -490,6 +490,49 @@ describe('AgentConfigService', () => {
|
|
|
490
490
|
});
|
|
491
491
|
});
|
|
492
492
|
|
|
493
|
+
describe('getAIFeatureFlags', () => {
|
|
494
|
+
it('should return AI feature flags successfully', async () => {
|
|
495
|
+
const mockResponse = {
|
|
496
|
+
statusCode: 200,
|
|
497
|
+
body: {
|
|
498
|
+
realtimeTranscripts: {enable: true},
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
mockWebexRequest.request.mockResolvedValue(mockResponse);
|
|
502
|
+
|
|
503
|
+
const result = await agentConfigService.getAIFeatureFlags(mockOrgId);
|
|
504
|
+
expect(result).toEqual(mockResponse.body);
|
|
505
|
+
expect(LoggerProxy.log).toHaveBeenCalledWith('getAIFeatureFlags api success.', {
|
|
506
|
+
module: CONFIG_FILE_NAME,
|
|
507
|
+
method: 'getAIFeatureFlags',
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it('should throw an error if API call returns non-200 status code', async () => {
|
|
512
|
+
const mockError = {statusCode: 500};
|
|
513
|
+
mockWebexRequest.request.mockResolvedValue(mockError);
|
|
514
|
+
|
|
515
|
+
await expect(agentConfigService.getAIFeatureFlags(mockOrgId)).rejects.toThrow(
|
|
516
|
+
'API call failed with 500'
|
|
517
|
+
);
|
|
518
|
+
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
519
|
+
'getAIFeatureFlags API call failed with Error: API call failed with 500',
|
|
520
|
+
{module: CONFIG_FILE_NAME, method: 'getAIFeatureFlags'}
|
|
521
|
+
);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('should handle network errors gracefully', async () => {
|
|
525
|
+
const networkError = new Error('Network Error');
|
|
526
|
+
mockWebexRequest.request.mockRejectedValue(networkError);
|
|
527
|
+
|
|
528
|
+
await expect(agentConfigService.getAIFeatureFlags(mockOrgId)).rejects.toThrow('Network Error');
|
|
529
|
+
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
530
|
+
'getAIFeatureFlags API call failed with Error: Network Error',
|
|
531
|
+
{module: CONFIG_FILE_NAME, method: 'getAIFeatureFlags'}
|
|
532
|
+
);
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
|
|
493
536
|
describe(`getDialPlanData`, () => {
|
|
494
537
|
it('should return dial plan data successfully', async () => {
|
|
495
538
|
const mockResponse = {statusCode: 200, body: {data: {}}}; // Adjust data accordingly
|
|
@@ -718,6 +761,7 @@ describe('AgentConfigService', () => {
|
|
|
718
761
|
const mockOrgInfo = {
|
|
719
762
|
tenantId: 'tenant123',
|
|
720
763
|
timezone: 'GMT',
|
|
764
|
+
environment: 'produs1',
|
|
721
765
|
};
|
|
722
766
|
|
|
723
767
|
const mockOrgSettings = {
|
|
@@ -754,6 +798,9 @@ describe('AgentConfigService', () => {
|
|
|
754
798
|
{id: 'aux1', type: 'WRAP_UP_CODE', name: 'Wrap Up Code 1', isDefault: true},
|
|
755
799
|
{id: 'aux2', type: 'IDLE_CODE', name: 'Idle Code 1', isDefault: true},
|
|
756
800
|
];
|
|
801
|
+
const mockAIFeatureFlags = {
|
|
802
|
+
data: [{realtimeTranscripts: {enable: true}}],
|
|
803
|
+
};
|
|
757
804
|
|
|
758
805
|
const parseAgentConfigsSpy = jest.spyOn(util, 'parseAgentConfigs');
|
|
759
806
|
agentConfigService.getUserUsingCI = jest.fn().mockResolvedValue(mockUserConfig);
|
|
@@ -762,6 +809,7 @@ describe('AgentConfigService', () => {
|
|
|
762
809
|
agentConfigService.getSiteInfo = jest.fn().mockResolvedValue(mockSiteInfo);
|
|
763
810
|
agentConfigService.getTenantData = jest.fn().mockResolvedValue(mockTenantData);
|
|
764
811
|
agentConfigService.getURLMapping = jest.fn().mockResolvedValue(mockURLMapping);
|
|
812
|
+
agentConfigService.getAIFeatureFlags = jest.fn().mockResolvedValue(mockAIFeatureFlags);
|
|
765
813
|
agentConfigService.getAllAuxCodes = jest.fn().mockResolvedValue(mockAuxCodes);
|
|
766
814
|
agentConfigService.getDesktopProfileById = jest.fn().mockResolvedValue(mockAgentProfile);
|
|
767
815
|
agentConfigService.getDialPlanData = jest.fn().mockResolvedValue(mockDialPlanData);
|
|
@@ -801,6 +849,7 @@ describe('AgentConfigService', () => {
|
|
|
801
849
|
dialPlanData: mockDialPlanData,
|
|
802
850
|
urlMapping: mockURLMapping,
|
|
803
851
|
multimediaProfileId: mockSiteInfo.multimediaProfileId,
|
|
852
|
+
aiFeatureFlags: mockAIFeatureFlags,
|
|
804
853
|
});
|
|
805
854
|
});
|
|
806
855
|
|
|
@@ -857,6 +906,7 @@ describe('AgentConfigService', () => {
|
|
|
857
906
|
const mockOrgInfo = {
|
|
858
907
|
tenantId: 'tenant123',
|
|
859
908
|
timezone: 'GMT',
|
|
909
|
+
environment: 'produs1',
|
|
860
910
|
};
|
|
861
911
|
|
|
862
912
|
const mockOrgSettings = {
|
|
@@ -894,6 +944,9 @@ describe('AgentConfigService', () => {
|
|
|
894
944
|
{id: 'aux1', type: 'WRAP_UP_CODE', name: 'Wrap Up Code 1'},
|
|
895
945
|
{id: 'aux2', type: 'IDLE_CODE', name: 'Idle Code 1'},
|
|
896
946
|
];
|
|
947
|
+
const mockAIFeatureFlags = {
|
|
948
|
+
data: [{realtimeTranscripts: {enable: true}}],
|
|
949
|
+
};
|
|
897
950
|
|
|
898
951
|
const parseAgentConfigsSpy = jest.spyOn(util, 'parseAgentConfigs');
|
|
899
952
|
agentConfigService.getUserUsingCI = jest.fn().mockResolvedValue(mockUserConfig);
|
|
@@ -902,6 +955,7 @@ describe('AgentConfigService', () => {
|
|
|
902
955
|
agentConfigService.getSiteInfo = jest.fn().mockResolvedValue(mockSiteInfo);
|
|
903
956
|
agentConfigService.getTenantData = jest.fn().mockResolvedValue(mockTenantData);
|
|
904
957
|
agentConfigService.getURLMapping = jest.fn().mockResolvedValue(mockURLMapping);
|
|
958
|
+
agentConfigService.getAIFeatureFlags = jest.fn().mockResolvedValue(mockAIFeatureFlags);
|
|
905
959
|
agentConfigService.getAllAuxCodes = jest.fn().mockResolvedValue(mockAuxCodes);
|
|
906
960
|
agentConfigService.getDesktopProfileById = jest.fn().mockResolvedValue(mockAgentProfile);
|
|
907
961
|
agentConfigService.getDialPlanData = jest.fn().mockResolvedValue(mockDialPlanData);
|
|
@@ -941,6 +995,7 @@ describe('AgentConfigService', () => {
|
|
|
941
995
|
dialPlanData: mockDialPlanData,
|
|
942
996
|
urlMapping: mockURLMapping,
|
|
943
997
|
multimediaProfileId: mockSiteInfo.multimediaProfileId,
|
|
998
|
+
aiFeatureFlags: mockAIFeatureFlags,
|
|
944
999
|
});
|
|
945
1000
|
});
|
|
946
1001
|
|
|
@@ -952,6 +1007,7 @@ describe('AgentConfigService', () => {
|
|
|
952
1007
|
agentConfigService.getOrganizationSetting = jest.fn().mockResolvedValue({});
|
|
953
1008
|
agentConfigService.getTenantData = jest.fn().mockResolvedValue({});
|
|
954
1009
|
agentConfigService.getURLMapping = jest.fn().mockResolvedValue({});
|
|
1010
|
+
agentConfigService.getAIFeatureFlags = jest.fn().mockResolvedValue({data: []});
|
|
955
1011
|
agentConfigService.getAllAuxCodes = jest.fn().mockResolvedValue({});
|
|
956
1012
|
agentConfigService.getDesktopProfileById = jest.fn().mockResolvedValue({});
|
|
957
1013
|
agentConfigService.getDialPlanData = jest.fn().mockResolvedValue({});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as Utils from '../../../../../src/services/core/Utils';
|
|
2
|
+
import {FALLBACK_DIAL_NUMBER_REGEX} from '../../../../../src/services/core/Utils';
|
|
2
3
|
import LoggerProxy from '../../../../../src/logger-proxy';
|
|
3
4
|
import WebexRequest from '../../../../../src/services/core/WebexRequest';
|
|
4
5
|
import {LoginOption, WebexRequestPayload} from '../../../../../src/types';
|
|
@@ -244,7 +245,7 @@ describe('Utils', () => {
|
|
|
244
245
|
const result = Utils.getStationLoginErrorData(failure, LoginOption.AGENT_DN);
|
|
245
246
|
expect(result).toEqual({
|
|
246
247
|
message:
|
|
247
|
-
'Enter a valid
|
|
248
|
+
'Enter a valid dial number. For help, reach out to your administrator or support team.',
|
|
248
249
|
fieldName: LoginOption.AGENT_DN,
|
|
249
250
|
});
|
|
250
251
|
});
|
|
@@ -557,4 +558,65 @@ describe('Utils', () => {
|
|
|
557
558
|
});
|
|
558
559
|
});
|
|
559
560
|
|
|
561
|
+
describe('isValidDialNumber', () => {
|
|
562
|
+
const anyFormatEntry = {
|
|
563
|
+
name: 'Any Format',
|
|
564
|
+
prefix: '',
|
|
565
|
+
regex: '([0-9a-zA-Z]+[-._])*[0-9a-zA-Z]+',
|
|
566
|
+
strippedChars: '( )-',
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
const usOnlyEntry = {
|
|
570
|
+
name: 'US',
|
|
571
|
+
prefix: '1',
|
|
572
|
+
regex: FALLBACK_DIAL_NUMBER_REGEX.source,
|
|
573
|
+
strippedChars: '( )-',
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
describe('with multiple dial plan entries (Any Format + US)', () => {
|
|
577
|
+
const dialPlanEntries = [anyFormatEntry, usOnlyEntry];
|
|
578
|
+
|
|
579
|
+
it('should return true for a valid US phone number', () => {
|
|
580
|
+
const result = Utils.isValidDialNumber('12223334567', dialPlanEntries);
|
|
581
|
+
expect(result).toBe(true);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('should return true for a UK phone number', () => {
|
|
585
|
+
const result = Utils.isValidDialNumber('+442030484377', dialPlanEntries);
|
|
586
|
+
expect(result).toBe(true);
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
describe('with US-only dial plan entry', () => {
|
|
591
|
+
const dialPlanEntries = [usOnlyEntry];
|
|
592
|
+
|
|
593
|
+
it('should return true for a valid US phone number', () => {
|
|
594
|
+
const result = Utils.isValidDialNumber('12223334567', dialPlanEntries);
|
|
595
|
+
expect(result).toBe(true);
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it('should return false for a UK phone number', () => {
|
|
599
|
+
const result = Utils.isValidDialNumber('+442030484377', dialPlanEntries);
|
|
600
|
+
expect(result).toBe(false);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
it('should return false for an invalid US number format', () => {
|
|
604
|
+
const result = Utils.isValidDialNumber('1234567890', dialPlanEntries);
|
|
605
|
+
expect(result).toBe(false);
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
|
|
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', []);
|
|
612
|
+
expect(result).toBe(true);
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
it('should return false for a UK phone number', () => {
|
|
616
|
+
const result = Utils.isValidDialNumber('+442030484377', []);
|
|
617
|
+
expect(result).toBe(false);
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
|
|
560
622
|
});
|