@webex/contact-center 3.12.0-llmrefactor.7 → 3.12.0-llmrefactor.9
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 +3 -2
- package/ai-docs/CONTRACTS.md +3 -2
- package/dist/cc.js +108 -6
- package/dist/cc.js.map +1 -1
- package/dist/metrics/MetricsManager.js +16 -0
- package/dist/metrics/MetricsManager.js.map +1 -1
- package/dist/metrics/behavioral-events.js +91 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +16 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/AnswerCallOnWebexService.js +34 -6
- package/dist/services/AnswerCallOnWebexService.js.map +1 -1
- package/dist/services/WebexCrossClientService.js +54 -3
- package/dist/services/WebexCrossClientService.js.map +1 -1
- package/dist/services/WxAppTelephonyMercurySync.js +21 -6
- package/dist/services/WxAppTelephonyMercurySync.js.map +1 -1
- package/dist/services/task/state-machine/TaskStateMachine.js +0 -17
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
- package/dist/services/task/state-machine/guards.js +7 -37
- package/dist/services/task/state-machine/guards.js.map +1 -1
- package/dist/services/task/voice/Voice.js +81 -14
- package/dist/services/task/voice/Voice.js.map +1 -1
- package/dist/services/task/voice/wxAppVoiceMethods.js +154 -1
- package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -1
- package/dist/services/wxAppDiagnosticLogging.js +122 -0
- package/dist/services/wxAppDiagnosticLogging.js.map +1 -0
- package/dist/types/cc.d.ts +1 -0
- package/dist/types/metrics/MetricsManager.d.ts +6 -0
- package/dist/types/metrics/constants.d.ts +15 -0
- package/dist/types/services/AnswerCallOnWebexService.d.ts +1 -0
- package/dist/types/services/WebexCrossClientService.d.ts +4 -0
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +0 -42
- package/dist/types/services/task/state-machine/guards.d.ts +4 -4
- package/dist/types/services/task/voice/Voice.d.ts +5 -1
- package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +3 -0
- package/dist/types/services/wxAppDiagnosticLogging.d.ts +51 -0
- package/dist/webex.js +1 -1
- package/package.json +8 -8
- package/src/cc.ts +155 -7
- package/src/metrics/MetricsManager.ts +16 -0
- package/src/metrics/ai-docs/metrics-spec.md +71 -9
- package/src/metrics/behavioral-events.ts +92 -0
- package/src/metrics/constants.ts +18 -0
- package/src/services/AnswerCallOnWebexService.ts +41 -8
- package/src/services/WebexCrossClientService.ts +85 -4
- package/src/services/WxAppTelephonyMercurySync.ts +31 -4
- package/src/services/agent/ai-docs/agent-spec.md +1 -0
- package/src/services/task/ai-docs/task-spec.md +17 -16
- package/src/services/task/state-machine/TaskStateMachine.ts +0 -20
- package/src/services/task/state-machine/ai-docs/AGENTS.md +2 -4
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +8 -9
- package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +5 -7
- package/src/services/task/state-machine/guards.ts +7 -63
- package/src/services/task/voice/Voice.ts +115 -26
- package/src/services/task/voice/wxAppVoiceMethods.ts +211 -2
- package/src/services/wxAppDiagnosticLogging.ts +174 -0
- package/test/unit/spec/cc.ts +251 -22
- package/test/unit/spec/metrics/MetricsManager.ts +25 -0
- package/test/unit/spec/metrics/behavioral-events.ts +42 -0
- package/test/unit/spec/services/AnswerCallOnWebexService.ts +41 -0
- package/test/unit/spec/services/WebexCrossClientService.ts +207 -1
- package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +23 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +72 -46
- package/test/unit/spec/services/task/state-machine/guards.ts +6 -6
- package/test/unit/spec/services/task/voice/Voice.ts +380 -0
- package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +145 -1
- package/test/unit/spec/services/wxAppDiagnosticLogging.ts +143 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
import WebexCrossClientService from '../../../../src/services/WebexCrossClientService';
|
|
2
|
+
import MetricsManager from '../../../../src/metrics/MetricsManager';
|
|
3
|
+
import {METRIC_EVENT_NAMES} from '../../../../src/metrics/constants';
|
|
4
|
+
|
|
5
|
+
jest.mock('../../../../src/metrics/MetricsManager', () => ({
|
|
6
|
+
__esModule: true,
|
|
7
|
+
default: {
|
|
8
|
+
getInstance: jest.fn(),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
2
11
|
|
|
3
12
|
describe('WebexCrossClientService', () => {
|
|
4
13
|
const userId = 'user-123';
|
|
5
14
|
const deviceUrl = 'https://wdm.example.com/devices/device-abc';
|
|
15
|
+
const trackEvent = jest.fn();
|
|
16
|
+
const timeEvent = jest.fn();
|
|
17
|
+
const cancelTimedEvent = jest.fn();
|
|
6
18
|
|
|
7
19
|
let webex: {request: jest.Mock; internal: {device: {userId: string; url: string}}};
|
|
8
20
|
let service: WebexCrossClientService;
|
|
9
21
|
|
|
10
22
|
beforeEach(() => {
|
|
23
|
+
trackEvent.mockClear();
|
|
24
|
+
timeEvent.mockClear();
|
|
25
|
+
cancelTimedEvent.mockClear();
|
|
26
|
+
(MetricsManager.getInstance as jest.Mock).mockReturnValue({trackEvent, timeEvent, cancelTimedEvent});
|
|
27
|
+
|
|
11
28
|
webex = {
|
|
12
29
|
request: jest.fn().mockResolvedValue({body: {}}),
|
|
13
30
|
internal: {
|
|
@@ -82,6 +99,26 @@ describe('WebexCrossClientService', () => {
|
|
|
82
99
|
'User ID is unavailable for cross-client publish'
|
|
83
100
|
);
|
|
84
101
|
expect(webex.request).not.toHaveBeenCalled();
|
|
102
|
+
expect(trackEvent).not.toHaveBeenCalled();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('emits usersub publish failed when user ID is unavailable and trackPublishMetrics is true', async () => {
|
|
106
|
+
webex.internal.device.userId = undefined as unknown as string;
|
|
107
|
+
|
|
108
|
+
await expect(
|
|
109
|
+
service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true})
|
|
110
|
+
).rejects.toThrow('User ID is unavailable for cross-client publish');
|
|
111
|
+
|
|
112
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
113
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
114
|
+
expect.objectContaining({
|
|
115
|
+
enableWxBetterTogether: true,
|
|
116
|
+
skipReason: 'user_id_unavailable',
|
|
117
|
+
}),
|
|
118
|
+
['operational', 'behavioral']
|
|
119
|
+
);
|
|
120
|
+
expect(timeEvent).not.toHaveBeenCalled();
|
|
121
|
+
expect(webex.request).not.toHaveBeenCalled();
|
|
85
122
|
});
|
|
86
123
|
|
|
87
124
|
it('throws when device URL is unavailable', async () => {
|
|
@@ -91,13 +128,182 @@ describe('WebexCrossClientService', () => {
|
|
|
91
128
|
'Device URL is unavailable for cross-client publish'
|
|
92
129
|
);
|
|
93
130
|
expect(webex.request).not.toHaveBeenCalled();
|
|
131
|
+
expect(trackEvent).not.toHaveBeenCalled();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('emits usersub publish failed when device URL is unavailable and trackPublishMetrics is true', async () => {
|
|
135
|
+
webex.internal.device.url = undefined as unknown as string;
|
|
136
|
+
|
|
137
|
+
await expect(
|
|
138
|
+
service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true})
|
|
139
|
+
).rejects.toThrow('Device URL is unavailable for cross-client publish');
|
|
140
|
+
|
|
141
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
142
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
143
|
+
expect.objectContaining({
|
|
144
|
+
enableWxBetterTogether: true,
|
|
145
|
+
skipReason: 'device_url_unavailable',
|
|
146
|
+
}),
|
|
147
|
+
['operational', 'behavioral']
|
|
148
|
+
);
|
|
149
|
+
expect(timeEvent).not.toHaveBeenCalled();
|
|
150
|
+
expect(webex.request).not.toHaveBeenCalled();
|
|
94
151
|
});
|
|
95
152
|
|
|
96
153
|
it('rejects when usersub publish fails', async () => {
|
|
97
154
|
const err = new Error('usersub failed');
|
|
98
155
|
webex.request = jest.fn().mockRejectedValue(err);
|
|
99
156
|
|
|
100
|
-
await expect(
|
|
157
|
+
await expect(
|
|
158
|
+
service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true})
|
|
159
|
+
).rejects.toThrow('usersub failed');
|
|
160
|
+
|
|
161
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
162
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
163
|
+
expect.objectContaining({enableWxBetterTogether: true}),
|
|
164
|
+
['operational', 'behavioral']
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('tracks usersub publish success when trackPublishMetrics is enabled', async () => {
|
|
169
|
+
await service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true});
|
|
170
|
+
|
|
171
|
+
expect(timeEvent).toHaveBeenCalledWith([
|
|
172
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
173
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
174
|
+
]);
|
|
175
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
176
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
177
|
+
{enableWxBetterTogether: true},
|
|
178
|
+
['operational', 'behavioral']
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('cancels usersub publish timer when publish completes after teardown bumps generation', async () => {
|
|
183
|
+
let resolvePublish: (value: {body: Record<string, never>}) => void;
|
|
184
|
+
const publishPromise = new Promise<{body: Record<string, never>}>((resolve) => {
|
|
185
|
+
resolvePublish = resolve;
|
|
186
|
+
});
|
|
187
|
+
webex.request = jest.fn().mockReturnValue(publishPromise);
|
|
188
|
+
|
|
189
|
+
const publishCall = service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true});
|
|
190
|
+
service.teardown();
|
|
191
|
+
resolvePublish!({body: {}});
|
|
192
|
+
await publishCall;
|
|
193
|
+
|
|
194
|
+
expect(cancelTimedEvent).toHaveBeenCalledWith([
|
|
195
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
196
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
197
|
+
]);
|
|
198
|
+
expect(trackEvent).not.toHaveBeenCalled();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('cancels usersub publish timer when publish fails after teardown bumps generation', async () => {
|
|
202
|
+
let rejectPublish: (error: Error) => void;
|
|
203
|
+
const publishPromise = new Promise((_, reject) => {
|
|
204
|
+
rejectPublish = reject;
|
|
205
|
+
});
|
|
206
|
+
webex.request = jest.fn().mockReturnValue(publishPromise);
|
|
207
|
+
|
|
208
|
+
const publishCall = service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true});
|
|
209
|
+
service.teardown();
|
|
210
|
+
rejectPublish!(new Error('usersub failed'));
|
|
211
|
+
await expect(publishCall).rejects.toThrow('usersub failed');
|
|
212
|
+
|
|
213
|
+
expect(cancelTimedEvent).toHaveBeenCalledWith([
|
|
214
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
215
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
216
|
+
]);
|
|
217
|
+
expect(trackEvent).not.toHaveBeenCalled();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('does not cancel newer publish timer when stale publish completes', async () => {
|
|
221
|
+
let resolveFirstPublish: (value: {body: Record<string, never>}) => void;
|
|
222
|
+
const firstPublishPromise = new Promise<{body: Record<string, never>}>((resolve) => {
|
|
223
|
+
resolveFirstPublish = resolve;
|
|
224
|
+
});
|
|
225
|
+
webex.request = jest
|
|
226
|
+
.fn()
|
|
227
|
+
.mockReturnValueOnce(firstPublishPromise)
|
|
228
|
+
.mockResolvedValueOnce({body: {}});
|
|
229
|
+
|
|
230
|
+
const firstPublishCall = service.setManageWebexCallingInWxcc(true, {
|
|
231
|
+
trackPublishMetrics: true,
|
|
232
|
+
});
|
|
233
|
+
const secondPublishCall = service.setManageWebexCallingInWxcc(false, {
|
|
234
|
+
trackPublishMetrics: true,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
await secondPublishCall;
|
|
238
|
+
|
|
239
|
+
service.teardown();
|
|
240
|
+
resolveFirstPublish!({body: {}});
|
|
241
|
+
await firstPublishCall;
|
|
242
|
+
|
|
243
|
+
expect(cancelTimedEvent).not.toHaveBeenCalled();
|
|
244
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
245
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
246
|
+
{enableWxBetterTogether: false},
|
|
247
|
+
['operational', 'behavioral']
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('preserves duration_ms on newer publish when stale publish completes', async () => {
|
|
252
|
+
const {default: RealMetricsManager} = jest.requireActual(
|
|
253
|
+
'../../../../src/metrics/MetricsManager'
|
|
254
|
+
);
|
|
255
|
+
RealMetricsManager.resetInstance();
|
|
256
|
+
const realManager = RealMetricsManager.getInstance();
|
|
257
|
+
jest.spyOn(realManager as any, 'isMetricsDisabled').mockReturnValue(false);
|
|
258
|
+
const trackEventSpy = jest.spyOn(realManager, 'trackEvent');
|
|
259
|
+
(MetricsManager.getInstance as jest.Mock).mockReturnValue(realManager);
|
|
260
|
+
|
|
261
|
+
let resolveFirstPublish: (value: {body: Record<string, never>}) => void;
|
|
262
|
+
const firstPublishPromise = new Promise<{body: Record<string, never>}>((resolve) => {
|
|
263
|
+
resolveFirstPublish = resolve;
|
|
264
|
+
});
|
|
265
|
+
webex.request = jest
|
|
266
|
+
.fn()
|
|
267
|
+
.mockReturnValueOnce(firstPublishPromise)
|
|
268
|
+
.mockResolvedValueOnce({body: {}});
|
|
269
|
+
|
|
270
|
+
const firstPublishCall = service.setManageWebexCallingInWxcc(true, {
|
|
271
|
+
trackPublishMetrics: true,
|
|
272
|
+
});
|
|
273
|
+
const secondPublishCall = service.setManageWebexCallingInWxcc(false, {
|
|
274
|
+
trackPublishMetrics: true,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
await secondPublishCall;
|
|
278
|
+
service.teardown();
|
|
279
|
+
resolveFirstPublish!({body: {}});
|
|
280
|
+
await firstPublishCall;
|
|
281
|
+
|
|
282
|
+
const successCall = trackEventSpy.mock.calls.find(
|
|
283
|
+
([eventName, payload]) =>
|
|
284
|
+
eventName === METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS &&
|
|
285
|
+
(payload as {enableWxBetterTogether?: boolean}).enableWxBetterTogether === false
|
|
286
|
+
);
|
|
287
|
+
expect(successCall?.[1]).toEqual(
|
|
288
|
+
expect.objectContaining({
|
|
289
|
+
enableWxBetterTogether: false,
|
|
290
|
+
duration_ms: expect.any(Number),
|
|
291
|
+
})
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
RealMetricsManager.resetInstance();
|
|
295
|
+
(MetricsManager.getInstance as jest.Mock).mockReturnValue({
|
|
296
|
+
trackEvent,
|
|
297
|
+
timeEvent,
|
|
298
|
+
cancelTimedEvent,
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('does not track usersub metrics by default', async () => {
|
|
303
|
+
await service.setManageWebexCallingInWxcc(true);
|
|
304
|
+
|
|
305
|
+
expect(trackEvent).not.toHaveBeenCalled();
|
|
306
|
+
expect(timeEvent).not.toHaveBeenCalled();
|
|
101
307
|
});
|
|
102
308
|
|
|
103
309
|
it('uses custom ttl from options', async () => {
|
|
@@ -2,15 +2,28 @@ import WxAppTelephonyMercurySync, {
|
|
|
2
2
|
TELEPHONY_CALL_MUTED,
|
|
3
3
|
TELEPHONY_CALL_UNMUTED,
|
|
4
4
|
} from '../../../../src/services/WxAppTelephonyMercurySync';
|
|
5
|
+
import MetricsManager from '../../../../src/metrics/MetricsManager';
|
|
6
|
+
import {METRIC_EVENT_NAMES} from '../../../../src/metrics/constants';
|
|
7
|
+
|
|
8
|
+
jest.mock('../../../../src/metrics/MetricsManager', () => ({
|
|
9
|
+
__esModule: true,
|
|
10
|
+
default: {
|
|
11
|
+
getInstance: jest.fn(),
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
5
14
|
|
|
6
15
|
describe('WxAppTelephonyMercurySync', () => {
|
|
7
16
|
const agentId = 'agent-123';
|
|
17
|
+
const trackEvent = jest.fn();
|
|
8
18
|
let mercury: {on: jest.Mock; off: jest.Mock};
|
|
9
19
|
let webex: {internal: {mercury?: typeof mercury}};
|
|
10
20
|
let sync: WxAppTelephonyMercurySync;
|
|
11
21
|
let onMuteChange: jest.Mock;
|
|
12
22
|
|
|
13
23
|
beforeEach(() => {
|
|
24
|
+
trackEvent.mockClear();
|
|
25
|
+
(MetricsManager.getInstance as jest.Mock).mockReturnValue({trackEvent});
|
|
26
|
+
|
|
14
27
|
mercury = {on: jest.fn(), off: jest.fn()};
|
|
15
28
|
webex = {internal: {mercury}};
|
|
16
29
|
sync = new WxAppTelephonyMercurySync(webex as never);
|
|
@@ -27,6 +40,11 @@ describe('WxAppTelephonyMercurySync', () => {
|
|
|
27
40
|
expect(mercury.on).toHaveBeenCalledWith(TELEPHONY_CALL_MUTED, expect.any(Function));
|
|
28
41
|
expect(mercury.on).toHaveBeenCalledWith(TELEPHONY_CALL_UNMUTED, expect.any(Function));
|
|
29
42
|
expect(sync.isSubscribed()).toBe(true);
|
|
43
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
44
|
+
METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_SUCCESS,
|
|
45
|
+
{},
|
|
46
|
+
['operational', 'behavioral']
|
|
47
|
+
);
|
|
30
48
|
});
|
|
31
49
|
|
|
32
50
|
it('invokes callback with decoded callId and muted when actorId matches agent', () => {
|
|
@@ -77,6 +95,11 @@ describe('WxAppTelephonyMercurySync', () => {
|
|
|
77
95
|
|
|
78
96
|
expect(sync.isSubscribed()).toBe(false);
|
|
79
97
|
expect(onMuteChange).not.toHaveBeenCalled();
|
|
98
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
99
|
+
METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_FAILED,
|
|
100
|
+
expect.objectContaining({error: expect.stringContaining('Mercury is unavailable')}),
|
|
101
|
+
['operational', 'behavioral']
|
|
102
|
+
);
|
|
80
103
|
});
|
|
81
104
|
|
|
82
105
|
it('invokes callback with muted false on unmuted Mercury event', () => {
|
|
@@ -199,7 +199,7 @@ describe('Task state machine', () => {
|
|
|
199
199
|
expect(emitTaskEnd).not.toHaveBeenCalled();
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
-
it('
|
|
202
|
+
it('ignores PARTICIPANT_LEAVE in CONNECTED even when it identifies the current agent', () => {
|
|
203
203
|
const {service, emitTaskEnd} = startMachineWithEndSpy();
|
|
204
204
|
const taskData = createActiveMainCallTaskData();
|
|
205
205
|
|
|
@@ -213,53 +213,71 @@ describe('Task state machine', () => {
|
|
|
213
213
|
taskData: createPartialMainCallTaskData(false, true),
|
|
214
214
|
});
|
|
215
215
|
|
|
216
|
+
expect(service.getSnapshot().value).toBe(TaskState.CONNECTED);
|
|
217
|
+
expect(emitTaskEnd).not.toHaveBeenCalled();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('ignores PARTICIPANT_LEAVE in HOLD_INITIATING when a remote Drop identifies the current agent', () => {
|
|
221
|
+
const {service, emitTaskEnd} = startMachineWithEndSpy();
|
|
222
|
+
const taskData = createActiveMainCallTaskData();
|
|
223
|
+
|
|
224
|
+
enterTransientHoldState(service, TaskState.HOLD_INITIATING, taskData);
|
|
225
|
+
service.send({
|
|
226
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
227
|
+
participantId: 'agent-1',
|
|
228
|
+
taskData: createPartialMainCallTaskData(false, true),
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
expect(service.getSnapshot().value).toBe(TaskState.HOLD_INITIATING);
|
|
232
|
+
expect(emitTaskEnd).not.toHaveBeenCalled();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('terminates RESUME_INITIATING when a remote Drop identifies the current agent', () => {
|
|
236
|
+
const {service, emitTaskEnd} = startMachineWithEndSpy();
|
|
237
|
+
const taskData = createActiveMainCallTaskData(true);
|
|
238
|
+
|
|
239
|
+
enterTransientHoldState(service, TaskState.RESUME_INITIATING, taskData);
|
|
240
|
+
service.send({
|
|
241
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
242
|
+
participantId: 'agent-1',
|
|
243
|
+
taskData: createPartialMainCallTaskData(true, true),
|
|
244
|
+
});
|
|
245
|
+
|
|
216
246
|
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
217
247
|
expect(emitTaskEnd).toHaveBeenCalledTimes(1);
|
|
218
248
|
});
|
|
219
249
|
|
|
220
|
-
it
|
|
221
|
-
|
|
222
|
-
(
|
|
223
|
-
const {service, emitTaskEnd} = startMachineWithEndSpy();
|
|
224
|
-
const taskData = createActiveMainCallTaskData(state === TaskState.RESUME_INITIATING);
|
|
225
|
-
|
|
226
|
-
enterTransientHoldState(service, state, taskData);
|
|
227
|
-
service.send({
|
|
228
|
-
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
229
|
-
participantId: 'agent-1',
|
|
230
|
-
taskData: createPartialMainCallTaskData(
|
|
231
|
-
state === TaskState.RESUME_INITIATING,
|
|
232
|
-
true
|
|
233
|
-
),
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
237
|
-
expect(emitTaskEnd).toHaveBeenCalledTimes(1);
|
|
238
|
-
}
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
it.each([TaskState.HOLD_INITIATING, TaskState.RESUME_INITIATING])(
|
|
242
|
-
'keeps %s when ParticipantLeftConference identifies another participant',
|
|
243
|
-
(state) => {
|
|
244
|
-
const {service, emitTaskEnd} = startMachineWithEndSpy();
|
|
245
|
-
const taskData = createActiveMainCallTaskData(state === TaskState.RESUME_INITIATING);
|
|
246
|
-
|
|
247
|
-
enterTransientHoldState(service, state, taskData);
|
|
248
|
-
service.send({
|
|
249
|
-
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
250
|
-
participantId: 'agent-2',
|
|
251
|
-
taskData: createPartialMainCallTaskData(
|
|
252
|
-
state === TaskState.RESUME_INITIATING,
|
|
253
|
-
true
|
|
254
|
-
),
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
expect(service.getSnapshot().value).toBe(state);
|
|
258
|
-
expect(emitTaskEnd).not.toHaveBeenCalled();
|
|
259
|
-
}
|
|
260
|
-
);
|
|
250
|
+
it('keeps HOLD_INITIATING when ParticipantLeftConference identifies another participant', () => {
|
|
251
|
+
const {service, emitTaskEnd} = startMachineWithEndSpy();
|
|
252
|
+
const taskData = createActiveMainCallTaskData();
|
|
261
253
|
|
|
262
|
-
|
|
254
|
+
enterTransientHoldState(service, TaskState.HOLD_INITIATING, taskData);
|
|
255
|
+
service.send({
|
|
256
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
257
|
+
participantId: 'agent-2',
|
|
258
|
+
taskData: createPartialMainCallTaskData(false, true),
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
expect(service.getSnapshot().value).toBe(TaskState.HOLD_INITIATING);
|
|
262
|
+
expect(emitTaskEnd).not.toHaveBeenCalled();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('terminates RESUME_INITIATING when another participant leave omits self from the roster', () => {
|
|
266
|
+
const {service, emitTaskEnd} = startMachineWithEndSpy();
|
|
267
|
+
const taskData = createActiveMainCallTaskData(true);
|
|
268
|
+
|
|
269
|
+
enterTransientHoldState(service, TaskState.RESUME_INITIATING, taskData);
|
|
270
|
+
service.send({
|
|
271
|
+
type: TaskEvent.PARTICIPANT_LEAVE,
|
|
272
|
+
participantId: 'agent-2',
|
|
273
|
+
taskData: createPartialMainCallTaskData(true, true),
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
277
|
+
expect(emitTaskEnd).toHaveBeenCalledTimes(1);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('terminates HELD when another participant leave event omits self from its roster', () => {
|
|
263
281
|
const {service, emitTaskEnd} = startMachineWithEndSpy();
|
|
264
282
|
const taskData = createActiveMainCallTaskData(true);
|
|
265
283
|
|
|
@@ -275,8 +293,8 @@ describe('Task state machine', () => {
|
|
|
275
293
|
taskData: createPartialMainCallTaskData(true, true),
|
|
276
294
|
});
|
|
277
295
|
|
|
278
|
-
expect(service.getSnapshot().value).toBe(TaskState.
|
|
279
|
-
expect(emitTaskEnd).
|
|
296
|
+
expect(service.getSnapshot().value).toBe(TaskState.TERMINATED);
|
|
297
|
+
expect(emitTaskEnd).toHaveBeenCalledTimes(1);
|
|
280
298
|
});
|
|
281
299
|
|
|
282
300
|
it('keeps HELD when CONSULT_END has a partial mainCall', () => {
|
|
@@ -2064,7 +2082,15 @@ describe('Task state machine', () => {
|
|
|
2064
2082
|
|
|
2065
2083
|
it('transitions to CONNECTED on CONSULT_END without isTerminated during CONF_INITIATING', () => {
|
|
2066
2084
|
const service = startMachine();
|
|
2067
|
-
const taskData = createTaskData({
|
|
2085
|
+
const taskData = createTaskData({
|
|
2086
|
+
consultingAgentId: 'agent-1',
|
|
2087
|
+
interaction: {
|
|
2088
|
+
participants: {
|
|
2089
|
+
'agent-1': {id: 'agent-1', pType: 'Agent', hasLeft: false},
|
|
2090
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false},
|
|
2091
|
+
},
|
|
2092
|
+
} as any,
|
|
2093
|
+
});
|
|
2068
2094
|
|
|
2069
2095
|
service.send({type: TaskEvent.TASK_INCOMING, taskData});
|
|
2070
2096
|
service.send({type: TaskEvent.ASSIGN, taskData});
|
|
@@ -636,7 +636,7 @@ describe('State Machine Guards', () => {
|
|
|
636
636
|
expect(guards.didCurrentAgentLeaveMainInteraction(createParams(context, event))).toBe(true);
|
|
637
637
|
});
|
|
638
638
|
|
|
639
|
-
it('returns
|
|
639
|
+
it('returns false when the current participant is marked hasLeft but remains in the map', () => {
|
|
640
640
|
const context = createContext({taskData: activeTaskData});
|
|
641
641
|
const updatedTaskData = createTaskData({
|
|
642
642
|
interaction: {
|
|
@@ -652,10 +652,10 @@ describe('State Machine Guards', () => {
|
|
|
652
652
|
guards.didCurrentAgentLeaveMainInteraction(
|
|
653
653
|
createParams(context, createEventWithTaskData(updatedTaskData))
|
|
654
654
|
)
|
|
655
|
-
).toBe(
|
|
655
|
+
).toBe(false);
|
|
656
656
|
});
|
|
657
657
|
|
|
658
|
-
it('
|
|
658
|
+
it('does not infer departure from CONSULT_END mainCall membership', () => {
|
|
659
659
|
const context = createContext({taskData: activeTaskData, consultFromConference: true});
|
|
660
660
|
const updatedTaskData = createTaskData({
|
|
661
661
|
interaction: {
|
|
@@ -690,10 +690,10 @@ describe('State Machine Guards', () => {
|
|
|
690
690
|
|
|
691
691
|
expect(
|
|
692
692
|
guards.didCurrentAgentLeaveMainInteraction(createParams(context, event))
|
|
693
|
-
).toBe(
|
|
693
|
+
).toBe(false);
|
|
694
694
|
});
|
|
695
695
|
|
|
696
|
-
it('
|
|
696
|
+
it('returns true when another participant leaves and the map omits self', () => {
|
|
697
697
|
const context = createContext({taskData: activeTaskData});
|
|
698
698
|
const partialTaskData = createTaskData({
|
|
699
699
|
interaction: {
|
|
@@ -718,7 +718,7 @@ describe('State Machine Guards', () => {
|
|
|
718
718
|
taskData: partialTaskData,
|
|
719
719
|
} as TaskEventPayload;
|
|
720
720
|
|
|
721
|
-
expect(guards.didCurrentAgentLeaveMainInteraction(createParams(context, event))).toBe(
|
|
721
|
+
expect(guards.didCurrentAgentLeaveMainInteraction(createParams(context, event))).toBe(true);
|
|
722
722
|
});
|
|
723
723
|
|
|
724
724
|
it.each(['connected', 'held'])(
|