@app-connect/core 1.7.11 → 1.7.12
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/handlers/admin.js +91 -1
- package/handlers/auth.js +1 -1
- package/handlers/disposition.js +3 -3
- package/handlers/log.js +2 -1
- package/index.js +67 -0
- package/lib/sharedSMSComposer.js +2 -2
- package/mcp/tools/checkAuthStatus.js +22 -2
- package/mcp/tools/collectAuthInfo.js +5 -0
- package/mcp/tools/createCallLog.js +9 -0
- package/mcp/tools/createContact.js +117 -0
- package/mcp/tools/doAuth.js +15 -10
- package/mcp/tools/findContactByName.js +5 -0
- package/mcp/tools/findContactByPhone.js +5 -0
- package/mcp/tools/getGoogleFilePicker.js +103 -0
- package/mcp/tools/getHelp.js +6 -1
- package/mcp/tools/getPublicConnectors.js +8 -1
- package/mcp/tools/index.js +14 -8
- package/mcp/tools/logout.js +5 -0
- package/mcp/tools/rcGetCallLogs.js +5 -0
- package/mcp/tools/setConnector.js +7 -2
- package/package.json +1 -1
- package/releaseNotes.json +12 -0
- package/test/handlers/log.test.js +6 -3
- package/test/lib/ringcentral.test.js +0 -6
- package/test/lib/sharedSMSComposer.test.js +1 -1
- package/test/mcp/tools/collectAuthInfo.test.js +42 -0
- package/test/mcp/tools/createCallLog.test.js +27 -14
- package/test/mcp/tools/doAuth.test.js +23 -10
- package/test/mcp/tools/getGoogleFilePicker.test.js +281 -0
- package/test/mcp/tools/getPublicConnectors.test.js +8 -8
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
const getGoogleFilePicker = require('../../../mcp/tools/getGoogleFilePicker');
|
|
2
|
+
const jwt = require('../../../lib/jwt');
|
|
3
|
+
const { UserModel } = require('../../../models/userModel');
|
|
4
|
+
const axios = require('axios');
|
|
5
|
+
|
|
6
|
+
// Mock dependencies
|
|
7
|
+
jest.mock('../../../lib/jwt');
|
|
8
|
+
jest.mock('../../../models/userModel');
|
|
9
|
+
jest.mock('axios');
|
|
10
|
+
|
|
11
|
+
describe('MCP Tool: getGoogleFilePicker', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
process.env.APP_SERVER = 'https://test-app-server.com';
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('tool definition', () => {
|
|
18
|
+
test('should have correct tool definition', () => {
|
|
19
|
+
expect(getGoogleFilePicker.definition).toBeDefined();
|
|
20
|
+
expect(getGoogleFilePicker.definition.name).toBe('getGoogleFilePicker');
|
|
21
|
+
expect(getGoogleFilePicker.definition.description).toContain('REQUIRES AUTHENTICATION');
|
|
22
|
+
expect(getGoogleFilePicker.definition.description).toContain('Google Sheets file picker');
|
|
23
|
+
expect(getGoogleFilePicker.definition.inputSchema).toBeDefined();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('should require jwtToken parameter', () => {
|
|
27
|
+
expect(getGoogleFilePicker.definition.inputSchema.required).toContain('jwtToken');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('should have optional sheetName parameter', () => {
|
|
31
|
+
expect(getGoogleFilePicker.definition.inputSchema.properties).toHaveProperty('sheetName');
|
|
32
|
+
expect(getGoogleFilePicker.definition.inputSchema.required).not.toContain('sheetName');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('should have correct annotations', () => {
|
|
36
|
+
expect(getGoogleFilePicker.definition.annotations).toEqual({
|
|
37
|
+
readOnlyHint: false,
|
|
38
|
+
openWorldHint: true,
|
|
39
|
+
destructiveHint: false
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('execute - file picker URL', () => {
|
|
45
|
+
test('should return file picker URL successfully', async () => {
|
|
46
|
+
// Arrange
|
|
47
|
+
jwt.decodeJwt.mockReturnValue({
|
|
48
|
+
id: 'user-123'
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
UserModel.findByPk.mockResolvedValue({
|
|
52
|
+
id: 'user-123',
|
|
53
|
+
name: 'Test User'
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Act
|
|
57
|
+
const result = await getGoogleFilePicker.execute({
|
|
58
|
+
jwtToken: 'mock-jwt-token'
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Assert
|
|
62
|
+
expect(result).toEqual({
|
|
63
|
+
success: true,
|
|
64
|
+
data: {
|
|
65
|
+
filePickerUrl: 'https://test-app-server.com/googleSheets/filePicker?token=mock-jwt-token}',
|
|
66
|
+
message: expect.stringContaining('Please open this URL')
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
expect(jwt.decodeJwt).toHaveBeenCalledWith('mock-jwt-token');
|
|
70
|
+
expect(UserModel.findByPk).toHaveBeenCalledWith('user-123');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('execute - create new sheet', () => {
|
|
75
|
+
test('should create new sheet when sheetName is provided', async () => {
|
|
76
|
+
// Arrange
|
|
77
|
+
jwt.decodeJwt.mockReturnValue({
|
|
78
|
+
id: 'user-123'
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
UserModel.findByPk.mockResolvedValue({
|
|
82
|
+
id: 'user-123',
|
|
83
|
+
name: 'Test User'
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const mockSheetResponse = {
|
|
87
|
+
data: {
|
|
88
|
+
success: true,
|
|
89
|
+
data: {
|
|
90
|
+
sheetId: 'sheet-123',
|
|
91
|
+
sheetName: 'My Call Logs'
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
axios.post.mockResolvedValue(mockSheetResponse);
|
|
97
|
+
|
|
98
|
+
// Act
|
|
99
|
+
const result = await getGoogleFilePicker.execute({
|
|
100
|
+
jwtToken: 'mock-jwt-token',
|
|
101
|
+
sheetName: 'My Call Logs'
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Assert
|
|
105
|
+
expect(result).toEqual({
|
|
106
|
+
success: true,
|
|
107
|
+
data: {
|
|
108
|
+
sheetId: 'sheet-123',
|
|
109
|
+
sheetName: 'My Call Logs'
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
expect(axios.post).toHaveBeenCalledWith(
|
|
113
|
+
'https://test-app-server.com/googleSheets/sheet?jwtToken=mock-jwt-token',
|
|
114
|
+
{ name: 'My Call Logs' }
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('should return error when sheet creation fails', async () => {
|
|
119
|
+
// Arrange
|
|
120
|
+
jwt.decodeJwt.mockReturnValue({
|
|
121
|
+
id: 'user-123'
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
UserModel.findByPk.mockResolvedValue({
|
|
125
|
+
id: 'user-123',
|
|
126
|
+
name: 'Test User'
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
axios.post.mockRejectedValue(new Error('Failed to create sheet'));
|
|
130
|
+
|
|
131
|
+
// Act
|
|
132
|
+
const result = await getGoogleFilePicker.execute({
|
|
133
|
+
jwtToken: 'mock-jwt-token',
|
|
134
|
+
sheetName: 'My Call Logs'
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Assert
|
|
138
|
+
expect(result.success).toBe(false);
|
|
139
|
+
expect(result.error).toBe('Failed to create sheet');
|
|
140
|
+
expect(result.errorDetails).toBeDefined();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('execute - error handling', () => {
|
|
145
|
+
test('should return error when jwtToken is missing', async () => {
|
|
146
|
+
// Act
|
|
147
|
+
const result = await getGoogleFilePicker.execute({});
|
|
148
|
+
|
|
149
|
+
// Assert
|
|
150
|
+
expect(result).toEqual({
|
|
151
|
+
success: false,
|
|
152
|
+
error: 'JWT token is required. Please authenticate with googleSheets platform first using the doAuth tool.'
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('should return error when jwtToken is null', async () => {
|
|
157
|
+
// Act
|
|
158
|
+
const result = await getGoogleFilePicker.execute({
|
|
159
|
+
jwtToken: null
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Assert
|
|
163
|
+
expect(result).toEqual({
|
|
164
|
+
success: false,
|
|
165
|
+
error: 'JWT token is required. Please authenticate with googleSheets platform first using the doAuth tool.'
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('should return error when JWT is invalid (no userId)', async () => {
|
|
170
|
+
// Arrange
|
|
171
|
+
jwt.decodeJwt.mockReturnValue({
|
|
172
|
+
// id is missing
|
|
173
|
+
platform: 'googleSheets'
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Act
|
|
177
|
+
const result = await getGoogleFilePicker.execute({
|
|
178
|
+
jwtToken: 'invalid-jwt-token'
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Assert
|
|
182
|
+
expect(result).toEqual({
|
|
183
|
+
success: false,
|
|
184
|
+
error: 'Invalid JWT token: userId not found'
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('should return error when JWT decode returns null', async () => {
|
|
189
|
+
// Arrange
|
|
190
|
+
jwt.decodeJwt.mockReturnValue(null);
|
|
191
|
+
|
|
192
|
+
// Act
|
|
193
|
+
const result = await getGoogleFilePicker.execute({
|
|
194
|
+
jwtToken: 'malformed-jwt-token'
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Assert
|
|
198
|
+
expect(result).toEqual({
|
|
199
|
+
success: false,
|
|
200
|
+
error: 'Invalid JWT token: userId not found'
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('should return error when user not found', async () => {
|
|
205
|
+
// Arrange
|
|
206
|
+
jwt.decodeJwt.mockReturnValue({
|
|
207
|
+
id: 'nonexistent-user'
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
UserModel.findByPk.mockResolvedValue(null);
|
|
211
|
+
|
|
212
|
+
// Act
|
|
213
|
+
const result = await getGoogleFilePicker.execute({
|
|
214
|
+
jwtToken: 'mock-jwt-token'
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Assert
|
|
218
|
+
expect(result).toEqual({
|
|
219
|
+
success: false,
|
|
220
|
+
error: 'User not found. Please authenticate with googleSheets platform first.'
|
|
221
|
+
});
|
|
222
|
+
expect(UserModel.findByPk).toHaveBeenCalledWith('nonexistent-user');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('should handle database errors gracefully', async () => {
|
|
226
|
+
// Arrange
|
|
227
|
+
jwt.decodeJwt.mockReturnValue({
|
|
228
|
+
id: 'user-123'
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
UserModel.findByPk.mockRejectedValue(new Error('Database connection failed'));
|
|
232
|
+
|
|
233
|
+
// Act
|
|
234
|
+
const result = await getGoogleFilePicker.execute({
|
|
235
|
+
jwtToken: 'mock-jwt-token'
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Assert
|
|
239
|
+
expect(result.success).toBe(false);
|
|
240
|
+
expect(result.error).toBe('Database connection failed');
|
|
241
|
+
expect(result.errorDetails).toBeDefined();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('should handle JWT decode errors gracefully', async () => {
|
|
245
|
+
// Arrange
|
|
246
|
+
jwt.decodeJwt.mockImplementation(() => {
|
|
247
|
+
throw new Error('JWT decode error');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Act
|
|
251
|
+
const result = await getGoogleFilePicker.execute({
|
|
252
|
+
jwtToken: 'corrupted-jwt-token'
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Assert
|
|
256
|
+
expect(result.success).toBe(false);
|
|
257
|
+
expect(result.error).toBe('JWT decode error');
|
|
258
|
+
expect(result.errorDetails).toBeDefined();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('should handle errors without message property', async () => {
|
|
262
|
+
// Arrange
|
|
263
|
+
jwt.decodeJwt.mockReturnValue({
|
|
264
|
+
id: 'user-123'
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const errorWithoutMessage = { code: 'UNKNOWN_ERROR' };
|
|
268
|
+
UserModel.findByPk.mockRejectedValue(errorWithoutMessage);
|
|
269
|
+
|
|
270
|
+
// Act
|
|
271
|
+
const result = await getGoogleFilePicker.execute({
|
|
272
|
+
jwtToken: 'mock-jwt-token'
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Assert
|
|
276
|
+
expect(result.success).toBe(false);
|
|
277
|
+
expect(result.error).toBe('Unknown error occurred');
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
@@ -26,10 +26,10 @@ describe('MCP Tool: getPublicConnectors', () => {
|
|
|
26
26
|
|
|
27
27
|
describe('execute', () => {
|
|
28
28
|
test('should return public connectors successfully', async () => {
|
|
29
|
-
// Arrange
|
|
29
|
+
// Arrange - use supported platform names: 'googleSheets' and 'clio'
|
|
30
30
|
const mockConnectors = [
|
|
31
|
-
{ id: '1', name: '
|
|
32
|
-
{ id: '2', name: '
|
|
31
|
+
{ id: '1', name: 'googleSheets', displayName: 'Google Sheets' },
|
|
32
|
+
{ id: '2', name: 'clio', displayName: 'Clio' }
|
|
33
33
|
];
|
|
34
34
|
|
|
35
35
|
developerPortal.getPublicConnectorList.mockResolvedValue({
|
|
@@ -42,20 +42,20 @@ describe('MCP Tool: getPublicConnectors', () => {
|
|
|
42
42
|
// Assert
|
|
43
43
|
expect(result).toEqual({
|
|
44
44
|
success: true,
|
|
45
|
-
data: ['
|
|
45
|
+
data: ['Google Sheets', 'Clio']
|
|
46
46
|
});
|
|
47
47
|
expect(developerPortal.getPublicConnectorList).toHaveBeenCalledTimes(1);
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
test('should include private connectors when RC_ACCOUNT_ID is set', async () => {
|
|
51
|
-
// Arrange
|
|
51
|
+
// Arrange - use supported platform names
|
|
52
52
|
process.env.RC_ACCOUNT_ID = 'test-account-id';
|
|
53
53
|
|
|
54
54
|
const mockPublicConnectors = [
|
|
55
|
-
{ id: '1', name: '
|
|
55
|
+
{ id: '1', name: 'googleSheets', displayName: 'Google Sheets' }
|
|
56
56
|
];
|
|
57
57
|
const mockPrivateConnectors = [
|
|
58
|
-
{ id: '3', name: '
|
|
58
|
+
{ id: '3', name: 'clio', displayName: 'Clio' }
|
|
59
59
|
];
|
|
60
60
|
|
|
61
61
|
developerPortal.getPublicConnectorList.mockResolvedValue({
|
|
@@ -71,7 +71,7 @@ describe('MCP Tool: getPublicConnectors', () => {
|
|
|
71
71
|
// Assert
|
|
72
72
|
expect(result).toEqual({
|
|
73
73
|
success: true,
|
|
74
|
-
data: ['
|
|
74
|
+
data: ['Google Sheets', 'Clio']
|
|
75
75
|
});
|
|
76
76
|
expect(developerPortal.getPublicConnectorList).toHaveBeenCalledTimes(1);
|
|
77
77
|
expect(developerPortal.getPrivateConnectorList).toHaveBeenCalledTimes(1);
|