@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,376 @@
|
|
|
1
|
+
const doAuth = require('../../../mcp/tools/doAuth');
|
|
2
|
+
const authCore = require('../../../handlers/auth');
|
|
3
|
+
const jwt = require('../../../lib/jwt');
|
|
4
|
+
|
|
5
|
+
// Mock dependencies
|
|
6
|
+
jest.mock('../../../handlers/auth');
|
|
7
|
+
jest.mock('../../../lib/jwt');
|
|
8
|
+
|
|
9
|
+
describe('MCP Tool: doAuth', () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
jest.clearAllMocks();
|
|
12
|
+
process.env.APP_SERVER_SECRET_KEY = 'test-secret-key';
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('tool definition', () => {
|
|
16
|
+
test('should have correct tool definition', () => {
|
|
17
|
+
expect(doAuth.definition).toBeDefined();
|
|
18
|
+
expect(doAuth.definition.name).toBe('doAuth');
|
|
19
|
+
expect(doAuth.definition.description).toContain('Auth flow step.4');
|
|
20
|
+
expect(doAuth.definition.inputSchema).toBeDefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('should have optional parameters', () => {
|
|
24
|
+
expect(doAuth.definition.inputSchema.properties).toHaveProperty('connectorManifest');
|
|
25
|
+
expect(doAuth.definition.inputSchema.properties).toHaveProperty('connectorName');
|
|
26
|
+
expect(doAuth.definition.inputSchema.properties).toHaveProperty('hostname');
|
|
27
|
+
expect(doAuth.definition.inputSchema.properties).toHaveProperty('apiKey');
|
|
28
|
+
expect(doAuth.definition.inputSchema.properties).toHaveProperty('additionalInfo');
|
|
29
|
+
expect(doAuth.definition.inputSchema.properties).toHaveProperty('callbackUri');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('execute - apiKey authentication', () => {
|
|
34
|
+
test('should authenticate with API key successfully', async () => {
|
|
35
|
+
// Arrange
|
|
36
|
+
const mockManifest = {
|
|
37
|
+
platforms: {
|
|
38
|
+
testCRM: {
|
|
39
|
+
name: 'testCRM',
|
|
40
|
+
auth: { type: 'apiKey', apiKey: { name: 'apiKey' } },
|
|
41
|
+
environment: { type: 'fixed' }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const mockUserInfo = {
|
|
47
|
+
id: 'test-user-123',
|
|
48
|
+
name: 'Test User'
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
authCore.onApiKeyLogin.mockResolvedValue({
|
|
52
|
+
userInfo: mockUserInfo
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
jwt.generateJwt.mockReturnValue('mock-jwt-token');
|
|
56
|
+
|
|
57
|
+
// Act
|
|
58
|
+
const result = await doAuth.execute({
|
|
59
|
+
connectorManifest: mockManifest,
|
|
60
|
+
connectorName: 'testCRM',
|
|
61
|
+
hostname: 'test.crm.com',
|
|
62
|
+
apiKey: 'test-api-key'
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Assert
|
|
66
|
+
expect(result).toEqual({
|
|
67
|
+
success: true,
|
|
68
|
+
data: {
|
|
69
|
+
jwtToken: 'mock-jwt-token',
|
|
70
|
+
message: expect.stringContaining('IMPORTANT')
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
expect(authCore.onApiKeyLogin).toHaveBeenCalledWith({
|
|
74
|
+
platform: 'testCRM',
|
|
75
|
+
hostname: 'test.crm.com',
|
|
76
|
+
apiKey: 'test-api-key',
|
|
77
|
+
additionalInfo: undefined
|
|
78
|
+
});
|
|
79
|
+
expect(jwt.generateJwt).toHaveBeenCalledWith({
|
|
80
|
+
id: 'test-user-123',
|
|
81
|
+
platform: 'testCRM'
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('should handle apiKey authentication with additional info', async () => {
|
|
86
|
+
// Arrange
|
|
87
|
+
const mockManifest = {
|
|
88
|
+
platforms: {
|
|
89
|
+
testCRM: {
|
|
90
|
+
name: 'testCRM',
|
|
91
|
+
auth: { type: 'apiKey', apiKey: { name: 'apiKey' } }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const additionalInfo = {
|
|
97
|
+
username: 'testuser',
|
|
98
|
+
password: 'testpass',
|
|
99
|
+
apiUrl: 'https://api.test.com'
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const mockUserInfo = {
|
|
103
|
+
id: 'test-user-456',
|
|
104
|
+
name: 'Test User'
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
authCore.onApiKeyLogin.mockResolvedValue({
|
|
108
|
+
userInfo: mockUserInfo
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
jwt.generateJwt.mockReturnValue('mock-jwt-token-2');
|
|
112
|
+
|
|
113
|
+
// Act
|
|
114
|
+
const result = await doAuth.execute({
|
|
115
|
+
connectorManifest: mockManifest,
|
|
116
|
+
connectorName: 'testCRM',
|
|
117
|
+
hostname: 'test.crm.com',
|
|
118
|
+
apiKey: 'test-api-key',
|
|
119
|
+
additionalInfo
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Assert
|
|
123
|
+
expect(result.success).toBe(true);
|
|
124
|
+
expect(authCore.onApiKeyLogin).toHaveBeenCalledWith({
|
|
125
|
+
platform: 'testCRM',
|
|
126
|
+
hostname: 'test.crm.com',
|
|
127
|
+
apiKey: 'test-api-key',
|
|
128
|
+
additionalInfo
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('should return error when user info not found', async () => {
|
|
133
|
+
// Arrange
|
|
134
|
+
const mockManifest = {
|
|
135
|
+
platforms: {
|
|
136
|
+
testCRM: {
|
|
137
|
+
name: 'testCRM',
|
|
138
|
+
auth: { type: 'apiKey', apiKey: { name: 'apiKey' } }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
authCore.onApiKeyLogin.mockResolvedValue({
|
|
144
|
+
userInfo: null
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Act
|
|
148
|
+
const result = await doAuth.execute({
|
|
149
|
+
connectorManifest: mockManifest,
|
|
150
|
+
connectorName: 'testCRM',
|
|
151
|
+
hostname: 'test.crm.com',
|
|
152
|
+
apiKey: 'invalid-api-key'
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Assert
|
|
156
|
+
expect(result).toEqual({
|
|
157
|
+
success: false,
|
|
158
|
+
error: 'Authentication failed',
|
|
159
|
+
errorDetails: 'User info not found'
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('execute - OAuth authentication', () => {
|
|
165
|
+
test('should return auth URI when callback not provided', async () => {
|
|
166
|
+
// Arrange
|
|
167
|
+
const mockManifest = {
|
|
168
|
+
platforms: {
|
|
169
|
+
salesforce: {
|
|
170
|
+
name: 'salesforce',
|
|
171
|
+
auth: {
|
|
172
|
+
type: 'oauth',
|
|
173
|
+
oauth: {
|
|
174
|
+
authUrl: 'https://login.salesforce.com/services/oauth2/authorize',
|
|
175
|
+
clientId: 'test-client-id',
|
|
176
|
+
scope: 'api refresh_token',
|
|
177
|
+
customState: ''
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Act
|
|
185
|
+
const result = await doAuth.execute({
|
|
186
|
+
connectorManifest: mockManifest,
|
|
187
|
+
connectorName: 'salesforce'
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Assert
|
|
191
|
+
expect(result.success).toBe(true);
|
|
192
|
+
expect(result.data.authUri).toContain('https://login.salesforce.com');
|
|
193
|
+
expect(result.data.authUri).toContain('client_id=test-client-id');
|
|
194
|
+
expect(result.data.authUri).toContain('response_type=code');
|
|
195
|
+
expect(result.data.message).toContain('IMPORTANT');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('should handle OAuth callback successfully', async () => {
|
|
199
|
+
// Arrange
|
|
200
|
+
const mockManifest = {
|
|
201
|
+
platforms: {
|
|
202
|
+
salesforce: {
|
|
203
|
+
name: 'salesforce',
|
|
204
|
+
auth: {
|
|
205
|
+
type: 'oauth',
|
|
206
|
+
oauth: {
|
|
207
|
+
authUrl: 'https://login.salesforce.com/services/oauth2/authorize',
|
|
208
|
+
clientId: 'test-client-id'
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const mockUserInfo = {
|
|
216
|
+
id: 'sf-user-123',
|
|
217
|
+
name: 'SF User'
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
authCore.onOAuthCallback.mockResolvedValue({
|
|
221
|
+
userInfo: mockUserInfo
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
jwt.generateJwt.mockReturnValue('mock-jwt-token-oauth');
|
|
225
|
+
|
|
226
|
+
// Act
|
|
227
|
+
const result = await doAuth.execute({
|
|
228
|
+
connectorManifest: mockManifest,
|
|
229
|
+
connectorName: 'salesforce',
|
|
230
|
+
hostname: 'login.salesforce.com',
|
|
231
|
+
callbackUri: 'https://redirect.com?code=test-code&state=test-state'
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Assert
|
|
235
|
+
expect(result).toEqual({
|
|
236
|
+
success: true,
|
|
237
|
+
data: {
|
|
238
|
+
jwtToken: 'mock-jwt-token-oauth',
|
|
239
|
+
message: expect.stringContaining('IMPORTANT')
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
expect(authCore.onOAuthCallback).toHaveBeenCalledWith({
|
|
243
|
+
platform: 'salesforce',
|
|
244
|
+
hostname: 'login.salesforce.com',
|
|
245
|
+
callbackUri: 'https://redirect.com?code=test-code&state=test-state',
|
|
246
|
+
query: expect.objectContaining({
|
|
247
|
+
hostname: 'login.salesforce.com'
|
|
248
|
+
})
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test('should return error when OAuth callback fails', async () => {
|
|
253
|
+
// Arrange
|
|
254
|
+
const mockManifest = {
|
|
255
|
+
platforms: {
|
|
256
|
+
salesforce: {
|
|
257
|
+
name: 'salesforce',
|
|
258
|
+
auth: {
|
|
259
|
+
type: 'oauth',
|
|
260
|
+
oauth: {
|
|
261
|
+
authUrl: 'https://login.salesforce.com/services/oauth2/authorize',
|
|
262
|
+
clientId: 'test-client-id'
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
authCore.onOAuthCallback.mockResolvedValue({
|
|
270
|
+
userInfo: null
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Act - callbackUri needs code= and state= to be treated as OAuth callback
|
|
274
|
+
const result = await doAuth.execute({
|
|
275
|
+
connectorManifest: mockManifest,
|
|
276
|
+
connectorName: 'salesforce',
|
|
277
|
+
hostname: 'login.salesforce.com',
|
|
278
|
+
callbackUri: 'https://redirect.com?code=invalid-code&state=test-state'
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Assert
|
|
282
|
+
expect(result).toEqual({
|
|
283
|
+
success: false,
|
|
284
|
+
error: 'Authentication failed',
|
|
285
|
+
errorDetails: 'User info not found'
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test('should include custom state in auth URI', async () => {
|
|
290
|
+
// Arrange
|
|
291
|
+
const mockManifest = {
|
|
292
|
+
platforms: {
|
|
293
|
+
customCRM: {
|
|
294
|
+
name: 'customCRM',
|
|
295
|
+
auth: {
|
|
296
|
+
type: 'oauth',
|
|
297
|
+
oauth: {
|
|
298
|
+
authUrl: 'https://custom.com/oauth',
|
|
299
|
+
clientId: 'custom-client-id',
|
|
300
|
+
scope: '',
|
|
301
|
+
customState: 'custom=state&other=value'
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Act
|
|
309
|
+
const result = await doAuth.execute({
|
|
310
|
+
connectorManifest: mockManifest,
|
|
311
|
+
connectorName: 'customCRM'
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Assert - state is now URL-encoded and includes sessionId, platform, hostname, plus customState
|
|
315
|
+
expect(result.success).toBe(true);
|
|
316
|
+
// The state parameter now contains session info and custom state appended
|
|
317
|
+
// Decode and verify custom state is included
|
|
318
|
+
const stateMatch = result.data.authUri.match(/state=([^&]+)/);
|
|
319
|
+
expect(stateMatch).toBeTruthy();
|
|
320
|
+
const decodedState = decodeURIComponent(stateMatch[1]);
|
|
321
|
+
expect(decodedState).toContain('custom=state&other=value');
|
|
322
|
+
expect(decodedState).toContain('sessionId=');
|
|
323
|
+
expect(decodedState).toContain('platform=customCRM');
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe('error handling', () => {
|
|
328
|
+
test('should handle authentication errors gracefully', async () => {
|
|
329
|
+
// Arrange
|
|
330
|
+
const mockManifest = {
|
|
331
|
+
platforms: {
|
|
332
|
+
testCRM: {
|
|
333
|
+
name: 'testCRM',
|
|
334
|
+
auth: { type: 'apiKey', apiKey: { name: 'apiKey' } }
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
authCore.onApiKeyLogin.mockRejectedValue(
|
|
340
|
+
new Error('Invalid credentials')
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
// Act
|
|
344
|
+
const result = await doAuth.execute({
|
|
345
|
+
connectorManifest: mockManifest,
|
|
346
|
+
connectorName: 'testCRM',
|
|
347
|
+
hostname: 'test.crm.com',
|
|
348
|
+
apiKey: 'bad-key'
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Assert
|
|
352
|
+
expect(result.success).toBe(false);
|
|
353
|
+
expect(result.error).toBe('Invalid credentials');
|
|
354
|
+
expect(result.errorDetails).toBeDefined();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test('should handle missing platform in manifest', async () => {
|
|
358
|
+
// Arrange
|
|
359
|
+
const mockManifest = {
|
|
360
|
+
platforms: {}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// Act
|
|
364
|
+
const result = await doAuth.execute({
|
|
365
|
+
connectorManifest: mockManifest,
|
|
366
|
+
connectorName: 'nonExistent',
|
|
367
|
+
apiKey: 'test-key'
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// Assert
|
|
371
|
+
expect(result.success).toBe(false);
|
|
372
|
+
expect(result.error).toBeDefined();
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
const findContactByName = require('../../../mcp/tools/findContactByName');
|
|
2
|
+
const jwt = require('../../../lib/jwt');
|
|
3
|
+
const connectorRegistry = require('../../../connector/registry');
|
|
4
|
+
const contactCore = require('../../../handlers/contact');
|
|
5
|
+
|
|
6
|
+
// Mock dependencies
|
|
7
|
+
jest.mock('../../../lib/jwt');
|
|
8
|
+
jest.mock('../../../connector/registry');
|
|
9
|
+
jest.mock('../../../handlers/contact');
|
|
10
|
+
|
|
11
|
+
describe('MCP Tool: findContactByName', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('tool definition', () => {
|
|
17
|
+
test('should have correct tool definition', () => {
|
|
18
|
+
expect(findContactByName.definition).toBeDefined();
|
|
19
|
+
expect(findContactByName.definition.name).toBe('findContactByName');
|
|
20
|
+
expect(findContactByName.definition.description).toContain('REQUIRES AUTHENTICATION');
|
|
21
|
+
expect(findContactByName.definition.inputSchema).toBeDefined();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('should require jwtToken and name parameters', () => {
|
|
25
|
+
expect(findContactByName.definition.inputSchema.required).toContain('jwtToken');
|
|
26
|
+
expect(findContactByName.definition.inputSchema.required).toContain('name');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('execute', () => {
|
|
31
|
+
test('should find contact by name successfully', async () => {
|
|
32
|
+
// Arrange
|
|
33
|
+
const mockContact = {
|
|
34
|
+
id: 'contact-123',
|
|
35
|
+
name: 'John Doe',
|
|
36
|
+
phone: '+1234567890',
|
|
37
|
+
type: 'Contact'
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
jwt.decodeJwt.mockReturnValue({
|
|
41
|
+
id: 'user-123',
|
|
42
|
+
platform: 'testCRM'
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const mockConnector = {
|
|
46
|
+
findContactWithName: jest.fn()
|
|
47
|
+
};
|
|
48
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
49
|
+
|
|
50
|
+
contactCore.findContactWithName.mockResolvedValue({
|
|
51
|
+
successful: true,
|
|
52
|
+
contact: mockContact,
|
|
53
|
+
returnMessage: { message: 'Contact found' }
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Act
|
|
57
|
+
const result = await findContactByName.execute({
|
|
58
|
+
jwtToken: 'mock-jwt-token',
|
|
59
|
+
name: 'John Doe'
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Assert
|
|
63
|
+
expect(result).toEqual({
|
|
64
|
+
success: true,
|
|
65
|
+
data: mockContact
|
|
66
|
+
});
|
|
67
|
+
expect(jwt.decodeJwt).toHaveBeenCalledWith('mock-jwt-token');
|
|
68
|
+
expect(connectorRegistry.getConnector).toHaveBeenCalledWith('testCRM');
|
|
69
|
+
expect(contactCore.findContactWithName).toHaveBeenCalledWith({
|
|
70
|
+
platform: 'testCRM',
|
|
71
|
+
userId: 'user-123',
|
|
72
|
+
name: 'John Doe'
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('should find contact with partial name', async () => {
|
|
77
|
+
// Arrange
|
|
78
|
+
const mockContact = {
|
|
79
|
+
id: 'contact-456',
|
|
80
|
+
name: 'Jane Smith',
|
|
81
|
+
phone: '+9876543210',
|
|
82
|
+
type: 'Contact'
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
jwt.decodeJwt.mockReturnValue({
|
|
86
|
+
id: 'user-123',
|
|
87
|
+
platform: 'testCRM'
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const mockConnector = {
|
|
91
|
+
findContactWithName: jest.fn()
|
|
92
|
+
};
|
|
93
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
94
|
+
|
|
95
|
+
contactCore.findContactWithName.mockResolvedValue({
|
|
96
|
+
successful: true,
|
|
97
|
+
contact: mockContact
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Act
|
|
101
|
+
const result = await findContactByName.execute({
|
|
102
|
+
jwtToken: 'mock-jwt-token',
|
|
103
|
+
name: 'Jane'
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Assert
|
|
107
|
+
expect(result.success).toBe(true);
|
|
108
|
+
expect(result.data).toEqual(mockContact);
|
|
109
|
+
expect(contactCore.findContactWithName).toHaveBeenCalledWith({
|
|
110
|
+
platform: 'testCRM',
|
|
111
|
+
userId: 'user-123',
|
|
112
|
+
name: 'Jane'
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('should return error when contact not found', async () => {
|
|
117
|
+
// Arrange
|
|
118
|
+
jwt.decodeJwt.mockReturnValue({
|
|
119
|
+
id: 'user-123',
|
|
120
|
+
platform: 'testCRM'
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const mockConnector = {
|
|
124
|
+
findContactWithName: jest.fn()
|
|
125
|
+
};
|
|
126
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
127
|
+
|
|
128
|
+
contactCore.findContactWithName.mockResolvedValue({
|
|
129
|
+
successful: false,
|
|
130
|
+
contact: null,
|
|
131
|
+
returnMessage: { message: 'Contact not found' }
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Act
|
|
135
|
+
const result = await findContactByName.execute({
|
|
136
|
+
jwtToken: 'mock-jwt-token',
|
|
137
|
+
name: 'NonExistent Person'
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Assert
|
|
141
|
+
expect(result).toEqual({
|
|
142
|
+
success: false,
|
|
143
|
+
error: 'Contact not found'
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('should return error when JWT is invalid', async () => {
|
|
148
|
+
// Arrange
|
|
149
|
+
jwt.decodeJwt.mockReturnValue({
|
|
150
|
+
platform: 'testCRM'
|
|
151
|
+
// id is missing
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Act
|
|
155
|
+
const result = await findContactByName.execute({
|
|
156
|
+
jwtToken: 'invalid-token',
|
|
157
|
+
name: 'John Doe'
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Assert
|
|
161
|
+
expect(result.success).toBe(false);
|
|
162
|
+
expect(result.error).toContain('Invalid JWT token');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('should return error when platform connector not found', async () => {
|
|
166
|
+
// Arrange
|
|
167
|
+
jwt.decodeJwt.mockReturnValue({
|
|
168
|
+
id: 'user-123',
|
|
169
|
+
platform: 'unknownCRM'
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
connectorRegistry.getConnector.mockReturnValue(null);
|
|
173
|
+
|
|
174
|
+
// Act
|
|
175
|
+
const result = await findContactByName.execute({
|
|
176
|
+
jwtToken: 'mock-jwt-token',
|
|
177
|
+
name: 'John Doe'
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Assert
|
|
181
|
+
expect(result.success).toBe(false);
|
|
182
|
+
expect(result.error).toContain('Platform connector not found');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('should return error when findContactWithName is not implemented', async () => {
|
|
186
|
+
// Arrange
|
|
187
|
+
jwt.decodeJwt.mockReturnValue({
|
|
188
|
+
id: 'user-123',
|
|
189
|
+
platform: 'testCRM'
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const mockConnector = {}; // No findContactWithName method
|
|
193
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
194
|
+
|
|
195
|
+
// Act
|
|
196
|
+
const result = await findContactByName.execute({
|
|
197
|
+
jwtToken: 'mock-jwt-token',
|
|
198
|
+
name: 'John Doe'
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Assert
|
|
202
|
+
expect(result.success).toBe(false);
|
|
203
|
+
expect(result.error).toContain('not implemented');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('should handle unexpected errors gracefully', async () => {
|
|
207
|
+
// Arrange
|
|
208
|
+
jwt.decodeJwt.mockReturnValue({
|
|
209
|
+
id: 'user-123',
|
|
210
|
+
platform: 'testCRM'
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const mockConnector = {
|
|
214
|
+
findContactWithName: jest.fn()
|
|
215
|
+
};
|
|
216
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
217
|
+
|
|
218
|
+
contactCore.findContactWithName.mockRejectedValue(
|
|
219
|
+
new Error('API rate limit exceeded')
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// Act
|
|
223
|
+
const result = await findContactByName.execute({
|
|
224
|
+
jwtToken: 'mock-jwt-token',
|
|
225
|
+
name: 'John Doe'
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Assert
|
|
229
|
+
expect(result.success).toBe(false);
|
|
230
|
+
expect(result.error).toBe('API rate limit exceeded');
|
|
231
|
+
expect(result.errorDetails).toBeDefined();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('should handle empty name parameter', async () => {
|
|
235
|
+
// Arrange
|
|
236
|
+
jwt.decodeJwt.mockReturnValue({
|
|
237
|
+
id: 'user-123',
|
|
238
|
+
platform: 'testCRM'
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const mockConnector = {
|
|
242
|
+
findContactWithName: jest.fn()
|
|
243
|
+
};
|
|
244
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
245
|
+
|
|
246
|
+
contactCore.findContactWithName.mockResolvedValue({
|
|
247
|
+
successful: false,
|
|
248
|
+
returnMessage: { message: 'Name parameter is required' }
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Act
|
|
252
|
+
const result = await findContactByName.execute({
|
|
253
|
+
jwtToken: 'mock-jwt-token',
|
|
254
|
+
name: ''
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Assert
|
|
258
|
+
expect(result.success).toBe(false);
|
|
259
|
+
expect(result.error).toBe('Name parameter is required');
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|