@app-connect/core 1.7.24 → 1.7.26
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/.env.test +5 -5
- package/README.md +441 -441
- package/connector/developerPortal.js +31 -42
- package/connector/mock.js +84 -77
- package/connector/proxy/engine.js +164 -163
- package/connector/proxy/index.js +500 -500
- package/connector/registry.js +252 -252
- package/docs/README.md +50 -50
- package/docs/architecture.md +93 -93
- package/docs/connectors.md +116 -117
- package/docs/handlers.md +125 -125
- package/docs/libraries.md +101 -101
- package/docs/models.md +144 -144
- package/docs/routes.md +115 -115
- package/docs/tests.md +73 -73
- package/handlers/admin.js +523 -523
- package/handlers/appointment.js +193 -0
- package/handlers/auth.js +296 -296
- package/handlers/calldown.js +99 -99
- package/handlers/contact.js +280 -280
- package/handlers/disposition.js +82 -80
- package/handlers/log.js +984 -973
- package/handlers/managedAuth.js +446 -446
- package/handlers/plugin.js +208 -208
- package/handlers/user.js +142 -142
- package/index.js +3140 -2652
- package/jest.config.js +56 -56
- package/lib/analytics.js +54 -54
- package/lib/authSession.js +109 -109
- package/lib/cacheCleanup.js +21 -0
- package/lib/callLogComposer.js +898 -898
- package/lib/callLogLookup.js +34 -0
- package/lib/constants.js +8 -8
- package/lib/debugTracer.js +177 -177
- package/lib/encode.js +30 -30
- package/lib/errorHandler.js +218 -206
- package/lib/generalErrorMessage.js +41 -41
- package/lib/jwt.js +18 -18
- package/lib/logger.js +190 -190
- package/lib/migrateCallLogsSchema.js +116 -0
- package/lib/ringcentral.js +266 -266
- package/lib/s3ErrorLogReport.js +65 -65
- package/lib/sharedSMSComposer.js +471 -471
- package/lib/util.js +67 -67
- package/mcp/README.md +412 -395
- package/mcp/lib/validator.js +91 -91
- package/mcp/mcpHandler.js +425 -425
- package/mcp/tools/cancelAppointment.js +101 -0
- package/mcp/tools/checkAuthStatus.js +105 -105
- package/mcp/tools/confirmAppointment.js +101 -0
- package/mcp/tools/createAppointment.js +157 -0
- package/mcp/tools/createCallLog.js +327 -316
- package/mcp/tools/createContact.js +117 -117
- package/mcp/tools/createMessageLog.js +287 -287
- package/mcp/tools/doAuth.js +60 -60
- package/mcp/tools/findContactByName.js +93 -93
- package/mcp/tools/findContactByPhone.js +101 -101
- package/mcp/tools/getCallLog.js +111 -102
- package/mcp/tools/getGoogleFilePicker.js +99 -99
- package/mcp/tools/getHelp.js +43 -43
- package/mcp/tools/getPublicConnectors.js +94 -94
- package/mcp/tools/getSessionInfo.js +90 -90
- package/mcp/tools/index.js +51 -41
- package/mcp/tools/listAppointments.js +163 -0
- package/mcp/tools/logout.js +96 -96
- package/mcp/tools/rcGetCallLogs.js +65 -65
- package/mcp/tools/updateAppointment.js +154 -0
- package/mcp/tools/updateCallLog.js +130 -126
- package/mcp/ui/App/App.tsx +358 -358
- package/mcp/ui/App/components/AuthInfoForm.tsx +113 -113
- package/mcp/ui/App/components/AuthSuccess.tsx +22 -22
- package/mcp/ui/App/components/ConnectorList.tsx +82 -82
- package/mcp/ui/App/components/DebugPanel.tsx +43 -43
- package/mcp/ui/App/components/OAuthConnect.tsx +270 -270
- package/mcp/ui/App/lib/callTool.ts +130 -130
- package/mcp/ui/App/lib/debugLog.ts +41 -41
- package/mcp/ui/App/lib/developerPortal.ts +111 -111
- package/mcp/ui/App/main.css +5 -5
- package/mcp/ui/App/root.tsx +13 -13
- package/mcp/ui/index.html +13 -13
- package/mcp/ui/package-lock.json +6356 -6356
- package/mcp/ui/package.json +25 -25
- package/mcp/ui/tsconfig.json +26 -26
- package/mcp/ui/vite.config.ts +16 -16
- package/models/accountDataModel.js +33 -33
- package/models/adminConfigModel.js +35 -35
- package/models/cacheModel.js +30 -26
- package/models/callDownListModel.js +34 -34
- package/models/callLogModel.js +33 -27
- package/models/dynamo/connectorSchema.js +146 -146
- package/models/dynamo/lockSchema.js +24 -24
- package/models/dynamo/noteCacheSchema.js +29 -29
- package/models/llmSessionModel.js +17 -17
- package/models/messageLogModel.js +25 -25
- package/models/sequelize.js +16 -16
- package/models/userModel.js +45 -45
- package/package.json +72 -72
- package/releaseNotes.json +1093 -1073
- package/test/connector/proxy/engine.test.js +126 -93
- package/test/connector/proxy/index.test.js +279 -279
- package/test/connector/proxy/sample.json +161 -161
- package/test/connector/registry.test.js +415 -415
- package/test/handlers/admin.test.js +616 -616
- package/test/handlers/auth.test.js +1018 -1015
- package/test/handlers/contact.test.js +1014 -1014
- package/test/handlers/log.test.js +1298 -1160
- package/test/handlers/managedAuth.test.js +458 -458
- package/test/handlers/plugin.test.js +380 -380
- package/test/index.test.js +105 -105
- package/test/lib/cacheCleanup.test.js +42 -0
- package/test/lib/callLogComposer.test.js +1231 -1231
- package/test/lib/debugTracer.test.js +328 -328
- package/test/lib/jwt.test.js +176 -176
- package/test/lib/logger.test.js +206 -206
- package/test/lib/oauth.test.js +359 -359
- package/test/lib/ringcentral.test.js +467 -467
- package/test/lib/sharedSMSComposer.test.js +1084 -1084
- package/test/lib/util.test.js +329 -329
- package/test/mcp/tools/checkAuthStatus.test.js +83 -82
- package/test/mcp/tools/createCallLog.test.js +436 -436
- package/test/mcp/tools/createContact.test.js +58 -58
- package/test/mcp/tools/createMessageLog.test.js +595 -595
- package/test/mcp/tools/doAuth.test.js +113 -113
- package/test/mcp/tools/findContactByName.test.js +275 -275
- package/test/mcp/tools/findContactByPhone.test.js +296 -296
- package/test/mcp/tools/getCallLog.test.js +298 -298
- package/test/mcp/tools/getGoogleFilePicker.test.js +281 -281
- package/test/mcp/tools/getPublicConnectors.test.js +107 -107
- package/test/mcp/tools/getSessionInfo.test.js +127 -127
- package/test/mcp/tools/logout.test.js +233 -233
- package/test/mcp/tools/rcGetCallLogs.test.js +56 -56
- package/test/mcp/tools/updateCallLog.test.js +360 -360
- package/test/models/accountDataModel.test.js +98 -98
- package/test/models/dynamo/connectorSchema.test.js +189 -189
- package/test/models/models.test.js +568 -539
- package/test/routes/managedAuthRoutes.test.js +104 -129
- package/test/setup.js +178 -178
|
@@ -1,281 +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 CRM CONNECTION');
|
|
22
|
-
expect(getGoogleFilePicker.definition.description).toContain('Google Sheets file picker');
|
|
23
|
-
expect(getGoogleFilePicker.definition.inputSchema).toBeDefined();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('should not require jwtToken in schema (it is server-injected)', () => {
|
|
27
|
-
expect(getGoogleFilePicker.definition.inputSchema.required).not.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 connect to the CRM first using getPublicConnectors.'
|
|
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 connect to the CRM first using getPublicConnectors.'
|
|
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 connect to the CRM first using getPublicConnectors.'
|
|
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
|
-
|
|
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 CRM CONNECTION');
|
|
22
|
+
expect(getGoogleFilePicker.definition.description).toContain('Google Sheets file picker');
|
|
23
|
+
expect(getGoogleFilePicker.definition.inputSchema).toBeDefined();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('should not require jwtToken in schema (it is server-injected)', () => {
|
|
27
|
+
expect(getGoogleFilePicker.definition.inputSchema.required).not.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 connect to the CRM first using getPublicConnectors.'
|
|
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 connect to the CRM first using getPublicConnectors.'
|
|
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 connect to the CRM first using getPublicConnectors.'
|
|
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
|
+
|