@app-connect/core 1.7.8 → 1.7.11
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/connector/developerPortal.js +43 -0
- package/connector/proxy/index.js +10 -3
- package/connector/registry.js +8 -6
- package/handlers/admin.js +44 -21
- package/handlers/auth.js +97 -69
- package/handlers/calldown.js +10 -4
- package/handlers/contact.js +45 -112
- package/handlers/disposition.js +4 -142
- package/handlers/log.js +174 -259
- package/handlers/user.js +19 -6
- package/index.js +310 -122
- package/lib/analytics.js +3 -1
- package/lib/authSession.js +68 -0
- package/lib/callLogComposer.js +498 -420
- package/lib/errorHandler.js +206 -0
- package/lib/jwt.js +2 -0
- package/lib/logger.js +190 -0
- package/lib/oauth.js +21 -12
- package/lib/ringcentral.js +2 -10
- package/lib/sharedSMSComposer.js +471 -0
- package/mcp/SupportedPlatforms.md +12 -0
- package/mcp/lib/validator.js +91 -0
- package/mcp/mcpHandler.js +166 -0
- package/mcp/tools/checkAuthStatus.js +90 -0
- package/mcp/tools/collectAuthInfo.js +86 -0
- package/mcp/tools/createCallLog.js +299 -0
- package/mcp/tools/createMessageLog.js +283 -0
- package/mcp/tools/doAuth.js +185 -0
- package/mcp/tools/findContactByName.js +87 -0
- package/mcp/tools/findContactByPhone.js +96 -0
- package/mcp/tools/getCallLog.js +98 -0
- package/mcp/tools/getHelp.js +39 -0
- package/mcp/tools/getPublicConnectors.js +46 -0
- package/mcp/tools/index.js +58 -0
- package/mcp/tools/logout.js +63 -0
- package/mcp/tools/rcGetCallLogs.js +73 -0
- package/mcp/tools/setConnector.js +64 -0
- package/mcp/tools/updateCallLog.js +122 -0
- package/models/accountDataModel.js +34 -0
- package/models/cacheModel.js +3 -0
- package/package.json +6 -4
- package/releaseNotes.json +36 -0
- package/test/connector/registry.test.js +145 -0
- package/test/handlers/admin.test.js +583 -0
- package/test/handlers/auth.test.js +355 -0
- package/test/handlers/contact.test.js +852 -0
- package/test/handlers/log.test.js +872 -0
- package/test/lib/callLogComposer.test.js +1231 -0
- package/test/lib/debugTracer.test.js +328 -0
- package/test/lib/logger.test.js +206 -0
- package/test/lib/oauth.test.js +359 -0
- package/test/lib/ringcentral.test.js +473 -0
- package/test/lib/sharedSMSComposer.test.js +1084 -0
- package/test/lib/util.test.js +282 -0
- package/test/mcp/tools/collectAuthInfo.test.js +192 -0
- package/test/mcp/tools/createCallLog.test.js +412 -0
- package/test/mcp/tools/createMessageLog.test.js +580 -0
- package/test/mcp/tools/doAuth.test.js +363 -0
- package/test/mcp/tools/findContactByName.test.js +263 -0
- package/test/mcp/tools/findContactByPhone.test.js +284 -0
- package/test/mcp/tools/getCallLog.test.js +286 -0
- package/test/mcp/tools/getPublicConnectors.test.js +128 -0
- package/test/mcp/tools/logout.test.js +169 -0
- package/test/mcp/tools/setConnector.test.js +177 -0
- package/test/mcp/tools/updateCallLog.test.js +346 -0
- package/test/models/accountDataModel.test.js +98 -0
- package/test/models/dynamo/connectorSchema.test.js +189 -0
- package/test/models/models.test.js +539 -0
- package/test/setup.js +176 -176
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
const createCallLog = require('../../../mcp/tools/createCallLog');
|
|
2
|
+
const jwt = require('../../../lib/jwt');
|
|
3
|
+
const connectorRegistry = require('../../../connector/registry');
|
|
4
|
+
const logCore = require('../../../handlers/log');
|
|
5
|
+
const util = require('../../../lib/util');
|
|
6
|
+
const { CallLogModel } = require('../../../models/callLogModel');
|
|
7
|
+
|
|
8
|
+
// Mock dependencies
|
|
9
|
+
jest.mock('../../../lib/jwt');
|
|
10
|
+
jest.mock('../../../connector/registry');
|
|
11
|
+
jest.mock('../../../handlers/log');
|
|
12
|
+
jest.mock('../../../lib/util');
|
|
13
|
+
jest.mock('../../../models/callLogModel');
|
|
14
|
+
|
|
15
|
+
describe('MCP Tool: createCallLog', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
process.env.HASH_KEY = 'test-hash-key';
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('tool definition', () => {
|
|
22
|
+
test('should have correct tool definition', () => {
|
|
23
|
+
expect(createCallLog.definition).toBeDefined();
|
|
24
|
+
expect(createCallLog.definition.name).toBe('createCallLog');
|
|
25
|
+
expect(createCallLog.definition.description).toContain('REQUIRES AUTHENTICATION');
|
|
26
|
+
expect(createCallLog.definition.inputSchema).toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('should require jwtToken and incomingData parameters', () => {
|
|
30
|
+
expect(createCallLog.definition.inputSchema.required).toContain('jwtToken');
|
|
31
|
+
expect(createCallLog.definition.inputSchema.required).toContain('incomingData');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('should have detailed inputSchema for incomingData', () => {
|
|
35
|
+
const incomingDataSchema = createCallLog.definition.inputSchema.properties.incomingData;
|
|
36
|
+
expect(incomingDataSchema.properties).toHaveProperty('logInfo');
|
|
37
|
+
expect(incomingDataSchema.properties).toHaveProperty('contactId');
|
|
38
|
+
expect(incomingDataSchema.properties).toHaveProperty('note');
|
|
39
|
+
expect(incomingDataSchema.required).toContain('logInfo');
|
|
40
|
+
expect(incomingDataSchema.required).toContain('contactId');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('execute', () => {
|
|
45
|
+
test('should create call log successfully', async () => {
|
|
46
|
+
// Arrange
|
|
47
|
+
const mockIncomingData = {
|
|
48
|
+
logInfo: {
|
|
49
|
+
id: 'rc-call-123',
|
|
50
|
+
sessionId: 'session-123',
|
|
51
|
+
direction: 'Inbound',
|
|
52
|
+
startTime: '2024-01-01T10:00:00Z',
|
|
53
|
+
duration: 120,
|
|
54
|
+
from: { phoneNumber: '+1234567890', name: 'John Doe' },
|
|
55
|
+
to: { phoneNumber: '+0987654321', name: 'Company' },
|
|
56
|
+
accountId: 'rc-account-123'
|
|
57
|
+
},
|
|
58
|
+
contactId: 'contact-123',
|
|
59
|
+
contactName: 'John Doe',
|
|
60
|
+
contactType: 'Contact',
|
|
61
|
+
note: 'Test call note'
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
jwt.decodeJwt.mockReturnValue({
|
|
65
|
+
id: 'user-123',
|
|
66
|
+
platform: 'testCRM'
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const mockConnector = {
|
|
70
|
+
createCallLog: jest.fn()
|
|
71
|
+
};
|
|
72
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
73
|
+
|
|
74
|
+
CallLogModel.findOne.mockResolvedValue(null); // No existing log
|
|
75
|
+
|
|
76
|
+
util.getHashValue.mockReturnValue('hashed-account-id');
|
|
77
|
+
|
|
78
|
+
logCore.createCallLog.mockResolvedValue({
|
|
79
|
+
successful: true,
|
|
80
|
+
logId: 'crm-log-123',
|
|
81
|
+
returnMessage: { message: 'Call logged successfully' }
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Act
|
|
85
|
+
const result = await createCallLog.execute({
|
|
86
|
+
jwtToken: 'mock-jwt-token',
|
|
87
|
+
incomingData: mockIncomingData
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Assert
|
|
91
|
+
expect(result).toEqual({
|
|
92
|
+
success: true,
|
|
93
|
+
data: {
|
|
94
|
+
logId: 'crm-log-123',
|
|
95
|
+
message: 'Call logged successfully'
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
expect(jwt.decodeJwt).toHaveBeenCalledWith('mock-jwt-token');
|
|
99
|
+
expect(CallLogModel.findOne).toHaveBeenCalledWith({
|
|
100
|
+
where: { sessionId: 'session-123' }
|
|
101
|
+
});
|
|
102
|
+
expect(logCore.createCallLog).toHaveBeenCalledWith({
|
|
103
|
+
platform: 'testCRM',
|
|
104
|
+
userId: 'user-123',
|
|
105
|
+
incomingData: mockIncomingData,
|
|
106
|
+
hashedAccountId: 'hashed-account-id',
|
|
107
|
+
isFromSSCL: false
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('should create call log with AI note and transcript', async () => {
|
|
112
|
+
// Arrange
|
|
113
|
+
const mockIncomingData = {
|
|
114
|
+
logInfo: {
|
|
115
|
+
id: 'rc-call-456',
|
|
116
|
+
sessionId: 'session-456',
|
|
117
|
+
direction: 'Outbound',
|
|
118
|
+
startTime: '2024-01-01T11:00:00Z',
|
|
119
|
+
duration: 300,
|
|
120
|
+
from: { phoneNumber: '+0987654321' },
|
|
121
|
+
to: { phoneNumber: '+1234567890' }
|
|
122
|
+
},
|
|
123
|
+
contactId: 'contact-456',
|
|
124
|
+
aiNote: 'AI generated summary of the call',
|
|
125
|
+
transcript: 'Full call transcript text'
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
jwt.decodeJwt.mockReturnValue({
|
|
129
|
+
id: 'user-123',
|
|
130
|
+
platform: 'testCRM'
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const mockConnector = {
|
|
134
|
+
createCallLog: jest.fn()
|
|
135
|
+
};
|
|
136
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
137
|
+
|
|
138
|
+
CallLogModel.findOne.mockResolvedValue(null);
|
|
139
|
+
|
|
140
|
+
logCore.createCallLog.mockResolvedValue({
|
|
141
|
+
successful: true,
|
|
142
|
+
logId: 'crm-log-456'
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Act
|
|
146
|
+
const result = await createCallLog.execute({
|
|
147
|
+
jwtToken: 'mock-jwt-token',
|
|
148
|
+
incomingData: mockIncomingData
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Assert
|
|
152
|
+
expect(result.success).toBe(true);
|
|
153
|
+
expect(result.data.logId).toBe('crm-log-456');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('should create call log with additional submission', async () => {
|
|
157
|
+
// Arrange
|
|
158
|
+
const mockIncomingData = {
|
|
159
|
+
logInfo: {
|
|
160
|
+
id: 'rc-call-789',
|
|
161
|
+
sessionId: 'session-789',
|
|
162
|
+
direction: 'Inbound',
|
|
163
|
+
startTime: '2024-01-01T12:00:00Z',
|
|
164
|
+
duration: 60,
|
|
165
|
+
from: { phoneNumber: '+1234567890' },
|
|
166
|
+
to: { phoneNumber: '+0987654321' }
|
|
167
|
+
},
|
|
168
|
+
contactId: 'contact-789',
|
|
169
|
+
additionalSubmission: {
|
|
170
|
+
isAssignedToUser: true,
|
|
171
|
+
adminAssignedUserToken: 'admin-jwt-token',
|
|
172
|
+
adminAssignedUserRcId: 'rc-ext-101'
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
jwt.decodeJwt.mockReturnValue({
|
|
177
|
+
id: 'user-123',
|
|
178
|
+
platform: 'testCRM'
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const mockConnector = {
|
|
182
|
+
createCallLog: jest.fn()
|
|
183
|
+
};
|
|
184
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
185
|
+
|
|
186
|
+
CallLogModel.findOne.mockResolvedValue(null);
|
|
187
|
+
|
|
188
|
+
logCore.createCallLog.mockResolvedValue({
|
|
189
|
+
successful: true,
|
|
190
|
+
logId: 'crm-log-789'
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Act
|
|
194
|
+
const result = await createCallLog.execute({
|
|
195
|
+
jwtToken: 'mock-jwt-token',
|
|
196
|
+
incomingData: mockIncomingData
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Assert
|
|
200
|
+
expect(result.success).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('should return error when call log already exists', async () => {
|
|
204
|
+
// Arrange
|
|
205
|
+
const mockIncomingData = {
|
|
206
|
+
logInfo: {
|
|
207
|
+
sessionId: 'existing-session'
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
CallLogModel.findOne.mockResolvedValue({
|
|
212
|
+
id: 'existing-log',
|
|
213
|
+
sessionId: 'existing-session'
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Act
|
|
217
|
+
const result = await createCallLog.execute({
|
|
218
|
+
jwtToken: 'mock-jwt-token',
|
|
219
|
+
incomingData: mockIncomingData
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Assert
|
|
223
|
+
expect(result.success).toBe(false);
|
|
224
|
+
expect(result.error).toContain('already exists');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('should return error when jwtToken is missing', async () => {
|
|
228
|
+
// Act
|
|
229
|
+
const result = await createCallLog.execute({
|
|
230
|
+
incomingData: { logInfo: {} }
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Assert
|
|
234
|
+
expect(result.success).toBe(false);
|
|
235
|
+
expect(result.error).toContain('authorize CRM platform');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('should return error when incomingData is missing', async () => {
|
|
239
|
+
// Act
|
|
240
|
+
const result = await createCallLog.execute({
|
|
241
|
+
jwtToken: 'mock-jwt-token'
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Assert
|
|
245
|
+
expect(result.success).toBe(false);
|
|
246
|
+
expect(result.error).toContain('Incoming data must be provided');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('should return error when logInfo is missing', async () => {
|
|
250
|
+
// Act
|
|
251
|
+
const result = await createCallLog.execute({
|
|
252
|
+
jwtToken: 'mock-jwt-token',
|
|
253
|
+
incomingData: { contactId: 'contact-123' }
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Assert
|
|
257
|
+
expect(result.success).toBe(false);
|
|
258
|
+
expect(result.error).toContain('logInfo is required');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('should return error when JWT is invalid', async () => {
|
|
262
|
+
// Arrange
|
|
263
|
+
const mockIncomingData = {
|
|
264
|
+
logInfo: {
|
|
265
|
+
sessionId: 'session-123'
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
CallLogModel.findOne.mockResolvedValue(null);
|
|
270
|
+
|
|
271
|
+
jwt.decodeJwt.mockReturnValue({
|
|
272
|
+
platform: 'testCRM'
|
|
273
|
+
// id is missing
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Act
|
|
277
|
+
const result = await createCallLog.execute({
|
|
278
|
+
jwtToken: 'invalid-token',
|
|
279
|
+
incomingData: mockIncomingData
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Assert
|
|
283
|
+
expect(result.success).toBe(false);
|
|
284
|
+
expect(result.error).toContain('Invalid JWT token');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test('should return error when platform connector not found', async () => {
|
|
288
|
+
// Arrange
|
|
289
|
+
const mockIncomingData = {
|
|
290
|
+
logInfo: {
|
|
291
|
+
sessionId: 'session-123'
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
CallLogModel.findOne.mockResolvedValue(null);
|
|
296
|
+
|
|
297
|
+
jwt.decodeJwt.mockReturnValue({
|
|
298
|
+
id: 'user-123',
|
|
299
|
+
platform: 'unknownCRM'
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
connectorRegistry.getConnector.mockReturnValue(null);
|
|
303
|
+
|
|
304
|
+
// Act
|
|
305
|
+
const result = await createCallLog.execute({
|
|
306
|
+
jwtToken: 'mock-jwt-token',
|
|
307
|
+
incomingData: mockIncomingData
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// Assert
|
|
311
|
+
expect(result.success).toBe(false);
|
|
312
|
+
expect(result.error).toContain('Platform connector not found');
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test('should return error when createCallLog is not implemented', async () => {
|
|
316
|
+
// Arrange
|
|
317
|
+
const mockIncomingData = {
|
|
318
|
+
logInfo: {
|
|
319
|
+
sessionId: 'session-123'
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
CallLogModel.findOne.mockResolvedValue(null);
|
|
324
|
+
|
|
325
|
+
jwt.decodeJwt.mockReturnValue({
|
|
326
|
+
id: 'user-123',
|
|
327
|
+
platform: 'testCRM'
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const mockConnector = {}; // No createCallLog method
|
|
331
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
332
|
+
|
|
333
|
+
// Act
|
|
334
|
+
const result = await createCallLog.execute({
|
|
335
|
+
jwtToken: 'mock-jwt-token',
|
|
336
|
+
incomingData: mockIncomingData
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Assert
|
|
340
|
+
expect(result.success).toBe(false);
|
|
341
|
+
expect(result.error).toContain('not implemented');
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test('should return error when creation fails', async () => {
|
|
345
|
+
// Arrange
|
|
346
|
+
const mockIncomingData = {
|
|
347
|
+
logInfo: {
|
|
348
|
+
id: 'rc-call-999',
|
|
349
|
+
sessionId: 'session-999',
|
|
350
|
+
direction: 'Inbound',
|
|
351
|
+
startTime: '2024-01-01T13:00:00Z',
|
|
352
|
+
duration: 45,
|
|
353
|
+
from: { phoneNumber: '+1234567890' },
|
|
354
|
+
to: { phoneNumber: '+0987654321' }
|
|
355
|
+
},
|
|
356
|
+
contactId: 'contact-999'
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
jwt.decodeJwt.mockReturnValue({
|
|
360
|
+
id: 'user-123',
|
|
361
|
+
platform: 'testCRM'
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
const mockConnector = {
|
|
365
|
+
createCallLog: jest.fn()
|
|
366
|
+
};
|
|
367
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
368
|
+
|
|
369
|
+
CallLogModel.findOne.mockResolvedValue(null);
|
|
370
|
+
|
|
371
|
+
logCore.createCallLog.mockResolvedValue({
|
|
372
|
+
successful: false,
|
|
373
|
+
returnMessage: { message: 'Failed to create log in CRM' }
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// Act
|
|
377
|
+
const result = await createCallLog.execute({
|
|
378
|
+
jwtToken: 'mock-jwt-token',
|
|
379
|
+
incomingData: mockIncomingData
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// Assert
|
|
383
|
+
expect(result.success).toBe(false);
|
|
384
|
+
expect(result.error).toBe('Failed to create log in CRM');
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test('should handle unexpected errors gracefully', async () => {
|
|
388
|
+
// Arrange
|
|
389
|
+
const mockIncomingData = {
|
|
390
|
+
logInfo: {
|
|
391
|
+
sessionId: 'session-error'
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
CallLogModel.findOne.mockRejectedValue(
|
|
396
|
+
new Error('Database connection failed')
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
// Act
|
|
400
|
+
const result = await createCallLog.execute({
|
|
401
|
+
jwtToken: 'mock-jwt-token',
|
|
402
|
+
incomingData: mockIncomingData
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// Assert
|
|
406
|
+
expect(result.success).toBe(false);
|
|
407
|
+
expect(result.error).toBe('Database connection failed');
|
|
408
|
+
expect(result.errorDetails).toBeDefined();
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|