@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.
@@ -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
+