@app-connect/core 1.7.8 → 1.7.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/handlers/auth.js +10 -4
- package/handlers/contact.js +42 -9
- package/handlers/log.js +4 -4
- package/index.js +101 -79
- package/lib/oauth.js +0 -2
- package/models/accountDataModel.js +34 -0
- package/package.json +70 -69
- package/releaseNotes.json +24 -0
- package/test/connector/registry.test.js +145 -0
- package/test/handlers/admin.test.js +583 -0
- package/test/handlers/auth.test.js +355 -0
- package/test/handlers/contact.test.js +852 -0
- package/test/handlers/log.test.js +868 -0
- package/test/lib/callLogComposer.test.js +1231 -0
- package/test/lib/debugTracer.test.js +328 -0
- package/test/lib/oauth.test.js +359 -0
- package/test/lib/ringcentral.test.js +473 -0
- package/test/lib/util.test.js +282 -0
- package/test/models/accountDataModel.test.js +98 -0
- package/test/models/dynamo/connectorSchema.test.js +189 -0
- package/test/models/models.test.js +539 -0
- package/test/setup.js +176 -176
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
// Use in-memory SQLite for isolated model tests
|
|
2
|
+
jest.mock('../../models/sequelize', () => {
|
|
3
|
+
const { Sequelize } = require('sequelize');
|
|
4
|
+
return {
|
|
5
|
+
sequelize: new Sequelize({
|
|
6
|
+
dialect: 'sqlite',
|
|
7
|
+
storage: ':memory:',
|
|
8
|
+
logging: false,
|
|
9
|
+
}),
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
jest.mock('../../connector/registry');
|
|
14
|
+
jest.mock('../../lib/oauth');
|
|
15
|
+
jest.mock('../../models/dynamo/connectorSchema', () => ({
|
|
16
|
+
Connector: {
|
|
17
|
+
getProxyConfig: jest.fn()
|
|
18
|
+
}
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
const contactHandler = require('../../handlers/contact');
|
|
22
|
+
const { UserModel } = require('../../models/userModel');
|
|
23
|
+
const { AccountDataModel } = require('../../models/accountDataModel');
|
|
24
|
+
const connectorRegistry = require('../../connector/registry');
|
|
25
|
+
const oauth = require('../../lib/oauth');
|
|
26
|
+
const { Connector } = require('../../models/dynamo/connectorSchema');
|
|
27
|
+
const { sequelize } = require('../../models/sequelize');
|
|
28
|
+
|
|
29
|
+
describe('Contact Handler', () => {
|
|
30
|
+
beforeAll(async () => {
|
|
31
|
+
await UserModel.sync({ force: true });
|
|
32
|
+
await AccountDataModel.sync({ force: true });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(async () => {
|
|
36
|
+
await UserModel.destroy({ where: {} });
|
|
37
|
+
await AccountDataModel.destroy({ where: {} });
|
|
38
|
+
jest.clearAllMocks();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterAll(async () => {
|
|
42
|
+
await sequelize.close();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('findContact', () => {
|
|
46
|
+
const mockUser = {
|
|
47
|
+
id: 'test-user-id',
|
|
48
|
+
platform: 'testCRM',
|
|
49
|
+
accessToken: 'test-access-token',
|
|
50
|
+
rcAccountId: 'rc-account-123',
|
|
51
|
+
platformAdditionalInfo: {}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
test('should return warning when user not found', async () => {
|
|
55
|
+
// Act
|
|
56
|
+
const result = await contactHandler.findContact({
|
|
57
|
+
platform: 'testCRM',
|
|
58
|
+
userId: 'non-existent-user',
|
|
59
|
+
phoneNumber: '+1234567890'
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Assert
|
|
63
|
+
expect(result.successful).toBe(false);
|
|
64
|
+
expect(result.returnMessage.message).toBe('Contact not found');
|
|
65
|
+
expect(result.returnMessage.messageType).toBe('warning');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('should return warning when user has no access token', async () => {
|
|
69
|
+
// Arrange
|
|
70
|
+
await UserModel.create({
|
|
71
|
+
id: 'test-user-id',
|
|
72
|
+
platform: 'testCRM',
|
|
73
|
+
accessToken: null
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Act
|
|
77
|
+
const result = await contactHandler.findContact({
|
|
78
|
+
platform: 'testCRM',
|
|
79
|
+
userId: 'test-user-id',
|
|
80
|
+
phoneNumber: '+1234567890'
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Assert
|
|
84
|
+
expect(result.successful).toBe(false);
|
|
85
|
+
expect(result.returnMessage.message).toBe('Contact not found');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('should return cached contact when available', async () => {
|
|
89
|
+
// Arrange
|
|
90
|
+
await UserModel.create(mockUser);
|
|
91
|
+
|
|
92
|
+
const cachedContact = [
|
|
93
|
+
{ id: 'contact-1', name: 'Cached Contact', type: 'Contact', phone: '+1234567890' }
|
|
94
|
+
];
|
|
95
|
+
await AccountDataModel.create({
|
|
96
|
+
rcAccountId: 'rc-account-123',
|
|
97
|
+
platformName: 'testCRM',
|
|
98
|
+
dataKey: 'contact-+1234567890',
|
|
99
|
+
data: cachedContact
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Act
|
|
103
|
+
const result = await contactHandler.findContact({
|
|
104
|
+
platform: 'testCRM',
|
|
105
|
+
userId: 'test-user-id',
|
|
106
|
+
phoneNumber: '+1234567890',
|
|
107
|
+
isForceRefreshAccountData: false
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Assert
|
|
111
|
+
expect(result.successful).toBe(true);
|
|
112
|
+
expect(result.contact).toEqual(cachedContact);
|
|
113
|
+
// Should not call platform module
|
|
114
|
+
expect(connectorRegistry.getConnector).not.toHaveBeenCalled();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('should bypass cache when isForceRefreshAccountData is true', async () => {
|
|
118
|
+
// Arrange
|
|
119
|
+
await UserModel.create(mockUser);
|
|
120
|
+
|
|
121
|
+
const cachedContact = [
|
|
122
|
+
{ id: 'contact-1', name: 'Cached Contact', type: 'Contact', phone: '+1234567890' }
|
|
123
|
+
];
|
|
124
|
+
await AccountDataModel.create({
|
|
125
|
+
rcAccountId: 'rc-account-123',
|
|
126
|
+
platformName: 'testCRM',
|
|
127
|
+
dataKey: 'contact-+1234567890',
|
|
128
|
+
data: cachedContact
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const freshContact = [
|
|
132
|
+
{ id: 'contact-1', name: 'Fresh Contact', type: 'Contact', phone: '+1234567890' }
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
const mockConnector = {
|
|
136
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
137
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
138
|
+
findContact: jest.fn().mockResolvedValue({
|
|
139
|
+
successful: true,
|
|
140
|
+
matchedContactInfo: freshContact
|
|
141
|
+
})
|
|
142
|
+
};
|
|
143
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
144
|
+
|
|
145
|
+
// Act
|
|
146
|
+
const result = await contactHandler.findContact({
|
|
147
|
+
platform: 'testCRM',
|
|
148
|
+
userId: 'test-user-id',
|
|
149
|
+
phoneNumber: '+1234567890',
|
|
150
|
+
isForceRefreshAccountData: true
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Assert
|
|
154
|
+
expect(result.successful).toBe(true);
|
|
155
|
+
expect(result.contact).toEqual(freshContact);
|
|
156
|
+
expect(mockConnector.findContact).toHaveBeenCalled();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('should find contact with apiKey auth and cache result', async () => {
|
|
160
|
+
// Arrange
|
|
161
|
+
await UserModel.create(mockUser);
|
|
162
|
+
|
|
163
|
+
const matchedContact = [
|
|
164
|
+
{ id: 'contact-new', name: 'New Contact', type: 'Contact', phone: '+9876543210' }
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
const mockConnector = {
|
|
168
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
169
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
170
|
+
findContact: jest.fn().mockResolvedValue({
|
|
171
|
+
successful: true,
|
|
172
|
+
matchedContactInfo: matchedContact
|
|
173
|
+
})
|
|
174
|
+
};
|
|
175
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
176
|
+
|
|
177
|
+
// Act
|
|
178
|
+
const result = await contactHandler.findContact({
|
|
179
|
+
platform: 'testCRM',
|
|
180
|
+
userId: 'test-user-id',
|
|
181
|
+
phoneNumber: '+9876543210'
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Assert
|
|
185
|
+
expect(result.successful).toBe(true);
|
|
186
|
+
expect(result.contact).toEqual(matchedContact);
|
|
187
|
+
expect(mockConnector.getBasicAuth).toHaveBeenCalledWith({ apiKey: 'test-access-token' });
|
|
188
|
+
|
|
189
|
+
// Verify contact was cached
|
|
190
|
+
const cachedData = await AccountDataModel.findOne({
|
|
191
|
+
where: {
|
|
192
|
+
rcAccountId: 'rc-account-123',
|
|
193
|
+
platformName: 'testCRM',
|
|
194
|
+
dataKey: 'contact-+9876543210'
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
expect(cachedData).not.toBeNull();
|
|
198
|
+
expect(cachedData.data).toEqual(matchedContact);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('should find contact with oauth auth', async () => {
|
|
202
|
+
// Arrange
|
|
203
|
+
await UserModel.create(mockUser);
|
|
204
|
+
|
|
205
|
+
const matchedContact = [
|
|
206
|
+
{ id: 'oauth-contact', name: 'OAuth Contact', type: 'Lead' }
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
const mockConnector = {
|
|
210
|
+
getAuthType: jest.fn().mockResolvedValue('oauth'),
|
|
211
|
+
getOauthInfo: jest.fn().mockResolvedValue({
|
|
212
|
+
clientId: 'client-id',
|
|
213
|
+
clientSecret: 'client-secret',
|
|
214
|
+
accessTokenUri: 'https://token.url',
|
|
215
|
+
authorizationUri: 'https://auth.url'
|
|
216
|
+
}),
|
|
217
|
+
findContact: jest.fn().mockResolvedValue({
|
|
218
|
+
successful: true,
|
|
219
|
+
matchedContactInfo: matchedContact
|
|
220
|
+
})
|
|
221
|
+
};
|
|
222
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
223
|
+
|
|
224
|
+
const mockOAuthApp = {};
|
|
225
|
+
oauth.getOAuthApp.mockReturnValue(mockOAuthApp);
|
|
226
|
+
oauth.checkAndRefreshAccessToken.mockResolvedValue({
|
|
227
|
+
...mockUser,
|
|
228
|
+
accessToken: 'refreshed-token'
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Act
|
|
232
|
+
const result = await contactHandler.findContact({
|
|
233
|
+
platform: 'testCRM',
|
|
234
|
+
userId: 'test-user-id',
|
|
235
|
+
phoneNumber: '+1111111111'
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Assert
|
|
239
|
+
expect(result.successful).toBe(true);
|
|
240
|
+
expect(oauth.checkAndRefreshAccessToken).toHaveBeenCalled();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('should use proxy config when proxyId is present', async () => {
|
|
244
|
+
// Arrange
|
|
245
|
+
const userWithProxy = {
|
|
246
|
+
...mockUser,
|
|
247
|
+
platformAdditionalInfo: { proxyId: 'proxy-123' }
|
|
248
|
+
};
|
|
249
|
+
await UserModel.create(userWithProxy);
|
|
250
|
+
|
|
251
|
+
const proxyConfig = { baseUrl: 'https://proxy.example.com' };
|
|
252
|
+
Connector.getProxyConfig.mockResolvedValue(proxyConfig);
|
|
253
|
+
|
|
254
|
+
const matchedContact = [{ id: 'proxy-contact', name: 'Proxy Contact' }];
|
|
255
|
+
|
|
256
|
+
const mockConnector = {
|
|
257
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
258
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
259
|
+
findContact: jest.fn().mockResolvedValue({
|
|
260
|
+
successful: true,
|
|
261
|
+
matchedContactInfo: matchedContact
|
|
262
|
+
})
|
|
263
|
+
};
|
|
264
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
265
|
+
|
|
266
|
+
// Act
|
|
267
|
+
const result = await contactHandler.findContact({
|
|
268
|
+
platform: 'testCRM',
|
|
269
|
+
userId: 'test-user-id',
|
|
270
|
+
phoneNumber: '+2222222222'
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Assert
|
|
274
|
+
expect(result.successful).toBe(true);
|
|
275
|
+
expect(Connector.getProxyConfig).toHaveBeenCalledWith('proxy-123');
|
|
276
|
+
expect(mockConnector.findContact).toHaveBeenCalledWith(
|
|
277
|
+
expect.objectContaining({ proxyConfig })
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('should return warning when no contacts found', async () => {
|
|
282
|
+
// Arrange
|
|
283
|
+
await UserModel.create(mockUser);
|
|
284
|
+
|
|
285
|
+
const mockConnector = {
|
|
286
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
287
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
288
|
+
findContact: jest.fn().mockResolvedValue({
|
|
289
|
+
successful: false,
|
|
290
|
+
matchedContactInfo: null
|
|
291
|
+
})
|
|
292
|
+
};
|
|
293
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
294
|
+
|
|
295
|
+
// Act
|
|
296
|
+
const result = await contactHandler.findContact({
|
|
297
|
+
platform: 'testCRM',
|
|
298
|
+
userId: 'test-user-id',
|
|
299
|
+
phoneNumber: '+9999999999'
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Assert
|
|
303
|
+
expect(result.successful).toBe(false);
|
|
304
|
+
expect(result.returnMessage.message).toBe('Contact not found');
|
|
305
|
+
expect(result.returnMessage.details[0].items[0].text).toContain('+9999999999');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('should return platform returnMessage when provided', async () => {
|
|
309
|
+
// Arrange
|
|
310
|
+
await UserModel.create(mockUser);
|
|
311
|
+
|
|
312
|
+
const customReturnMessage = {
|
|
313
|
+
message: 'Custom platform message',
|
|
314
|
+
messageType: 'info',
|
|
315
|
+
ttl: 5000
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const mockConnector = {
|
|
319
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
320
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
321
|
+
findContact: jest.fn().mockResolvedValue({
|
|
322
|
+
successful: false,
|
|
323
|
+
matchedContactInfo: null,
|
|
324
|
+
returnMessage: customReturnMessage
|
|
325
|
+
})
|
|
326
|
+
};
|
|
327
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
328
|
+
|
|
329
|
+
// Act
|
|
330
|
+
const result = await contactHandler.findContact({
|
|
331
|
+
platform: 'testCRM',
|
|
332
|
+
userId: 'test-user-id',
|
|
333
|
+
phoneNumber: '+8888888888'
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// Assert
|
|
337
|
+
expect(result.returnMessage).toEqual(customReturnMessage);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test('should handle 429 rate limit error', async () => {
|
|
341
|
+
// Arrange
|
|
342
|
+
await UserModel.create(mockUser);
|
|
343
|
+
|
|
344
|
+
const mockConnector = {
|
|
345
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
346
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
347
|
+
findContact: jest.fn().mockRejectedValue({
|
|
348
|
+
response: { status: 429 }
|
|
349
|
+
})
|
|
350
|
+
};
|
|
351
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
352
|
+
|
|
353
|
+
// Act
|
|
354
|
+
const result = await contactHandler.findContact({
|
|
355
|
+
platform: 'testCRM',
|
|
356
|
+
userId: 'test-user-id',
|
|
357
|
+
phoneNumber: '+7777777777'
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Assert
|
|
361
|
+
expect(result.successful).toBe(false);
|
|
362
|
+
expect(result.extraDataTracking.statusCode).toBe(429);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test('should handle 401 authorization error', async () => {
|
|
366
|
+
// Arrange
|
|
367
|
+
await UserModel.create(mockUser);
|
|
368
|
+
|
|
369
|
+
const mockConnector = {
|
|
370
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
371
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
372
|
+
findContact: jest.fn().mockRejectedValue({
|
|
373
|
+
response: { status: 401 }
|
|
374
|
+
})
|
|
375
|
+
};
|
|
376
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
377
|
+
|
|
378
|
+
// Act
|
|
379
|
+
const result = await contactHandler.findContact({
|
|
380
|
+
platform: 'testCRM',
|
|
381
|
+
userId: 'test-user-id',
|
|
382
|
+
phoneNumber: '+6666666666'
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// Assert
|
|
386
|
+
expect(result.successful).toBe(false);
|
|
387
|
+
expect(result.extraDataTracking.statusCode).toBe(401);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test('should update existing cached contact', async () => {
|
|
391
|
+
// Arrange
|
|
392
|
+
await UserModel.create(mockUser);
|
|
393
|
+
|
|
394
|
+
// Create existing cached contact
|
|
395
|
+
await AccountDataModel.create({
|
|
396
|
+
rcAccountId: 'rc-account-123',
|
|
397
|
+
platformName: 'testCRM',
|
|
398
|
+
dataKey: 'contact-+5555555555',
|
|
399
|
+
data: [{ id: 'old-contact', name: 'Old Name' }]
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
const updatedContact = [
|
|
403
|
+
{ id: 'old-contact', name: 'Updated Name', type: 'Contact' }
|
|
404
|
+
];
|
|
405
|
+
|
|
406
|
+
const mockConnector = {
|
|
407
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
408
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
409
|
+
findContact: jest.fn().mockResolvedValue({
|
|
410
|
+
successful: true,
|
|
411
|
+
matchedContactInfo: updatedContact
|
|
412
|
+
})
|
|
413
|
+
};
|
|
414
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
415
|
+
|
|
416
|
+
// Act
|
|
417
|
+
const result = await contactHandler.findContact({
|
|
418
|
+
platform: 'testCRM',
|
|
419
|
+
userId: 'test-user-id',
|
|
420
|
+
phoneNumber: '+5555555555',
|
|
421
|
+
isForceRefreshAccountData: true
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Assert
|
|
425
|
+
expect(result.successful).toBe(true);
|
|
426
|
+
expect(result.contact).toEqual(updatedContact);
|
|
427
|
+
|
|
428
|
+
// Verify cache was updated
|
|
429
|
+
const cachedData = await AccountDataModel.findOne({
|
|
430
|
+
where: {
|
|
431
|
+
rcAccountId: 'rc-account-123',
|
|
432
|
+
platformName: 'testCRM',
|
|
433
|
+
dataKey: 'contact-+5555555555'
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
expect(cachedData.data).toEqual(updatedContact);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test('should work with tracer when provided', async () => {
|
|
440
|
+
// Arrange
|
|
441
|
+
await UserModel.create(mockUser);
|
|
442
|
+
|
|
443
|
+
const mockTracer = {
|
|
444
|
+
trace: jest.fn(),
|
|
445
|
+
traceError: jest.fn()
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const matchedContact = [{ id: 'traced-contact', name: 'Traced Contact' }];
|
|
449
|
+
|
|
450
|
+
const mockConnector = {
|
|
451
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
452
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
453
|
+
findContact: jest.fn().mockResolvedValue({
|
|
454
|
+
successful: true,
|
|
455
|
+
matchedContactInfo: matchedContact
|
|
456
|
+
})
|
|
457
|
+
};
|
|
458
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
459
|
+
|
|
460
|
+
// Act
|
|
461
|
+
const result = await contactHandler.findContact({
|
|
462
|
+
platform: 'testCRM',
|
|
463
|
+
userId: 'test-user-id',
|
|
464
|
+
phoneNumber: '+4444444444',
|
|
465
|
+
tracer: mockTracer
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// Assert
|
|
469
|
+
expect(result.successful).toBe(true);
|
|
470
|
+
expect(mockTracer.trace).toHaveBeenCalled();
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
describe('createContact', () => {
|
|
475
|
+
const mockUser = {
|
|
476
|
+
id: 'test-user-id',
|
|
477
|
+
platform: 'testCRM',
|
|
478
|
+
accessToken: 'test-access-token',
|
|
479
|
+
platformAdditionalInfo: {}
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
test('should return error when user not found', async () => {
|
|
483
|
+
// Act
|
|
484
|
+
const result = await contactHandler.createContact({
|
|
485
|
+
platform: 'testCRM',
|
|
486
|
+
userId: 'non-existent-user',
|
|
487
|
+
phoneNumber: '+1234567890',
|
|
488
|
+
newContactName: 'New Contact',
|
|
489
|
+
newContactType: 'Contact'
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// Assert
|
|
493
|
+
expect(result.successful).toBe(false);
|
|
494
|
+
expect(result.message).toBe('Contact not found');
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
test('should return error when user has no access token', async () => {
|
|
498
|
+
// Arrange
|
|
499
|
+
await UserModel.create({
|
|
500
|
+
id: 'test-user-id',
|
|
501
|
+
platform: 'testCRM',
|
|
502
|
+
accessToken: null
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// Act
|
|
506
|
+
const result = await contactHandler.createContact({
|
|
507
|
+
platform: 'testCRM',
|
|
508
|
+
userId: 'test-user-id',
|
|
509
|
+
phoneNumber: '+1234567890',
|
|
510
|
+
newContactName: 'New Contact',
|
|
511
|
+
newContactType: 'Contact'
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Assert
|
|
515
|
+
expect(result.successful).toBe(false);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test('should successfully create contact with apiKey auth', async () => {
|
|
519
|
+
// Arrange
|
|
520
|
+
await UserModel.create(mockUser);
|
|
521
|
+
|
|
522
|
+
const createdContact = {
|
|
523
|
+
id: 'new-contact-123',
|
|
524
|
+
name: 'New Contact',
|
|
525
|
+
type: 'Contact',
|
|
526
|
+
phone: '+1234567890'
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
const mockConnector = {
|
|
530
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
531
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
532
|
+
createContact: jest.fn().mockResolvedValue({
|
|
533
|
+
contactInfo: createdContact,
|
|
534
|
+
returnMessage: { message: 'Contact created', messageType: 'success', ttl: 2000 }
|
|
535
|
+
})
|
|
536
|
+
};
|
|
537
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
538
|
+
|
|
539
|
+
// Act
|
|
540
|
+
const result = await contactHandler.createContact({
|
|
541
|
+
platform: 'testCRM',
|
|
542
|
+
userId: 'test-user-id',
|
|
543
|
+
phoneNumber: '+1234567890',
|
|
544
|
+
newContactName: 'New Contact',
|
|
545
|
+
newContactType: 'Contact',
|
|
546
|
+
additionalSubmission: {}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// Assert
|
|
550
|
+
expect(result.successful).toBe(true);
|
|
551
|
+
expect(result.contact).toEqual(createdContact);
|
|
552
|
+
expect(mockConnector.createContact).toHaveBeenCalledWith({
|
|
553
|
+
user: expect.any(Object),
|
|
554
|
+
authHeader: 'Basic base64-encoded',
|
|
555
|
+
phoneNumber: '+1234567890',
|
|
556
|
+
newContactName: 'New Contact',
|
|
557
|
+
newContactType: 'Contact',
|
|
558
|
+
additionalSubmission: {},
|
|
559
|
+
proxyConfig: null
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
test('should successfully create contact with oauth auth', async () => {
|
|
564
|
+
// Arrange
|
|
565
|
+
await UserModel.create(mockUser);
|
|
566
|
+
|
|
567
|
+
const createdContact = {
|
|
568
|
+
id: 'oauth-contact-123',
|
|
569
|
+
name: 'OAuth Contact'
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
const mockConnector = {
|
|
573
|
+
getAuthType: jest.fn().mockResolvedValue('oauth'),
|
|
574
|
+
getOauthInfo: jest.fn().mockResolvedValue({
|
|
575
|
+
clientId: 'client-id',
|
|
576
|
+
clientSecret: 'client-secret',
|
|
577
|
+
accessTokenUri: 'https://token.url',
|
|
578
|
+
authorizationUri: 'https://auth.url'
|
|
579
|
+
}),
|
|
580
|
+
createContact: jest.fn().mockResolvedValue({
|
|
581
|
+
contactInfo: createdContact,
|
|
582
|
+
returnMessage: { message: 'Created', messageType: 'success' }
|
|
583
|
+
})
|
|
584
|
+
};
|
|
585
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
586
|
+
|
|
587
|
+
oauth.getOAuthApp.mockReturnValue({});
|
|
588
|
+
oauth.checkAndRefreshAccessToken.mockResolvedValue({
|
|
589
|
+
...mockUser,
|
|
590
|
+
accessToken: 'refreshed-token'
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// Act
|
|
594
|
+
const result = await contactHandler.createContact({
|
|
595
|
+
platform: 'testCRM',
|
|
596
|
+
userId: 'test-user-id',
|
|
597
|
+
phoneNumber: '+1234567890',
|
|
598
|
+
newContactName: 'OAuth Contact',
|
|
599
|
+
newContactType: 'Lead'
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
// Assert
|
|
603
|
+
expect(result.successful).toBe(true);
|
|
604
|
+
expect(result.contact).toEqual(createdContact);
|
|
605
|
+
expect(oauth.checkAndRefreshAccessToken).toHaveBeenCalled();
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
test('should return unsuccessful when platform returns null contactInfo', async () => {
|
|
609
|
+
// Arrange
|
|
610
|
+
await UserModel.create(mockUser);
|
|
611
|
+
|
|
612
|
+
const mockConnector = {
|
|
613
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
614
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
615
|
+
createContact: jest.fn().mockResolvedValue({
|
|
616
|
+
contactInfo: null,
|
|
617
|
+
returnMessage: { message: 'Failed to create', messageType: 'error' }
|
|
618
|
+
})
|
|
619
|
+
};
|
|
620
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
621
|
+
|
|
622
|
+
// Act
|
|
623
|
+
const result = await contactHandler.createContact({
|
|
624
|
+
platform: 'testCRM',
|
|
625
|
+
userId: 'test-user-id',
|
|
626
|
+
phoneNumber: '+1234567890',
|
|
627
|
+
newContactName: 'Failed Contact',
|
|
628
|
+
newContactType: 'Contact'
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
// Assert
|
|
632
|
+
expect(result.successful).toBe(false);
|
|
633
|
+
expect(result.returnMessage.message).toBe('Failed to create');
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
test('should handle 429 rate limit error', async () => {
|
|
637
|
+
// Arrange
|
|
638
|
+
await UserModel.create(mockUser);
|
|
639
|
+
|
|
640
|
+
const mockConnector = {
|
|
641
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
642
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
643
|
+
createContact: jest.fn().mockRejectedValue({
|
|
644
|
+
response: { status: 429 }
|
|
645
|
+
})
|
|
646
|
+
};
|
|
647
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
648
|
+
|
|
649
|
+
// Act
|
|
650
|
+
const result = await contactHandler.createContact({
|
|
651
|
+
platform: 'testCRM',
|
|
652
|
+
userId: 'test-user-id',
|
|
653
|
+
phoneNumber: '+1234567890',
|
|
654
|
+
newContactName: 'Rate Limited',
|
|
655
|
+
newContactType: 'Contact'
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
// Assert
|
|
659
|
+
expect(result.successful).toBe(false);
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
test('should handle 401 authorization error', async () => {
|
|
663
|
+
// Arrange
|
|
664
|
+
await UserModel.create(mockUser);
|
|
665
|
+
|
|
666
|
+
const mockConnector = {
|
|
667
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
668
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
669
|
+
createContact: jest.fn().mockRejectedValue({
|
|
670
|
+
response: { status: 401 }
|
|
671
|
+
})
|
|
672
|
+
};
|
|
673
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
674
|
+
|
|
675
|
+
// Act
|
|
676
|
+
const result = await contactHandler.createContact({
|
|
677
|
+
platform: 'testCRM',
|
|
678
|
+
userId: 'test-user-id',
|
|
679
|
+
phoneNumber: '+1234567890',
|
|
680
|
+
newContactName: 'Unauthorized',
|
|
681
|
+
newContactType: 'Contact'
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
// Assert
|
|
685
|
+
expect(result.successful).toBe(false);
|
|
686
|
+
expect(result.extraDataTracking.statusCode).toBe(401);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
test('should handle generic errors', async () => {
|
|
690
|
+
// Arrange
|
|
691
|
+
await UserModel.create(mockUser);
|
|
692
|
+
|
|
693
|
+
const mockConnector = {
|
|
694
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
695
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
696
|
+
createContact: jest.fn().mockRejectedValue(new Error('Unknown error'))
|
|
697
|
+
};
|
|
698
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
699
|
+
|
|
700
|
+
// Act
|
|
701
|
+
const result = await contactHandler.createContact({
|
|
702
|
+
platform: 'testCRM',
|
|
703
|
+
userId: 'test-user-id',
|
|
704
|
+
phoneNumber: '+1234567890',
|
|
705
|
+
newContactName: 'Error Contact',
|
|
706
|
+
newContactType: 'Contact'
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
// Assert
|
|
710
|
+
expect(result.successful).toBe(false);
|
|
711
|
+
expect(result.returnMessage.message).toBe('Error creating contact');
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
describe('findContactWithName', () => {
|
|
716
|
+
const mockUser = {
|
|
717
|
+
id: 'test-user-id',
|
|
718
|
+
platform: 'testCRM',
|
|
719
|
+
accessToken: 'test-access-token',
|
|
720
|
+
platformAdditionalInfo: {}
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
test('should return warning when user not found', async () => {
|
|
724
|
+
// Act
|
|
725
|
+
const result = await contactHandler.findContactWithName({
|
|
726
|
+
platform: 'testCRM',
|
|
727
|
+
userId: 'non-existent-user',
|
|
728
|
+
name: 'John Doe'
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
// Assert
|
|
732
|
+
expect(result.successful).toBe(false);
|
|
733
|
+
expect(result.returnMessage.message).toContain('No contact found with name');
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
test('should find contact by name with apiKey auth', async () => {
|
|
737
|
+
// Arrange
|
|
738
|
+
await UserModel.create(mockUser);
|
|
739
|
+
|
|
740
|
+
const matchedContacts = [
|
|
741
|
+
{ id: 'contact-1', name: 'John Doe', type: 'Contact' }
|
|
742
|
+
];
|
|
743
|
+
|
|
744
|
+
const mockConnector = {
|
|
745
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
746
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
747
|
+
findContactWithName: jest.fn().mockResolvedValue({
|
|
748
|
+
successful: true,
|
|
749
|
+
matchedContactInfo: matchedContacts
|
|
750
|
+
})
|
|
751
|
+
};
|
|
752
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
753
|
+
|
|
754
|
+
// Act
|
|
755
|
+
const result = await contactHandler.findContactWithName({
|
|
756
|
+
platform: 'testCRM',
|
|
757
|
+
userId: 'test-user-id',
|
|
758
|
+
name: 'John Doe'
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
// Assert
|
|
762
|
+
expect(result.successful).toBe(true);
|
|
763
|
+
expect(result.contact).toEqual(matchedContacts);
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
test('should return warning when no contacts found by name', async () => {
|
|
767
|
+
// Arrange
|
|
768
|
+
await UserModel.create(mockUser);
|
|
769
|
+
|
|
770
|
+
const mockConnector = {
|
|
771
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
772
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
773
|
+
findContactWithName: jest.fn().mockResolvedValue({
|
|
774
|
+
successful: false,
|
|
775
|
+
matchedContactInfo: null
|
|
776
|
+
})
|
|
777
|
+
};
|
|
778
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
779
|
+
|
|
780
|
+
// Act
|
|
781
|
+
const result = await contactHandler.findContactWithName({
|
|
782
|
+
platform: 'testCRM',
|
|
783
|
+
userId: 'test-user-id',
|
|
784
|
+
name: 'Unknown Person'
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
// Assert
|
|
788
|
+
expect(result.successful).toBe(false);
|
|
789
|
+
expect(result.returnMessage.message).toContain('No contact found with name');
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
test('should handle 429 rate limit error', async () => {
|
|
793
|
+
// Arrange
|
|
794
|
+
await UserModel.create(mockUser);
|
|
795
|
+
|
|
796
|
+
const mockConnector = {
|
|
797
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
798
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
799
|
+
findContactWithName: jest.fn().mockRejectedValue({
|
|
800
|
+
response: { status: 429 }
|
|
801
|
+
})
|
|
802
|
+
};
|
|
803
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
804
|
+
|
|
805
|
+
// Act
|
|
806
|
+
const result = await contactHandler.findContactWithName({
|
|
807
|
+
platform: 'testCRM',
|
|
808
|
+
userId: 'test-user-id',
|
|
809
|
+
name: 'Rate Limited'
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
// Assert
|
|
813
|
+
expect(result.successful).toBe(false);
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
test('should use proxy config when proxyId is present', async () => {
|
|
817
|
+
// Arrange
|
|
818
|
+
const userWithProxy = {
|
|
819
|
+
...mockUser,
|
|
820
|
+
platformAdditionalInfo: { proxyId: 'proxy-123' }
|
|
821
|
+
};
|
|
822
|
+
await UserModel.create(userWithProxy);
|
|
823
|
+
|
|
824
|
+
const proxyConfig = { baseUrl: 'https://proxy.example.com' };
|
|
825
|
+
Connector.getProxyConfig.mockResolvedValue(proxyConfig);
|
|
826
|
+
|
|
827
|
+
const matchedContacts = [{ id: 'proxy-contact', name: 'Proxy Contact' }];
|
|
828
|
+
|
|
829
|
+
const mockConnector = {
|
|
830
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
831
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
832
|
+
findContactWithName: jest.fn().mockResolvedValue({
|
|
833
|
+
successful: true,
|
|
834
|
+
matchedContactInfo: matchedContacts
|
|
835
|
+
})
|
|
836
|
+
};
|
|
837
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
838
|
+
|
|
839
|
+
// Act
|
|
840
|
+
const result = await contactHandler.findContactWithName({
|
|
841
|
+
platform: 'testCRM',
|
|
842
|
+
userId: 'test-user-id',
|
|
843
|
+
name: 'Proxy Contact'
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
// Assert
|
|
847
|
+
expect(result.successful).toBe(true);
|
|
848
|
+
expect(Connector.getProxyConfig).toHaveBeenCalledWith('proxy-123');
|
|
849
|
+
});
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
|