@app-connect/core 1.7.10 → 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/connector/developerPortal.js +43 -0
- package/connector/proxy/index.js +10 -3
- package/connector/registry.js +8 -6
- package/handlers/admin.js +135 -22
- package/handlers/auth.js +89 -67
- package/handlers/calldown.js +10 -4
- package/handlers/contact.js +4 -104
- package/handlers/disposition.js +7 -145
- package/handlers/log.js +174 -258
- package/handlers/user.js +19 -6
- package/index.js +280 -47
- 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 -10
- 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 +110 -0
- package/mcp/tools/collectAuthInfo.js +91 -0
- package/mcp/tools/createCallLog.js +308 -0
- package/mcp/tools/createContact.js +117 -0
- package/mcp/tools/createMessageLog.js +283 -0
- package/mcp/tools/doAuth.js +190 -0
- package/mcp/tools/findContactByName.js +92 -0
- package/mcp/tools/findContactByPhone.js +101 -0
- package/mcp/tools/getCallLog.js +98 -0
- package/mcp/tools/getGoogleFilePicker.js +103 -0
- package/mcp/tools/getHelp.js +44 -0
- package/mcp/tools/getPublicConnectors.js +53 -0
- package/mcp/tools/index.js +64 -0
- package/mcp/tools/logout.js +68 -0
- package/mcp/tools/rcGetCallLogs.js +78 -0
- package/mcp/tools/setConnector.js +69 -0
- package/mcp/tools/updateCallLog.js +122 -0
- package/models/cacheModel.js +3 -0
- package/package.json +71 -70
- package/releaseNotes.json +24 -0
- package/test/handlers/log.test.js +11 -4
- package/test/lib/logger.test.js +206 -0
- package/test/lib/ringcentral.test.js +0 -6
- package/test/lib/sharedSMSComposer.test.js +1084 -0
- package/test/mcp/tools/collectAuthInfo.test.js +234 -0
- package/test/mcp/tools/createCallLog.test.js +425 -0
- package/test/mcp/tools/createMessageLog.test.js +580 -0
- package/test/mcp/tools/doAuth.test.js +376 -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/getGoogleFilePicker.test.js +281 -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
|
@@ -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
|
+
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const getPublicConnectors = require('../../../mcp/tools/getPublicConnectors');
|
|
2
|
+
const developerPortal = require('../../../connector/developerPortal');
|
|
3
|
+
|
|
4
|
+
// Mock the developerPortal module
|
|
5
|
+
jest.mock('../../../connector/developerPortal');
|
|
6
|
+
|
|
7
|
+
describe('MCP Tool: getPublicConnectors', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
jest.clearAllMocks();
|
|
10
|
+
delete process.env.RC_ACCOUNT_ID;
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('tool definition', () => {
|
|
14
|
+
test('should have correct tool definition', () => {
|
|
15
|
+
expect(getPublicConnectors.definition).toBeDefined();
|
|
16
|
+
expect(getPublicConnectors.definition.name).toBe('getPublicConnectors');
|
|
17
|
+
expect(getPublicConnectors.definition.description).toContain('Auth flow step.1');
|
|
18
|
+
expect(getPublicConnectors.definition.inputSchema).toBeDefined();
|
|
19
|
+
expect(getPublicConnectors.definition.inputSchema.type).toBe('object');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('should have no required parameters', () => {
|
|
23
|
+
expect(getPublicConnectors.definition.inputSchema.required).toEqual([]);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('execute', () => {
|
|
28
|
+
test('should return public connectors successfully', async () => {
|
|
29
|
+
// Arrange - use supported platform names: 'googleSheets' and 'clio'
|
|
30
|
+
const mockConnectors = [
|
|
31
|
+
{ id: '1', name: 'googleSheets', displayName: 'Google Sheets' },
|
|
32
|
+
{ id: '2', name: 'clio', displayName: 'Clio' }
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
developerPortal.getPublicConnectorList.mockResolvedValue({
|
|
36
|
+
connectors: mockConnectors
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Act
|
|
40
|
+
const result = await getPublicConnectors.execute();
|
|
41
|
+
|
|
42
|
+
// Assert
|
|
43
|
+
expect(result).toEqual({
|
|
44
|
+
success: true,
|
|
45
|
+
data: ['Google Sheets', 'Clio']
|
|
46
|
+
});
|
|
47
|
+
expect(developerPortal.getPublicConnectorList).toHaveBeenCalledTimes(1);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('should include private connectors when RC_ACCOUNT_ID is set', async () => {
|
|
51
|
+
// Arrange - use supported platform names
|
|
52
|
+
process.env.RC_ACCOUNT_ID = 'test-account-id';
|
|
53
|
+
|
|
54
|
+
const mockPublicConnectors = [
|
|
55
|
+
{ id: '1', name: 'googleSheets', displayName: 'Google Sheets' }
|
|
56
|
+
];
|
|
57
|
+
const mockPrivateConnectors = [
|
|
58
|
+
{ id: '3', name: 'clio', displayName: 'Clio' }
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
developerPortal.getPublicConnectorList.mockResolvedValue({
|
|
62
|
+
connectors: mockPublicConnectors
|
|
63
|
+
});
|
|
64
|
+
developerPortal.getPrivateConnectorList.mockResolvedValue({
|
|
65
|
+
privateConnectors: mockPrivateConnectors
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Act
|
|
69
|
+
const result = await getPublicConnectors.execute();
|
|
70
|
+
|
|
71
|
+
// Assert
|
|
72
|
+
expect(result).toEqual({
|
|
73
|
+
success: true,
|
|
74
|
+
data: ['Google Sheets', 'Clio']
|
|
75
|
+
});
|
|
76
|
+
expect(developerPortal.getPublicConnectorList).toHaveBeenCalledTimes(1);
|
|
77
|
+
expect(developerPortal.getPrivateConnectorList).toHaveBeenCalledTimes(1);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('should return empty array when no connectors available', async () => {
|
|
81
|
+
// Arrange
|
|
82
|
+
developerPortal.getPublicConnectorList.mockResolvedValue({
|
|
83
|
+
connectors: []
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Act
|
|
87
|
+
const result = await getPublicConnectors.execute();
|
|
88
|
+
|
|
89
|
+
// Assert
|
|
90
|
+
expect(result).toEqual({
|
|
91
|
+
success: true,
|
|
92
|
+
data: []
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('should handle errors gracefully', async () => {
|
|
97
|
+
// Arrange
|
|
98
|
+
const errorMessage = 'Failed to fetch connectors';
|
|
99
|
+
developerPortal.getPublicConnectorList.mockRejectedValue(
|
|
100
|
+
new Error(errorMessage)
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// Act
|
|
104
|
+
const result = await getPublicConnectors.execute();
|
|
105
|
+
|
|
106
|
+
// Assert
|
|
107
|
+
expect(result.success).toBe(false);
|
|
108
|
+
expect(result.error).toBe(errorMessage);
|
|
109
|
+
expect(result.errorDetails).toBeDefined();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('should handle network errors', async () => {
|
|
113
|
+
// Arrange
|
|
114
|
+
const networkError = new Error('Network request failed');
|
|
115
|
+
networkError.code = 'ECONNREFUSED';
|
|
116
|
+
developerPortal.getPublicConnectorList.mockRejectedValue(networkError);
|
|
117
|
+
|
|
118
|
+
// Act
|
|
119
|
+
const result = await getPublicConnectors.execute();
|
|
120
|
+
|
|
121
|
+
// Assert
|
|
122
|
+
expect(result.success).toBe(false);
|
|
123
|
+
expect(result.error).toBe('Network request failed');
|
|
124
|
+
expect(result.errorDetails).toBeDefined();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const logout = require('../../../mcp/tools/logout');
|
|
2
|
+
const jwt = require('../../../lib/jwt');
|
|
3
|
+
const { UserModel } = require('../../../models/userModel');
|
|
4
|
+
const connectorRegistry = require('../../../connector/registry');
|
|
5
|
+
|
|
6
|
+
// Mock dependencies
|
|
7
|
+
jest.mock('../../../lib/jwt');
|
|
8
|
+
jest.mock('../../../models/userModel');
|
|
9
|
+
jest.mock('../../../connector/registry');
|
|
10
|
+
|
|
11
|
+
describe('MCP Tool: logout', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('tool definition', () => {
|
|
17
|
+
test('should have correct tool definition', () => {
|
|
18
|
+
expect(logout.definition).toBeDefined();
|
|
19
|
+
expect(logout.definition.name).toBe('logout');
|
|
20
|
+
expect(logout.definition.description).toContain('Logout');
|
|
21
|
+
expect(logout.definition.inputSchema).toBeDefined();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('should have jwtToken property', () => {
|
|
25
|
+
expect(logout.definition.inputSchema.properties).toHaveProperty('jwtToken');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('execute', () => {
|
|
30
|
+
test('should logout user successfully', async () => {
|
|
31
|
+
// Arrange
|
|
32
|
+
const mockUser = {
|
|
33
|
+
id: 'test-user-id',
|
|
34
|
+
platform: 'testCRM',
|
|
35
|
+
accessToken: 'test-access-token'
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const mockConnector = {
|
|
39
|
+
unAuthorize: jest.fn().mockResolvedValue({
|
|
40
|
+
returnMessage: {
|
|
41
|
+
messageType: 'success',
|
|
42
|
+
message: 'Logged out successfully'
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
jwt.decodeJwt.mockReturnValue({
|
|
48
|
+
id: 'test-user-id',
|
|
49
|
+
platform: 'testCRM'
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
UserModel.findByPk.mockResolvedValue(mockUser);
|
|
53
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
54
|
+
|
|
55
|
+
// Act
|
|
56
|
+
const result = await logout.execute({
|
|
57
|
+
jwtToken: 'mock-jwt-token'
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Assert
|
|
61
|
+
expect(result).toEqual({
|
|
62
|
+
success: true,
|
|
63
|
+
data: {
|
|
64
|
+
message: expect.stringContaining('IMPORTANT')
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
expect(jwt.decodeJwt).toHaveBeenCalledWith('mock-jwt-token');
|
|
68
|
+
expect(UserModel.findByPk).toHaveBeenCalledWith('test-user-id');
|
|
69
|
+
expect(connectorRegistry.getConnector).toHaveBeenCalledWith('testCRM');
|
|
70
|
+
expect(mockConnector.unAuthorize).toHaveBeenCalledWith({
|
|
71
|
+
user: mockUser
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('should return error when user not found', async () => {
|
|
76
|
+
// Arrange
|
|
77
|
+
jwt.decodeJwt.mockReturnValue({
|
|
78
|
+
id: 'non-existent-user',
|
|
79
|
+
platform: 'testCRM'
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
UserModel.findByPk.mockResolvedValue(null);
|
|
83
|
+
|
|
84
|
+
// Act
|
|
85
|
+
const result = await logout.execute({
|
|
86
|
+
jwtToken: 'mock-jwt-token'
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Assert
|
|
90
|
+
expect(result).toEqual({
|
|
91
|
+
success: false,
|
|
92
|
+
error: 'User not found',
|
|
93
|
+
errorDetails: 'User not found'
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('should handle logout errors gracefully', async () => {
|
|
98
|
+
// Arrange
|
|
99
|
+
const mockUser = {
|
|
100
|
+
id: 'test-user-id',
|
|
101
|
+
platform: 'testCRM'
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const mockConnector = {
|
|
105
|
+
unAuthorize: jest.fn().mockRejectedValue(
|
|
106
|
+
new Error('Logout API failed')
|
|
107
|
+
)
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
jwt.decodeJwt.mockReturnValue({
|
|
111
|
+
id: 'test-user-id',
|
|
112
|
+
platform: 'testCRM'
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
UserModel.findByPk.mockResolvedValue(mockUser);
|
|
116
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
117
|
+
|
|
118
|
+
// Act
|
|
119
|
+
const result = await logout.execute({
|
|
120
|
+
jwtToken: 'mock-jwt-token'
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Assert
|
|
124
|
+
expect(result.success).toBe(false);
|
|
125
|
+
expect(result.error).toBe('Logout API failed');
|
|
126
|
+
expect(result.errorDetails).toBeDefined();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('should handle invalid JWT token', async () => {
|
|
130
|
+
// Arrange
|
|
131
|
+
jwt.decodeJwt.mockReturnValue(null);
|
|
132
|
+
|
|
133
|
+
// Act
|
|
134
|
+
const result = await logout.execute({
|
|
135
|
+
jwtToken: 'invalid-token'
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Assert
|
|
139
|
+
expect(result.success).toBe(false);
|
|
140
|
+
expect(result.error).toBeDefined();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('should handle missing platform connector', async () => {
|
|
144
|
+
// Arrange
|
|
145
|
+
const mockUser = {
|
|
146
|
+
id: 'test-user-id',
|
|
147
|
+
platform: 'unknownCRM'
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
jwt.decodeJwt.mockReturnValue({
|
|
151
|
+
id: 'test-user-id',
|
|
152
|
+
platform: 'unknownCRM'
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
UserModel.findByPk.mockResolvedValue(mockUser);
|
|
156
|
+
connectorRegistry.getConnector.mockReturnValue(null);
|
|
157
|
+
|
|
158
|
+
// Act
|
|
159
|
+
const result = await logout.execute({
|
|
160
|
+
jwtToken: 'mock-jwt-token'
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Assert
|
|
164
|
+
expect(result.success).toBe(false);
|
|
165
|
+
expect(result.error).toBeDefined();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|