@webex/contact-center 3.12.0-next.147 → 3.12.0-next.149
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/.sdd/manifest.json +4 -2
- package/ai-docs/CONTRACTS.md +2 -0
- package/dist/cc.js +3 -1
- package/dist/cc.js.map +1 -1
- package/dist/constants.js +10 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/metrics/constants.js +9 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/ApiAiAssistant.js +87 -10
- package/dist/services/ApiAiAssistant.js.map +1 -1
- package/dist/services/config/types.js +10 -4
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/core/types.js.map +1 -1
- package/dist/services/task/Task.js +202 -1
- package/dist/services/task/Task.js.map +1 -1
- package/dist/services/task/TaskFactory.js +4 -4
- package/dist/services/task/TaskFactory.js.map +1 -1
- package/dist/services/task/TaskManager.js +113 -26
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +11 -1
- package/dist/services/task/TaskUtils.js.map +1 -1
- package/dist/services/task/constants.js +11 -2
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/digital/Digital.js +2 -2
- package/dist/services/task/digital/Digital.js.map +1 -1
- package/dist/services/task/types.js +31 -0
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +2 -2
- package/dist/services/task/voice/Voice.js.map +1 -1
- package/dist/services/task/voice/WebRTC.js +2 -2
- package/dist/services/task/voice/WebRTC.js.map +1 -1
- package/dist/types/constants.d.ts +9 -0
- package/dist/types/index.d.ts +4 -5
- package/dist/types/metrics/constants.d.ts +8 -0
- package/dist/types/services/ApiAiAssistant.d.ts +4 -2
- package/dist/types/services/config/types.d.ts +17 -7
- package/dist/types/services/task/Task.d.ts +15 -4
- package/dist/types/services/task/TaskFactory.d.ts +3 -2
- package/dist/types/services/task/TaskUtils.d.ts +6 -0
- package/dist/types/services/task/constants.d.ts +9 -0
- package/dist/types/services/task/digital/Digital.d.ts +3 -2
- package/dist/types/services/task/types.d.ts +109 -1
- package/dist/types/services/task/voice/Voice.d.ts +3 -2
- package/dist/types/services/task/voice/WebRTC.d.ts +3 -2
- package/dist/types/types.d.ts +10 -4
- package/dist/types.js +8 -4
- package/dist/types.js.map +1 -1
- package/dist/webex.js +1 -1
- package/package.json +3 -3
- package/src/cc.ts +4 -0
- package/src/constants.ts +12 -0
- package/src/index.ts +10 -2
- package/src/metrics/ai-docs/metrics-spec.md +12 -4
- package/src/metrics/constants.ts +10 -0
- package/src/services/ApiAiAssistant.ts +120 -12
- package/src/services/config/types.ts +10 -4
- package/src/services/core/types.ts +31 -1
- package/src/services/task/Task.ts +308 -5
- package/src/services/task/TaskFactory.ts +26 -5
- package/src/services/task/TaskManager.ts +148 -29
- package/src/services/task/TaskUtils.ts +13 -0
- package/src/services/task/ai-docs/task-spec.md +30 -5
- package/src/services/task/constants.ts +10 -0
- package/src/services/task/digital/Digital.ts +8 -3
- package/src/services/task/types.ts +139 -0
- package/src/services/task/voice/Voice.ts +8 -3
- package/src/services/task/voice/WebRTC.ts +6 -3
- package/src/types.ts +10 -4
- package/test/unit/fixtures/aiSummaryTestUtils.ts +40 -0
- package/test/unit/spec/cc.ts +1314 -32
- package/test/unit/spec/index.ts +881 -0
- package/test/unit/spec/services/ApiAiAssistant.ts +122 -6
- package/test/unit/spec/services/core/WebexRequest.ts +5 -6
- package/test/unit/spec/services/task/Task.ts +1988 -10
- package/test/unit/spec/services/task/TaskFactory.ts +17 -0
- package/test/unit/spec/services/task/TaskManager.ts +2327 -180
- package/test/unit/spec/services/task/TaskUtils.ts +72 -0
- package/test/unit/spec/services/task/voice/Voice.ts +353 -18
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
package/test/unit/spec/cc.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import 'jsdom-global/register';
|
|
2
|
+
import EventEmitter from 'events';
|
|
2
3
|
import {
|
|
4
|
+
AIAssistantEventName,
|
|
5
|
+
AIAssistantEventType,
|
|
3
6
|
BuddyAgents,
|
|
4
7
|
BuddyAgentsResponse,
|
|
5
8
|
LoginOption,
|
|
@@ -15,10 +18,14 @@ import type {ContactServiceQueuesResponse} from '../../../src/types';
|
|
|
15
18
|
import MockWebex from '@webex/test-helper-mock-webex';
|
|
16
19
|
import {StationLoginSuccess, AGENT_EVENTS} from '../../../src/services/agent/types';
|
|
17
20
|
import {SetStateResponse} from '../../../src/types';
|
|
18
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
AGENT,
|
|
23
|
+
SUBSCRIBE_API,
|
|
24
|
+
WEB_RTC_PREFIX,
|
|
25
|
+
} from '../../../src/services/constants';
|
|
19
26
|
import Services from '../../../src/services';
|
|
20
27
|
import config from '../../../src/config';
|
|
21
|
-
import {CC_EVENTS} from '../../../src/services/config/types';
|
|
28
|
+
import {CC_TASK_EVENTS, CC_EVENTS} from '../../../src/services/config/types';
|
|
22
29
|
import LoggerProxy from '../../../src/logger-proxy';
|
|
23
30
|
import * as Utils from '../../../src/services/core/Utils';
|
|
24
31
|
import {
|
|
@@ -33,11 +40,26 @@ import {
|
|
|
33
40
|
import '../../../__mocks__/workerMock';
|
|
34
41
|
import {Profile} from '../../../src/services/config/types';
|
|
35
42
|
import TaskManager from '../../../src/services/task/TaskManager';
|
|
36
|
-
import
|
|
43
|
+
import Task from '../../../src/services/task/Task';
|
|
44
|
+
import {
|
|
45
|
+
AgentContact,
|
|
46
|
+
AISummaryResponse,
|
|
47
|
+
TASK_CHANNEL_TYPE,
|
|
48
|
+
TASK_EVENTS,
|
|
49
|
+
TaskData,
|
|
50
|
+
} from '../../../src/services/task/types';
|
|
51
|
+
import {AI_SUMMARY_REQUEST_CANCELLED} from '../../../src/services/task/constants';
|
|
52
|
+
import type {ApiAIAssistant} from '../../../src/services/ApiAiAssistant';
|
|
53
|
+
import type {AIFeatureFlags} from '../../../src/types';
|
|
37
54
|
import MetricsManager from '../../../src/metrics/MetricsManager';
|
|
38
55
|
import {METRIC_EVENT_NAMES} from '../../../src/metrics/constants';
|
|
39
56
|
import Mercury from '@webex/internal-plugin-mercury';
|
|
40
57
|
import WebexRequest from '../../../src/services/core/WebexRequest';
|
|
58
|
+
import {
|
|
59
|
+
createDeferred,
|
|
60
|
+
flushEventLoopTurn,
|
|
61
|
+
flushMicrotasks,
|
|
62
|
+
} from '../fixtures/aiSummaryTestUtils';
|
|
41
63
|
|
|
42
64
|
jest.mock('../../../src/logger-proxy', () => ({
|
|
43
65
|
__esModule: true,
|
|
@@ -45,18 +67,306 @@ jest.mock('../../../src/logger-proxy', () => ({
|
|
|
45
67
|
log: jest.fn(),
|
|
46
68
|
error: jest.fn(),
|
|
47
69
|
info: jest.fn(),
|
|
70
|
+
warn: jest.fn(),
|
|
48
71
|
initialize: jest.fn(),
|
|
49
72
|
},
|
|
50
73
|
}));
|
|
51
74
|
|
|
75
|
+
jest.mock('../../../src/services', () => ({
|
|
76
|
+
__esModule: true,
|
|
77
|
+
default: {
|
|
78
|
+
getInstance: jest.fn(() => ({
|
|
79
|
+
agent: {
|
|
80
|
+
stationLogin: jest.fn(),
|
|
81
|
+
logout: jest.fn(),
|
|
82
|
+
reload: jest.fn(),
|
|
83
|
+
stateChange: jest.fn(),
|
|
84
|
+
buddyAgents: jest.fn(),
|
|
85
|
+
},
|
|
86
|
+
config: {
|
|
87
|
+
getAgentConfig: jest.fn(),
|
|
88
|
+
getOutdialAniEntries: jest.fn(),
|
|
89
|
+
},
|
|
90
|
+
webSocketManager: {
|
|
91
|
+
initWebSocket: jest.fn(),
|
|
92
|
+
on: jest.fn(),
|
|
93
|
+
off: jest.fn(),
|
|
94
|
+
close: jest.fn(),
|
|
95
|
+
isSocketClosed: false,
|
|
96
|
+
},
|
|
97
|
+
rtdWebSocketManager: {
|
|
98
|
+
initWebSocket: jest.fn(),
|
|
99
|
+
on: jest.fn(),
|
|
100
|
+
off: jest.fn(),
|
|
101
|
+
close: jest.fn(),
|
|
102
|
+
isSocketClosed: false,
|
|
103
|
+
},
|
|
104
|
+
connectionService: {
|
|
105
|
+
on: jest.fn(),
|
|
106
|
+
off: jest.fn(),
|
|
107
|
+
},
|
|
108
|
+
contact: {},
|
|
109
|
+
dialer: {
|
|
110
|
+
startOutdial: jest.fn(),
|
|
111
|
+
acceptPreviewContact: jest.fn(),
|
|
112
|
+
skipPreviewContact: jest.fn(),
|
|
113
|
+
removePreviewContact: jest.fn(),
|
|
114
|
+
},
|
|
115
|
+
})),
|
|
116
|
+
},
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
jest.mock('../../../src/services/task/TaskManager', () => ({
|
|
120
|
+
__esModule: true,
|
|
121
|
+
default: {
|
|
122
|
+
getTaskManager: jest.fn(() => ({
|
|
123
|
+
on: jest.fn(),
|
|
124
|
+
off: jest.fn(),
|
|
125
|
+
emit: jest.fn(),
|
|
126
|
+
setConfigFlags: jest.fn(),
|
|
127
|
+
setWrapupData: jest.fn(),
|
|
128
|
+
setAgentId: jest.fn(),
|
|
129
|
+
setAgentName: jest.fn(),
|
|
130
|
+
setWebRtcEnabled: jest.fn(),
|
|
131
|
+
setAnswerCallOnWebexService: jest.fn(),
|
|
132
|
+
registerIncomingCallEvent: jest.fn(),
|
|
133
|
+
registerTaskListeners: jest.fn(),
|
|
134
|
+
unregisterIncomingCallEvent: jest.fn(),
|
|
135
|
+
getTask: jest.fn(),
|
|
136
|
+
getActiveTasks: jest.fn(),
|
|
137
|
+
handleRealtimeWebsocketEvent: jest.fn(),
|
|
138
|
+
clearAISummaryState: jest.fn(),
|
|
139
|
+
})),
|
|
140
|
+
},
|
|
141
|
+
}));
|
|
142
|
+
|
|
143
|
+
jest.mock('../../../src/metrics/MetricsManager', () => ({
|
|
144
|
+
__esModule: true,
|
|
145
|
+
default: {
|
|
146
|
+
getInstance: jest.fn(),
|
|
147
|
+
getCommonTrackingFieldForAQMResponse: jest.fn(() => ({})),
|
|
148
|
+
getCommonTrackingFieldForAQMResponseFailed: jest.fn(() => ({})),
|
|
149
|
+
},
|
|
150
|
+
}));
|
|
151
|
+
|
|
152
|
+
jest.mock('../../../src/services/core/WebexRequest', () => ({
|
|
153
|
+
__esModule: true,
|
|
154
|
+
default: {
|
|
155
|
+
getInstance: jest.fn(() => ({
|
|
156
|
+
request: jest.fn(),
|
|
157
|
+
uploadLogs: jest.fn(),
|
|
158
|
+
})),
|
|
159
|
+
},
|
|
160
|
+
}));
|
|
161
|
+
|
|
162
|
+
jest.mock('../../../src/services/ApiAiAssistant', () => {
|
|
163
|
+
class MockApiAIAssistant {
|
|
164
|
+
public sendEvent = jest.fn();
|
|
165
|
+
public requestAndWaitForRtd = jest.fn();
|
|
166
|
+
public resolveFromRtdEvent = jest.fn();
|
|
167
|
+
public clearAllRtdRequests = jest.fn();
|
|
168
|
+
public getSuggestedResponse = jest.fn();
|
|
169
|
+
public fetchHistoricTranscripts = jest.fn();
|
|
170
|
+
public requestWellnessBreak = jest.fn();
|
|
171
|
+
public respondToWellnessBreak = jest.fn();
|
|
172
|
+
public setAIFeatureFlags = jest.fn();
|
|
173
|
+
public setAgentId = jest.fn();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
__esModule: true,
|
|
178
|
+
default: MockApiAIAssistant,
|
|
179
|
+
ApiAIAssistant: MockApiAIAssistant,
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
jest.mock('../../../src/services/WebCallingService', () => {
|
|
184
|
+
class MockWebCallingService {
|
|
185
|
+
public loginOption = 'AGENT_DN';
|
|
186
|
+
public registerWebCallingLine = jest.fn().mockResolvedValue(undefined);
|
|
187
|
+
public deregisterWebCallingLine = jest.fn().mockResolvedValue(undefined);
|
|
188
|
+
public setLoginOption = jest.fn((loginOption: string) => {
|
|
189
|
+
this.loginOption = loginOption;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
__esModule: true,
|
|
195
|
+
default: MockWebCallingService,
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
jest.mock('../../../src/services/task/TaskFactory', () => ({
|
|
200
|
+
__esModule: true,
|
|
201
|
+
default: {
|
|
202
|
+
createTask: jest.fn(),
|
|
203
|
+
},
|
|
204
|
+
}));
|
|
205
|
+
|
|
206
|
+
jest.mock('../../../src/services/task/voice/WebRTC', () => ({
|
|
207
|
+
__esModule: true,
|
|
208
|
+
default: class MockWebRTC {},
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
jest.mock('../../../src/services/task/taskDataNormalizer', () => ({
|
|
212
|
+
normalizeTaskData: jest.fn((data) => data),
|
|
213
|
+
}));
|
|
214
|
+
|
|
215
|
+
jest.mock('../../../src/services/task/state-machine', () => ({
|
|
216
|
+
...jest.requireActual('../../../src/services/task/state-machine'),
|
|
217
|
+
createTaskStateMachine: jest.fn(
|
|
218
|
+
jest.requireActual('../../../src/services/task/state-machine').createTaskStateMachine
|
|
219
|
+
),
|
|
220
|
+
}));
|
|
221
|
+
|
|
222
|
+
jest.mock('../../../src/services/task/state-machine/uiControlsComputer', () => ({
|
|
223
|
+
computeUIControls: jest.fn(() => ({})),
|
|
224
|
+
getDefaultUIControls: jest.fn(() => ({})),
|
|
225
|
+
haveUIControlsChanged: jest.fn(() => false),
|
|
226
|
+
}));
|
|
227
|
+
|
|
228
|
+
jest.mock('../../../src/services/task/AutoWrapup', () => ({
|
|
229
|
+
__esModule: true,
|
|
230
|
+
default: class MockAutoWrapup {},
|
|
231
|
+
}));
|
|
232
|
+
|
|
233
|
+
jest.mock('../../../src/services/EntryPoint', () => {
|
|
234
|
+
class MockEntryPoint {
|
|
235
|
+
public getEntryPoints() {
|
|
236
|
+
return Promise.resolve({});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
__esModule: true,
|
|
242
|
+
default: MockEntryPoint,
|
|
243
|
+
EntryPoint: MockEntryPoint,
|
|
244
|
+
};
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
jest.mock('../../../src/services/AddressBook', () => {
|
|
248
|
+
class MockAddressBook {
|
|
249
|
+
public getEntries() {
|
|
250
|
+
return Promise.resolve({});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
__esModule: true,
|
|
256
|
+
default: MockAddressBook,
|
|
257
|
+
AddressBook: MockAddressBook,
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
jest.mock('../../../src/services/Queue', () => {
|
|
262
|
+
class MockQueue {
|
|
263
|
+
public getQueues() {
|
|
264
|
+
return Promise.resolve({});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
__esModule: true,
|
|
270
|
+
default: MockQueue,
|
|
271
|
+
Queue: MockQueue,
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
|
|
52
275
|
jest.mock('../../../src/services/config');
|
|
53
276
|
jest.mock('../../../src/services/core/websocket/WebSocketManager');
|
|
54
277
|
jest.mock('../../../src/services/core/websocket/connection-service');
|
|
55
|
-
jest.mock('../../../src/services/WebCallingService');
|
|
56
278
|
jest.mock('uuid', () => ({v4: () => 'mock-tracking-uuid'}));
|
|
57
279
|
|
|
58
280
|
global.URL.createObjectURL = jest.fn(() => 'blob:http://localhost:3000/12345');
|
|
59
281
|
|
|
282
|
+
class EventEmitterDouble extends EventEmitter {
|
|
283
|
+
public isSocketClosed = false;
|
|
284
|
+
public initWebSocket = jest.fn().mockResolvedValue({});
|
|
285
|
+
public close = jest.fn(() => {
|
|
286
|
+
this.isSocketClosed = true;
|
|
287
|
+
});
|
|
288
|
+
public mapCallToTask = jest.fn();
|
|
289
|
+
|
|
290
|
+
public on = jest.fn((eventName: string | symbol, listener: (...args: unknown[]) => void) => {
|
|
291
|
+
super.on(eventName, listener);
|
|
292
|
+
|
|
293
|
+
return this;
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
public off = jest.fn((eventName: string | symbol, listener: (...args: unknown[]) => void) => {
|
|
297
|
+
super.off(eventName, listener);
|
|
298
|
+
|
|
299
|
+
return this;
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
class AISummaryLifecycleTask extends Task {
|
|
304
|
+
public constructor(data: TaskData, apiAIAssistant?: ApiAIAssistant, agentId = 'agent-1') {
|
|
305
|
+
super(
|
|
306
|
+
{} as any,
|
|
307
|
+
data,
|
|
308
|
+
{
|
|
309
|
+
channelType: TASK_CHANNEL_TYPE.VOICE,
|
|
310
|
+
isEndTaskEnabled: true,
|
|
311
|
+
isEndConsultEnabled: true,
|
|
312
|
+
},
|
|
313
|
+
undefined,
|
|
314
|
+
agentId,
|
|
315
|
+
undefined,
|
|
316
|
+
apiAIAssistant
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
public accept() {
|
|
321
|
+
return Promise.resolve({} as any);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const createAISummaryLifecycleTask = (
|
|
326
|
+
data: TaskData = createAISummaryLifecycleTaskData(),
|
|
327
|
+
apiAIAssistant?: ApiAIAssistant
|
|
328
|
+
): AISummaryLifecycleTask => new AISummaryLifecycleTask(data, apiAIAssistant);
|
|
329
|
+
|
|
330
|
+
const createAISummaryLifecycleTaskData = (
|
|
331
|
+
overrides: Partial<TaskData> & {conversationId?: string} = {}
|
|
332
|
+
): TaskData => {
|
|
333
|
+
const {
|
|
334
|
+
conversationId = 'conversation-1',
|
|
335
|
+
interaction: interactionOverrides,
|
|
336
|
+
...taskOverrides
|
|
337
|
+
} = overrides;
|
|
338
|
+
const interactionId = taskOverrides.interactionId ?? 'interaction-1';
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
mediaResourceId: 'media-resource-1',
|
|
342
|
+
eventType: CC_EVENTS.AGENT_CONTACT_ASSIGNED,
|
|
343
|
+
agentId: 'agent-1',
|
|
344
|
+
destAgentId: '',
|
|
345
|
+
trackingId: 'tracking-1',
|
|
346
|
+
consultMediaResourceId: '',
|
|
347
|
+
interactionId,
|
|
348
|
+
orgId: 'org-1',
|
|
349
|
+
owner: 'agent-1',
|
|
350
|
+
queueMgr: 'queue-manager-1',
|
|
351
|
+
type: 'telephony',
|
|
352
|
+
isConferencing: false,
|
|
353
|
+
taskId: 'task-owner-1',
|
|
354
|
+
...taskOverrides,
|
|
355
|
+
interaction: {
|
|
356
|
+
interactionId,
|
|
357
|
+
mainInteractionId: conversationId,
|
|
358
|
+
mediaType: 'telephony',
|
|
359
|
+
media: {
|
|
360
|
+
[interactionId]: {
|
|
361
|
+
mediaResourceId: 'media-resource-1',
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
callProcessingDetails: {},
|
|
365
|
+
...(interactionOverrides as Record<string, unknown> | undefined),
|
|
366
|
+
},
|
|
367
|
+
} as TaskData;
|
|
368
|
+
};
|
|
369
|
+
|
|
60
370
|
describe('webex.cc', () => {
|
|
61
371
|
let webex;
|
|
62
372
|
let mockApiAIAssistant;
|
|
@@ -169,6 +479,7 @@ describe('webex.cc', () => {
|
|
|
169
479
|
setConfigFlags: jest.fn(),
|
|
170
480
|
setWrapupData: jest.fn(),
|
|
171
481
|
setAgentId: jest.fn(),
|
|
482
|
+
setAgentName: jest.fn(),
|
|
172
483
|
setWebRtcEnabled: jest.fn(),
|
|
173
484
|
setAnswerCallOnWebexService: jest.fn(),
|
|
174
485
|
handleRealtimeWebsocketEvent: jest.fn(),
|
|
@@ -183,6 +494,8 @@ describe('webex.cc', () => {
|
|
|
183
494
|
off: jest.fn(),
|
|
184
495
|
emit: jest.fn(),
|
|
185
496
|
unregisterIncomingCallEvent: jest.fn(),
|
|
497
|
+
handleRealtimeWebsocketEvent: jest.fn(),
|
|
498
|
+
clearAISummaryState: jest.fn(),
|
|
186
499
|
};
|
|
187
500
|
|
|
188
501
|
mockMetricsManager = {
|
|
@@ -206,6 +519,7 @@ describe('webex.cc', () => {
|
|
|
206
519
|
|
|
207
520
|
afterEach(() => {
|
|
208
521
|
jest.clearAllMocks();
|
|
522
|
+
jest.restoreAllMocks();
|
|
209
523
|
});
|
|
210
524
|
|
|
211
525
|
it('should initialize services and logger proxy on ready event', () => {
|
|
@@ -272,7 +586,7 @@ describe('webex.cc', () => {
|
|
|
272
586
|
});
|
|
273
587
|
|
|
274
588
|
describe('register', () => {
|
|
275
|
-
const
|
|
589
|
+
const createFreshAgentProfile = (overrides: Partial<Profile> = {}): Profile => ({
|
|
276
590
|
agentId: 'agent123',
|
|
277
591
|
agentMailId: '',
|
|
278
592
|
agentName: 'John',
|
|
@@ -325,7 +639,10 @@ describe('webex.cc', () => {
|
|
|
325
639
|
webRtcEnabled: true,
|
|
326
640
|
lostConnectionRecoveryTimeout: 0,
|
|
327
641
|
isWellnessBreakEnabled: false,
|
|
328
|
-
|
|
642
|
+
...overrides,
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
const mockAgentProfile: Profile = createFreshAgentProfile();
|
|
329
646
|
|
|
330
647
|
it('should register successfully and return agent profile', async () => {
|
|
331
648
|
mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: true}} as any;
|
|
@@ -376,7 +693,7 @@ describe('webex.cc', () => {
|
|
|
376
693
|
resource: SUBSCRIBE_API,
|
|
377
694
|
});
|
|
378
695
|
|
|
379
|
-
//
|
|
696
|
+
// SPARK-626777 tracks moving listener cleanup into the future de-register API.
|
|
380
697
|
expect(mockTaskManager.on).toHaveBeenCalledWith(
|
|
381
698
|
TASK_EVENTS.TASK_INCOMING,
|
|
382
699
|
expect.any(Function)
|
|
@@ -426,6 +743,7 @@ describe('webex.cc', () => {
|
|
|
426
743
|
getWxAppUsersubPublished: expect.any(Function),
|
|
427
744
|
})
|
|
428
745
|
);
|
|
746
|
+
expect(mockTaskManager.clearAISummaryState).not.toHaveBeenCalled();
|
|
429
747
|
expect(reloadSpy).toHaveBeenCalled();
|
|
430
748
|
expect(result).toEqual({...mockAgentProfile, agentSessionId: 'session-after-refresh'});
|
|
431
749
|
expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
|
|
@@ -722,6 +1040,909 @@ describe('webex.cc', () => {
|
|
|
722
1040
|
expect.any(Function)
|
|
723
1041
|
);
|
|
724
1042
|
});
|
|
1043
|
+
|
|
1044
|
+
it('should connect RTD websocket when either generated summary organization flag is enabled', async () => {
|
|
1045
|
+
jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
|
|
1046
|
+
jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
|
|
1047
|
+
data: {
|
|
1048
|
+
auxCodeId: 'auxCodeId',
|
|
1049
|
+
agentId: 'agentId',
|
|
1050
|
+
deviceType: LoginOption.EXTENSION,
|
|
1051
|
+
dn: '12345',
|
|
1052
|
+
},
|
|
1053
|
+
});
|
|
1054
|
+
mockWebSocketManager.initWebSocket.mockResolvedValue({agentId: 'agent123'});
|
|
1055
|
+
|
|
1056
|
+
for (const generatedSummaries of [
|
|
1057
|
+
{wrapUpSummariesEnabled: true, consultTransferSummariesEnabled: false},
|
|
1058
|
+
{wrapUpSummariesEnabled: false, consultTransferSummariesEnabled: true},
|
|
1059
|
+
]) {
|
|
1060
|
+
jest.clearAllMocks();
|
|
1061
|
+
jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue({
|
|
1062
|
+
...mockAgentProfile,
|
|
1063
|
+
aiFeature: {generatedSummaries} as any,
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
await webex.cc.register();
|
|
1067
|
+
|
|
1068
|
+
expect(webex.cc.services.rtdWebSocketManager.initWebSocket).toHaveBeenCalledWith({
|
|
1069
|
+
body: {
|
|
1070
|
+
force: true,
|
|
1071
|
+
isKeepAliveEnabled: false,
|
|
1072
|
+
clientType: 'WebexCCSDK',
|
|
1073
|
+
allowMultiLogin: false,
|
|
1074
|
+
},
|
|
1075
|
+
resource: 'v1/realtime/subscribe',
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
it.each([
|
|
1081
|
+
{
|
|
1082
|
+
title: 'aiFeature is absent',
|
|
1083
|
+
profileAgentId: 'profile-agent-without-ai-feature',
|
|
1084
|
+
defaultDn: 'normalized-dn-without-ai-feature',
|
|
1085
|
+
aiFeature: undefined,
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
title: 'aiFeature has no optional feature objects',
|
|
1089
|
+
profileAgentId: 'profile-agent-with-empty-ai-feature',
|
|
1090
|
+
defaultDn: 'normalized-dn-with-empty-ai-feature',
|
|
1091
|
+
aiFeature: {} as any,
|
|
1092
|
+
},
|
|
1093
|
+
])(
|
|
1094
|
+
'should tolerate absent AI feature shapes without summary-driven RTD setup when $title',
|
|
1095
|
+
async ({profileAgentId, defaultDn, aiFeature}) => {
|
|
1096
|
+
const welcomeAgentId = 'welcome-agent-for-absent-ai-feature';
|
|
1097
|
+
const configuredProfile = createFreshAgentProfile({
|
|
1098
|
+
agentId: profileAgentId,
|
|
1099
|
+
defaultDn,
|
|
1100
|
+
dn: 'pre-register-dn',
|
|
1101
|
+
webRtcEnabled: true,
|
|
1102
|
+
aiFeature,
|
|
1103
|
+
});
|
|
1104
|
+
const expectedProfile = {...configuredProfile, dn: defaultDn};
|
|
1105
|
+
webex.cc.$config = {...webex.cc.$config, allowAutomatedRelogin: false};
|
|
1106
|
+
jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
|
|
1107
|
+
const configSpy = jest
|
|
1108
|
+
.spyOn(webex.cc.services.config, 'getAgentConfig')
|
|
1109
|
+
.mockResolvedValue(configuredProfile);
|
|
1110
|
+
mockWebSocketManager.initWebSocket.mockResolvedValue({agentId: welcomeAgentId});
|
|
1111
|
+
|
|
1112
|
+
await expect(webex.cc.register()).resolves.toEqual(expectedProfile);
|
|
1113
|
+
|
|
1114
|
+
expect(configSpy).toHaveBeenCalledWith('mockOrgId', welcomeAgentId);
|
|
1115
|
+
expect(webex.cc.services.rtdWebSocketManager.initWebSocket).not.toHaveBeenCalled();
|
|
1116
|
+
}
|
|
1117
|
+
);
|
|
1118
|
+
|
|
1119
|
+
it.each([
|
|
1120
|
+
{
|
|
1121
|
+
title: 'all generated summary organization flags are disabled',
|
|
1122
|
+
aiFeature: {
|
|
1123
|
+
realtimeTranscripts: {enable: false},
|
|
1124
|
+
suggestedResponses: {enable: false},
|
|
1125
|
+
generatedSummaries: {
|
|
1126
|
+
wrapUpSummariesEnabled: false,
|
|
1127
|
+
consultTransferSummariesEnabled: false,
|
|
1128
|
+
},
|
|
1129
|
+
} as any,
|
|
1130
|
+
},
|
|
1131
|
+
])(
|
|
1132
|
+
'should keep registration operational without summary-driven RTD setup when $title',
|
|
1133
|
+
async ({aiFeature}) => {
|
|
1134
|
+
const welcomeAgentId = 'welcome-agent-with-disabled-summaries';
|
|
1135
|
+
const configuredProfile = createFreshAgentProfile({
|
|
1136
|
+
agentId: 'profile-agent-with-disabled-summaries',
|
|
1137
|
+
defaultDn: 'normalized-dn-with-disabled-summaries',
|
|
1138
|
+
dn: 'pre-register-dn',
|
|
1139
|
+
webRtcEnabled: true,
|
|
1140
|
+
aiFeature,
|
|
1141
|
+
});
|
|
1142
|
+
const expectedProfile = {...configuredProfile, dn: configuredProfile.defaultDn};
|
|
1143
|
+
webex.cc.$config = {...webex.cc.$config, allowAutomatedRelogin: false};
|
|
1144
|
+
const mercuryConnect = jest
|
|
1145
|
+
.spyOn(webex.internal.mercury, 'connect')
|
|
1146
|
+
.mockResolvedValue(true);
|
|
1147
|
+
const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
|
|
1148
|
+
const configSpy = jest
|
|
1149
|
+
.spyOn(webex.cc.services.config, 'getAgentConfig')
|
|
1150
|
+
.mockResolvedValue(configuredProfile);
|
|
1151
|
+
mockWebSocketManager.initWebSocket.mockResolvedValue({agentId: welcomeAgentId});
|
|
1152
|
+
|
|
1153
|
+
const result = await webex.cc.register();
|
|
1154
|
+
|
|
1155
|
+
expect(mockWebSocketManager.initWebSocket).toHaveBeenCalledWith({
|
|
1156
|
+
body: {
|
|
1157
|
+
force: true,
|
|
1158
|
+
isKeepAliveEnabled: false,
|
|
1159
|
+
clientType: 'WebexCCSDK',
|
|
1160
|
+
allowMultiLogin: false,
|
|
1161
|
+
},
|
|
1162
|
+
resource: SUBSCRIBE_API,
|
|
1163
|
+
});
|
|
1164
|
+
expect(configSpy).toHaveBeenCalledWith('mockOrgId', welcomeAgentId);
|
|
1165
|
+
expect(setupEventListenersSpy).toHaveBeenCalled();
|
|
1166
|
+
expect(mockTaskManager.on).toHaveBeenCalledWith(
|
|
1167
|
+
TASK_EVENTS.TASK_INCOMING,
|
|
1168
|
+
expect.any(Function)
|
|
1169
|
+
);
|
|
1170
|
+
expect(mockTaskManager.on).toHaveBeenCalledWith(
|
|
1171
|
+
TASK_EVENTS.TASK_HYDRATE,
|
|
1172
|
+
expect.any(Function)
|
|
1173
|
+
);
|
|
1174
|
+
expect(mockTaskManager.on).toHaveBeenCalledWith(
|
|
1175
|
+
TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
|
|
1176
|
+
expect.any(Function)
|
|
1177
|
+
);
|
|
1178
|
+
expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
|
|
1179
|
+
expect(mockTaskManager.setConfigFlags).toHaveBeenCalledWith({
|
|
1180
|
+
isEndTaskEnabled: configuredProfile.isEndTaskEnabled,
|
|
1181
|
+
isEndConsultEnabled: configuredProfile.isEndConsultEnabled,
|
|
1182
|
+
webRtcEnabled: configuredProfile.webRtcEnabled,
|
|
1183
|
+
autoWrapup: configuredProfile.wrapUpData.wrapUpProps.autoWrapup ?? false,
|
|
1184
|
+
aiFeature,
|
|
1185
|
+
getWxAppUsersubPublished: expect.any(Function),
|
|
1186
|
+
consultTransfer: {
|
|
1187
|
+
allowConsultToQueue: configuredProfile.allowConsultToQueue,
|
|
1188
|
+
accessQueue: configuredProfile.accessQueue,
|
|
1189
|
+
accessEntryPoint: configuredProfile.accessEntryPoint,
|
|
1190
|
+
accessBuddyTeam: configuredProfile.accessBuddyTeam,
|
|
1191
|
+
},
|
|
1192
|
+
enableWxBetterTogether: false,
|
|
1193
|
+
});
|
|
1194
|
+
expect(webex.cc.services.rtdWebSocketManager.initWebSocket).not.toHaveBeenCalled();
|
|
1195
|
+
expect(webex.cc.services.rtdWebSocketManager.on).not.toHaveBeenCalledWith(
|
|
1196
|
+
'message',
|
|
1197
|
+
expect.any(Function)
|
|
1198
|
+
);
|
|
1199
|
+
expect(mercuryConnect).toHaveBeenCalled();
|
|
1200
|
+
expect(result).toEqual(expectedProfile);
|
|
1201
|
+
}
|
|
1202
|
+
);
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
describe('AI summary lifecycle wiring', () => {
|
|
1206
|
+
const sentinels = [
|
|
1207
|
+
'summary-sentinel-cc-lifecycle',
|
|
1208
|
+
'section-key-sentinel-cc-lifecycle',
|
|
1209
|
+
'section-value-sentinel-cc-lifecycle',
|
|
1210
|
+
'adaptive-card-sentinel-cc-lifecycle',
|
|
1211
|
+
'agent-name-sentinel-cc-lifecycle',
|
|
1212
|
+
];
|
|
1213
|
+
|
|
1214
|
+
const createSummaryHarness = () => {
|
|
1215
|
+
const ActualTaskManager = jest.requireActual('../../../src/services/task/TaskManager')
|
|
1216
|
+
.default as typeof TaskManager;
|
|
1217
|
+
const webSocketManager = new EventEmitterDouble();
|
|
1218
|
+
const rtdWebSocketManager = new EventEmitterDouble();
|
|
1219
|
+
const connectionService = new EventEmitterDouble();
|
|
1220
|
+
const webCallingService = new EventEmitterDouble();
|
|
1221
|
+
const transportDeferreds: Deferred<void>[] = [];
|
|
1222
|
+
const pendingRequests = new Map<string, any>();
|
|
1223
|
+
const apiAIAssistant: any = {
|
|
1224
|
+
aiFeature: {
|
|
1225
|
+
id: 'test-ai-feature',
|
|
1226
|
+
generatedSummaries: {
|
|
1227
|
+
wrapUpSummariesEnabled: true,
|
|
1228
|
+
consultTransferSummariesEnabled: true,
|
|
1229
|
+
},
|
|
1230
|
+
},
|
|
1231
|
+
getSuggestedResponse: jest.fn(),
|
|
1232
|
+
fetchHistoricTranscripts: jest.fn(),
|
|
1233
|
+
setAIFeatureFlags: jest.fn((flags: AIFeatureFlags) => {
|
|
1234
|
+
apiAIAssistant.aiFeature = flags;
|
|
1235
|
+
}),
|
|
1236
|
+
setAgentId: jest.fn(),
|
|
1237
|
+
pendingRequests,
|
|
1238
|
+
sendEvent: jest.fn((_agentId: string, _interactionId: string, _eventType: string, eventName: string) => {
|
|
1239
|
+
if (eventName.includes('SUMMARY_RESPONSE')) {
|
|
1240
|
+
return Promise.resolve(undefined);
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
const deferred = createDeferred<void>();
|
|
1244
|
+
|
|
1245
|
+
transportDeferreds.push(deferred);
|
|
1246
|
+
|
|
1247
|
+
return deferred.promise;
|
|
1248
|
+
}),
|
|
1249
|
+
};
|
|
1250
|
+
apiAIAssistant.requestAndWaitForRtd = jest.fn(async (options: any) => {
|
|
1251
|
+
const publishTimestamp = Date.now();
|
|
1252
|
+
let resolveResult: (payload: any) => void = () => undefined;
|
|
1253
|
+
let rejectResult: (error: Error) => void = () => undefined;
|
|
1254
|
+
const result = new Promise((resolve, reject) => {
|
|
1255
|
+
resolveResult = resolve;
|
|
1256
|
+
rejectResult = reject;
|
|
1257
|
+
});
|
|
1258
|
+
const key = JSON.stringify([options.rtdEventType, options.correlationId]);
|
|
1259
|
+
pendingRequests.set(key, {
|
|
1260
|
+
eventType: options.rtdEventType,
|
|
1261
|
+
correlationId: options.correlationId,
|
|
1262
|
+
resolve: resolveResult,
|
|
1263
|
+
reject: rejectResult,
|
|
1264
|
+
result,
|
|
1265
|
+
});
|
|
1266
|
+
try {
|
|
1267
|
+
const acknowledgement = apiAIAssistant.sendEvent(
|
|
1268
|
+
options.agentId,
|
|
1269
|
+
options.interactionId,
|
|
1270
|
+
options.eventType,
|
|
1271
|
+
options.eventName,
|
|
1272
|
+
options.eventMetaData,
|
|
1273
|
+
undefined,
|
|
1274
|
+
undefined,
|
|
1275
|
+
publishTimestamp,
|
|
1276
|
+
options.timeout
|
|
1277
|
+
);
|
|
1278
|
+
const [payload] = await Promise.all([result, acknowledgement]);
|
|
1279
|
+
pendingRequests.delete(key);
|
|
1280
|
+
return payload;
|
|
1281
|
+
} catch (error) {
|
|
1282
|
+
pendingRequests.delete(key);
|
|
1283
|
+
throw error;
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
apiAIAssistant.resolveFromRtdEvent = jest.fn(
|
|
1287
|
+
(eventType: string, correlationId: string, payload: any) => {
|
|
1288
|
+
const key = JSON.stringify([eventType, correlationId]);
|
|
1289
|
+
const request = pendingRequests.get(key);
|
|
1290
|
+
if (!request) return 'not-found';
|
|
1291
|
+
pendingRequests.delete(key);
|
|
1292
|
+
request.resolve(payload);
|
|
1293
|
+
return 'resolved';
|
|
1294
|
+
}
|
|
1295
|
+
);
|
|
1296
|
+
apiAIAssistant.clearAllRtdRequests = jest.fn(() => {
|
|
1297
|
+
pendingRequests.forEach((request) => {
|
|
1298
|
+
const error = new Error(AI_SUMMARY_REQUEST_CANCELLED) as Error & {
|
|
1299
|
+
data?: Record<string, unknown>;
|
|
1300
|
+
};
|
|
1301
|
+
error.data = {errorCode: AI_SUMMARY_REQUEST_CANCELLED};
|
|
1302
|
+
request.reject(error);
|
|
1303
|
+
});
|
|
1304
|
+
pendingRequests.clear();
|
|
1305
|
+
});
|
|
1306
|
+
const taskManager = new ActualTaskManager(
|
|
1307
|
+
apiAIAssistant as any,
|
|
1308
|
+
mockContact,
|
|
1309
|
+
webCallingService as any,
|
|
1310
|
+
webSocketManager as any,
|
|
1311
|
+
rtdWebSocketManager as any
|
|
1312
|
+
);
|
|
1313
|
+
const taskFactory = jest.requireMock('../../../src/services/task/TaskFactory')
|
|
1314
|
+
.default as {createTask: jest.Mock};
|
|
1315
|
+
|
|
1316
|
+
taskFactory.createTask.mockImplementation(
|
|
1317
|
+
(
|
|
1318
|
+
_contact,
|
|
1319
|
+
_webCallingService,
|
|
1320
|
+
taskData: TaskData,
|
|
1321
|
+
_configFlags,
|
|
1322
|
+
_wrapupData,
|
|
1323
|
+
_agentId,
|
|
1324
|
+
_agentName,
|
|
1325
|
+
_answerCallService,
|
|
1326
|
+
taskApiAIAssistant: ApiAIAssistant
|
|
1327
|
+
) => createAISummaryLifecycleTask(taskData, taskApiAIAssistant)
|
|
1328
|
+
);
|
|
1329
|
+
const task = createAISummaryLifecycleTask(undefined, apiAIAssistant);
|
|
1330
|
+
|
|
1331
|
+
taskManager.setConfigFlags({
|
|
1332
|
+
isEndTaskEnabled: true,
|
|
1333
|
+
isEndConsultEnabled: true,
|
|
1334
|
+
webRtcEnabled: false,
|
|
1335
|
+
autoWrapup: false,
|
|
1336
|
+
aiFeature: {
|
|
1337
|
+
generatedSummaries: {
|
|
1338
|
+
wrapUpSummariesEnabled: true,
|
|
1339
|
+
consultTransferSummariesEnabled: true,
|
|
1340
|
+
},
|
|
1341
|
+
},
|
|
1342
|
+
} as any);
|
|
1343
|
+
taskManager.setAgentId('agent-1');
|
|
1344
|
+
taskManager.setWebRtcEnabled(false);
|
|
1345
|
+
(taskManager as any).taskCollection[task.data.interactionId] = task;
|
|
1346
|
+
|
|
1347
|
+
(webex.cc as any).taskManager = taskManager;
|
|
1348
|
+
webex.cc.apiAIAssistant = apiAIAssistant as any;
|
|
1349
|
+
webex.cc.services.webSocketManager = webSocketManager as any;
|
|
1350
|
+
webex.cc.services.rtdWebSocketManager = rtdWebSocketManager as any;
|
|
1351
|
+
webex.cc.services.connectionService = connectionService as any;
|
|
1352
|
+
|
|
1353
|
+
const emitRtdFrame = (type: string, data: Record<string, unknown>) => {
|
|
1354
|
+
rtdWebSocketManager.emit('message', JSON.stringify({type, data: {data}}));
|
|
1355
|
+
};
|
|
1356
|
+
const dispatchRtdFrame = (type: string, data: Record<string, unknown>) => {
|
|
1357
|
+
webex.cc['handleRTDWebsocketMessage'](JSON.stringify({type, data: {data}}));
|
|
1358
|
+
};
|
|
1359
|
+
const getApiAIAssistant = () => apiAIAssistant as any;
|
|
1360
|
+
const getSummaryMapCounts = () => {
|
|
1361
|
+
const api = getApiAIAssistant() as any;
|
|
1362
|
+
const manager = taskManager as any;
|
|
1363
|
+
const pendingRequests = Array.from(api.pendingRequests.values());
|
|
1364
|
+
|
|
1365
|
+
return {
|
|
1366
|
+
pendingPostCall: pendingRequests.filter(
|
|
1367
|
+
(request: any) => request.eventType === 'POST_CALL_SUMMARY'
|
|
1368
|
+
).length,
|
|
1369
|
+
pendingMidCall: pendingRequests.filter(
|
|
1370
|
+
(request: any) => request.eventType === 'MID_CALL_SUMMARY'
|
|
1371
|
+
).length,
|
|
1372
|
+
receiving: manager.receivingSummaryBuffer.size,
|
|
1373
|
+
featureEnablement: manager.pendingFeatureEnablement.size,
|
|
1374
|
+
};
|
|
1375
|
+
};
|
|
1376
|
+
const expectSummaryStateCleared = () => {
|
|
1377
|
+
expect(getSummaryMapCounts()).toEqual({
|
|
1378
|
+
pendingPostCall: 0,
|
|
1379
|
+
pendingMidCall: 0,
|
|
1380
|
+
receiving: 0,
|
|
1381
|
+
featureEnablement: 0,
|
|
1382
|
+
});
|
|
1383
|
+
};
|
|
1384
|
+
|
|
1385
|
+
return {
|
|
1386
|
+
apiAIAssistant,
|
|
1387
|
+
connectionService,
|
|
1388
|
+
dispatchRtdFrame,
|
|
1389
|
+
emitRtdFrame,
|
|
1390
|
+
expectSummaryStateCleared,
|
|
1391
|
+
getApiAIAssistant,
|
|
1392
|
+
getSummaryMapCounts,
|
|
1393
|
+
rtdWebSocketManager,
|
|
1394
|
+
task,
|
|
1395
|
+
taskManager,
|
|
1396
|
+
transportDeferreds,
|
|
1397
|
+
webSocketManager,
|
|
1398
|
+
};
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1401
|
+
const attachRtdMessageListener = (rtdWebSocketManager: EventEmitterDouble) => {
|
|
1402
|
+
rtdWebSocketManager.on('message', webex.cc['handleRTDWebsocketMessage']);
|
|
1403
|
+
};
|
|
1404
|
+
|
|
1405
|
+
const countAISummaryRequestFinalMetrics = () =>
|
|
1406
|
+
mockMetricsManager.trackEvent.mock.calls.filter(([eventName]) =>
|
|
1407
|
+
[
|
|
1408
|
+
METRIC_EVENT_NAMES.AI_SUMMARY_GET_POST_CALL_SUCCESS,
|
|
1409
|
+
METRIC_EVENT_NAMES.AI_SUMMARY_GET_POST_CALL_FAILED,
|
|
1410
|
+
METRIC_EVENT_NAMES.AI_SUMMARY_GET_MID_CALL_SUCCESS,
|
|
1411
|
+
METRIC_EVENT_NAMES.AI_SUMMARY_GET_MID_CALL_FAILED,
|
|
1412
|
+
].includes(eventName)
|
|
1413
|
+
).length;
|
|
1414
|
+
|
|
1415
|
+
const expectSentinelsNotObserved = () => {
|
|
1416
|
+
const observedArguments = JSON.stringify([
|
|
1417
|
+
LoggerProxy.log.mock.calls,
|
|
1418
|
+
LoggerProxy.info.mock.calls,
|
|
1419
|
+
LoggerProxy.warn.mock.calls,
|
|
1420
|
+
LoggerProxy.error.mock.calls,
|
|
1421
|
+
mockMetricsManager.trackEvent.mock.calls,
|
|
1422
|
+
mockMetricsManager.timeEvent.mock.calls,
|
|
1423
|
+
]);
|
|
1424
|
+
|
|
1425
|
+
sentinels.forEach((sentinel) => {
|
|
1426
|
+
expect(observedArguments).not.toContain(sentinel);
|
|
1427
|
+
});
|
|
1428
|
+
};
|
|
1429
|
+
|
|
1430
|
+
const emitFeatureEnablement = (
|
|
1431
|
+
harness: ReturnType<typeof createSummaryHarness>,
|
|
1432
|
+
payload = {
|
|
1433
|
+
interactionId: 'interaction-1',
|
|
1434
|
+
postCallEnabled: true,
|
|
1435
|
+
midCallEnabled: true,
|
|
1436
|
+
actionTimestamp: 1,
|
|
1437
|
+
}
|
|
1438
|
+
) => {
|
|
1439
|
+
harness.emitRtdFrame(CC_TASK_EVENTS.FEATURE_ENABLEMENT, payload);
|
|
1440
|
+
};
|
|
1441
|
+
|
|
1442
|
+
const dispatchFeatureEnablement = (
|
|
1443
|
+
harness: ReturnType<typeof createSummaryHarness>,
|
|
1444
|
+
payload = {
|
|
1445
|
+
interactionId: 'interaction-1',
|
|
1446
|
+
postCallEnabled: true,
|
|
1447
|
+
midCallEnabled: true,
|
|
1448
|
+
actionTimestamp: 1,
|
|
1449
|
+
}
|
|
1450
|
+
) => {
|
|
1451
|
+
harness.dispatchRtdFrame(CC_TASK_EVENTS.FEATURE_ENABLEMENT, payload);
|
|
1452
|
+
};
|
|
1453
|
+
|
|
1454
|
+
const emitTaskLifecycleEvent = (
|
|
1455
|
+
harness: ReturnType<typeof createSummaryHarness>,
|
|
1456
|
+
eventType: CC_EVENTS,
|
|
1457
|
+
overrides: Partial<TaskData> & {conversationId?: string}
|
|
1458
|
+
) => {
|
|
1459
|
+
const interactionId = overrides.interactionId ?? 'interaction-1';
|
|
1460
|
+
|
|
1461
|
+
harness.webSocketManager.emit(
|
|
1462
|
+
'message',
|
|
1463
|
+
JSON.stringify({
|
|
1464
|
+
data: {
|
|
1465
|
+
...createAISummaryLifecycleTaskData({
|
|
1466
|
+
mediaResourceId: interactionId,
|
|
1467
|
+
taskId: `task-owner-${interactionId}`,
|
|
1468
|
+
...overrides,
|
|
1469
|
+
}),
|
|
1470
|
+
type: eventType,
|
|
1471
|
+
},
|
|
1472
|
+
})
|
|
1473
|
+
);
|
|
1474
|
+
};
|
|
1475
|
+
|
|
1476
|
+
const emitSentinelPostCallSummary = (
|
|
1477
|
+
harness: ReturnType<typeof createSummaryHarness>,
|
|
1478
|
+
conversationId = 'conversation-1'
|
|
1479
|
+
) => {
|
|
1480
|
+
harness.emitRtdFrame(CC_TASK_EVENTS.POST_CALL_SUMMARY, {
|
|
1481
|
+
conversationId,
|
|
1482
|
+
summaryText: sentinels[0],
|
|
1483
|
+
sections: {
|
|
1484
|
+
[sentinels[1]]: sentinels[2],
|
|
1485
|
+
initialContactReason: sentinels[2],
|
|
1486
|
+
},
|
|
1487
|
+
adaptiveCard: {
|
|
1488
|
+
body: [{text: sentinels[3]}],
|
|
1489
|
+
},
|
|
1490
|
+
});
|
|
1491
|
+
};
|
|
1492
|
+
|
|
1493
|
+
const dispatchSentinelPostCallSummary = (
|
|
1494
|
+
harness: ReturnType<typeof createSummaryHarness>,
|
|
1495
|
+
conversationId = 'conversation-1'
|
|
1496
|
+
) => {
|
|
1497
|
+
harness.dispatchRtdFrame(CC_TASK_EVENTS.POST_CALL_SUMMARY, {
|
|
1498
|
+
conversationId,
|
|
1499
|
+
summaryText: sentinels[0],
|
|
1500
|
+
sections: {
|
|
1501
|
+
[sentinels[1]]: sentinels[2],
|
|
1502
|
+
initialContactReason: sentinels[2],
|
|
1503
|
+
},
|
|
1504
|
+
adaptiveCard: {
|
|
1505
|
+
body: [{text: sentinels[3]}],
|
|
1506
|
+
},
|
|
1507
|
+
});
|
|
1508
|
+
};
|
|
1509
|
+
|
|
1510
|
+
const emitSentinelMidCallSummary = (
|
|
1511
|
+
harness: ReturnType<typeof createSummaryHarness>,
|
|
1512
|
+
conversationId = 'conversation-1'
|
|
1513
|
+
) => {
|
|
1514
|
+
harness.emitRtdFrame(CC_TASK_EVENTS.MID_CALL_SUMMARY, {
|
|
1515
|
+
conversationId,
|
|
1516
|
+
summaryText: sentinels[0],
|
|
1517
|
+
agentName: sentinels[4],
|
|
1518
|
+
sections: {
|
|
1519
|
+
[sentinels[1]]: sentinels[2],
|
|
1520
|
+
reasonForTransferOrConsult: sentinels[2],
|
|
1521
|
+
},
|
|
1522
|
+
adaptiveCard: {
|
|
1523
|
+
body: [{text: sentinels[3]}],
|
|
1524
|
+
},
|
|
1525
|
+
});
|
|
1526
|
+
};
|
|
1527
|
+
|
|
1528
|
+
afterEach(() => {
|
|
1529
|
+
jest.useRealTimers();
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
it('forwards task-manager handlers through ContactCenter trigger and delegates RTD messages', () => {
|
|
1533
|
+
const triggerSpy = jest.spyOn(webex.cc, 'trigger');
|
|
1534
|
+
const task = {data: {interactionId: 'interaction-1'}} as any;
|
|
1535
|
+
|
|
1536
|
+
webex.cc['handleTaskHydrate'](task);
|
|
1537
|
+
webex.cc['handleTaskMultiLoginHydrate'](task);
|
|
1538
|
+
webex.cc['handleTaskMerged'](task);
|
|
1539
|
+
webex.cc['handleCampaignPreviewReservation'](task);
|
|
1540
|
+
webex.cc['handleRTDWebsocketMessage']('summary-frame');
|
|
1541
|
+
|
|
1542
|
+
expect(triggerSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_HYDRATE, task);
|
|
1543
|
+
expect(triggerSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, task);
|
|
1544
|
+
expect(triggerSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_MERGED, task);
|
|
1545
|
+
expect(triggerSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, task);
|
|
1546
|
+
expect(mockTaskManager.handleRealtimeWebsocketEvent).toHaveBeenCalledWith('summary-frame');
|
|
1547
|
+
});
|
|
1548
|
+
|
|
1549
|
+
it('clears active summary state on deregister', async () => {
|
|
1550
|
+
jest.useFakeTimers();
|
|
1551
|
+
|
|
1552
|
+
const harness = createSummaryHarness();
|
|
1553
|
+
const triggerSpy = jest.spyOn(webex.cc, 'trigger');
|
|
1554
|
+
const profile = {
|
|
1555
|
+
agentId: 'agent-1',
|
|
1556
|
+
webRtcEnabled: false,
|
|
1557
|
+
loginVoiceOptions: [LoginOption.EXTENSION],
|
|
1558
|
+
isEndTaskEnabled: true,
|
|
1559
|
+
isEndConsultEnabled: true,
|
|
1560
|
+
wrapUpData: {wrapUpProps: {autoWrapup: false}},
|
|
1561
|
+
aiFeature: {
|
|
1562
|
+
generatedSummaries: {
|
|
1563
|
+
wrapUpSummariesEnabled: true,
|
|
1564
|
+
consultTransferSummariesEnabled: true,
|
|
1565
|
+
},
|
|
1566
|
+
},
|
|
1567
|
+
} as any;
|
|
1568
|
+
|
|
1569
|
+
webex.cc.$config = {...webex.cc.$config, allowAutomatedRelogin: false};
|
|
1570
|
+
harness.webSocketManager.initWebSocket.mockResolvedValue({agentId: 'agent-1'});
|
|
1571
|
+
jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue(profile);
|
|
1572
|
+
|
|
1573
|
+
await webex.cc.register();
|
|
1574
|
+
await flushMicrotasks();
|
|
1575
|
+
|
|
1576
|
+
expect(harness.rtdWebSocketManager.listenerCount('message')).toBe(1);
|
|
1577
|
+
|
|
1578
|
+
dispatchFeatureEnablement(harness);
|
|
1579
|
+
expect(harness.task.aiSummaryCapabilities).toEqual({
|
|
1580
|
+
midCallEnabled: true,
|
|
1581
|
+
postCallEnabled: true,
|
|
1582
|
+
});
|
|
1583
|
+
|
|
1584
|
+
dispatchFeatureEnablement(harness, {
|
|
1585
|
+
interactionId: 'orphan-before-deregister',
|
|
1586
|
+
postCallEnabled: true,
|
|
1587
|
+
midCallEnabled: true,
|
|
1588
|
+
actionTimestamp: 4,
|
|
1589
|
+
});
|
|
1590
|
+
harness.dispatchRtdFrame(CC_TASK_EVENTS.MID_CALL_SUMMARY_RESPONSE_SUBSEQUENT_AGENT, {
|
|
1591
|
+
conversationId: 'queued-before-deregister',
|
|
1592
|
+
summaryText: sentinels[0],
|
|
1593
|
+
});
|
|
1594
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
1595
|
+
expect(harness.task.aiSummaryCapabilities).toEqual({
|
|
1596
|
+
midCallEnabled: true,
|
|
1597
|
+
postCallEnabled: true,
|
|
1598
|
+
});
|
|
1599
|
+
expect(harness.getSummaryMapCounts()).toEqual({
|
|
1600
|
+
pendingPostCall: 0,
|
|
1601
|
+
pendingMidCall: 0,
|
|
1602
|
+
receiving: 1,
|
|
1603
|
+
featureEnablement: 1,
|
|
1604
|
+
});
|
|
1605
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
1606
|
+
|
|
1607
|
+
await webex.cc.deregister();
|
|
1608
|
+
|
|
1609
|
+
harness.expectSummaryStateCleared();
|
|
1610
|
+
expect(harness.task.aiSummaryCapabilities).toEqual({
|
|
1611
|
+
midCallEnabled: false,
|
|
1612
|
+
postCallEnabled: false,
|
|
1613
|
+
});
|
|
1614
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
1615
|
+
expect(harness.rtdWebSocketManager.listenerCount('message')).toBe(0);
|
|
1616
|
+
triggerSpy.mockClear();
|
|
1617
|
+
|
|
1618
|
+
dispatchSentinelPostCallSummary(harness, 'queued-after-deregister');
|
|
1619
|
+
|
|
1620
|
+
expect(harness.getSummaryMapCounts()).toEqual({
|
|
1621
|
+
pendingPostCall: 0,
|
|
1622
|
+
pendingMidCall: 0,
|
|
1623
|
+
receiving: 0,
|
|
1624
|
+
featureEnablement: 0,
|
|
1625
|
+
});
|
|
1626
|
+
expect(triggerSpy).not.toHaveBeenCalled();
|
|
1627
|
+
expectSentinelsNotObserved();
|
|
1628
|
+
});
|
|
1629
|
+
|
|
1630
|
+
it('flushes a buffered receiving summary through real TaskManager task publication once the task is answered', () => {
|
|
1631
|
+
jest.useFakeTimers();
|
|
1632
|
+
|
|
1633
|
+
const harness = createSummaryHarness();
|
|
1634
|
+
const interactionId = 'receiver-arrival-interaction';
|
|
1635
|
+
const conversationId = interactionId;
|
|
1636
|
+
const receivingPayload = {
|
|
1637
|
+
conversationId,
|
|
1638
|
+
summaryText: sentinels[0],
|
|
1639
|
+
adaptiveCard: {body: [{text: sentinels[3]}]},
|
|
1640
|
+
};
|
|
1641
|
+
const receiverHandler = jest.fn();
|
|
1642
|
+
const observedOrder: string[] = [];
|
|
1643
|
+
let publishedTask: Task | undefined;
|
|
1644
|
+
let receiverEmitSpy: jest.SpyInstance | undefined;
|
|
1645
|
+
|
|
1646
|
+
harness.dispatchRtdFrame(CC_TASK_EVENTS.MID_CALL_SUMMARY_RESPONSE_SUBSEQUENT_AGENT, {
|
|
1647
|
+
...receivingPayload,
|
|
1648
|
+
});
|
|
1649
|
+
|
|
1650
|
+
expect(harness.getSummaryMapCounts()).toEqual({
|
|
1651
|
+
pendingPostCall: 0,
|
|
1652
|
+
pendingMidCall: 0,
|
|
1653
|
+
receiving: 1,
|
|
1654
|
+
featureEnablement: 0,
|
|
1655
|
+
});
|
|
1656
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
1657
|
+
|
|
1658
|
+
harness.taskManager.on(TASK_EVENTS.TASK_INCOMING, (task: Task) => {
|
|
1659
|
+
publishedTask = task;
|
|
1660
|
+
receiverEmitSpy = jest.spyOn(task, 'emit');
|
|
1661
|
+
task.on(TASK_EVENTS.TASK_ASSIGNED, () => observedOrder.push('answered'));
|
|
1662
|
+
task.on(TASK_EVENTS.TASK_MID_CALL_SUMMARY_RECEIVED, (payload) => {
|
|
1663
|
+
observedOrder.push('receiver-summary');
|
|
1664
|
+
receiverHandler(payload);
|
|
1665
|
+
});
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
emitTaskLifecycleEvent(harness, CC_EVENTS.AGENT_CONTACT_RESERVED, {
|
|
1669
|
+
interactionId,
|
|
1670
|
+
conversationId,
|
|
1671
|
+
});
|
|
1672
|
+
|
|
1673
|
+
expect(publishedTask).toBe(harness.taskManager.getTask(interactionId));
|
|
1674
|
+
expect(publishedTask).toBeInstanceOf(Task);
|
|
1675
|
+
expect(receiverHandler).not.toHaveBeenCalled();
|
|
1676
|
+
expect(harness.getSummaryMapCounts().receiving).toBe(1);
|
|
1677
|
+
|
|
1678
|
+
emitTaskLifecycleEvent(harness, CC_EVENTS.AGENT_CONTACT_ASSIGNED, {
|
|
1679
|
+
interactionId,
|
|
1680
|
+
conversationId,
|
|
1681
|
+
});
|
|
1682
|
+
|
|
1683
|
+
expect(observedOrder).toEqual(['answered', 'receiver-summary']);
|
|
1684
|
+
expect(receiverHandler).toHaveBeenCalledTimes(1);
|
|
1685
|
+
expect(receiverHandler).toHaveBeenCalledWith(receivingPayload);
|
|
1686
|
+
expect(
|
|
1687
|
+
receiverEmitSpy?.mock.calls.filter(
|
|
1688
|
+
([eventName]) => eventName === TASK_EVENTS.TASK_MID_CALL_SUMMARY_RECEIVED
|
|
1689
|
+
)
|
|
1690
|
+
).toHaveLength(1);
|
|
1691
|
+
expect(harness.getSummaryMapCounts()).toEqual({
|
|
1692
|
+
pendingPostCall: 0,
|
|
1693
|
+
pendingMidCall: 0,
|
|
1694
|
+
receiving: 0,
|
|
1695
|
+
featureEnablement: 0,
|
|
1696
|
+
});
|
|
1697
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
1698
|
+
expectSentinelsNotObserved();
|
|
1699
|
+
});
|
|
1700
|
+
|
|
1701
|
+
it('resolves a real post-call request through RTD and keeps summary sentinels out of metrics', async () => {
|
|
1702
|
+
jest.useFakeTimers();
|
|
1703
|
+
|
|
1704
|
+
const harness = createSummaryHarness();
|
|
1705
|
+
|
|
1706
|
+
attachRtdMessageListener(harness.rtdWebSocketManager);
|
|
1707
|
+
emitFeatureEnablement(harness);
|
|
1708
|
+
jest.clearAllMocks();
|
|
1709
|
+
|
|
1710
|
+
const postCallRequest = harness.task.requestPostCallSummary();
|
|
1711
|
+
|
|
1712
|
+
await flushMicrotasks();
|
|
1713
|
+
expect(harness.apiAIAssistant.sendEvent).toHaveBeenCalledWith(
|
|
1714
|
+
'agent-1',
|
|
1715
|
+
'interaction-1',
|
|
1716
|
+
AIAssistantEventType.CTI_EVENT,
|
|
1717
|
+
AIAssistantEventName.GET_POST_CALL_SUMMARY,
|
|
1718
|
+
expect.objectContaining({conversationId: 'conversation-1'}),
|
|
1719
|
+
undefined,
|
|
1720
|
+
undefined,
|
|
1721
|
+
expect.any(Number),
|
|
1722
|
+
15000
|
|
1723
|
+
);
|
|
1724
|
+
emitSentinelPostCallSummary(harness);
|
|
1725
|
+
harness.transportDeferreds[0].resolve(undefined);
|
|
1726
|
+
|
|
1727
|
+
await expect(postCallRequest).resolves.toMatchObject({
|
|
1728
|
+
conversationId: 'conversation-1',
|
|
1729
|
+
summaryText: sentinels[0],
|
|
1730
|
+
});
|
|
1731
|
+
expect(harness.getSummaryMapCounts()).toEqual({
|
|
1732
|
+
pendingPostCall: 0,
|
|
1733
|
+
pendingMidCall: 0,
|
|
1734
|
+
receiving: 0,
|
|
1735
|
+
featureEnablement: 0,
|
|
1736
|
+
});
|
|
1737
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
1738
|
+
expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
|
|
1739
|
+
METRIC_EVENT_NAMES.AI_SUMMARY_GET_POST_CALL_SUCCESS,
|
|
1740
|
+
expect.objectContaining({
|
|
1741
|
+
conversationId: 'conversation-1',
|
|
1742
|
+
interactionId: 'interaction-1',
|
|
1743
|
+
}),
|
|
1744
|
+
['operational']
|
|
1745
|
+
);
|
|
1746
|
+
harness.taskManager.clearAISummaryState();
|
|
1747
|
+
harness.expectSummaryStateCleared();
|
|
1748
|
+
expectSentinelsNotObserved();
|
|
1749
|
+
});
|
|
1750
|
+
|
|
1751
|
+
it('sends post-call responses using current task data after manager cleanup', async () => {
|
|
1752
|
+
jest.useFakeTimers();
|
|
1753
|
+
|
|
1754
|
+
const harness = createSummaryHarness();
|
|
1755
|
+
const interactionId = 'post-call-cleanup-interaction';
|
|
1756
|
+
const conversationId = 'post-call-cleanup-conversation';
|
|
1757
|
+
const apiAIAssistant = harness.getApiAIAssistant();
|
|
1758
|
+
let applicationTask: Task | undefined;
|
|
1759
|
+
|
|
1760
|
+
harness.taskManager.on(TASK_EVENTS.TASK_INCOMING, (task: Task) => {
|
|
1761
|
+
if (task.data.interactionId === interactionId) {
|
|
1762
|
+
applicationTask = task;
|
|
1763
|
+
}
|
|
1764
|
+
});
|
|
1765
|
+
emitTaskLifecycleEvent(harness, CC_EVENTS.AGENT_CONTACT_RESERVED, {
|
|
1766
|
+
interactionId,
|
|
1767
|
+
conversationId,
|
|
1768
|
+
});
|
|
1769
|
+
emitTaskLifecycleEvent(harness, CC_EVENTS.AGENT_CONTACT_ASSIGNED, {
|
|
1770
|
+
interactionId,
|
|
1771
|
+
conversationId,
|
|
1772
|
+
});
|
|
1773
|
+
|
|
1774
|
+
expect(applicationTask).toBe(harness.taskManager.getTask(interactionId));
|
|
1775
|
+
const retainedTask = applicationTask as Task;
|
|
1776
|
+
|
|
1777
|
+
dispatchFeatureEnablement(harness, {
|
|
1778
|
+
interactionId,
|
|
1779
|
+
postCallEnabled: true,
|
|
1780
|
+
midCallEnabled: true,
|
|
1781
|
+
actionTimestamp: 9,
|
|
1782
|
+
});
|
|
1783
|
+
|
|
1784
|
+
const postCallRequest = retainedTask.requestPostCallSummary();
|
|
1785
|
+
|
|
1786
|
+
await flushMicrotasks();
|
|
1787
|
+
expect(postCallRequest).toBeDefined();
|
|
1788
|
+
expect(harness.apiAIAssistant.sendEvent).toHaveBeenCalledWith(
|
|
1789
|
+
'agent-1',
|
|
1790
|
+
interactionId,
|
|
1791
|
+
AIAssistantEventType.CTI_EVENT,
|
|
1792
|
+
AIAssistantEventName.GET_POST_CALL_SUMMARY,
|
|
1793
|
+
expect.objectContaining({conversationId}),
|
|
1794
|
+
undefined,
|
|
1795
|
+
undefined,
|
|
1796
|
+
expect.any(Number),
|
|
1797
|
+
15000
|
|
1798
|
+
);
|
|
1799
|
+
harness.dispatchRtdFrame(CC_TASK_EVENTS.POST_CALL_SUMMARY, {
|
|
1800
|
+
conversationId,
|
|
1801
|
+
summaryText: sentinels[0],
|
|
1802
|
+
});
|
|
1803
|
+
harness.transportDeferreds[0].resolve(undefined);
|
|
1804
|
+
|
|
1805
|
+
await expect(postCallRequest).resolves.toMatchObject({
|
|
1806
|
+
conversationId,
|
|
1807
|
+
summaryText: sentinels[0],
|
|
1808
|
+
});
|
|
1809
|
+
expect(harness.getSummaryMapCounts()).toEqual({
|
|
1810
|
+
pendingPostCall: 0,
|
|
1811
|
+
pendingMidCall: 0,
|
|
1812
|
+
receiving: 0,
|
|
1813
|
+
featureEnablement: 0,
|
|
1814
|
+
});
|
|
1815
|
+
|
|
1816
|
+
emitTaskLifecycleEvent(harness, CC_EVENTS.AGENT_WRAPUP, {
|
|
1817
|
+
interactionId,
|
|
1818
|
+
conversationId,
|
|
1819
|
+
});
|
|
1820
|
+
emitTaskLifecycleEvent(harness, CC_EVENTS.AGENT_WRAPPEDUP, {
|
|
1821
|
+
interactionId,
|
|
1822
|
+
conversationId,
|
|
1823
|
+
});
|
|
1824
|
+
|
|
1825
|
+
expect(harness.taskManager.getTask(interactionId)).toBeUndefined();
|
|
1826
|
+
expect(harness.getSummaryMapCounts()).toEqual({
|
|
1827
|
+
pendingPostCall: 0,
|
|
1828
|
+
pendingMidCall: 0,
|
|
1829
|
+
receiving: 0,
|
|
1830
|
+
featureEnablement: 0,
|
|
1831
|
+
});
|
|
1832
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
1833
|
+
|
|
1834
|
+
const requestCallsAfterRemoval = (apiAIAssistant as any).pendingRequests.size;
|
|
1835
|
+
const responseInteractionId = 'post-call-response-current-interaction';
|
|
1836
|
+
const responseConversationId = 'post-call-response-current-conversation';
|
|
1837
|
+
|
|
1838
|
+
retainedTask.updateTaskData(
|
|
1839
|
+
createAISummaryLifecycleTaskData({
|
|
1840
|
+
interactionId: responseInteractionId,
|
|
1841
|
+
conversationId: responseConversationId,
|
|
1842
|
+
}),
|
|
1843
|
+
true
|
|
1844
|
+
);
|
|
1845
|
+
|
|
1846
|
+
const responsePayload: AISummaryResponse = {
|
|
1847
|
+
summary: {initialContactReason: 'resolved'},
|
|
1848
|
+
feedback: 'thumbs_up',
|
|
1849
|
+
state: 'DEFAULT',
|
|
1850
|
+
wrapUpCode: 'resolved',
|
|
1851
|
+
numberOfTimesViewed: 1,
|
|
1852
|
+
numberOfTimesEdited: 0,
|
|
1853
|
+
numberOfTimesCopied: 0,
|
|
1854
|
+
};
|
|
1855
|
+
|
|
1856
|
+
await expect(
|
|
1857
|
+
retainedTask.sendPostCallSummaryResponse(responsePayload)
|
|
1858
|
+
).resolves.toBeUndefined();
|
|
1859
|
+
|
|
1860
|
+
expect((apiAIAssistant as any).pendingRequests.size).toBe(requestCallsAfterRemoval);
|
|
1861
|
+
expect(harness.apiAIAssistant.sendEvent).toHaveBeenCalledTimes(2);
|
|
1862
|
+
expect(harness.apiAIAssistant.sendEvent).toHaveBeenNthCalledWith(
|
|
1863
|
+
2,
|
|
1864
|
+
'agent-1',
|
|
1865
|
+
responseInteractionId,
|
|
1866
|
+
AIAssistantEventType.CTI_EVENT,
|
|
1867
|
+
AIAssistantEventName.POST_CALL_SUMMARY_RESPONSE,
|
|
1868
|
+
{
|
|
1869
|
+
conversationId: responseConversationId,
|
|
1870
|
+
clientType: 'WxCC',
|
|
1871
|
+
action: AIAssistantEventName.POST_CALL_SUMMARY_RESPONSE,
|
|
1872
|
+
summary: {initialContactReason: 'resolved'},
|
|
1873
|
+
feedback: 'thumbs_up',
|
|
1874
|
+
wrapUpCode: 'resolved',
|
|
1875
|
+
numberOfTimesViewed: 1,
|
|
1876
|
+
numberOfTimesEdited: 0,
|
|
1877
|
+
numberOfTimesCopied: 0,
|
|
1878
|
+
state: 'DEFAULT',
|
|
1879
|
+
},
|
|
1880
|
+
undefined,
|
|
1881
|
+
undefined,
|
|
1882
|
+
expect.any(Number),
|
|
1883
|
+
15000
|
|
1884
|
+
);
|
|
1885
|
+
});
|
|
1886
|
+
|
|
1887
|
+
it('cancels pending real public requests on deregister and ignores late HTTP settlement', async () => {
|
|
1888
|
+
const harness = createSummaryHarness();
|
|
1889
|
+
const unhandledRejections: unknown[] = [];
|
|
1890
|
+
const unhandledRejectionListener = jest.fn((reason) => {
|
|
1891
|
+
unhandledRejections.push(reason);
|
|
1892
|
+
});
|
|
1893
|
+
|
|
1894
|
+
attachRtdMessageListener(harness.rtdWebSocketManager);
|
|
1895
|
+
emitFeatureEnablement(harness);
|
|
1896
|
+
jest.clearAllMocks();
|
|
1897
|
+
|
|
1898
|
+
webex.cc.agentConfig = {
|
|
1899
|
+
agentId: 'agent-1',
|
|
1900
|
+
webRtcEnabled: false,
|
|
1901
|
+
loginVoiceOptions: [LoginOption.EXTENSION],
|
|
1902
|
+
};
|
|
1903
|
+
|
|
1904
|
+
process.on('unhandledRejection', unhandledRejectionListener);
|
|
1905
|
+
try {
|
|
1906
|
+
const postCallRequest = harness.task.requestPostCallSummary();
|
|
1907
|
+
const midCallRequest = harness.task.requestMidCallSummary('CONSULT');
|
|
1908
|
+
postCallRequest.catch(() => undefined);
|
|
1909
|
+
midCallRequest.catch(() => undefined);
|
|
1910
|
+
|
|
1911
|
+
await flushMicrotasks();
|
|
1912
|
+
expect(harness.transportDeferreds).toHaveLength(2);
|
|
1913
|
+
|
|
1914
|
+
await webex.cc.deregister();
|
|
1915
|
+
await flushEventLoopTurn();
|
|
1916
|
+
|
|
1917
|
+
expect(unhandledRejectionListener).not.toHaveBeenCalled();
|
|
1918
|
+
expect(unhandledRejections).toHaveLength(0);
|
|
1919
|
+
await expect(postCallRequest).rejects.toMatchObject({
|
|
1920
|
+
message: AI_SUMMARY_REQUEST_CANCELLED,
|
|
1921
|
+
data: {errorCode: AI_SUMMARY_REQUEST_CANCELLED},
|
|
1922
|
+
});
|
|
1923
|
+
await expect(midCallRequest).rejects.toMatchObject({
|
|
1924
|
+
message: AI_SUMMARY_REQUEST_CANCELLED,
|
|
1925
|
+
data: {errorCode: AI_SUMMARY_REQUEST_CANCELLED},
|
|
1926
|
+
});
|
|
1927
|
+
harness.expectSummaryStateCleared();
|
|
1928
|
+
|
|
1929
|
+
const finalMetricCountAfterCancellation = countAISummaryRequestFinalMetrics();
|
|
1930
|
+
|
|
1931
|
+
dispatchSentinelPostCallSummary(harness, 'queued-after-deregister');
|
|
1932
|
+
harness.transportDeferreds[0].resolve(undefined);
|
|
1933
|
+
harness.transportDeferreds[1].reject(new Error('late-http-rejected-after-deregister'));
|
|
1934
|
+
await flushEventLoopTurn();
|
|
1935
|
+
|
|
1936
|
+
expect(unhandledRejectionListener).not.toHaveBeenCalled();
|
|
1937
|
+
expect(unhandledRejections).toHaveLength(0);
|
|
1938
|
+
expect(countAISummaryRequestFinalMetrics()).toBe(finalMetricCountAfterCancellation);
|
|
1939
|
+
harness.expectSummaryStateCleared();
|
|
1940
|
+
expectSentinelsNotObserved();
|
|
1941
|
+
} finally {
|
|
1942
|
+
process.off('unhandledRejection', unhandledRejectionListener);
|
|
1943
|
+
harness.taskManager.clearAISummaryState();
|
|
1944
|
+
}
|
|
1945
|
+
});
|
|
725
1946
|
});
|
|
726
1947
|
|
|
727
1948
|
describe('stationLogin', () => {
|
|
@@ -1861,7 +3082,7 @@ describe('webex.cc', () => {
|
|
|
1861
3082
|
});
|
|
1862
3083
|
|
|
1863
3084
|
expect(stationLogoutMock).toHaveBeenCalledWith({data: data});
|
|
1864
|
-
//
|
|
3085
|
+
// SPARK-626777 tracks moving listener cleanup into the future de-register API.
|
|
1865
3086
|
// expect(mockTaskManager.unregisterIncomingCallEvent).toHaveBeenCalledWith();
|
|
1866
3087
|
// expect(mockTaskManager.off).toHaveBeenCalledWith(
|
|
1867
3088
|
// TASK_EVENTS.TASK_INCOMING,
|
|
@@ -2662,8 +3883,6 @@ describe('webex.cc', () => {
|
|
|
2662
3883
|
);
|
|
2663
3884
|
|
|
2664
3885
|
const setLoginOptionSpy = jest.spyOn(webex.cc.webCallingService, 'setLoginOption');
|
|
2665
|
-
const incomingTaskListenerSpy = jest.spyOn(webex.cc, 'incomingTaskListener');
|
|
2666
|
-
const webSocketManagerOnSpy = jest.spyOn(webex.cc.services.webSocketManager, 'on');
|
|
2667
3886
|
await webex.cc['silentRelogin']();
|
|
2668
3887
|
|
|
2669
3888
|
expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
|
|
@@ -2694,8 +3913,8 @@ describe('webex.cc', () => {
|
|
|
2694
3913
|
expect(webex.cc.agentConfig.deviceType).toBe(LoginOption.BROWSER);
|
|
2695
3914
|
expect(registerWebCallingLineSpy).toHaveBeenCalled();
|
|
2696
3915
|
expect(setLoginOptionSpy).toHaveBeenCalledWith(LoginOption.BROWSER);
|
|
2697
|
-
//
|
|
2698
|
-
// expect(
|
|
3916
|
+
// SPARK-626777 tracks moving listener cleanup into the future de-register API.
|
|
3917
|
+
// expect(eventForwarderRefreshSpy).toHaveBeenCalled();
|
|
2699
3918
|
// expect(webSocketManagerOnSpy).toHaveBeenCalledWith('message', expect.any(Function));
|
|
2700
3919
|
// expect(mockTaskManager.on).toHaveBeenCalledWith(
|
|
2701
3920
|
// TASK_EVENTS.TASK_HYDRATE,
|
|
@@ -3030,6 +4249,9 @@ describe('webex.cc', () => {
|
|
|
3030
4249
|
{module: CC_FILE, method: `startOutdial`, trackingId: error.details.trackingId}
|
|
3031
4250
|
);
|
|
3032
4251
|
expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'startOutdial', CC_FILE);
|
|
4252
|
+
expect(mockWebexRequest.uploadLogs).toHaveBeenCalledWith({
|
|
4253
|
+
correlationId: error.details.trackingId,
|
|
4254
|
+
});
|
|
3033
4255
|
});
|
|
3034
4256
|
});
|
|
3035
4257
|
|
|
@@ -3124,6 +4346,13 @@ describe('webex.cc', () => {
|
|
|
3124
4346
|
deviceUnregisterSpy = jest.spyOn(webex.internal.device, 'unregister');
|
|
3125
4347
|
});
|
|
3126
4348
|
|
|
4349
|
+
afterEach(() => {
|
|
4350
|
+
mockTaskManager.off.mockImplementation(() => undefined);
|
|
4351
|
+
mockTaskManager.clearAISummaryState.mockImplementation(() => undefined);
|
|
4352
|
+
mockRTDWebSocketManager.off.mockImplementation(() => undefined);
|
|
4353
|
+
mockRTDWebSocketManager.close.mockImplementation(() => undefined);
|
|
4354
|
+
});
|
|
4355
|
+
|
|
3127
4356
|
it('invalidates wellness context before deregistration cleanup can fail', async () => {
|
|
3128
4357
|
const cleanupError = new Error('cleanup failed');
|
|
3129
4358
|
let rejectCleanup: (error: Error) => void = () => undefined;
|
|
@@ -3136,6 +4365,9 @@ describe('webex.cc', () => {
|
|
|
3136
4365
|
};
|
|
3137
4366
|
webex.cc['updateWellnessSession']('session-1');
|
|
3138
4367
|
jest.spyOn(webex.cc as any, 'teardownWxAppLocalState').mockReturnValue(cleanup);
|
|
4368
|
+
webex.cc.apiAIAssistant.requestWellnessBreak.mockRejectedValue(
|
|
4369
|
+
new Error('WELLNESS_BREAK_AGENT_SESSION_REQUIRED')
|
|
4370
|
+
);
|
|
3139
4371
|
|
|
3140
4372
|
const deregistration = webex.cc.deregister();
|
|
3141
4373
|
const rejection = expect(deregistration).rejects.toThrow(cleanupError);
|
|
@@ -3195,10 +4427,17 @@ describe('webex.cc', () => {
|
|
|
3195
4427
|
);
|
|
3196
4428
|
|
|
3197
4429
|
expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
|
|
3198
|
-
expect(webex.cc.services.rtdWebSocketManager.close).
|
|
4430
|
+
expect(webex.cc.services.rtdWebSocketManager.close).toHaveBeenNthCalledWith(
|
|
4431
|
+
1,
|
|
4432
|
+
false,
|
|
4433
|
+
'Unregistering the SDK'
|
|
4434
|
+
);
|
|
4435
|
+
expect(webex.cc.services.rtdWebSocketManager.close).toHaveBeenNthCalledWith(
|
|
4436
|
+
2,
|
|
3199
4437
|
false,
|
|
3200
4438
|
'Unregistering the RTD websocket'
|
|
3201
4439
|
);
|
|
4440
|
+
expect(webex.cc.services.rtdWebSocketManager.close).toHaveBeenCalledTimes(2);
|
|
3202
4441
|
expect(webex.cc.agentConfig).toBeNull();
|
|
3203
4442
|
|
|
3204
4443
|
expect(webex.internal.mercury.off).toHaveBeenCalledWith('online');
|
|
@@ -3210,6 +4449,7 @@ describe('webex.cc', () => {
|
|
|
3210
4449
|
METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_SUCCESS,
|
|
3211
4450
|
METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_FAIL,
|
|
3212
4451
|
]);
|
|
4452
|
+
expect(mockTaskManager.clearAISummaryState).toHaveBeenCalled();
|
|
3213
4453
|
expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
|
|
3214
4454
|
METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_SUCCESS,
|
|
3215
4455
|
{},
|
|
@@ -3229,22 +4469,23 @@ describe('webex.cc', () => {
|
|
|
3229
4469
|
const incomingCalls = mockTaskManager.off.mock.calls.filter(
|
|
3230
4470
|
([evt]) => evt === TASK_EVENTS.TASK_INCOMING
|
|
3231
4471
|
);
|
|
3232
|
-
expect(incomingCalls).
|
|
3233
|
-
const [, incomingCallback] = incomingCalls[
|
|
4472
|
+
expect(incomingCalls.length).toBeGreaterThanOrEqual(1);
|
|
4473
|
+
const [, incomingCallback] = incomingCalls[incomingCalls.length - 1];
|
|
3234
4474
|
expect(incomingCallback).toBe(webex.cc['handleIncomingTask']);
|
|
3235
4475
|
|
|
3236
4476
|
const hydrateCalls = mockTaskManager.off.mock.calls.filter(
|
|
3237
4477
|
([evt]) => evt === TASK_EVENTS.TASK_HYDRATE
|
|
3238
4478
|
);
|
|
3239
|
-
expect(hydrateCalls).
|
|
3240
|
-
const [, hydrateCallback] = hydrateCalls[
|
|
4479
|
+
expect(hydrateCalls.length).toBeGreaterThanOrEqual(1);
|
|
4480
|
+
const [, hydrateCallback] = hydrateCalls[hydrateCalls.length - 1];
|
|
3241
4481
|
expect(hydrateCallback).toBe(webex.cc['handleTaskHydrate']);
|
|
3242
4482
|
|
|
3243
4483
|
const multiLoginHydrateCalls = mockTaskManager.off.mock.calls.filter(
|
|
3244
4484
|
([evt]) => evt === TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE
|
|
3245
4485
|
);
|
|
3246
|
-
expect(multiLoginHydrateCalls).
|
|
3247
|
-
const [, multiLoginHydrateCallback] =
|
|
4486
|
+
expect(multiLoginHydrateCalls.length).toBeGreaterThanOrEqual(1);
|
|
4487
|
+
const [, multiLoginHydrateCallback] =
|
|
4488
|
+
multiLoginHydrateCalls[multiLoginHydrateCalls.length - 1];
|
|
3248
4489
|
expect(multiLoginHydrateCallback).toBe(webex.cc['handleTaskMultiLoginHydrate']);
|
|
3249
4490
|
|
|
3250
4491
|
const messageCalls = mockWebSocketManager.off.mock.calls.filter(([evt]) => evt === 'message');
|
|
@@ -3318,7 +4559,17 @@ describe('webex.cc', () => {
|
|
|
3318
4559
|
expect(webex.internal.mercury.off).not.toHaveBeenCalled();
|
|
3319
4560
|
expect(mercuryDisconnectSpy).not.toHaveBeenCalled();
|
|
3320
4561
|
expect(deviceUnregisterSpy).not.toHaveBeenCalled();
|
|
3321
|
-
expect(mockRTDWebSocketManager.close).
|
|
4562
|
+
expect(mockRTDWebSocketManager.close).toHaveBeenNthCalledWith(
|
|
4563
|
+
1,
|
|
4564
|
+
false,
|
|
4565
|
+
'Unregistering the SDK'
|
|
4566
|
+
);
|
|
4567
|
+
expect(mockRTDWebSocketManager.close).toHaveBeenNthCalledWith(
|
|
4568
|
+
2,
|
|
4569
|
+
false,
|
|
4570
|
+
'Unregistering the RTD websocket'
|
|
4571
|
+
);
|
|
4572
|
+
expect(mockRTDWebSocketManager.close).toHaveBeenCalledTimes(2);
|
|
3322
4573
|
});
|
|
3323
4574
|
|
|
3324
4575
|
it('should skip internal mercury cleanup when loginVoiceOptions does not include BROWSER', async () => {
|
|
@@ -3336,7 +4587,17 @@ describe('webex.cc', () => {
|
|
|
3336
4587
|
expect(deviceUnregisterSpy).not.toHaveBeenCalled();
|
|
3337
4588
|
|
|
3338
4589
|
expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
|
|
3339
|
-
expect(mockRTDWebSocketManager.close).
|
|
4590
|
+
expect(mockRTDWebSocketManager.close).toHaveBeenNthCalledWith(
|
|
4591
|
+
1,
|
|
4592
|
+
false,
|
|
4593
|
+
'Unregistering the SDK'
|
|
4594
|
+
);
|
|
4595
|
+
expect(mockRTDWebSocketManager.close).toHaveBeenNthCalledWith(
|
|
4596
|
+
2,
|
|
4597
|
+
false,
|
|
4598
|
+
'Unregistering the RTD websocket'
|
|
4599
|
+
);
|
|
4600
|
+
expect(mockRTDWebSocketManager.close).toHaveBeenCalledTimes(2);
|
|
3340
4601
|
expect(webex.cc.agentConfig).toBeNull();
|
|
3341
4602
|
});
|
|
3342
4603
|
|
|
@@ -3492,6 +4753,11 @@ describe('webex.cc', () => {
|
|
|
3492
4753
|
module: CC_FILE,
|
|
3493
4754
|
method: 'deregister',
|
|
3494
4755
|
});
|
|
4756
|
+
expect(mockTaskManager.clearAISummaryState).toHaveBeenCalled();
|
|
4757
|
+
expect(webex.cc.services.rtdWebSocketManager.off).toHaveBeenCalledWith(
|
|
4758
|
+
'message',
|
|
4759
|
+
webex.cc['handleRTDWebsocketMessage']
|
|
4760
|
+
);
|
|
3495
4761
|
|
|
3496
4762
|
expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
|
|
3497
4763
|
METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_FAIL,
|
|
@@ -4004,16 +5270,15 @@ describe('webex.cc', () => {
|
|
|
4004
5270
|
mockError.details = {
|
|
4005
5271
|
trackingId: 'test-tracking-id',
|
|
4006
5272
|
orgId: 'mockOrgId',
|
|
4007
|
-
|
|
5273
|
+
type: 'OutdialAniEntriesFailed',
|
|
5274
|
+
data: {
|
|
5275
|
+
reason: 'Detailed service error',
|
|
5276
|
+
},
|
|
4008
5277
|
};
|
|
4009
5278
|
|
|
4010
5279
|
// Mock the service call to throw an error
|
|
4011
5280
|
webex.cc.services.config.getOutdialAniEntries.mockRejectedValue(mockError);
|
|
4012
5281
|
|
|
4013
|
-
// Mock getErrorDetails to return a detailed error
|
|
4014
|
-
const detailedError = new Error('Detailed service error');
|
|
4015
|
-
getErrorDetailsSpy.mockReturnValue({error: detailedError});
|
|
4016
|
-
|
|
4017
5282
|
await expect(webex.cc.getOutdialAniEntries(mockParams)).rejects.toThrow(
|
|
4018
5283
|
'Detailed service error'
|
|
4019
5284
|
);
|
|
@@ -4038,9 +5303,20 @@ describe('webex.cc', () => {
|
|
|
4038
5303
|
trackingId: 'test-tracking-id',
|
|
4039
5304
|
}
|
|
4040
5305
|
);
|
|
5306
|
+
expect(LoggerProxy.error).toHaveBeenCalledWith(
|
|
5307
|
+
'getOutdialAniEntries failed with reason: Detailed service error',
|
|
5308
|
+
{
|
|
5309
|
+
module: CC_FILE,
|
|
5310
|
+
method: 'getOutdialAniEntries',
|
|
5311
|
+
trackingId: 'test-tracking-id',
|
|
5312
|
+
}
|
|
5313
|
+
);
|
|
4041
5314
|
|
|
4042
5315
|
// Verify getErrorDetails was called
|
|
4043
5316
|
expect(getErrorDetailsSpy).toHaveBeenCalledWith(mockError, 'getOutdialAniEntries', CC_FILE);
|
|
5317
|
+
expect(mockWebexRequest.uploadLogs).toHaveBeenCalledWith({
|
|
5318
|
+
correlationId: 'test-tracking-id',
|
|
5319
|
+
});
|
|
4044
5320
|
});
|
|
4045
5321
|
|
|
4046
5322
|
it('should throw error when orgId is not found', async () => {
|
|
@@ -4123,8 +5399,7 @@ describe('webex.cc', () => {
|
|
|
4123
5399
|
});
|
|
4124
5400
|
|
|
4125
5401
|
it('should handle error during acceptPreviewContact', async () => {
|
|
4126
|
-
getErrorDetailsSpy.
|
|
4127
|
-
getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
|
|
5402
|
+
getErrorDetailsSpy.mockClear();
|
|
4128
5403
|
|
|
4129
5404
|
const error = {
|
|
4130
5405
|
details: {
|
|
@@ -4150,6 +5425,9 @@ describe('webex.cc', () => {
|
|
|
4150
5425
|
{module: CC_FILE, method: 'acceptPreviewContact', trackingId: error.details.trackingId}
|
|
4151
5426
|
);
|
|
4152
5427
|
expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'acceptPreviewContact', CC_FILE);
|
|
5428
|
+
expect(mockWebexRequest.uploadLogs).toHaveBeenCalledWith({
|
|
5429
|
+
correlationId: error.details.trackingId,
|
|
5430
|
+
});
|
|
4153
5431
|
});
|
|
4154
5432
|
});
|
|
4155
5433
|
|
|
@@ -4187,8 +5465,7 @@ describe('webex.cc', () => {
|
|
|
4187
5465
|
});
|
|
4188
5466
|
|
|
4189
5467
|
it('should handle error during skipPreviewContact', async () => {
|
|
4190
|
-
getErrorDetailsSpy.
|
|
4191
|
-
getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
|
|
5468
|
+
getErrorDetailsSpy.mockClear();
|
|
4192
5469
|
|
|
4193
5470
|
const error = {
|
|
4194
5471
|
details: {
|
|
@@ -4214,6 +5491,9 @@ describe('webex.cc', () => {
|
|
|
4214
5491
|
{module: CC_FILE, method: 'skipPreviewContact', trackingId: error.details.trackingId}
|
|
4215
5492
|
);
|
|
4216
5493
|
expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'skipPreviewContact', CC_FILE);
|
|
5494
|
+
expect(mockWebexRequest.uploadLogs).toHaveBeenCalledWith({
|
|
5495
|
+
correlationId: error.details.trackingId,
|
|
5496
|
+
});
|
|
4217
5497
|
});
|
|
4218
5498
|
});
|
|
4219
5499
|
|
|
@@ -4251,8 +5531,7 @@ describe('webex.cc', () => {
|
|
|
4251
5531
|
});
|
|
4252
5532
|
|
|
4253
5533
|
it('should handle error during removePreviewContact', async () => {
|
|
4254
|
-
getErrorDetailsSpy.
|
|
4255
|
-
getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
|
|
5534
|
+
getErrorDetailsSpy.mockClear();
|
|
4256
5535
|
|
|
4257
5536
|
const error = {
|
|
4258
5537
|
details: {
|
|
@@ -4278,6 +5557,9 @@ describe('webex.cc', () => {
|
|
|
4278
5557
|
{module: CC_FILE, method: 'removePreviewContact', trackingId: error.details.trackingId}
|
|
4279
5558
|
);
|
|
4280
5559
|
expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'removePreviewContact', CC_FILE);
|
|
5560
|
+
expect(mockWebexRequest.uploadLogs).toHaveBeenCalledWith({
|
|
5561
|
+
correlationId: error.details.trackingId,
|
|
5562
|
+
});
|
|
4281
5563
|
});
|
|
4282
5564
|
});
|
|
4283
5565
|
|