@app-connect/core 1.7.34 → 1.7.36
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/mock.js +9 -4
- package/connector/proxy/engine.js +3 -2
- package/connector/proxy/index.js +2 -2
- package/docs/architecture.md +1 -0
- package/docs/handlers.md +1 -1
- package/docs/models.md +2 -0
- package/docs/routes.md +1 -1
- package/handlers/disposition.js +3 -2
- package/handlers/log.js +18 -6
- package/handlers/plugin.js +13 -6
- package/index.js +22 -11
- package/lib/callLogLookup.js +82 -9
- package/lib/migrateCallLogsSchema.js +128 -7
- package/models/callLogModel.js +6 -0
- package/package.json +1 -1
- package/releaseNotes.json +44 -0
- package/test/connector/developerPortal.test.js +166 -0
- package/test/connector/mock.test.js +131 -0
- package/test/connector/proxy/engine.test.js +85 -0
- package/test/connector/proxy/index.test.js +246 -0
- package/test/connector/proxy/sample.json +1 -0
- package/test/handlers/admin.test.js +344 -0
- package/test/handlers/appointment.test.js +260 -0
- package/test/handlers/auth.test.js +6 -2
- package/test/handlers/calldown.test.js +310 -0
- package/test/handlers/disposition.test.js +396 -0
- package/test/handlers/log.test.js +327 -3
- package/test/handlers/managedOAuth.test.js +262 -0
- package/test/handlers/plugin.test.js +305 -0
- package/test/handlers/user.test.js +381 -0
- package/test/index.test.js +166 -1
- package/test/lib/analytics.test.js +146 -0
- package/test/lib/authSession.test.js +173 -0
- package/test/lib/encode.test.js +59 -0
- package/test/lib/errorHandler.test.js +246 -0
- package/test/lib/generalErrorMessage.test.js +82 -0
- package/test/lib/s3ErrorLogReport.test.js +187 -0
- package/test/mcp/mcpHandlerMore.test.js +384 -0
- package/test/mcp/tools/appointmentTools.test.js +362 -0
- package/test/models/callDownListModel.test.js +125 -0
- package/test/models/dynamo/lockSchema.test.js +37 -0
- package/test/models/dynamo/noteCacheSchema.test.js +45 -0
- package/test/models/llmSessionModel.test.js +91 -0
- package/test/models/models.test.js +92 -0
- package/test/routes/calldownRoutes.test.js +224 -0
- package/test/routes/coreRouterBroadRoutes.test.js +855 -0
- package/test/routes/dispositionRoutes.test.js +192 -0
- package/test/routes/managedAuthRoutes.test.js +151 -0
- package/test/routes/pluginRoutes.test.js +262 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const request = require('supertest');
|
|
3
|
+
|
|
4
|
+
jest.mock('../../handlers/disposition', () => ({
|
|
5
|
+
upsertCallDisposition: jest.fn(),
|
|
6
|
+
}));
|
|
7
|
+
jest.mock('../../lib/analytics', () => ({
|
|
8
|
+
init: jest.fn(),
|
|
9
|
+
track: jest.fn(),
|
|
10
|
+
}));
|
|
11
|
+
jest.mock('../../lib/jwt', () => ({
|
|
12
|
+
decodeJwt: jest.fn(),
|
|
13
|
+
generateJwt: jest.fn().mockReturnValue('refreshed-crm-jwt'),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
const disposition = require('../../handlers/disposition');
|
|
17
|
+
const analytics = require('../../lib/analytics');
|
|
18
|
+
const jwt = require('../../lib/jwt');
|
|
19
|
+
const { createCoreRouter } = require('../../index');
|
|
20
|
+
|
|
21
|
+
describe('Disposition Routes', () => {
|
|
22
|
+
let app;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
jest.clearAllMocks();
|
|
26
|
+
jwt.decodeJwt.mockReturnValue({
|
|
27
|
+
id: 'crm-user-id',
|
|
28
|
+
platform: 'testCRM',
|
|
29
|
+
});
|
|
30
|
+
app = express();
|
|
31
|
+
app.use(express.json());
|
|
32
|
+
app.use('/', createCoreRouter());
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('PUT /callDisposition delegates decoded CRM identity and request body', async () => {
|
|
36
|
+
disposition.upsertCallDisposition.mockResolvedValue({
|
|
37
|
+
successful: true,
|
|
38
|
+
returnMessage: {
|
|
39
|
+
message: 'Disposition saved',
|
|
40
|
+
messageType: 'success',
|
|
41
|
+
ttl: 2000,
|
|
42
|
+
},
|
|
43
|
+
extraDataTracking: {
|
|
44
|
+
statusCode: 204,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const response = await request(app)
|
|
49
|
+
.put('/callDisposition')
|
|
50
|
+
.query({ jwtToken: 'crm-jwt' })
|
|
51
|
+
.send({
|
|
52
|
+
sessionId: 'session-1',
|
|
53
|
+
extensionNumber: '101',
|
|
54
|
+
hashedExtensionId: 'hashed-extension-1',
|
|
55
|
+
dispositions: [{ id: 'disp-1' }],
|
|
56
|
+
additionalSubmission: { note: 'extra' },
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(response.status).toBe(200);
|
|
60
|
+
expect(response.body).toEqual({
|
|
61
|
+
successful: true,
|
|
62
|
+
returnMessage: {
|
|
63
|
+
message: 'Disposition saved',
|
|
64
|
+
messageType: 'success',
|
|
65
|
+
ttl: 2000,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
expect(disposition.upsertCallDisposition).toHaveBeenCalledWith({
|
|
69
|
+
platform: 'testCRM',
|
|
70
|
+
userId: 'crm-user-id',
|
|
71
|
+
sessionId: 'session-1',
|
|
72
|
+
extensionNumber: '101',
|
|
73
|
+
hashedExtensionId: 'hashed-extension-1',
|
|
74
|
+
dispositions: [{ id: 'disp-1' }],
|
|
75
|
+
additionalSubmission: { note: 'extra' },
|
|
76
|
+
});
|
|
77
|
+
expect(analytics.track).toHaveBeenCalledWith(expect.objectContaining({
|
|
78
|
+
eventName: 'Disposition call log',
|
|
79
|
+
interfaceName: 'dispositionCallLog',
|
|
80
|
+
connectorName: 'testCRM',
|
|
81
|
+
success: true,
|
|
82
|
+
extras: {
|
|
83
|
+
statusCode: 204,
|
|
84
|
+
},
|
|
85
|
+
}));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('PUT /callDisposition rejects missing CRM auth token', async () => {
|
|
89
|
+
const response = await request(app)
|
|
90
|
+
.put('/callDisposition')
|
|
91
|
+
.send({
|
|
92
|
+
sessionId: 'session-1',
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(response.status).toBe(400);
|
|
96
|
+
expect(response.text).toBe('Please go to Settings and authorize CRM platform');
|
|
97
|
+
expect(disposition.upsertCallDisposition).not.toHaveBeenCalled();
|
|
98
|
+
expect(analytics.track).toHaveBeenCalledWith(expect.objectContaining({
|
|
99
|
+
eventName: 'Disposition call log',
|
|
100
|
+
success: false,
|
|
101
|
+
}));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('PUT /callDisposition rejects invalid JWT token', async () => {
|
|
105
|
+
jwt.decodeJwt.mockReturnValue(null);
|
|
106
|
+
|
|
107
|
+
const response = await request(app)
|
|
108
|
+
.put('/callDisposition')
|
|
109
|
+
.query({ jwtToken: 'invalid-jwt' })
|
|
110
|
+
.send({
|
|
111
|
+
sessionId: 'session-1',
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(response.status).toBe(400);
|
|
115
|
+
expect(response.text).toBe('Invalid JWT token');
|
|
116
|
+
expect(disposition.upsertCallDisposition).not.toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('PUT /callDisposition rejects decoded token without user id', async () => {
|
|
120
|
+
jwt.decodeJwt.mockReturnValue({
|
|
121
|
+
platform: 'testCRM',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const response = await request(app)
|
|
125
|
+
.put('/callDisposition')
|
|
126
|
+
.query({ jwtToken: 'crm-jwt' })
|
|
127
|
+
.send({
|
|
128
|
+
sessionId: 'session-1',
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(response.status).toBe(400);
|
|
132
|
+
expect(response.text).toBe('Please go to Settings and authorize CRM platform');
|
|
133
|
+
expect(disposition.upsertCallDisposition).not.toHaveBeenCalled();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('PUT /callDisposition returns 401 when handler asks to revoke user session', async () => {
|
|
137
|
+
disposition.upsertCallDisposition.mockResolvedValue({
|
|
138
|
+
successful: false,
|
|
139
|
+
returnMessage: {
|
|
140
|
+
message: 'User session expired. Please connect again.',
|
|
141
|
+
messageType: 'warning',
|
|
142
|
+
ttl: 5000,
|
|
143
|
+
},
|
|
144
|
+
isRevokeUserSession: true,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const response = await request(app)
|
|
148
|
+
.put('/callDisposition')
|
|
149
|
+
.query({ jwtToken: 'crm-jwt' })
|
|
150
|
+
.send({
|
|
151
|
+
sessionId: 'session-1',
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
expect(response.status).toBe(401);
|
|
155
|
+
expect(response.body).toEqual({
|
|
156
|
+
successful: false,
|
|
157
|
+
returnMessage: {
|
|
158
|
+
message: 'User session expired. Please connect again.',
|
|
159
|
+
messageType: 'warning',
|
|
160
|
+
ttl: 5000,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
expect(analytics.track).toHaveBeenCalledWith(expect.objectContaining({
|
|
164
|
+
eventName: 'Disposition call log',
|
|
165
|
+
success: false,
|
|
166
|
+
}));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('PUT /callDisposition maps handler exceptions to 400 error response', async () => {
|
|
170
|
+
disposition.upsertCallDisposition.mockRejectedValue(new Error('provider failed'));
|
|
171
|
+
|
|
172
|
+
const response = await request(app)
|
|
173
|
+
.put('/callDisposition')
|
|
174
|
+
.query({ jwtToken: 'crm-jwt' })
|
|
175
|
+
.send({
|
|
176
|
+
sessionId: 'session-1',
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
expect(response.status).toBe(400);
|
|
180
|
+
expect(response.body).toEqual({
|
|
181
|
+
error: 'provider failed',
|
|
182
|
+
});
|
|
183
|
+
expect(analytics.track).toHaveBeenCalledWith(expect.objectContaining({
|
|
184
|
+
eventName: 'Disposition call log',
|
|
185
|
+
success: false,
|
|
186
|
+
extras: {
|
|
187
|
+
statusCode: 'unknown',
|
|
188
|
+
},
|
|
189
|
+
}));
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
@@ -7,6 +7,9 @@ jest.mock('../../handlers/admin', () => ({
|
|
|
7
7
|
}));
|
|
8
8
|
jest.mock('../../handlers/managedAuth', () => ({
|
|
9
9
|
getManagedAuthState: jest.fn(),
|
|
10
|
+
getManagedAuthAdminSettings: jest.fn(),
|
|
11
|
+
upsertOrgManagedAuthValues: jest.fn(),
|
|
12
|
+
upsertUserManagedAuthValues: jest.fn(),
|
|
10
13
|
}));
|
|
11
14
|
jest.mock('../../handlers/managedOAuth', () => ({
|
|
12
15
|
getManagedOAuthState: jest.fn(),
|
|
@@ -21,11 +24,18 @@ jest.mock('../../lib/jwt', () => ({
|
|
|
21
24
|
generateJwt: jest.fn().mockReturnValue('jwt-token'),
|
|
22
25
|
decodeJwt: jest.fn(),
|
|
23
26
|
}));
|
|
27
|
+
jest.mock('../../models/userModel', () => ({
|
|
28
|
+
UserModel: {
|
|
29
|
+
findByPk: jest.fn(),
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
24
32
|
|
|
25
33
|
const adminCore = require('../../handlers/admin');
|
|
26
34
|
const managedAuthCore = require('../../handlers/managedAuth');
|
|
27
35
|
const managedOAuthCore = require('../../handlers/managedOAuth');
|
|
28
36
|
const authCore = require('../../handlers/auth');
|
|
37
|
+
const jwt = require('../../lib/jwt');
|
|
38
|
+
const { UserModel } = require('../../models/userModel');
|
|
29
39
|
const { createCoreRouter } = require('../../index');
|
|
30
40
|
|
|
31
41
|
describe('Managed Auth Routes', () => {
|
|
@@ -194,6 +204,147 @@ describe('Managed Auth Routes', () => {
|
|
|
194
204
|
});
|
|
195
205
|
});
|
|
196
206
|
|
|
207
|
+
|
|
208
|
+
describe('GET /admin/managedAuth', () => {
|
|
209
|
+
test('should use CRM jwt platform and validated admin account for settings lookup', async () => {
|
|
210
|
+
jwt.decodeJwt.mockReturnValue({ id: 'crm-user-id' });
|
|
211
|
+
UserModel.findByPk.mockResolvedValue({
|
|
212
|
+
id: 'crm-user-id',
|
|
213
|
+
platform: 'salesforce',
|
|
214
|
+
});
|
|
215
|
+
adminCore.validateAdminRole.mockResolvedValue({
|
|
216
|
+
isValidated: true,
|
|
217
|
+
rcAccountId: 'validated-account-id',
|
|
218
|
+
});
|
|
219
|
+
managedAuthCore.getManagedAuthAdminSettings.mockResolvedValue({
|
|
220
|
+
orgFields: [{ const: 'apiKey' }],
|
|
221
|
+
userFields: [{ const: 'userToken' }],
|
|
222
|
+
orgValues: {},
|
|
223
|
+
userValues: [],
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const response = await request(app)
|
|
227
|
+
.get('/admin/managedAuth')
|
|
228
|
+
.query({
|
|
229
|
+
jwtToken: 'crm-jwt',
|
|
230
|
+
rcAccessToken: 'valid-rc-token',
|
|
231
|
+
connectorId: 'shared-connector',
|
|
232
|
+
isPrivate: 'true',
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
expect(response.status).toBe(200);
|
|
236
|
+
expect(jwt.decodeJwt).toHaveBeenCalledWith('crm-jwt');
|
|
237
|
+
expect(UserModel.findByPk).toHaveBeenCalledWith('crm-user-id');
|
|
238
|
+
expect(adminCore.validateAdminRole).toHaveBeenCalledWith({ rcAccessToken: 'valid-rc-token' });
|
|
239
|
+
expect(managedAuthCore.getManagedAuthAdminSettings).toHaveBeenCalledWith({
|
|
240
|
+
platform: 'salesforce',
|
|
241
|
+
rcAccountId: 'validated-account-id',
|
|
242
|
+
connectorId: 'shared-connector',
|
|
243
|
+
isPrivate: true,
|
|
244
|
+
});
|
|
245
|
+
expect(response.body.orgFields).toEqual([{ const: 'apiKey' }]);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test('should reject non-admin users before reading managed auth settings', async () => {
|
|
249
|
+
jwt.decodeJwt.mockReturnValue({ id: 'crm-user-id' });
|
|
250
|
+
UserModel.findByPk.mockResolvedValue({
|
|
251
|
+
id: 'crm-user-id',
|
|
252
|
+
platform: 'salesforce',
|
|
253
|
+
});
|
|
254
|
+
adminCore.validateAdminRole.mockResolvedValue({
|
|
255
|
+
isValidated: false,
|
|
256
|
+
rcAccountId: 'validated-account-id',
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const response = await request(app)
|
|
260
|
+
.get('/admin/managedAuth')
|
|
261
|
+
.query({
|
|
262
|
+
jwtToken: 'crm-jwt',
|
|
263
|
+
rcAccessToken: 'valid-rc-token',
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(response.status).toBe(403);
|
|
267
|
+
expect(response.text).toBe('Admin validation failed');
|
|
268
|
+
expect(managedAuthCore.getManagedAuthAdminSettings).not.toHaveBeenCalled();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('POST /admin/managedAuth', () => {
|
|
273
|
+
test('should save org scoped values under the validated admin account and CRM platform', async () => {
|
|
274
|
+
jwt.decodeJwt.mockReturnValue({ id: 'crm-user-id' });
|
|
275
|
+
UserModel.findByPk.mockResolvedValue({
|
|
276
|
+
id: 'crm-user-id',
|
|
277
|
+
platform: 'salesforce',
|
|
278
|
+
});
|
|
279
|
+
adminCore.validateAdminRole.mockResolvedValue({
|
|
280
|
+
isValidated: true,
|
|
281
|
+
rcAccountId: 'validated-account-id',
|
|
282
|
+
});
|
|
283
|
+
managedAuthCore.upsertOrgManagedAuthValues.mockResolvedValue({});
|
|
284
|
+
|
|
285
|
+
const response = await request(app)
|
|
286
|
+
.post('/admin/managedAuth')
|
|
287
|
+
.query({
|
|
288
|
+
jwtToken: 'crm-jwt',
|
|
289
|
+
rcAccessToken: 'valid-rc-token',
|
|
290
|
+
connectorId: 'private-connector',
|
|
291
|
+
isPrivate: 'true',
|
|
292
|
+
})
|
|
293
|
+
.send({
|
|
294
|
+
scope: 'org',
|
|
295
|
+
values: { apiKey: 'api-key' },
|
|
296
|
+
fieldsToRemove: ['oldApiKey'],
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
expect(response.status).toBe(200);
|
|
300
|
+
expect(response.text).toBe('Shared authentication updated');
|
|
301
|
+
expect(managedAuthCore.upsertOrgManagedAuthValues).toHaveBeenCalledWith({
|
|
302
|
+
rcAccountId: 'validated-account-id',
|
|
303
|
+
platform: 'salesforce',
|
|
304
|
+
values: { apiKey: 'api-key' },
|
|
305
|
+
fieldsToRemove: ['oldApiKey'],
|
|
306
|
+
});
|
|
307
|
+
expect(managedAuthCore.upsertUserManagedAuthValues).not.toHaveBeenCalled();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test('should save user scoped values with selected RingCentral extension details', async () => {
|
|
311
|
+
jwt.decodeJwt.mockReturnValue({ id: 'crm-user-id' });
|
|
312
|
+
UserModel.findByPk.mockResolvedValue({
|
|
313
|
+
id: 'crm-user-id',
|
|
314
|
+
platform: 'salesforce',
|
|
315
|
+
});
|
|
316
|
+
adminCore.validateAdminRole.mockResolvedValue({
|
|
317
|
+
isValidated: true,
|
|
318
|
+
rcAccountId: 'validated-account-id',
|
|
319
|
+
});
|
|
320
|
+
managedAuthCore.upsertUserManagedAuthValues.mockResolvedValue({});
|
|
321
|
+
|
|
322
|
+
const response = await request(app)
|
|
323
|
+
.post('/admin/managedAuth')
|
|
324
|
+
.query({
|
|
325
|
+
jwtToken: 'crm-jwt',
|
|
326
|
+
rcAccessToken: 'valid-rc-token',
|
|
327
|
+
})
|
|
328
|
+
.send({
|
|
329
|
+
scope: 'user',
|
|
330
|
+
rcExtensionId: '101',
|
|
331
|
+
rcUserName: 'Ada Lovelace',
|
|
332
|
+
values: { userToken: 'token-101' },
|
|
333
|
+
fieldsToRemove: ['oldUserToken'],
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
expect(response.status).toBe(200);
|
|
337
|
+
expect(managedAuthCore.upsertUserManagedAuthValues).toHaveBeenCalledWith({
|
|
338
|
+
rcAccountId: 'validated-account-id',
|
|
339
|
+
platform: 'salesforce',
|
|
340
|
+
rcExtensionId: '101',
|
|
341
|
+
rcUserName: 'Ada Lovelace',
|
|
342
|
+
values: { userToken: 'token-101' },
|
|
343
|
+
fieldsToRemove: ['oldUserToken'],
|
|
344
|
+
});
|
|
345
|
+
expect(managedAuthCore.upsertOrgManagedAuthValues).not.toHaveBeenCalled();
|
|
346
|
+
});
|
|
347
|
+
});
|
|
197
348
|
describe('POST /apiKeyLogin', () => {
|
|
198
349
|
|
|
199
350
|
test('should validate rcAccessToken and ignore spoofed rc ids in body', async () => {
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const request = require('supertest');
|
|
3
|
+
|
|
4
|
+
jest.mock('../../handlers/admin', () => ({
|
|
5
|
+
validateAdminRole: jest.fn(),
|
|
6
|
+
}));
|
|
7
|
+
jest.mock('../../handlers/plugin', () => ({
|
|
8
|
+
registerPluginAccount: jest.fn(),
|
|
9
|
+
unregisterPluginAccount: jest.fn(),
|
|
10
|
+
getPluginLicenseStatus: jest.fn(),
|
|
11
|
+
}));
|
|
12
|
+
jest.mock('../../lib/analytics', () => ({
|
|
13
|
+
init: jest.fn(),
|
|
14
|
+
track: jest.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
jest.mock('../../lib/jwt', () => ({
|
|
18
|
+
decodeJwt: jest.fn(),
|
|
19
|
+
generateJwt: jest.fn().mockReturnValue('refreshed-crm-jwt'),
|
|
20
|
+
}));
|
|
21
|
+
jest.mock('../../models/userModel', () => ({
|
|
22
|
+
UserModel: {
|
|
23
|
+
findByPk: jest.fn(),
|
|
24
|
+
},
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const adminCore = require('../../handlers/admin');
|
|
28
|
+
const pluginCore = require('../../handlers/plugin');
|
|
29
|
+
const analytics = require('../../lib/analytics');
|
|
30
|
+
const jwt = require('../../lib/jwt');
|
|
31
|
+
const { UserModel } = require('../../models/userModel');
|
|
32
|
+
const { createCoreRouter } = require('../../index');
|
|
33
|
+
|
|
34
|
+
describe('Plugin Routes', () => {
|
|
35
|
+
let app;
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
jest.clearAllMocks();
|
|
39
|
+
app = express();
|
|
40
|
+
app.use(express.json());
|
|
41
|
+
app.use('/', createCoreRouter());
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('POST /plugin/register', () => {
|
|
45
|
+
test('should validate admin role, reject spoofed account ids, and register the selected plugin', async () => {
|
|
46
|
+
adminCore.validateAdminRole.mockResolvedValue({
|
|
47
|
+
isValidated: true,
|
|
48
|
+
rcAccountId: 'validated-account-id',
|
|
49
|
+
});
|
|
50
|
+
pluginCore.registerPluginAccount.mockResolvedValue({ successful: true });
|
|
51
|
+
|
|
52
|
+
const response = await request(app)
|
|
53
|
+
.post('/plugin/register')
|
|
54
|
+
.query({ rcAccessToken: 'valid-rc-token' })
|
|
55
|
+
.send({
|
|
56
|
+
pluginId: 'plugin-1',
|
|
57
|
+
rcAccountId: 'validated-account-id',
|
|
58
|
+
pluginAccess: 'shared',
|
|
59
|
+
pluginName: 'plugin.sample',
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(response.status).toBe(200);
|
|
63
|
+
expect(response.body).toEqual({ successful: true });
|
|
64
|
+
expect(adminCore.validateAdminRole).toHaveBeenCalledWith({ rcAccessToken: 'valid-rc-token' });
|
|
65
|
+
expect(pluginCore.registerPluginAccount).toHaveBeenCalledWith({
|
|
66
|
+
pluginId: 'plugin-1',
|
|
67
|
+
rcAccessToken: 'valid-rc-token',
|
|
68
|
+
rcAccountId: 'validated-account-id',
|
|
69
|
+
pluginAccess: 'shared',
|
|
70
|
+
pluginName: 'plugin.sample',
|
|
71
|
+
});
|
|
72
|
+
expect(analytics.track).toHaveBeenCalledWith(expect.objectContaining({
|
|
73
|
+
eventName: 'Plugin Register',
|
|
74
|
+
interfaceName: 'pluginRegister',
|
|
75
|
+
success: true,
|
|
76
|
+
}));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('should return a failed register response when plugin provider registration fails', async () => {
|
|
80
|
+
adminCore.validateAdminRole.mockResolvedValue({
|
|
81
|
+
isValidated: true,
|
|
82
|
+
rcAccountId: 'validated-account-id',
|
|
83
|
+
});
|
|
84
|
+
pluginCore.registerPluginAccount.mockRejectedValue(new Error('Plugin register API did not return jwtToken'));
|
|
85
|
+
|
|
86
|
+
const response = await request(app)
|
|
87
|
+
.post('/plugin/register')
|
|
88
|
+
.query({ rcAccessToken: 'valid-rc-token' })
|
|
89
|
+
.send({
|
|
90
|
+
pluginId: 'plugin-1',
|
|
91
|
+
rcAccountId: 'validated-account-id',
|
|
92
|
+
pluginAccess: 'shared',
|
|
93
|
+
pluginName: 'plugin.sample',
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(response.status).toBe(400);
|
|
97
|
+
expect(response.body).toEqual({
|
|
98
|
+
successful: false,
|
|
99
|
+
returnMessage: 'Plugin register API did not return jwtToken',
|
|
100
|
+
});
|
|
101
|
+
expect(analytics.track).toHaveBeenCalledWith(expect.objectContaining({
|
|
102
|
+
eventName: 'Plugin Register',
|
|
103
|
+
interfaceName: 'pluginRegister',
|
|
104
|
+
success: false,
|
|
105
|
+
}));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('should reject register requests when the body account does not match the validated admin account', async () => {
|
|
109
|
+
adminCore.validateAdminRole.mockResolvedValue({
|
|
110
|
+
isValidated: true,
|
|
111
|
+
rcAccountId: 'validated-account-id',
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const response = await request(app)
|
|
115
|
+
.post('/plugin/register')
|
|
116
|
+
.query({ rcAccessToken: 'valid-rc-token' })
|
|
117
|
+
.send({
|
|
118
|
+
pluginId: 'plugin-1',
|
|
119
|
+
rcAccountId: 'spoofed-account-id',
|
|
120
|
+
pluginAccess: 'shared',
|
|
121
|
+
pluginName: 'plugin.sample',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(response.status).toBe(403);
|
|
125
|
+
expect(response.body).toEqual({
|
|
126
|
+
successful: false,
|
|
127
|
+
returnMessage: 'rcAccountId mismatch',
|
|
128
|
+
});
|
|
129
|
+
expect(pluginCore.registerPluginAccount).not.toHaveBeenCalled();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
describe('GET /plugin/licenseStatus', () => {
|
|
135
|
+
test('should require a CRM user session and return plugin license status for the requested account', async () => {
|
|
136
|
+
jwt.decodeJwt.mockReturnValue({ id: 'crm-user-id', platform: 'salesforce' });
|
|
137
|
+
UserModel.findByPk.mockResolvedValue({
|
|
138
|
+
id: 'crm-user-id',
|
|
139
|
+
platform: 'salesforce',
|
|
140
|
+
});
|
|
141
|
+
pluginCore.getPluginLicenseStatus.mockResolvedValue({
|
|
142
|
+
licenseStatus: true,
|
|
143
|
+
licenseStatusDescription: 'Active',
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const response = await request(app)
|
|
147
|
+
.get('/plugin/licenseStatus')
|
|
148
|
+
.set('Authorization', 'Bearer crm-jwt')
|
|
149
|
+
.query({
|
|
150
|
+
rcAccountId: 'validated-account-id',
|
|
151
|
+
pluginId: 'plugin-1',
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
expect(response.status).toBe(200);
|
|
155
|
+
expect(jwt.decodeJwt).toHaveBeenCalledWith('crm-jwt');
|
|
156
|
+
expect(UserModel.findByPk).toHaveBeenCalledWith('crm-user-id');
|
|
157
|
+
expect(pluginCore.getPluginLicenseStatus).toHaveBeenCalledWith({
|
|
158
|
+
rcAccountId: 'validated-account-id',
|
|
159
|
+
pluginId: 'plugin-1',
|
|
160
|
+
});
|
|
161
|
+
expect(response.body).toEqual({
|
|
162
|
+
licenseStatus: true,
|
|
163
|
+
licenseStatusDescription: 'Active',
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('should degrade plugin license provider errors to an invalid license response', async () => {
|
|
168
|
+
jwt.decodeJwt.mockReturnValue({ id: 'crm-user-id', platform: 'salesforce' });
|
|
169
|
+
UserModel.findByPk.mockResolvedValue({
|
|
170
|
+
id: 'crm-user-id',
|
|
171
|
+
platform: 'salesforce',
|
|
172
|
+
});
|
|
173
|
+
pluginCore.getPluginLicenseStatus.mockRejectedValue(new Error('provider timeout'));
|
|
174
|
+
|
|
175
|
+
const response = await request(app)
|
|
176
|
+
.get('/plugin/licenseStatus')
|
|
177
|
+
.set('Authorization', 'Bearer crm-jwt')
|
|
178
|
+
.query({
|
|
179
|
+
rcAccountId: 'validated-account-id',
|
|
180
|
+
pluginId: 'plugin-1',
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(response.status).toBe(200);
|
|
184
|
+
expect(pluginCore.getPluginLicenseStatus).toHaveBeenCalledWith({
|
|
185
|
+
rcAccountId: 'validated-account-id',
|
|
186
|
+
pluginId: 'plugin-1',
|
|
187
|
+
});
|
|
188
|
+
expect(response.body).toEqual({
|
|
189
|
+
licenseStatus: false,
|
|
190
|
+
licenseStatusDescription: 'provider timeout',
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('should not query plugin license status without a CRM user session', async () => {
|
|
195
|
+
const response = await request(app)
|
|
196
|
+
.get('/plugin/licenseStatus')
|
|
197
|
+
.query({
|
|
198
|
+
rcAccountId: 'validated-account-id',
|
|
199
|
+
pluginId: 'plugin-1',
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
expect(response.status).toBe(400);
|
|
203
|
+
expect(response.text).toBe('Please go to Settings and authorize CRM platform');
|
|
204
|
+
expect(pluginCore.getPluginLicenseStatus).not.toHaveBeenCalled();
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
describe('DELETE /plugin/unregister', () => {
|
|
208
|
+
test('should validate admin role and unregister the selected plugin for the validated account', async () => {
|
|
209
|
+
adminCore.validateAdminRole.mockResolvedValue({
|
|
210
|
+
isValidated: true,
|
|
211
|
+
rcAccountId: 'validated-account-id',
|
|
212
|
+
});
|
|
213
|
+
pluginCore.unregisterPluginAccount.mockResolvedValue({ successful: true });
|
|
214
|
+
|
|
215
|
+
const response = await request(app)
|
|
216
|
+
.delete('/plugin/unregister')
|
|
217
|
+
.query({
|
|
218
|
+
rcAccessToken: 'valid-rc-token',
|
|
219
|
+
pluginId: 'plugin-1',
|
|
220
|
+
rcAccountId: 'validated-account-id',
|
|
221
|
+
pluginName: 'plugin.sample',
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
expect(response.status).toBe(200);
|
|
225
|
+
expect(response.body).toEqual({ successful: true });
|
|
226
|
+
expect(adminCore.validateAdminRole).toHaveBeenCalledWith({ rcAccessToken: 'valid-rc-token' });
|
|
227
|
+
expect(pluginCore.unregisterPluginAccount).toHaveBeenCalledWith({
|
|
228
|
+
pluginId: 'plugin-1',
|
|
229
|
+
rcAccountId: 'validated-account-id',
|
|
230
|
+
pluginName: 'plugin.sample',
|
|
231
|
+
});
|
|
232
|
+
expect(analytics.track).toHaveBeenCalledWith(expect.objectContaining({
|
|
233
|
+
eventName: 'Plugin Unregister',
|
|
234
|
+
interfaceName: 'pluginUnregister',
|
|
235
|
+
success: true,
|
|
236
|
+
}));
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('should reject unregister requests from non-admin users', async () => {
|
|
240
|
+
adminCore.validateAdminRole.mockResolvedValue({
|
|
241
|
+
isValidated: false,
|
|
242
|
+
rcAccountId: 'validated-account-id',
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const response = await request(app)
|
|
246
|
+
.delete('/plugin/unregister')
|
|
247
|
+
.query({
|
|
248
|
+
rcAccessToken: 'valid-rc-token',
|
|
249
|
+
pluginId: 'plugin-1',
|
|
250
|
+
rcAccountId: 'validated-account-id',
|
|
251
|
+
pluginName: 'plugin.sample',
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
expect(response.status).toBe(403);
|
|
255
|
+
expect(response.body).toEqual({
|
|
256
|
+
successful: false,
|
|
257
|
+
returnMessage: 'Admin validation failed',
|
|
258
|
+
});
|
|
259
|
+
expect(pluginCore.unregisterPluginAccount).not.toHaveBeenCalled();
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
});
|