@webex/contact-center 3.12.0-next.73 → 3.12.0-next.74
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/AGENTS.md +438 -0
- package/ai-docs/README.md +131 -0
- package/ai-docs/RULES.md +455 -0
- package/ai-docs/patterns/event-driven-patterns.md +485 -0
- package/ai-docs/patterns/testing-patterns.md +480 -0
- package/ai-docs/patterns/typescript-patterns.md +365 -0
- package/ai-docs/templates/README.md +102 -0
- package/ai-docs/templates/documentation/create-agents-md.md +240 -0
- package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
- package/ai-docs/templates/existing-service/bug-fix.md +254 -0
- package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
- package/ai-docs/templates/new-method/00-master.md +80 -0
- package/ai-docs/templates/new-method/01-requirements.md +232 -0
- package/ai-docs/templates/new-method/02-implementation.md +295 -0
- package/ai-docs/templates/new-method/03-tests.md +201 -0
- package/ai-docs/templates/new-method/04-validation.md +141 -0
- package/ai-docs/templates/new-service/00-master.md +109 -0
- package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
- package/ai-docs/templates/new-service/02-code-generation.md +346 -0
- package/ai-docs/templates/new-service/03-integration.md +178 -0
- package/ai-docs/templates/new-service/04-test-generation.md +205 -0
- package/ai-docs/templates/new-service/05-validation.md +145 -0
- package/dist/cc.js +53 -38
- package/dist/cc.js.map +1 -1
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +14 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.js +13 -5
- package/dist/index.js.map +1 -1
- package/dist/metrics/behavioral-events.js +26 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +9 -3
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/ApiAiAssistant.js +74 -3
- package/dist/services/ApiAiAssistant.js.map +1 -1
- package/dist/services/config/Util.js +2 -2
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/types.js +25 -9
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/core/Utils.js +74 -27
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/core/websocket/WebSocketManager.js +2 -1
- package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
- package/dist/services/core/websocket/types.js.map +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/task/Task.js +688 -0
- package/dist/services/task/Task.js.map +1 -0
- package/dist/services/task/TaskFactory.js +45 -0
- package/dist/services/task/TaskFactory.js.map +1 -0
- package/dist/services/task/TaskManager.js +725 -612
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +162 -26
- package/dist/services/task/TaskUtils.js.map +1 -1
- package/dist/services/task/constants.js +6 -1
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/digital/Digital.js +77 -0
- package/dist/services/task/digital/Digital.js.map +1 -0
- package/dist/services/task/state-machine/TaskStateMachine.js +837 -0
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
- package/dist/services/task/state-machine/actions.js +543 -0
- package/dist/services/task/state-machine/actions.js.map +1 -0
- package/dist/services/task/state-machine/constants.js +161 -0
- package/dist/services/task/state-machine/constants.js.map +1 -0
- package/dist/services/task/state-machine/guards.js +340 -0
- package/dist/services/task/state-machine/guards.js.map +1 -0
- package/dist/services/task/state-machine/index.js +53 -0
- package/dist/services/task/state-machine/index.js.map +1 -0
- package/dist/services/task/state-machine/types.js +54 -0
- package/dist/services/task/state-machine/types.js.map +1 -0
- package/dist/services/task/state-machine/uiControlsComputer.js +553 -0
- package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
- package/dist/services/task/taskDataNormalizer.js +99 -0
- package/dist/services/task/taskDataNormalizer.js.map +1 -0
- package/dist/services/task/types.js +168 -16
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +1042 -0
- package/dist/services/task/voice/Voice.js.map +1 -0
- package/dist/services/task/voice/WebRTC.js +149 -0
- package/dist/services/task/voice/WebRTC.js.map +1 -0
- package/dist/types/cc.d.ts +19 -18
- package/dist/types/config.d.ts +6 -0
- package/dist/types/constants.d.ts +14 -1
- package/dist/types/index.d.ts +11 -5
- package/dist/types/metrics/constants.d.ts +7 -1
- package/dist/types/services/ApiAiAssistant.d.ts +11 -3
- package/dist/types/services/config/types.d.ts +113 -9
- package/dist/types/services/core/Utils.d.ts +23 -10
- package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
- package/dist/types/services/core/websocket/types.d.ts +1 -1
- package/dist/types/services/index.d.ts +1 -1
- package/dist/types/services/task/Task.d.ts +157 -0
- package/dist/types/services/task/TaskFactory.d.ts +12 -0
- package/dist/types/services/task/TaskUtils.d.ts +46 -2
- package/dist/types/services/task/constants.d.ts +5 -0
- package/dist/types/services/task/digital/Digital.d.ts +22 -0
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1144 -0
- package/dist/types/services/task/state-machine/actions.d.ts +10 -0
- package/dist/types/services/task/state-machine/constants.d.ts +107 -0
- package/dist/types/services/task/state-machine/guards.d.ts +90 -0
- package/dist/types/services/task/state-machine/index.d.ts +13 -0
- package/dist/types/services/task/state-machine/types.d.ts +267 -0
- package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
- package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
- package/dist/types/services/task/types.d.ts +551 -78
- package/dist/types/services/task/voice/Voice.d.ts +184 -0
- package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
- package/dist/types/types.d.ts +92 -0
- package/dist/types/webex.d.ts +1 -0
- package/dist/types.js +85 -0
- package/dist/types.js.map +1 -1
- package/dist/webex.js +14 -2
- package/dist/webex.js.map +1 -1
- package/package.json +15 -12
- package/src/cc.ts +63 -43
- package/src/config.ts +6 -0
- package/src/constants.ts +14 -1
- package/src/index.ts +14 -5
- package/src/metrics/ai-docs/AGENTS.md +348 -0
- package/src/metrics/ai-docs/ARCHITECTURE.md +336 -0
- package/src/metrics/behavioral-events.ts +28 -0
- package/src/metrics/constants.ts +9 -4
- package/src/services/ApiAiAssistant.ts +104 -3
- package/src/services/agent/ai-docs/AGENTS.md +238 -0
- package/src/services/agent/ai-docs/ARCHITECTURE.md +302 -0
- package/src/services/ai-docs/AGENTS.md +384 -0
- package/src/services/config/Util.ts +2 -2
- package/src/services/config/ai-docs/AGENTS.md +253 -0
- package/src/services/config/ai-docs/ARCHITECTURE.md +424 -0
- package/src/services/config/types.ts +116 -10
- package/src/services/core/Utils.ts +85 -34
- package/src/services/core/ai-docs/AGENTS.md +379 -0
- package/src/services/core/ai-docs/ARCHITECTURE.md +696 -0
- package/src/services/core/websocket/WebSocketManager.ts +2 -0
- package/src/services/core/websocket/types.ts +1 -1
- package/src/services/index.ts +1 -1
- package/src/services/task/Task.ts +837 -0
- package/src/services/task/TaskFactory.ts +55 -0
- package/src/services/task/TaskManager.ts +738 -697
- package/src/services/task/TaskUtils.ts +205 -25
- package/src/services/task/ai-docs/AGENTS.md +455 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +585 -0
- package/src/services/task/constants.ts +5 -0
- package/src/services/task/digital/Digital.ts +95 -0
- package/src/services/task/state-machine/TaskStateMachine.ts +1077 -0
- package/src/services/task/state-machine/actions.ts +685 -0
- package/src/services/task/state-machine/ai-docs/AGENTS.md +495 -0
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1135 -0
- package/src/services/task/state-machine/constants.ts +172 -0
- package/src/services/task/state-machine/guards.ts +406 -0
- package/src/services/task/state-machine/index.ts +28 -0
- package/src/services/task/state-machine/types.ts +241 -0
- package/src/services/task/state-machine/uiControlsComputer.ts +867 -0
- package/src/services/task/taskDataNormalizer.ts +137 -0
- package/src/services/task/types.ts +654 -85
- package/src/services/task/voice/Voice.ts +1267 -0
- package/src/services/task/voice/WebRTC.ts +187 -0
- package/src/types.ts +112 -1
- package/src/utils/AGENTS.md +276 -0
- package/src/webex.js +2 -0
- package/test/unit/spec/cc.ts +133 -3
- package/test/unit/spec/logger-proxy.ts +70 -0
- package/test/unit/spec/services/ApiAiAssistant.ts +122 -17
- package/test/unit/spec/services/WebCallingService.ts +7 -1
- package/test/unit/spec/services/config/index.ts +27 -27
- package/test/unit/spec/services/core/Utils.ts +335 -1
- package/test/unit/spec/services/core/websocket/WebSocketManager.ts +66 -40
- package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
- package/test/unit/spec/services/task/Task.ts +477 -0
- package/test/unit/spec/services/task/TaskFactory.ts +62 -0
- package/test/unit/spec/services/task/TaskManager.ts +821 -1936
- package/test/unit/spec/services/task/TaskUtils.ts +206 -0
- package/test/unit/spec/services/task/digital/Digital.ts +105 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +1825 -0
- package/test/unit/spec/services/task/state-machine/guards.ts +479 -0
- package/test/unit/spec/services/task/state-machine/types.ts +18 -0
- package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +2020 -0
- package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
- package/test/unit/spec/services/task/voice/Voice.ts +631 -0
- package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
- package/dist/services/task/index.js +0 -1530
- package/dist/services/task/index.js.map +0 -1
- package/dist/types/services/task/index.d.ts +0 -650
- package/src/services/task/index.ts +0 -1806
- package/test/unit/spec/services/task/index.ts +0 -2205
package/test/unit/spec/cc.ts
CHANGED
|
@@ -59,6 +59,7 @@ global.URL.createObjectURL = jest.fn(() => 'blob:http://localhost:3000/12345');
|
|
|
59
59
|
|
|
60
60
|
describe('webex.cc', () => {
|
|
61
61
|
let webex;
|
|
62
|
+
let mockApiAIAssistant;
|
|
62
63
|
let mockContact;
|
|
63
64
|
let mockTaskManager;
|
|
64
65
|
let mockMetricsManager;
|
|
@@ -109,6 +110,14 @@ describe('webex.cc', () => {
|
|
|
109
110
|
cancelCtq: jest.fn(),
|
|
110
111
|
};
|
|
111
112
|
|
|
113
|
+
mockApiAIAssistant = {
|
|
114
|
+
sendEvent: jest.fn(),
|
|
115
|
+
getSuggestedResponse: jest.fn(),
|
|
116
|
+
fetchHistoricTranscripts: jest.fn(),
|
|
117
|
+
setAIFeatureFlags: jest.fn(),
|
|
118
|
+
setAgentId: jest.fn(),
|
|
119
|
+
};
|
|
120
|
+
|
|
112
121
|
// Mock Services instance
|
|
113
122
|
const mockServicesInstance = {
|
|
114
123
|
agent: {
|
|
@@ -149,17 +158,17 @@ describe('webex.cc', () => {
|
|
|
149
158
|
};
|
|
150
159
|
|
|
151
160
|
mockTaskManager = {
|
|
161
|
+
apiAIAssistant: mockApiAIAssistant,
|
|
152
162
|
contact: mockContact,
|
|
153
163
|
call: undefined,
|
|
154
164
|
taskCollection: {},
|
|
155
165
|
webCallingService: undefined,
|
|
156
166
|
webSocketManager: mockWebSocketManager,
|
|
157
167
|
task: undefined,
|
|
168
|
+
setConfigFlags: jest.fn(),
|
|
158
169
|
setWrapupData: jest.fn(),
|
|
159
170
|
setAgentId: jest.fn(),
|
|
160
171
|
setWebRtcEnabled: jest.fn(),
|
|
161
|
-
handleRealtimeTranscriptEvent: jest.fn(),
|
|
162
|
-
setApiAIAssistant: jest.fn(),
|
|
163
172
|
registerIncomingCallEvent: jest.fn(),
|
|
164
173
|
registerTaskListeners: jest.fn(),
|
|
165
174
|
getTask: jest.fn(),
|
|
@@ -298,7 +307,7 @@ describe('webex.cc', () => {
|
|
|
298
307
|
isAgentAvailableAfterOutdial: false,
|
|
299
308
|
isCampaignManagementEnabled: false,
|
|
300
309
|
outDialEp: '',
|
|
301
|
-
|
|
310
|
+
isEndTaskEnabled: false,
|
|
302
311
|
isEndConsultEnabled: false,
|
|
303
312
|
agentDbId: '',
|
|
304
313
|
allowConsultToQueue: false,
|
|
@@ -389,6 +398,13 @@ describe('webex.cc', () => {
|
|
|
389
398
|
module: CC_FILE,
|
|
390
399
|
method: 'connectWebsocket',
|
|
391
400
|
});
|
|
401
|
+
expect(mockTaskManager.setConfigFlags).toHaveBeenCalledWith({
|
|
402
|
+
isEndTaskEnabled: mockAgentProfile.isEndTaskEnabled,
|
|
403
|
+
isEndConsultEnabled: mockAgentProfile.isEndConsultEnabled,
|
|
404
|
+
webRtcEnabled: mockAgentProfile.webRtcEnabled,
|
|
405
|
+
autoWrapup: mockAgentProfile.wrapUpData.wrapUpProps.autoWrapup ?? false,
|
|
406
|
+
aiFeature: mockAgentProfile.aiFeature,
|
|
407
|
+
});
|
|
392
408
|
expect(reloadSpy).toHaveBeenCalled();
|
|
393
409
|
expect(result).toEqual(mockAgentProfile);
|
|
394
410
|
expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
|
|
@@ -532,6 +548,36 @@ describe('webex.cc', () => {
|
|
|
532
548
|
expect(result).toEqual(mockAgentProfile);
|
|
533
549
|
});
|
|
534
550
|
|
|
551
|
+
it('should skip mercury connection when disableWebRTCRegistration is enabled', async () => {
|
|
552
|
+
webex.cc.$config = {
|
|
553
|
+
...webex.cc.$config,
|
|
554
|
+
allowAutomatedRelogin: false,
|
|
555
|
+
disableWebRTCRegistration: true,
|
|
556
|
+
};
|
|
557
|
+
mockAgentProfile.webRtcEnabled = true;
|
|
558
|
+
const mercurySpy = jest.spyOn(webex.internal.mercury, 'connect');
|
|
559
|
+
const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
|
|
560
|
+
const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
|
|
561
|
+
jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue(mockAgentProfile);
|
|
562
|
+
mockWebSocketManager.initWebSocket.mockResolvedValue({
|
|
563
|
+
agentId: 'agent123',
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
const result = await webex.cc.register();
|
|
567
|
+
|
|
568
|
+
expect(connectWebsocketSpy).toHaveBeenCalled();
|
|
569
|
+
expect(setupEventListenersSpy).toHaveBeenCalled();
|
|
570
|
+
expect(mercurySpy).not.toHaveBeenCalled();
|
|
571
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith(
|
|
572
|
+
'Skipping Mobius registration because disableWebRTCRegistration is enabled',
|
|
573
|
+
{
|
|
574
|
+
module: CC_FILE,
|
|
575
|
+
method: 'connectWebsocket',
|
|
576
|
+
}
|
|
577
|
+
);
|
|
578
|
+
expect(result).toEqual(mockAgentProfile);
|
|
579
|
+
});
|
|
580
|
+
|
|
535
581
|
it('should not attempt for mercury connection when webrtc is disabled', async () => {
|
|
536
582
|
mockAgentProfile.webRtcEnabled = false;
|
|
537
583
|
mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: false}} as any;
|
|
@@ -729,6 +775,59 @@ describe('webex.cc', () => {
|
|
|
729
775
|
);
|
|
730
776
|
});
|
|
731
777
|
|
|
778
|
+
it('should skip web calling line registration when disableWebRTCRegistration is enabled', async () => {
|
|
779
|
+
const options = {
|
|
780
|
+
teamId: 'teamId',
|
|
781
|
+
loginOption: LoginOption.BROWSER,
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
webex.cc.$config = {
|
|
785
|
+
...webex.cc.$config,
|
|
786
|
+
disableWebRTCRegistration: true,
|
|
787
|
+
};
|
|
788
|
+
webex.cc.agentConfig = {
|
|
789
|
+
agentId: 'agentId',
|
|
790
|
+
webRtcEnabled: true,
|
|
791
|
+
loginVoiceOptions: ['BROWSER', 'EXTENSION', 'AGENT_DN'],
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
const registerWebCallingLineSpy = jest.spyOn(
|
|
795
|
+
webex.cc.webCallingService,
|
|
796
|
+
'registerWebCallingLine'
|
|
797
|
+
);
|
|
798
|
+
|
|
799
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
800
|
+
data: {
|
|
801
|
+
loginOption: LoginOption.BROWSER,
|
|
802
|
+
agentId: 'agentId',
|
|
803
|
+
teamId: 'teamId',
|
|
804
|
+
siteId: 'siteId',
|
|
805
|
+
roles: [AGENT],
|
|
806
|
+
channelsMap: {
|
|
807
|
+
chat: [],
|
|
808
|
+
email: [],
|
|
809
|
+
social: [],
|
|
810
|
+
telephony: [],
|
|
811
|
+
},
|
|
812
|
+
},
|
|
813
|
+
trackingId: 'notifs_52628',
|
|
814
|
+
orgId: 'orgId',
|
|
815
|
+
type: 'StationLoginSuccess',
|
|
816
|
+
eventType: 'STATION_LOGIN',
|
|
817
|
+
} as unknown as StationLoginSuccess);
|
|
818
|
+
|
|
819
|
+
await webex.cc.stationLogin(options);
|
|
820
|
+
|
|
821
|
+
expect(registerWebCallingLineSpy).not.toHaveBeenCalled();
|
|
822
|
+
expect(LoggerProxy.info).toHaveBeenCalledWith(
|
|
823
|
+
'Skipping web calling line registration because disableWebRTCRegistration is enabled',
|
|
824
|
+
{
|
|
825
|
+
module: CC_FILE,
|
|
826
|
+
method: 'stationLogin',
|
|
827
|
+
}
|
|
828
|
+
);
|
|
829
|
+
});
|
|
830
|
+
|
|
732
831
|
it('should not attempt mobius registration for LoginOption.BROWSER if webrtc is disabled', async () => {
|
|
733
832
|
const options = {
|
|
734
833
|
teamId: 'teamId',
|
|
@@ -1543,6 +1642,27 @@ describe('webex.cc', () => {
|
|
|
1543
1642
|
});
|
|
1544
1643
|
});
|
|
1545
1644
|
|
|
1645
|
+
describe('getQueues', () => {
|
|
1646
|
+
it('delegates to the queue service when successful', async () => {
|
|
1647
|
+
const mockQueuesResponse = [{queueId: 'queue1', queueName: 'Queue 1'}];
|
|
1648
|
+
const queueSpy = jest
|
|
1649
|
+
.spyOn(webex.cc.queue, 'getQueues')
|
|
1650
|
+
.mockResolvedValue(mockQueuesResponse as any);
|
|
1651
|
+
|
|
1652
|
+
const result = await webex.cc.getQueues({page: 1});
|
|
1653
|
+
|
|
1654
|
+
expect(queueSpy).toHaveBeenCalledWith({page: 1});
|
|
1655
|
+
expect(result).toBe(mockQueuesResponse);
|
|
1656
|
+
});
|
|
1657
|
+
|
|
1658
|
+
it('propagates queue service errors', async () => {
|
|
1659
|
+
const error = new Error('Test error.');
|
|
1660
|
+
jest.spyOn(webex.cc.queue, 'getQueues').mockRejectedValue(error);
|
|
1661
|
+
|
|
1662
|
+
await expect(webex.cc.getQueues()).rejects.toThrow('Test error.');
|
|
1663
|
+
});
|
|
1664
|
+
});
|
|
1665
|
+
|
|
1546
1666
|
describe('uploadLogs', () => {
|
|
1547
1667
|
it('should upload logs successfully', async () => {
|
|
1548
1668
|
const uploadLogsMock = jest.spyOn(webex.cc.webexRequest, 'uploadLogs').mockResolvedValue({
|
|
@@ -1572,6 +1692,7 @@ describe('webex.cc', () => {
|
|
|
1572
1692
|
|
|
1573
1693
|
describe('unregister', () => {
|
|
1574
1694
|
let mockWebSocketManager;
|
|
1695
|
+
let mockRTDWebSocketManager;
|
|
1575
1696
|
let mercuryDisconnectSpy;
|
|
1576
1697
|
let deviceUnregisterSpy;
|
|
1577
1698
|
|
|
@@ -1588,8 +1709,15 @@ describe('webex.cc', () => {
|
|
|
1588
1709
|
off: jest.fn(),
|
|
1589
1710
|
on: jest.fn(),
|
|
1590
1711
|
};
|
|
1712
|
+
mockRTDWebSocketManager = {
|
|
1713
|
+
isSocketClosed: false,
|
|
1714
|
+
close: jest.fn(),
|
|
1715
|
+
off: jest.fn(),
|
|
1716
|
+
on: jest.fn(),
|
|
1717
|
+
};
|
|
1591
1718
|
|
|
1592
1719
|
webex.cc.services.webSocketManager = mockWebSocketManager;
|
|
1720
|
+
webex.cc.services.rtdWebSocketManager = mockRTDWebSocketManager;
|
|
1593
1721
|
|
|
1594
1722
|
webex.internal = webex.internal || {};
|
|
1595
1723
|
webex.internal.mercury = {
|
|
@@ -1728,6 +1856,7 @@ describe('webex.cc', () => {
|
|
|
1728
1856
|
expect(webex.internal.mercury.off).not.toHaveBeenCalled();
|
|
1729
1857
|
expect(mercuryDisconnectSpy).not.toHaveBeenCalled();
|
|
1730
1858
|
expect(deviceUnregisterSpy).not.toHaveBeenCalled();
|
|
1859
|
+
expect(mockRTDWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
|
|
1731
1860
|
});
|
|
1732
1861
|
|
|
1733
1862
|
it('should skip internal mercury cleanup when loginVoiceOptions does not include BROWSER', async () => {
|
|
@@ -1745,6 +1874,7 @@ describe('webex.cc', () => {
|
|
|
1745
1874
|
expect(deviceUnregisterSpy).not.toHaveBeenCalled();
|
|
1746
1875
|
|
|
1747
1876
|
expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
|
|
1877
|
+
expect(mockRTDWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
|
|
1748
1878
|
expect(webex.cc.agentConfig).toBeNull();
|
|
1749
1879
|
});
|
|
1750
1880
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import LoggerProxy from '../../../src/logger-proxy';
|
|
2
|
+
|
|
3
|
+
describe('LoggerProxy', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
// Reset between tests
|
|
6
|
+
(LoggerProxy as any).logger = undefined;
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('no-ops when logger is not initialized', () => {
|
|
10
|
+
expect(() => {
|
|
11
|
+
LoggerProxy.log('msg');
|
|
12
|
+
LoggerProxy.info('msg');
|
|
13
|
+
LoggerProxy.warn('msg');
|
|
14
|
+
LoggerProxy.trace('msg');
|
|
15
|
+
LoggerProxy.error('msg');
|
|
16
|
+
}).not.toThrow();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('routes calls to the injected logger with formatted context', () => {
|
|
20
|
+
const logger = {
|
|
21
|
+
log: jest.fn(),
|
|
22
|
+
info: jest.fn(),
|
|
23
|
+
warn: jest.fn(),
|
|
24
|
+
trace: jest.fn(),
|
|
25
|
+
error: jest.fn(),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
LoggerProxy.initialize(logger as any);
|
|
29
|
+
|
|
30
|
+
LoggerProxy.log('hello', {module: 'm', method: 'fn', interactionId: 'i', trackingId: 't'});
|
|
31
|
+
expect(logger.log).toHaveBeenCalled();
|
|
32
|
+
expect(String(logger.log.mock.calls[0][0])).toContain('module:m - method:fn');
|
|
33
|
+
expect(String(logger.log.mock.calls[0][0])).toContain('interactionId:i');
|
|
34
|
+
expect(String(logger.log.mock.calls[0][0])).toContain('trackingId:t');
|
|
35
|
+
|
|
36
|
+
LoggerProxy.info('hello', {module: 'm', method: 'fn'});
|
|
37
|
+
LoggerProxy.warn('hello', {module: 'm', method: 'fn'});
|
|
38
|
+
LoggerProxy.trace('hello', {module: 'm', method: 'fn'});
|
|
39
|
+
LoggerProxy.error('hello', {module: 'm', method: 'fn'});
|
|
40
|
+
expect(logger.info).toHaveBeenCalled();
|
|
41
|
+
expect(logger.warn).toHaveBeenCalled();
|
|
42
|
+
expect(logger.trace).toHaveBeenCalled();
|
|
43
|
+
expect(logger.error).toHaveBeenCalled();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('handles unserializable data and different error shapes', () => {
|
|
47
|
+
const logger = {
|
|
48
|
+
log: jest.fn(),
|
|
49
|
+
info: jest.fn(),
|
|
50
|
+
warn: jest.fn(),
|
|
51
|
+
trace: jest.fn(),
|
|
52
|
+
error: jest.fn(),
|
|
53
|
+
};
|
|
54
|
+
LoggerProxy.initialize(logger as any);
|
|
55
|
+
|
|
56
|
+
const circular: any = {};
|
|
57
|
+
circular.self = circular;
|
|
58
|
+
|
|
59
|
+
LoggerProxy.info('msg', {module: 'm', method: 'fn', data: circular, error: 'oops'});
|
|
60
|
+
expect(String(logger.info.mock.calls[0][0])).toContain('data:[object]');
|
|
61
|
+
expect(String(logger.info.mock.calls[0][0])).toContain('error:oops');
|
|
62
|
+
|
|
63
|
+
const err = new Error('boom');
|
|
64
|
+
LoggerProxy.error('msg', {module: 'm', method: 'fn', error: err});
|
|
65
|
+
const errorLine = String(logger.error.mock.calls[0][0]);
|
|
66
|
+
expect(errorLine).toContain('error:Error:boom');
|
|
67
|
+
expect(errorLine).toContain('stack:');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ApiAIAssistant from '../../../../src/services/ApiAiAssistant';
|
|
2
2
|
import MetricsManager from '../../../../src/metrics/MetricsManager';
|
|
3
3
|
import LoggerProxy from '../../../../src/logger-proxy';
|
|
4
|
+
import WebexRequest from '../../../../src/services/core/WebexRequest';
|
|
4
5
|
import {HTTP_METHODS, WebexSDK} from '../../../../src/types';
|
|
5
6
|
|
|
6
7
|
jest.mock('../../../../src/metrics/MetricsManager');
|
|
@@ -13,6 +14,9 @@ describe('ApiAIAssistant', () => {
|
|
|
13
14
|
|
|
14
15
|
beforeEach(() => {
|
|
15
16
|
jest.clearAllMocks();
|
|
17
|
+
jest.spyOn(WebexRequest, 'getInstance').mockReturnValue({
|
|
18
|
+
uploadLogs: jest.fn(),
|
|
19
|
+
} as any);
|
|
16
20
|
|
|
17
21
|
mockWebex = {
|
|
18
22
|
credentials: {
|
|
@@ -53,23 +57,18 @@ describe('ApiAIAssistant', () => {
|
|
|
53
57
|
'START'
|
|
54
58
|
);
|
|
55
59
|
|
|
56
|
-
expect(mockWebex.request).
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
action: 'START',
|
|
69
|
-
}),
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
});
|
|
60
|
+
expect(mockWebex.request).toHaveBeenCalledTimes(1);
|
|
61
|
+
const requestArgs = (mockWebex.request as jest.Mock).mock.calls[0][0];
|
|
62
|
+
|
|
63
|
+
expect(requestArgs.uri).toBe('https://api-ai-assistant.produs1.ciscoccservice.com/event');
|
|
64
|
+
expect(requestArgs.method).toBe(HTTP_METHODS.POST);
|
|
65
|
+
expect(requestArgs.addAuthHeader).toBe(true);
|
|
66
|
+
expect(requestArgs.body.agentId).toBe('test-agent-id');
|
|
67
|
+
expect(requestArgs.body.orgId).toBe('test-org-id');
|
|
68
|
+
expect(requestArgs.body.eventType).toBe('CUSTOM_EVENT');
|
|
69
|
+
expect(requestArgs.body.eventName).toBe('GET_TRANSCRIPTS');
|
|
70
|
+
expect(requestArgs.body.eventDetails.data.interactionId).toBe('interaction-1');
|
|
71
|
+
expect(requestArgs.body.eventDetails.data.action).toBe('START');
|
|
73
72
|
expect(result).toEqual({ok: true});
|
|
74
73
|
});
|
|
75
74
|
|
|
@@ -93,6 +92,83 @@ describe('ApiAIAssistant', () => {
|
|
|
93
92
|
expect(result).toEqual(responseBody as any);
|
|
94
93
|
});
|
|
95
94
|
|
|
95
|
+
it('should request suggested response without extra context using sendEvent', async () => {
|
|
96
|
+
const sendEventSpy = jest.spyOn(apiAIAssistant, 'sendEvent').mockResolvedValue({ok: true});
|
|
97
|
+
apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: true}} as any);
|
|
98
|
+
|
|
99
|
+
const result = await apiAIAssistant.getSuggestedResponse({
|
|
100
|
+
agentId: 'test-agent-id',
|
|
101
|
+
interactionId: 'interaction-1',
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(sendEventSpy).toHaveBeenCalledTimes(1);
|
|
105
|
+
const [agentId, interactionId, eventType, eventName, action, context, languageCode, trackingId] =
|
|
106
|
+
sendEventSpy.mock.calls[0];
|
|
107
|
+
|
|
108
|
+
expect(agentId).toBe('test-agent-id');
|
|
109
|
+
expect(interactionId).toBe('interaction-1');
|
|
110
|
+
expect(eventType).toBe('CUSTOM_EVENT');
|
|
111
|
+
expect(eventName).toBe('GET_SUGGESTIONS');
|
|
112
|
+
expect(action).toBeUndefined();
|
|
113
|
+
expect(context).toBeUndefined();
|
|
114
|
+
expect(languageCode).toBe('en');
|
|
115
|
+
expect(typeof trackingId).toBe('string');
|
|
116
|
+
expect(trackingId.startsWith('WX_CC_SDK_')).toBe(true);
|
|
117
|
+
expect(result).toEqual({ok: true});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should request suggested response with extra context using sendEvent', async () => {
|
|
121
|
+
const sendEventSpy = jest.spyOn(apiAIAssistant, 'sendEvent').mockResolvedValue({ok: true});
|
|
122
|
+
apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: true}} as any);
|
|
123
|
+
|
|
124
|
+
const result = await apiAIAssistant.getSuggestedResponse({
|
|
125
|
+
agentId: 'test-agent-id',
|
|
126
|
+
interactionId: 'interaction-1',
|
|
127
|
+
context: 'Need assistance with credit card payment due date',
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
expect(sendEventSpy).toHaveBeenCalledTimes(1);
|
|
131
|
+
const [agentId, interactionId, eventType, eventName, action, context, languageCode, trackingId] =
|
|
132
|
+
sendEventSpy.mock.calls[0];
|
|
133
|
+
|
|
134
|
+
expect(agentId).toBe('test-agent-id');
|
|
135
|
+
expect(interactionId).toBe('interaction-1');
|
|
136
|
+
expect(eventType).toBe('CUSTOM_EVENT');
|
|
137
|
+
expect(eventName).toBe('ADD_SUGGESTIONS_EXTRA_CONTEXT');
|
|
138
|
+
expect(action).toBeUndefined();
|
|
139
|
+
expect(context).toBe('Need assistance with credit card payment due date');
|
|
140
|
+
expect(languageCode).toBe('en');
|
|
141
|
+
expect(typeof trackingId).toBe('string');
|
|
142
|
+
expect(trackingId.startsWith('WX_CC_SDK_')).toBe(true);
|
|
143
|
+
expect(result).toEqual({ok: true});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should treat whitespace-only context as GET_SUGGESTIONS', async () => {
|
|
147
|
+
const sendEventSpy = jest.spyOn(apiAIAssistant, 'sendEvent').mockResolvedValue({ok: true});
|
|
148
|
+
apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: true}} as any);
|
|
149
|
+
|
|
150
|
+
const result = await apiAIAssistant.getSuggestedResponse({
|
|
151
|
+
agentId: 'test-agent-id',
|
|
152
|
+
interactionId: 'interaction-1',
|
|
153
|
+
context: ' ',
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(sendEventSpy).toHaveBeenCalledTimes(1);
|
|
157
|
+
const [agentId, interactionId, eventType, eventName, action, context, languageCode, trackingId] =
|
|
158
|
+
sendEventSpy.mock.calls[0];
|
|
159
|
+
|
|
160
|
+
expect(agentId).toBe('test-agent-id');
|
|
161
|
+
expect(interactionId).toBe('interaction-1');
|
|
162
|
+
expect(eventType).toBe('CUSTOM_EVENT');
|
|
163
|
+
expect(eventName).toBe('GET_SUGGESTIONS');
|
|
164
|
+
expect(action).toBeUndefined();
|
|
165
|
+
expect(context).toBe('');
|
|
166
|
+
expect(languageCode).toBe('en');
|
|
167
|
+
expect(typeof trackingId).toBe('string');
|
|
168
|
+
expect(trackingId.startsWith('WX_CC_SDK_')).toBe(true);
|
|
169
|
+
expect(result).toEqual({ok: true});
|
|
170
|
+
});
|
|
171
|
+
|
|
96
172
|
it('should fail when base URL mapping is not available', async () => {
|
|
97
173
|
(mockWebex.internal.services.get as jest.Mock).mockReturnValue('https://unknown-host.invalid');
|
|
98
174
|
|
|
@@ -112,4 +188,33 @@ describe('ApiAIAssistant', () => {
|
|
|
112
188
|
expect(failed).toBe(true);
|
|
113
189
|
expect(LoggerProxy.error).toHaveBeenCalled();
|
|
114
190
|
});
|
|
191
|
+
|
|
192
|
+
it('should fail when realtime transcripts feature is disabled', async () => {
|
|
193
|
+
apiAIAssistant.setAIFeatureFlags({realtimeTranscripts: {enable: false}} as any);
|
|
194
|
+
let errorMessage = '';
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
await apiAIAssistant.fetchHistoricTranscripts('test-agent-id', 'interaction-1');
|
|
198
|
+
} catch (error) {
|
|
199
|
+
errorMessage = (error as Error)?.message || '';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
expect(errorMessage).toBe('Error while performing fetchHistoricTranscripts');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should fail when suggested responses feature is disabled', async () => {
|
|
206
|
+
apiAIAssistant.setAIFeatureFlags({suggestedResponses: {enable: false}} as any);
|
|
207
|
+
let errorMessage = '';
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
await apiAIAssistant.getSuggestedResponse({
|
|
211
|
+
agentId: 'test-agent-id',
|
|
212
|
+
interactionId: 'interaction-1',
|
|
213
|
+
});
|
|
214
|
+
} catch (error) {
|
|
215
|
+
errorMessage = (error as Error)?.message || '';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
expect(errorMessage).toBe('Error while performing getSuggestedResponse');
|
|
219
|
+
});
|
|
115
220
|
});
|
|
@@ -200,11 +200,17 @@ describe('WebCallingService', () => {
|
|
|
200
200
|
|
|
201
201
|
it('should reject if registration times out', async () => {
|
|
202
202
|
line = callingClient.getLines().line1 as ILine;
|
|
203
|
+
jest.spyOn(global, 'setTimeout').mockImplementation(((handler: TimerHandler) => {
|
|
204
|
+
if (typeof handler === 'function') {
|
|
205
|
+
handler();
|
|
206
|
+
}
|
|
207
|
+
return 0 as unknown as NodeJS.Timeout;
|
|
208
|
+
}) as typeof setTimeout);
|
|
203
209
|
|
|
204
210
|
const promise = webRTCCalling.registerWebCallingLine();
|
|
205
211
|
|
|
206
212
|
await expect(promise).rejects.toThrow('WebCallingService Registration timed out');
|
|
207
|
-
}
|
|
213
|
+
});
|
|
208
214
|
|
|
209
215
|
it('should handle incoming calls', async () => {
|
|
210
216
|
line = callingClient.getLines().line1 as ILine;
|
|
@@ -66,7 +66,6 @@ describe('AgentConfigService', () => {
|
|
|
66
66
|
method: 'GET',
|
|
67
67
|
});
|
|
68
68
|
expect(result).toEqual(mockResponse.body);
|
|
69
|
-
|
|
70
69
|
expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching user data using CI', {
|
|
71
70
|
module: CONFIG_FILE_NAME,
|
|
72
71
|
method: 'getUserUsingCI',
|
|
@@ -124,7 +123,6 @@ describe('AgentConfigService', () => {
|
|
|
124
123
|
method: 'GET',
|
|
125
124
|
});
|
|
126
125
|
expect(result).toEqual(mockResponse.body);
|
|
127
|
-
|
|
128
126
|
expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching desktop profile', {
|
|
129
127
|
module: CONFIG_FILE_NAME,
|
|
130
128
|
method: 'getDesktopProfileById',
|
|
@@ -183,7 +181,6 @@ describe('AgentConfigService', () => {
|
|
|
183
181
|
method: 'GET',
|
|
184
182
|
});
|
|
185
183
|
expect(result).toEqual(mockResponse.body);
|
|
186
|
-
|
|
187
184
|
expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching list of teams', {
|
|
188
185
|
module: CONFIG_FILE_NAME,
|
|
189
186
|
method: 'getListOfTeams',
|
|
@@ -267,7 +264,6 @@ describe('AgentConfigService', () => {
|
|
|
267
264
|
method: 'GET',
|
|
268
265
|
});
|
|
269
266
|
expect(result).toEqual(mockResponse.body);
|
|
270
|
-
|
|
271
267
|
expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching list of aux codes', {
|
|
272
268
|
module: CONFIG_FILE_NAME,
|
|
273
269
|
method: 'getListOfAuxCodes',
|
|
@@ -495,7 +491,7 @@ describe('AgentConfigService', () => {
|
|
|
495
491
|
const mockResponse = {
|
|
496
492
|
statusCode: 200,
|
|
497
493
|
body: {
|
|
498
|
-
realtimeTranscripts: {enable: true},
|
|
494
|
+
data: [{realtimeTranscripts: {enable: true}}],
|
|
499
495
|
},
|
|
500
496
|
};
|
|
501
497
|
mockWebexRequest.request.mockResolvedValue(mockResponse);
|
|
@@ -710,6 +706,32 @@ describe('AgentConfigService', () => {
|
|
|
710
706
|
});
|
|
711
707
|
|
|
712
708
|
describe('getAgentConfig', () => {
|
|
709
|
+
const mockTeamData = [
|
|
710
|
+
{id: 'team1', name: 'Support Team'},
|
|
711
|
+
{id: 'team2', name: 'Sales Team'},
|
|
712
|
+
];
|
|
713
|
+
|
|
714
|
+
const mockOrgInfo = {
|
|
715
|
+
tenantId: 'tenant123',
|
|
716
|
+
timezone: 'GMT',
|
|
717
|
+
environment: 'produs1',
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
const mockSiteInfo = {
|
|
721
|
+
id: 'c6a5451f-5ba7-49a1-aee8-fbef70c19ece',
|
|
722
|
+
name: 'Site-1',
|
|
723
|
+
multimediaProfileId: 'c5888e6f-5661-4871-9936-cbcec7658d41',
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
const mockURLMapping = [
|
|
727
|
+
{key: 'ACQUEON_API_URL', url: 'https://api.example.com'},
|
|
728
|
+
{key: 'ACQUEON_CONSOLE_URL', url: 'https://console.example.com'},
|
|
729
|
+
];
|
|
730
|
+
|
|
731
|
+
const mockAIFeatureFlags = {
|
|
732
|
+
data: [{realtimeTranscripts: {enable: true}}],
|
|
733
|
+
};
|
|
734
|
+
|
|
713
735
|
beforeEach(() => {
|
|
714
736
|
jest.clearAllMocks();
|
|
715
737
|
});
|
|
@@ -770,12 +792,6 @@ describe('AgentConfigService', () => {
|
|
|
770
792
|
maskSensitiveData: false,
|
|
771
793
|
};
|
|
772
794
|
|
|
773
|
-
const mockSiteInfo = {
|
|
774
|
-
id: 'c6a5451f-5ba7-49a1-aee8-fbef70c19ece',
|
|
775
|
-
name: 'Site-1',
|
|
776
|
-
multimediaProfileId: 'c5888e6f-5661-4871-9936-cbcec7658d41',
|
|
777
|
-
};
|
|
778
|
-
|
|
779
795
|
const mockTenantData = {
|
|
780
796
|
timeoutDesktopInactivityEnabled: false,
|
|
781
797
|
timeoutDesktopInactivityMins: 15,
|
|
@@ -789,11 +805,6 @@ describe('AgentConfigService', () => {
|
|
|
789
805
|
callVariablesSuppressed: false,
|
|
790
806
|
};
|
|
791
807
|
|
|
792
|
-
const mockURLMapping = [
|
|
793
|
-
{key: 'ACQUEON_API_URL', url: 'https://api.example.com'},
|
|
794
|
-
{key: 'ACQUEON_CONSOLE_URL', url: 'https://console.example.com'},
|
|
795
|
-
];
|
|
796
|
-
|
|
797
808
|
const mockAuxCodes = [
|
|
798
809
|
{id: 'aux1', type: 'WRAP_UP_CODE', name: 'Wrap Up Code 1', isDefault: true},
|
|
799
810
|
{id: 'aux2', type: 'IDLE_CODE', name: 'Idle Code 1', isDefault: true},
|
|
@@ -915,12 +926,6 @@ describe('AgentConfigService', () => {
|
|
|
915
926
|
maskSensitiveData: true,
|
|
916
927
|
};
|
|
917
928
|
|
|
918
|
-
const mockSiteInfo = {
|
|
919
|
-
id: 'c6a5451f-5ba7-49a1-aee8-fbef70c19ece',
|
|
920
|
-
name: 'Site-1',
|
|
921
|
-
multimediaProfileId: 'c5888e6f-5661-4871-9936-cbcec7658d41',
|
|
922
|
-
};
|
|
923
|
-
|
|
924
929
|
const mockTenantData = {
|
|
925
930
|
timeoutDesktopInactivityEnabled: true,
|
|
926
931
|
timeoutDesktopInactivityMins: 15,
|
|
@@ -935,11 +940,6 @@ describe('AgentConfigService', () => {
|
|
|
935
940
|
lostConnectionRecoveryTimeout: 30,
|
|
936
941
|
};
|
|
937
942
|
|
|
938
|
-
const mockURLMapping = [
|
|
939
|
-
{key: 'ACQUEON_API_URL', url: 'https://api.example.com'},
|
|
940
|
-
{key: 'ACQUEON_CONSOLE_URL', url: 'https://console.example.com'},
|
|
941
|
-
];
|
|
942
|
-
|
|
943
943
|
const mockAuxCodes = [
|
|
944
944
|
{id: 'aux1', type: 'WRAP_UP_CODE', name: 'Wrap Up Code 1'},
|
|
945
945
|
{id: 'aux2', type: 'IDLE_CODE', name: 'Idle Code 1'},
|