@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
|
@@ -0,0 +1,881 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
|
|
4
|
+
const CONTACT_CENTER_ROOT = path.resolve(__dirname, '../../..');
|
|
5
|
+
const SRC_ROOT = path.join(CONTACT_CENTER_ROOT, 'src');
|
|
6
|
+
const INDEX_PATH = path.join(SRC_ROOT, 'index.ts');
|
|
7
|
+
const TYPES_PATH = path.join(SRC_ROOT, 'types.ts');
|
|
8
|
+
const CONFIG_TYPES_PATH = path.join(SRC_ROOT, 'services/config/types.ts');
|
|
9
|
+
|
|
10
|
+
const EXPECTED_ROOT_EXPORTS = Object.freeze([
|
|
11
|
+
'AGENT_EVENTS',
|
|
12
|
+
'AISummaryAction',
|
|
13
|
+
'AISummary',
|
|
14
|
+
'AISummaryFeedback',
|
|
15
|
+
'AISummaryResponse',
|
|
16
|
+
'AISummarySections',
|
|
17
|
+
'AISummaryState',
|
|
18
|
+
'AI_SUMMARY_ERROR_CODES',
|
|
19
|
+
'AddressBook',
|
|
20
|
+
'AddressBookEntriesResponse',
|
|
21
|
+
'AddressBookEntry',
|
|
22
|
+
'AddressBookEntrySearchParams',
|
|
23
|
+
'AgentContact',
|
|
24
|
+
'AgentEvents',
|
|
25
|
+
'AgentLogin',
|
|
26
|
+
'AgentProfileUpdate',
|
|
27
|
+
'AgentResponse',
|
|
28
|
+
'AgentState',
|
|
29
|
+
'ApiAIAssistant',
|
|
30
|
+
'AuxCode',
|
|
31
|
+
'AuxCodeType',
|
|
32
|
+
'BuddyAgents',
|
|
33
|
+
'BuddyAgentsResponse',
|
|
34
|
+
'BuddyAgentsSuccess',
|
|
35
|
+
'BuddyDetails',
|
|
36
|
+
'CCPluginConfig',
|
|
37
|
+
'CC_AGENT_EVENTS',
|
|
38
|
+
'CC_TASK_EVENTS',
|
|
39
|
+
'CC_EVENTS',
|
|
40
|
+
'ConsultEndPayload',
|
|
41
|
+
'ConsultPayload',
|
|
42
|
+
'ConsultTransferPayLoad',
|
|
43
|
+
'ContactCenter',
|
|
44
|
+
'ContactCenterEvents',
|
|
45
|
+
'ContactServiceQueue',
|
|
46
|
+
'ContactServiceQueueSearchParams',
|
|
47
|
+
'ContactServiceQueuesResponse',
|
|
48
|
+
'CreateUserPreferenceRequest',
|
|
49
|
+
'DesktopProfileResponse',
|
|
50
|
+
'DeviceType',
|
|
51
|
+
'DeviceTypeUpdateSuccess',
|
|
52
|
+
'DialPlan',
|
|
53
|
+
'DialPlanEntity',
|
|
54
|
+
'DialerPayload',
|
|
55
|
+
'DropConferenceParticipantPayload',
|
|
56
|
+
'Entity',
|
|
57
|
+
'EntryPointListResponse',
|
|
58
|
+
'EntryPointRecord',
|
|
59
|
+
'EntryPointSearchParams',
|
|
60
|
+
'AISummaryFeatureEnablement',
|
|
61
|
+
'GenericError',
|
|
62
|
+
'GetUserPreferenceParams',
|
|
63
|
+
'IContactCenter',
|
|
64
|
+
'IDLE_CODE',
|
|
65
|
+
'IEventEmitter',
|
|
66
|
+
'ITask',
|
|
67
|
+
'Interaction',
|
|
68
|
+
'InteractionUIControls',
|
|
69
|
+
'ListAuxCodesResponse',
|
|
70
|
+
'ListTeamsResponse',
|
|
71
|
+
'LoginOption',
|
|
72
|
+
'Logout',
|
|
73
|
+
'LogoutSuccess',
|
|
74
|
+
'MultimediaProfileResponse',
|
|
75
|
+
'OrgInfo',
|
|
76
|
+
'OrgSettings',
|
|
77
|
+
'Profile',
|
|
78
|
+
'ReloginSuccess',
|
|
79
|
+
'RespondToWellnessBreakParams',
|
|
80
|
+
'ResumeRecordingPayload',
|
|
81
|
+
'SetStateResponse',
|
|
82
|
+
'SiteInfo',
|
|
83
|
+
'StateChange',
|
|
84
|
+
'StateChangeSuccess',
|
|
85
|
+
'StationLoginResponse',
|
|
86
|
+
'StationLoginSuccess',
|
|
87
|
+
'StationLoginSuccessResponse',
|
|
88
|
+
'StationLogoutResponse',
|
|
89
|
+
'SubscribeRequest',
|
|
90
|
+
'TASK_EVENTS',
|
|
91
|
+
'Task',
|
|
92
|
+
'TaskData',
|
|
93
|
+
'TaskEvents',
|
|
94
|
+
'TaskResponse',
|
|
95
|
+
'TaskUIControlState',
|
|
96
|
+
'TaskUIControls',
|
|
97
|
+
'TaskUILeg',
|
|
98
|
+
'TeamList',
|
|
99
|
+
'TenantData',
|
|
100
|
+
'TransferPayLoad',
|
|
101
|
+
'URLMapping',
|
|
102
|
+
'UpdateDeviceTypeResponse',
|
|
103
|
+
'UpdateUserPreferenceRequest',
|
|
104
|
+
'UploadLogsResponse',
|
|
105
|
+
'UserPreference',
|
|
106
|
+
'UserPreferenceData',
|
|
107
|
+
'UserStationLogin',
|
|
108
|
+
'WELLNESS_BREAK_NOTIFICATION_ACTIONS',
|
|
109
|
+
'WELLNESS_BREAK_USER_ACTIONS',
|
|
110
|
+
'WRAP_UP_CODE',
|
|
111
|
+
'WebSocketEvent',
|
|
112
|
+
'WebexSDK',
|
|
113
|
+
'WellnessBreakEvent',
|
|
114
|
+
'WellnessBreakNotificationAction',
|
|
115
|
+
'WellnessBreakUserAction',
|
|
116
|
+
'WrapUpReason',
|
|
117
|
+
'WrapupData',
|
|
118
|
+
'WrapupPayLoad',
|
|
119
|
+
'default',
|
|
120
|
+
'getDefaultUIControls',
|
|
121
|
+
'routingAgent',
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
const INTERNAL_ROOT_EXPORTS = Object.freeze([
|
|
125
|
+
'AIAssistantEventName',
|
|
126
|
+
'AISummaryTimeoutCodeByInboundType',
|
|
127
|
+
'AI_SUMMARY_DURATION_MS',
|
|
128
|
+
'AI_SUMMARY_REQUEST_CANCELLED',
|
|
129
|
+
'AI_ASSISTANT_CLIENT_TYPE',
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
const EXPECTED_AI_SUMMARY_ERROR_CODES = Object.freeze([
|
|
133
|
+
['POST_CALL_SUMMARY_DISABLED', 'POST_CALL_SUMMARY_DISABLED'],
|
|
134
|
+
['MID_CALL_SUMMARY_DISABLED', 'MID_CALL_SUMMARY_DISABLED'],
|
|
135
|
+
['AI_ASSISTANT_BASE_URL_NOT_AVAILABLE', 'AI_ASSISTANT_BASE_URL_NOT_AVAILABLE'],
|
|
136
|
+
['POST_CALL_SUMMARY_TIMEOUT', 'POST_CALL_SUMMARY_TIMEOUT'],
|
|
137
|
+
['MID_CALL_SUMMARY_TIMEOUT', 'MID_CALL_SUMMARY_TIMEOUT'],
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
const EXPECTED_AI_SUMMARY_WIRE_LITERALS = Object.freeze({
|
|
141
|
+
AIAssistantEventName: Object.freeze({
|
|
142
|
+
GET_POST_CALL_SUMMARY: 'GET_POST_CALL_SUMMARY',
|
|
143
|
+
GET_MID_CALL_CONSULT_SUMMARY: 'GET_MID_CALL_CONSULT_SUMMARY',
|
|
144
|
+
GET_MID_CALL_TRANSFER_SUMMARY: 'GET_MID_CALL_TRANSFER_SUMMARY',
|
|
145
|
+
POST_CALL_SUMMARY_RESPONSE: 'POST_CALL_SUMMARY_RESPONSE',
|
|
146
|
+
MID_CALL_CONSULT_SUMMARY_RESPONSE: 'MID_CALL_CONSULT_SUMMARY_RESPONSE',
|
|
147
|
+
MID_CALL_TRANSFER_SUMMARY_RESPONSE: 'MID_CALL_TRANSFER_SUMMARY_RESPONSE',
|
|
148
|
+
}),
|
|
149
|
+
CC_TASK_EVENTS: Object.freeze({
|
|
150
|
+
REAL_TIME_TRANSCRIPTION: 'REAL_TIME_TRANSCRIPTION',
|
|
151
|
+
SUGGESTED_RESPONSE: 'SUGGESTED_RESPONSE',
|
|
152
|
+
SUGGESTED_RESPONSE_ACKNOWLEDGE: 'SUGGESTED_RESPONSE_ACKNOWLEDGE',
|
|
153
|
+
POST_CALL_SUMMARY: 'POST_CALL_SUMMARY',
|
|
154
|
+
MID_CALL_SUMMARY: 'MID_CALL_SUMMARY',
|
|
155
|
+
FEATURE_ENABLEMENT: 'FEATURE_ENABLEMENT',
|
|
156
|
+
MID_CALL_SUMMARY_RESPONSE_SUBSEQUENT_AGENT: 'MID_CALL_SUMMARY_RESPONSE_SUBSEQUENT_AGENT',
|
|
157
|
+
}),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const MISSING_EXPORT_DIAGNOSTIC_CODES = Object.freeze([2305, 2614, 2724]);
|
|
161
|
+
|
|
162
|
+
const VALID_PUBLIC_CONTRACT_FIXTURE = `
|
|
163
|
+
import {
|
|
164
|
+
AI_SUMMARY_ERROR_CODES,
|
|
165
|
+
CC_TASK_EVENTS,
|
|
166
|
+
TASK_EVENTS,
|
|
167
|
+
} from '../../../src';
|
|
168
|
+
import type {
|
|
169
|
+
AISummaryAction,
|
|
170
|
+
AISummaryFeedback,
|
|
171
|
+
AISummaryResponse,
|
|
172
|
+
AISummaryFeatureEnablement,
|
|
173
|
+
ITask,
|
|
174
|
+
} from '../../../src';
|
|
175
|
+
|
|
176
|
+
const action: AISummaryAction = 'CONSULT';
|
|
177
|
+
const transferAction: AISummaryAction = 'TRANSFER';
|
|
178
|
+
const feedback: AISummaryFeedback = 'thumbs_down';
|
|
179
|
+
const postCallEnabledTrue: AISummaryFeatureEnablement = {
|
|
180
|
+
interactionId: 'post-call-true',
|
|
181
|
+
postCallEnabled: true,
|
|
182
|
+
};
|
|
183
|
+
const postCallEnabledFalse: AISummaryFeatureEnablement = {
|
|
184
|
+
interactionId: 'post-call-false',
|
|
185
|
+
postCallEnabled: false,
|
|
186
|
+
};
|
|
187
|
+
const postCallEnabledOmitted: AISummaryFeatureEnablement = {
|
|
188
|
+
interactionId: 'post-call-omitted',
|
|
189
|
+
midCallEnabled: true,
|
|
190
|
+
};
|
|
191
|
+
const midCallEnabledTrue: AISummaryFeatureEnablement = {
|
|
192
|
+
interactionId: 'mid-call-true',
|
|
193
|
+
midCallEnabled: true,
|
|
194
|
+
};
|
|
195
|
+
const midCallEnabledFalse: AISummaryFeatureEnablement = {
|
|
196
|
+
interactionId: 'mid-call-false',
|
|
197
|
+
midCallEnabled: false,
|
|
198
|
+
};
|
|
199
|
+
const midCallEnabledOmitted: AISummaryFeatureEnablement = {
|
|
200
|
+
interactionId: 'mid-call-omitted',
|
|
201
|
+
postCallEnabled: true,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const structuredPostCall: AISummaryResponse = {
|
|
205
|
+
summary: {
|
|
206
|
+
initialContactReason: 'billing',
|
|
207
|
+
additionalContactReasons: 'renewal',
|
|
208
|
+
additionalContext: 'caller asked for an email',
|
|
209
|
+
keyActionsTaken: 'verified account',
|
|
210
|
+
nextSteps: 'send receipt',
|
|
211
|
+
},
|
|
212
|
+
feedback,
|
|
213
|
+
state: 'DEFAULT',
|
|
214
|
+
wrapUpCode: 'resolved',
|
|
215
|
+
numberOfTimesViewed: 1,
|
|
216
|
+
numberOfTimesEdited: 0,
|
|
217
|
+
numberOfTimesCopied: 0,
|
|
218
|
+
};
|
|
219
|
+
const textPostCall: AISummaryResponse = {
|
|
220
|
+
summary: 'plain post call summary',
|
|
221
|
+
feedback: 'thumbs_up',
|
|
222
|
+
state: 'IGNORED',
|
|
223
|
+
wrapUpCode: 'ignored',
|
|
224
|
+
numberOfTimesViewed: 2,
|
|
225
|
+
numberOfTimesEdited: 1,
|
|
226
|
+
numberOfTimesCopied: 1,
|
|
227
|
+
};
|
|
228
|
+
const noPostCallSummary: AISummaryResponse = {
|
|
229
|
+
summary: '',
|
|
230
|
+
feedback: 'none',
|
|
231
|
+
state: 'NOT_RECEIVED',
|
|
232
|
+
wrapUpCode: 'not-received',
|
|
233
|
+
numberOfTimesViewed: 0,
|
|
234
|
+
numberOfTimesEdited: 0,
|
|
235
|
+
numberOfTimesCopied: 0,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const structuredMidCall: AISummaryResponse = {
|
|
239
|
+
summaryReceived: true,
|
|
240
|
+
summary: {
|
|
241
|
+
reasonForTransferOrConsult: 'expert needed',
|
|
242
|
+
additionalContext: 'customer has prior ticket',
|
|
243
|
+
keyActionsTaken: 'authenticated caller',
|
|
244
|
+
},
|
|
245
|
+
feedback: 'none',
|
|
246
|
+
state: 'EXCLUDED',
|
|
247
|
+
numberOfTimesViewed: 1,
|
|
248
|
+
numberOfTimesEdited: 0,
|
|
249
|
+
numberOfTimesCopied: 0,
|
|
250
|
+
};
|
|
251
|
+
const textMidCall: AISummaryResponse = {
|
|
252
|
+
summaryReceived: true,
|
|
253
|
+
summary: 'plain mid call summary',
|
|
254
|
+
feedback: 'thumbs_up',
|
|
255
|
+
state: 'MID_CALL_CANCELLED',
|
|
256
|
+
numberOfTimesViewed: 0,
|
|
257
|
+
numberOfTimesEdited: 0,
|
|
258
|
+
numberOfTimesCopied: 0,
|
|
259
|
+
};
|
|
260
|
+
const unavailableMidCall: AISummaryResponse = {
|
|
261
|
+
summaryReceived: false,
|
|
262
|
+
summary: '',
|
|
263
|
+
feedback: 'thumbs_down',
|
|
264
|
+
state: 'NOT_RECEIVED',
|
|
265
|
+
numberOfTimesViewed: 0,
|
|
266
|
+
numberOfTimesEdited: 0,
|
|
267
|
+
numberOfTimesCopied: 0,
|
|
268
|
+
};
|
|
269
|
+
const cancelledMidCall: AISummaryResponse = {
|
|
270
|
+
summaryReceived: false,
|
|
271
|
+
summary: '',
|
|
272
|
+
feedback: 'none',
|
|
273
|
+
state: 'MID_CALL_CANCELLED',
|
|
274
|
+
numberOfTimesViewed: 0,
|
|
275
|
+
numberOfTimesEdited: 0,
|
|
276
|
+
numberOfTimesCopied: 0,
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const taskSummaryMethods: Pick<
|
|
280
|
+
ITask,
|
|
281
|
+
| 'requestPostCallSummary'
|
|
282
|
+
| 'sendPostCallSummaryResponse'
|
|
283
|
+
| 'requestMidCallSummary'
|
|
284
|
+
| 'sendMidCallSummaryResponse'
|
|
285
|
+
> = {
|
|
286
|
+
requestPostCallSummary: async () => ({conversationId: 'conversation-1'}),
|
|
287
|
+
sendPostCallSummaryResponse: async (payload) => {
|
|
288
|
+
const responseState = payload.state;
|
|
289
|
+
void responseState;
|
|
290
|
+
},
|
|
291
|
+
requestMidCallSummary: async (requestedAction) => {
|
|
292
|
+
const selectedAction: AISummaryAction = requestedAction;
|
|
293
|
+
void selectedAction;
|
|
294
|
+
return {conversationId: 'conversation-1'};
|
|
295
|
+
},
|
|
296
|
+
sendMidCallSummaryResponse: async (payload, requestedAction) => {
|
|
297
|
+
const selectedAction: AISummaryAction = requestedAction;
|
|
298
|
+
const responseState = payload.state;
|
|
299
|
+
void selectedAction;
|
|
300
|
+
void responseState;
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const inboundPost = CC_TASK_EVENTS.POST_CALL_SUMMARY;
|
|
305
|
+
const receivingEvent = TASK_EVENTS.TASK_MID_CALL_SUMMARY_RECEIVED;
|
|
306
|
+
const disabledCode = AI_SUMMARY_ERROR_CODES.POST_CALL_SUMMARY_DISABLED;
|
|
307
|
+
void action;
|
|
308
|
+
void transferAction;
|
|
309
|
+
void postCallEnabledTrue;
|
|
310
|
+
void postCallEnabledFalse;
|
|
311
|
+
void postCallEnabledOmitted;
|
|
312
|
+
void midCallEnabledTrue;
|
|
313
|
+
void midCallEnabledFalse;
|
|
314
|
+
void midCallEnabledOmitted;
|
|
315
|
+
void structuredPostCall;
|
|
316
|
+
void textPostCall;
|
|
317
|
+
void noPostCallSummary;
|
|
318
|
+
void structuredMidCall;
|
|
319
|
+
void textMidCall;
|
|
320
|
+
void unavailableMidCall;
|
|
321
|
+
void cancelledMidCall;
|
|
322
|
+
void taskSummaryMethods;
|
|
323
|
+
void inboundPost;
|
|
324
|
+
void receivingEvent;
|
|
325
|
+
void disabledCode;
|
|
326
|
+
`;
|
|
327
|
+
|
|
328
|
+
const createInternalRootImportFixture = () => `
|
|
329
|
+
import {
|
|
330
|
+
${INTERNAL_ROOT_EXPORTS.map((name) => ` ${name},`).join('\n')}
|
|
331
|
+
} from '../../../src';
|
|
332
|
+
|
|
333
|
+
void [
|
|
334
|
+
${INTERNAL_ROOT_EXPORTS.map((name) => ` ${name},`).join('\n')}
|
|
335
|
+
];
|
|
336
|
+
`;
|
|
337
|
+
|
|
338
|
+
const INVALID_PUBLIC_CONTRACT_FIXTURE = `
|
|
339
|
+
import {
|
|
340
|
+
type AISummaryAction,
|
|
341
|
+
type AISummaryResponse,
|
|
342
|
+
type AISummaryFeatureEnablement,
|
|
343
|
+
} from '../../../src';
|
|
344
|
+
|
|
345
|
+
let invalidAction: AISummaryAction;
|
|
346
|
+
// @ts-expect-error invalid summary action type must not widen
|
|
347
|
+
invalidAction = 'WRAP_UP';
|
|
348
|
+
void invalidAction;
|
|
349
|
+
|
|
350
|
+
// @ts-expect-error postCallEnabled must stay boolean; string values must not satisfy the public flag contract
|
|
351
|
+
const invalidPostCallEnabledString: AISummaryFeatureEnablement = {interactionId: 'interaction-1', postCallEnabled: 'true'};
|
|
352
|
+
void invalidPostCallEnabledString;
|
|
353
|
+
|
|
354
|
+
// @ts-expect-error postCallEnabled must stay boolean; number values must not satisfy the public flag contract
|
|
355
|
+
const invalidPostCallEnabledNumber: AISummaryFeatureEnablement = {interactionId: 'interaction-2', postCallEnabled: 1};
|
|
356
|
+
void invalidPostCallEnabledNumber;
|
|
357
|
+
|
|
358
|
+
// @ts-expect-error midCallEnabled must stay boolean; string values must not satisfy the public flag contract
|
|
359
|
+
const invalidMidCallEnabledString: AISummaryFeatureEnablement = {interactionId: 'interaction-3', midCallEnabled: 'false'};
|
|
360
|
+
void invalidMidCallEnabledString;
|
|
361
|
+
|
|
362
|
+
// @ts-expect-error midCallEnabled must stay boolean; number values must not satisfy the public flag contract
|
|
363
|
+
const invalidMidCallEnabledNumber: AISummaryFeatureEnablement = {interactionId: 'interaction-4', midCallEnabled: 0};
|
|
364
|
+
void invalidMidCallEnabledNumber;
|
|
365
|
+
|
|
366
|
+
// @ts-expect-error invalid summary feedback must not be accepted
|
|
367
|
+
const invalidFeedback: AISummaryResponse = {summary: 'x', feedback: 'ok', state: 'DEFAULT', wrapUpCode: 'wrap', numberOfTimesViewed: 1, numberOfTimesEdited: 0, numberOfTimesCopied: 0};
|
|
368
|
+
void invalidFeedback;
|
|
369
|
+
|
|
370
|
+
// @ts-expect-error summaryReceived must stay boolean when supplied
|
|
371
|
+
const invalidDiscriminator: AISummaryResponse = {summaryReceived: 'true', summary: 'x', feedback: 'none', state: 'DEFAULT', numberOfTimesViewed: 1, numberOfTimesEdited: 0, numberOfTimesCopied: 0};
|
|
372
|
+
void invalidDiscriminator;
|
|
373
|
+
|
|
374
|
+
// @ts-expect-error unknown summary states must not be accepted
|
|
375
|
+
const invalidSummaryState: AISummaryResponse = {summaryReceived: true, summary: 'x', feedback: 'none', state: 'POST_CALL_ONLY', numberOfTimesViewed: 1, numberOfTimesEdited: 0, numberOfTimesCopied: 0};
|
|
376
|
+
void invalidSummaryState;
|
|
377
|
+
|
|
378
|
+
// @ts-expect-error actionTimeStamp is transport metadata generated by the SDK
|
|
379
|
+
const invalidActionTimestamp: AISummaryResponse = {summary: 'x', feedback: 'none', state: 'DEFAULT', numberOfTimesViewed: 1, numberOfTimesEdited: 0, numberOfTimesCopied: 0, actionTimeStamp: 100};
|
|
380
|
+
void invalidActionTimestamp;
|
|
381
|
+
|
|
382
|
+
// @ts-expect-error publishTimestamp is transport metadata generated by the SDK
|
|
383
|
+
const invalidPublishTimestamp: AISummaryResponse = {summary: 'x', feedback: 'none', state: 'DEFAULT', numberOfTimesViewed: 1, numberOfTimesEdited: 0, numberOfTimesCopied: 0, publishTimestamp: 100};
|
|
384
|
+
void invalidPublishTimestamp;
|
|
385
|
+
`;
|
|
386
|
+
|
|
387
|
+
const LEGACY_TASK_CALL_CONTROL_CONTRACT_FIXTURE = `
|
|
388
|
+
import type {
|
|
389
|
+
ITask,
|
|
390
|
+
TaskResponse,
|
|
391
|
+
} from '../../../src';
|
|
392
|
+
|
|
393
|
+
type IsExact<Actual, Expected> =
|
|
394
|
+
(<Value>() => Value extends Actual ? 1 : 2) extends
|
|
395
|
+
(<Value>() => Value extends Expected ? 1 : 2)
|
|
396
|
+
? (<Value>() => Value extends Expected ? 1 : 2) extends
|
|
397
|
+
(<Value>() => Value extends Actual ? 1 : 2)
|
|
398
|
+
? true
|
|
399
|
+
: false
|
|
400
|
+
: false;
|
|
401
|
+
type AssertExact<Actual extends true> = Actual;
|
|
402
|
+
|
|
403
|
+
type ExpectedWrapupParameters = [
|
|
404
|
+
wrapupPayload: {
|
|
405
|
+
wrapUpReason: string;
|
|
406
|
+
auxCodeId: string;
|
|
407
|
+
},
|
|
408
|
+
];
|
|
409
|
+
type ExpectedConsultParameters = [
|
|
410
|
+
consultPayload: {
|
|
411
|
+
to: string | undefined;
|
|
412
|
+
destinationType: string;
|
|
413
|
+
holdParticipants?: boolean;
|
|
414
|
+
},
|
|
415
|
+
];
|
|
416
|
+
type ExpectedTransferParameters = [
|
|
417
|
+
transferPayload: {
|
|
418
|
+
to: string;
|
|
419
|
+
destinationType: string;
|
|
420
|
+
},
|
|
421
|
+
options?: {
|
|
422
|
+
[key: string]: unknown;
|
|
423
|
+
},
|
|
424
|
+
];
|
|
425
|
+
|
|
426
|
+
type WrapupParametersAreRetained = AssertExact<
|
|
427
|
+
IsExact<Parameters<ITask['wrapup']>, ExpectedWrapupParameters>
|
|
428
|
+
>;
|
|
429
|
+
type WrapupReturnIsRetained = AssertExact<
|
|
430
|
+
IsExact<ReturnType<ITask['wrapup']>, Promise<TaskResponse>>
|
|
431
|
+
>;
|
|
432
|
+
type ConsultParametersAreRetained = AssertExact<
|
|
433
|
+
IsExact<Parameters<ITask['consult']>, ExpectedConsultParameters>
|
|
434
|
+
>;
|
|
435
|
+
type ConsultReturnIsRetained = AssertExact<
|
|
436
|
+
IsExact<ReturnType<ITask['consult']>, Promise<TaskResponse>>
|
|
437
|
+
>;
|
|
438
|
+
type TransferParametersAreRetained = AssertExact<
|
|
439
|
+
IsExact<Parameters<ITask['transfer']>, ExpectedTransferParameters>
|
|
440
|
+
>;
|
|
441
|
+
type TransferReturnIsRetained = AssertExact<
|
|
442
|
+
IsExact<ReturnType<ITask['transfer']>, Promise<TaskResponse>>
|
|
443
|
+
>;
|
|
444
|
+
|
|
445
|
+
declare const task: ITask;
|
|
446
|
+
|
|
447
|
+
const wrapupResult: Promise<TaskResponse> = task.wrapup({
|
|
448
|
+
wrapUpReason: 'Customer issue resolved',
|
|
449
|
+
auxCodeId: 'RESOLVED',
|
|
450
|
+
});
|
|
451
|
+
const consultResult: Promise<TaskResponse> = task.consult({
|
|
452
|
+
to: 'agent-1',
|
|
453
|
+
destinationType: 'agent',
|
|
454
|
+
});
|
|
455
|
+
const consultWithOptionalHoldResult: Promise<TaskResponse> = task.consult({
|
|
456
|
+
to: undefined,
|
|
457
|
+
destinationType: 'queue',
|
|
458
|
+
holdParticipants: true,
|
|
459
|
+
});
|
|
460
|
+
const transferResult: Promise<TaskResponse> = task.transfer({
|
|
461
|
+
to: 'queue-1',
|
|
462
|
+
destinationType: 'queue',
|
|
463
|
+
});
|
|
464
|
+
const transferWithOptionsResult: Promise<TaskResponse> = task.transfer(
|
|
465
|
+
{
|
|
466
|
+
to: 'agent-2',
|
|
467
|
+
destinationType: 'agent',
|
|
468
|
+
},
|
|
469
|
+
{trackingId: 'transfer-1'}
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
// @ts-expect-error wrapup must keep requiring its retained payload tuple
|
|
473
|
+
task.wrapup();
|
|
474
|
+
// @ts-expect-error wrapup payload must keep auxCodeId as a required field
|
|
475
|
+
task.wrapup({wrapUpReason: 'Customer issue resolved'});
|
|
476
|
+
// @ts-expect-error wrapup payload must not accept AI summary response fields threaded into the call control API
|
|
477
|
+
task.wrapup({wrapUpReason: 'Customer issue resolved', auxCodeId: 'RESOLVED', actionTimeStamp: 100});
|
|
478
|
+
// @ts-expect-error wrapup payload must not accept post-call summary state as a control argument
|
|
479
|
+
task.wrapup({wrapUpReason: 'Customer issue resolved', auxCodeId: 'RESOLVED', state: 'DEFAULT'});
|
|
480
|
+
|
|
481
|
+
// @ts-expect-error consult must keep requiring its retained payload tuple
|
|
482
|
+
task.consult();
|
|
483
|
+
// @ts-expect-error consult payload must keep destinationType as a string
|
|
484
|
+
task.consult({to: 'agent-1', destinationType: 1});
|
|
485
|
+
// @ts-expect-error consult payload must not accept mid-call summary action fields threaded into the payload
|
|
486
|
+
task.consult({to: 'agent-1', destinationType: 'agent', actionType: 'CONSULT'});
|
|
487
|
+
// @ts-expect-error consult must not accept a feature action as a second argument
|
|
488
|
+
task.consult({to: 'agent-1', destinationType: 'agent'}, 'CONSULT');
|
|
489
|
+
|
|
490
|
+
// @ts-expect-error transfer must keep requiring its retained payload tuple
|
|
491
|
+
task.transfer();
|
|
492
|
+
// @ts-expect-error transfer payload must keep to as a required string field
|
|
493
|
+
task.transfer({destinationType: 'queue'});
|
|
494
|
+
// @ts-expect-error transfer payload must keep destinationType as a string
|
|
495
|
+
task.transfer({to: 'queue-1', destinationType: 1});
|
|
496
|
+
// @ts-expect-error transfer payload must not accept mid-call summary action fields threaded into the payload
|
|
497
|
+
task.transfer({to: 'agent-2', destinationType: 'agent', actionType: 'TRANSFER'});
|
|
498
|
+
// @ts-expect-error transfer options must not be replaced by a feature action argument
|
|
499
|
+
task.transfer({to: 'agent-2', destinationType: 'agent'}, 'TRANSFER');
|
|
500
|
+
// @ts-expect-error transfer must not accept a third feature action argument
|
|
501
|
+
task.transfer({to: 'agent-2', destinationType: 'agent'}, {trackingId: 'transfer-2'}, 'TRANSFER');
|
|
502
|
+
|
|
503
|
+
void wrapupResult;
|
|
504
|
+
void consultResult;
|
|
505
|
+
void consultWithOptionalHoldResult;
|
|
506
|
+
void transferResult;
|
|
507
|
+
void transferWithOptionsResult;
|
|
508
|
+
`;
|
|
509
|
+
|
|
510
|
+
const createProgram = (virtualSources: Record<string, string> = {}) => {
|
|
511
|
+
const configPath = path.join(CONTACT_CENTER_ROOT, 'tsconfig.json');
|
|
512
|
+
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
513
|
+
const parsed = ts.parseJsonConfigFileContent(
|
|
514
|
+
configFile.config,
|
|
515
|
+
ts.sys,
|
|
516
|
+
CONTACT_CENTER_ROOT,
|
|
517
|
+
{emitDeclarationOnly: false, noEmit: true},
|
|
518
|
+
configPath
|
|
519
|
+
);
|
|
520
|
+
const virtualFileNames = Object.keys(virtualSources);
|
|
521
|
+
const host = ts.createCompilerHost(parsed.options, true);
|
|
522
|
+
const originalGetSourceFile = host.getSourceFile.bind(host);
|
|
523
|
+
const originalFileExists = host.fileExists.bind(host);
|
|
524
|
+
const originalReadFile = host.readFile.bind(host);
|
|
525
|
+
|
|
526
|
+
host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
|
527
|
+
const normalizedFileName = path.normalize(fileName);
|
|
528
|
+
if (Object.prototype.hasOwnProperty.call(virtualSources, normalizedFileName)) {
|
|
529
|
+
return ts.createSourceFile(
|
|
530
|
+
normalizedFileName,
|
|
531
|
+
virtualSources[normalizedFileName],
|
|
532
|
+
languageVersion,
|
|
533
|
+
true
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return originalGetSourceFile(
|
|
538
|
+
fileName,
|
|
539
|
+
languageVersion,
|
|
540
|
+
onError,
|
|
541
|
+
shouldCreateNewSourceFile
|
|
542
|
+
);
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
host.fileExists = (fileName) => {
|
|
546
|
+
const normalizedFileName = path.normalize(fileName);
|
|
547
|
+
return Object.prototype.hasOwnProperty.call(virtualSources, normalizedFileName)
|
|
548
|
+
? true
|
|
549
|
+
: originalFileExists(fileName);
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
host.readFile = (fileName) => {
|
|
553
|
+
const normalizedFileName = path.normalize(fileName);
|
|
554
|
+
return Object.prototype.hasOwnProperty.call(virtualSources, normalizedFileName)
|
|
555
|
+
? virtualSources[normalizedFileName]
|
|
556
|
+
: originalReadFile(fileName);
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
return ts.createProgram([...parsed.fileNames, ...virtualFileNames], parsed.options, host);
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
const getRootModuleSymbol = (program: ts.Program) => {
|
|
563
|
+
const checker = program.getTypeChecker();
|
|
564
|
+
const indexSource = program.getSourceFile(INDEX_PATH);
|
|
565
|
+
if (!indexSource) {
|
|
566
|
+
throw new Error(`Missing source file ${INDEX_PATH}`);
|
|
567
|
+
}
|
|
568
|
+
const moduleSymbol = checker.getSymbolAtLocation(indexSource);
|
|
569
|
+
if (!moduleSymbol) {
|
|
570
|
+
throw new Error('Unable to resolve contact-center root module symbol');
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return moduleSymbol;
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
const getRootExportNames = (program: ts.Program) => {
|
|
577
|
+
const checker = program.getTypeChecker();
|
|
578
|
+
|
|
579
|
+
return checker
|
|
580
|
+
.getExportsOfModule(getRootModuleSymbol(program))
|
|
581
|
+
.map((symbol) => symbol.getName())
|
|
582
|
+
.sort();
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
const getRootExportSymbol = (program: ts.Program, exportName: string) => {
|
|
586
|
+
return getModuleExportSymbol(program, INDEX_PATH, exportName);
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
const getModuleExportSymbol = (
|
|
590
|
+
program: ts.Program,
|
|
591
|
+
modulePath: string,
|
|
592
|
+
exportName: string
|
|
593
|
+
) => {
|
|
594
|
+
const checker = program.getTypeChecker();
|
|
595
|
+
const sourceFile = program.getSourceFile(modulePath);
|
|
596
|
+
if (!sourceFile) {
|
|
597
|
+
throw new Error(`Missing source file ${modulePath}`);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
|
|
601
|
+
if (!moduleSymbol) {
|
|
602
|
+
throw new Error(`Unable to resolve module symbol ${modulePath}`);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const exportSymbol = checker
|
|
606
|
+
.getExportsOfModule(moduleSymbol)
|
|
607
|
+
.find((symbol) => symbol.getName() === exportName);
|
|
608
|
+
|
|
609
|
+
if (!exportSymbol) {
|
|
610
|
+
throw new Error(`Missing root export ${exportName}`);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return resolveSymbol(checker, exportSymbol);
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
const getDiagnosticsForFiles = (program: ts.Program, fileNames: string[]) =>
|
|
617
|
+
ts.getPreEmitDiagnostics(program).filter((diagnostic) => {
|
|
618
|
+
const fileName = diagnostic.file?.fileName;
|
|
619
|
+
|
|
620
|
+
return !!fileName && fileNames.includes(fileName);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
const getMissingExportNameFromDiagnostic = (diagnostic: ts.Diagnostic) => {
|
|
624
|
+
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
|
625
|
+
const match = message.match(/has no exported member(?: named)? '([^']+)'/);
|
|
626
|
+
|
|
627
|
+
if (!match) {
|
|
628
|
+
throw new Error(`Unexpected TS${diagnostic.code} diagnostic: ${message}`);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return match[1];
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
const getCanonicalMissingExportDiagnosticCode = (diagnostic: ts.Diagnostic) => {
|
|
635
|
+
if (
|
|
636
|
+
MISSING_EXPORT_DIAGNOSTIC_CODES.includes(diagnostic.code) &&
|
|
637
|
+
getMissingExportNameFromDiagnostic(diagnostic)
|
|
638
|
+
) {
|
|
639
|
+
return 2305;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return diagnostic.code;
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
const getObjectLiteralInitializer = (symbol: ts.Symbol | undefined, symbolName: string) => {
|
|
646
|
+
const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
|
|
647
|
+
|
|
648
|
+
if (!declaration || !ts.isVariableDeclaration(declaration)) {
|
|
649
|
+
throw new Error(`Root export ${symbolName} does not resolve to a variable declaration`);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
let initializer = declaration.initializer;
|
|
653
|
+
if (!initializer) {
|
|
654
|
+
throw new Error(`Root export ${symbolName} is missing an initializer`);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
while (ts.isAsExpression(initializer)) {
|
|
658
|
+
initializer = initializer.expression;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (!ts.isObjectLiteralExpression(initializer)) {
|
|
662
|
+
throw new Error(`Root export ${symbolName} does not resolve to an object literal`);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return initializer;
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
const getStringLiteralObjectEntries = (objectLiteral: ts.ObjectLiteralExpression) =>
|
|
669
|
+
objectLiteral.properties.map((property) => {
|
|
670
|
+
if (
|
|
671
|
+
!ts.isPropertyAssignment(property) ||
|
|
672
|
+
!ts.isIdentifier(property.name) ||
|
|
673
|
+
!ts.isStringLiteral(property.initializer)
|
|
674
|
+
) {
|
|
675
|
+
throw new Error('Expected string-literal object property assignment');
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
return [property.name.text, property.initializer.text];
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
const getNamedStringLiteralObjectEntries = (
|
|
682
|
+
objectLiteral: ts.ObjectLiteralExpression,
|
|
683
|
+
propertyNames: string[],
|
|
684
|
+
symbolName: string
|
|
685
|
+
) =>
|
|
686
|
+
propertyNames.map((propertyName) => {
|
|
687
|
+
const property = objectLiteral.properties.find(
|
|
688
|
+
(candidate): candidate is ts.PropertyAssignment =>
|
|
689
|
+
ts.isPropertyAssignment(candidate) &&
|
|
690
|
+
ts.isIdentifier(candidate.name) &&
|
|
691
|
+
candidate.name.text === propertyName
|
|
692
|
+
);
|
|
693
|
+
|
|
694
|
+
if (!property) {
|
|
695
|
+
throw new Error(`Missing ${symbolName}.${propertyName}`);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (!ts.isStringLiteral(property.initializer)) {
|
|
699
|
+
throw new Error(`Expected ${symbolName}.${propertyName} to be a string literal`);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return [propertyName, property.initializer.text];
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
const getObjectLiteralVariableInitializer = (
|
|
706
|
+
sourceFile: ts.SourceFile,
|
|
707
|
+
variableName: string
|
|
708
|
+
) => {
|
|
709
|
+
let objectLiteral: ts.ObjectLiteralExpression | undefined;
|
|
710
|
+
|
|
711
|
+
const visit = (node: ts.Node) => {
|
|
712
|
+
if (
|
|
713
|
+
ts.isVariableDeclaration(node) &&
|
|
714
|
+
ts.isIdentifier(node.name) &&
|
|
715
|
+
node.name.text === variableName &&
|
|
716
|
+
node.initializer
|
|
717
|
+
) {
|
|
718
|
+
const initializer = ts.isAsExpression(node.initializer)
|
|
719
|
+
? node.initializer.expression
|
|
720
|
+
: node.initializer;
|
|
721
|
+
|
|
722
|
+
if (!ts.isObjectLiteralExpression(initializer)) {
|
|
723
|
+
throw new Error(`${variableName} does not resolve to an object literal`);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
objectLiteral = initializer;
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
ts.forEachChild(node, visit);
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
visit(sourceFile);
|
|
734
|
+
|
|
735
|
+
if (!objectLiteral) {
|
|
736
|
+
throw new Error(`Missing ${variableName} object literal`);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
return objectLiteral;
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
const getObjectLiteralPropertyAssignment = (
|
|
743
|
+
objectLiteral: ts.ObjectLiteralExpression,
|
|
744
|
+
propertyName: string,
|
|
745
|
+
symbolName: string
|
|
746
|
+
) => {
|
|
747
|
+
const property = objectLiteral.properties.find(
|
|
748
|
+
(candidate): candidate is ts.PropertyAssignment =>
|
|
749
|
+
ts.isPropertyAssignment(candidate) &&
|
|
750
|
+
ts.isIdentifier(candidate.name) &&
|
|
751
|
+
candidate.name.text === propertyName
|
|
752
|
+
);
|
|
753
|
+
|
|
754
|
+
if (!property) {
|
|
755
|
+
throw new Error(`Missing ${symbolName}.${propertyName}`);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return property;
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
const entriesToObject = (entries: string[][]) =>
|
|
762
|
+
entries.reduce<Record<string, string>>((accumulator, [name, value]) => {
|
|
763
|
+
accumulator[name] = value;
|
|
764
|
+
|
|
765
|
+
return accumulator;
|
|
766
|
+
}, {});
|
|
767
|
+
|
|
768
|
+
const resolveSymbol = (checker: ts.TypeChecker, symbol: ts.Symbol | undefined) => {
|
|
769
|
+
if (!symbol) return undefined;
|
|
770
|
+
|
|
771
|
+
return symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
describe('contact-center root public contract', () => {
|
|
775
|
+
it('preserves the literal root barrel export list and omits package internals', () => {
|
|
776
|
+
const program = createProgram();
|
|
777
|
+
const exportNames = getRootExportNames(program);
|
|
778
|
+
|
|
779
|
+
expect(exportNames).toEqual([...EXPECTED_ROOT_EXPORTS].sort());
|
|
780
|
+
INTERNAL_ROOT_EXPORTS.forEach((name) => {
|
|
781
|
+
expect(exportNames).not.toContain(name);
|
|
782
|
+
});
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
it('rejects root imports of package internals with TS2305 diagnostics', () => {
|
|
786
|
+
const internalFixture = path.join(
|
|
787
|
+
CONTACT_CENTER_ROOT,
|
|
788
|
+
'test/unit/spec/__root_internals.ts'
|
|
789
|
+
);
|
|
790
|
+
const program = createProgram({
|
|
791
|
+
[internalFixture]: createInternalRootImportFixture(),
|
|
792
|
+
});
|
|
793
|
+
const diagnostics = getDiagnosticsForFiles(program, [internalFixture]);
|
|
794
|
+
|
|
795
|
+
expect(diagnostics.map(getCanonicalMissingExportDiagnosticCode)).toEqual(
|
|
796
|
+
INTERNAL_ROOT_EXPORTS.map(() => 2305)
|
|
797
|
+
);
|
|
798
|
+
expect(diagnostics.map(getMissingExportNameFromDiagnostic).sort()).toEqual(
|
|
799
|
+
[...INTERNAL_ROOT_EXPORTS].sort()
|
|
800
|
+
);
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
it('re-exports the exact AI summary error code object from the root symbol', () => {
|
|
804
|
+
const program = createProgram();
|
|
805
|
+
const errorCodeEntries = getStringLiteralObjectEntries(
|
|
806
|
+
getObjectLiteralInitializer(
|
|
807
|
+
getRootExportSymbol(program, 'AI_SUMMARY_ERROR_CODES'),
|
|
808
|
+
'AI_SUMMARY_ERROR_CODES'
|
|
809
|
+
)
|
|
810
|
+
);
|
|
811
|
+
|
|
812
|
+
expect(errorCodeEntries).toEqual(EXPECTED_AI_SUMMARY_ERROR_CODES);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
it('pins AI summary wire event literals to the backend contract', () => {
|
|
816
|
+
const program = createProgram();
|
|
817
|
+
const aiAssistantEventNameLiterals = entriesToObject(
|
|
818
|
+
getNamedStringLiteralObjectEntries(
|
|
819
|
+
getObjectLiteralInitializer(
|
|
820
|
+
getModuleExportSymbol(program, TYPES_PATH, 'AIAssistantEventName'),
|
|
821
|
+
'AIAssistantEventName'
|
|
822
|
+
),
|
|
823
|
+
Object.keys(EXPECTED_AI_SUMMARY_WIRE_LITERALS.AIAssistantEventName),
|
|
824
|
+
'AIAssistantEventName'
|
|
825
|
+
)
|
|
826
|
+
);
|
|
827
|
+
const ccAISummaryEventLiterals = entriesToObject(
|
|
828
|
+
getNamedStringLiteralObjectEntries(
|
|
829
|
+
getObjectLiteralInitializer(
|
|
830
|
+
getRootExportSymbol(program, 'CC_TASK_EVENTS'),
|
|
831
|
+
'CC_TASK_EVENTS'
|
|
832
|
+
),
|
|
833
|
+
Object.keys(EXPECTED_AI_SUMMARY_WIRE_LITERALS.CC_TASK_EVENTS),
|
|
834
|
+
'CC_TASK_EVENTS'
|
|
835
|
+
)
|
|
836
|
+
);
|
|
837
|
+
|
|
838
|
+
expect({
|
|
839
|
+
AIAssistantEventName: aiAssistantEventNameLiterals,
|
|
840
|
+
CC_TASK_EVENTS: ccAISummaryEventLiterals,
|
|
841
|
+
}).toEqual(EXPECTED_AI_SUMMARY_WIRE_LITERALS);
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
it('type-checks public AI summary assignments and keeps invalid branches narrow', () => {
|
|
845
|
+
const validFixture = path.join(CONTACT_CENTER_ROOT, 'test/unit/spec/__ai_summary_valid.ts');
|
|
846
|
+
const invalidFixture = path.join(
|
|
847
|
+
CONTACT_CENTER_ROOT,
|
|
848
|
+
'test/unit/spec/__ai_summary_invalid.ts'
|
|
849
|
+
);
|
|
850
|
+
const program = createProgram({
|
|
851
|
+
[validFixture]: VALID_PUBLIC_CONTRACT_FIXTURE,
|
|
852
|
+
[invalidFixture]: INVALID_PUBLIC_CONTRACT_FIXTURE,
|
|
853
|
+
});
|
|
854
|
+
const fixtureDiagnostics = getDiagnosticsForFiles(program, [validFixture, invalidFixture]);
|
|
855
|
+
|
|
856
|
+
expect(
|
|
857
|
+
fixtureDiagnostics.map((diagnostic) => ({
|
|
858
|
+
code: diagnostic.code,
|
|
859
|
+
message: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
|
|
860
|
+
}))
|
|
861
|
+
).toEqual([]);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
it('pins retained ITask wrapup, consult, and transfer signatures', () => {
|
|
865
|
+
const legacyTaskContractFixture = path.join(
|
|
866
|
+
CONTACT_CENTER_ROOT,
|
|
867
|
+
'test/unit/spec/__legacy_task_call_control_contract.ts'
|
|
868
|
+
);
|
|
869
|
+
const program = createProgram({
|
|
870
|
+
[legacyTaskContractFixture]: LEGACY_TASK_CALL_CONTROL_CONTRACT_FIXTURE,
|
|
871
|
+
});
|
|
872
|
+
const fixtureDiagnostics = getDiagnosticsForFiles(program, [legacyTaskContractFixture]);
|
|
873
|
+
|
|
874
|
+
expect(
|
|
875
|
+
fixtureDiagnostics.map((diagnostic) => ({
|
|
876
|
+
code: diagnostic.code,
|
|
877
|
+
message: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
|
|
878
|
+
}))
|
|
879
|
+
).toEqual([]);
|
|
880
|
+
});
|
|
881
|
+
});
|