@app-connect/core 1.7.19 → 1.7.21

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.
@@ -36,6 +36,9 @@ exports.UserModel = sequelize.define('users', {
36
36
  platformAdditionalInfo: {
37
37
  type: Sequelize.JSON
38
38
  },
39
+ hashedRcExtensionId: {
40
+ type: Sequelize.STRING,
41
+ },
39
42
  userSettings: {
40
43
  type: Sequelize.JSON
41
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@app-connect/core",
3
- "version": "1.7.19",
3
+ "version": "1.7.21",
4
4
  "description": "RingCentral App Connect Core",
5
5
  "main": "index.js",
6
6
  "repository": {
package/releaseNotes.json CHANGED
@@ -1,4 +1,28 @@
1
1
  {
2
+ "1.7.21": {
3
+ "global": [
4
+ {
5
+ "type": "Fix",
6
+ "description": "Refresh contact for deleted contact issue"
7
+ },
8
+ {
9
+ "type": "Fix",
10
+ "description": "Click-to-sms button issue"
11
+ }
12
+ ]
13
+ },
14
+ "1.7.20": {
15
+ "global": [
16
+ {
17
+ "type": "Fix",
18
+ "description": "Click-to-dial not detecting numbers in input fields"
19
+ },
20
+ {
21
+ "type": "Fix",
22
+ "description": "Warm transfer call logging issue"
23
+ }
24
+ ]
25
+ },
2
26
  "1.7.19": {
3
27
  "global": [
4
28
  {
@@ -501,6 +501,7 @@ describe('Auth Handler', () => {
501
501
  describe('getLicenseStatus', () => {
502
502
  test('should return license status from platform module', async () => {
503
503
  // Arrange
504
+ const mockUser = global.testUtils.createMockUser({ id: 'user-123' });
504
505
  const mockLicenseStatus = {
505
506
  isValid: true,
506
507
  expiresAt: '2025-12-31',
@@ -513,6 +514,9 @@ describe('Auth Handler', () => {
513
514
 
514
515
  connectorRegistry.getConnector.mockReturnValue(mockConnector);
515
516
 
517
+ const { UserModel } = require('../../models/userModel');
518
+ jest.spyOn(UserModel, 'findByPk').mockResolvedValue(mockUser);
519
+
516
520
  // Act
517
521
  const result = await authHandler.getLicenseStatus({
518
522
  userId: 'user-123',
@@ -523,8 +527,35 @@ describe('Auth Handler', () => {
523
527
  expect(result).toEqual(mockLicenseStatus);
524
528
  expect(mockConnector.getLicenseStatus).toHaveBeenCalledWith({
525
529
  userId: 'user-123',
530
+ platform: 'testCRM',
531
+ user: mockUser
532
+ });
533
+ });
534
+
535
+ test('should return invalid license status when user not found', async () => {
536
+ // Arrange
537
+ const mockConnector = global.testUtils.createMockConnector({
538
+ getLicenseStatus: jest.fn()
539
+ });
540
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
541
+
542
+ const { UserModel } = require('../../models/userModel');
543
+ jest.spyOn(UserModel, 'findByPk').mockResolvedValue(null);
544
+
545
+ // Act
546
+ const result = await authHandler.getLicenseStatus({
547
+ userId: 'missing-user',
526
548
  platform: 'testCRM'
527
549
  });
550
+
551
+ // Assert
552
+ expect(result).toEqual({
553
+ isLicenseValid: false,
554
+ licenseStatus: 'Invalid (User not found)',
555
+ licenseStatusDescription: ''
556
+ });
557
+ expect(connectorRegistry.getConnector).not.toHaveBeenCalled();
558
+ expect(mockConnector.getLicenseStatus).not.toHaveBeenCalled();
528
559
  });
529
560
  });
530
561
 
@@ -436,6 +436,168 @@ describe('Contact Handler', () => {
436
436
  expect(cachedData.data).toEqual(updatedContact);
437
437
  });
438
438
 
439
+ test('should remove cached contact when force refresh returns null', async () => {
440
+ // Arrange
441
+ await UserModel.create(mockUser);
442
+ await AccountDataModel.create({
443
+ rcAccountId: 'rc-account-123',
444
+ platformName: 'testCRM',
445
+ dataKey: 'contact-+3333333333',
446
+ data: [{ id: 'stale-contact', name: 'Stale Contact' }]
447
+ });
448
+
449
+ const mockConnector = {
450
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
451
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
452
+ findContact: jest.fn().mockResolvedValue({
453
+ successful: false,
454
+ matchedContactInfo: null
455
+ })
456
+ };
457
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
458
+
459
+ // Act
460
+ const result = await contactHandler.findContact({
461
+ platform: 'testCRM',
462
+ userId: 'test-user-id',
463
+ phoneNumber: '+3333333333',
464
+ isForceRefreshAccountData: true
465
+ });
466
+
467
+ // Assert
468
+ expect(result.successful).toBe(false);
469
+ const cachedData = await AccountDataModel.findOne({
470
+ where: {
471
+ rcAccountId: 'rc-account-123',
472
+ platformName: 'testCRM',
473
+ dataKey: 'contact-+3333333333'
474
+ }
475
+ });
476
+ expect(cachedData).toBeNull();
477
+ });
478
+
479
+ test('should remove cached contact when force refresh returns empty array', async () => {
480
+ // Arrange
481
+ await UserModel.create(mockUser);
482
+ await AccountDataModel.create({
483
+ rcAccountId: 'rc-account-123',
484
+ platformName: 'testCRM',
485
+ dataKey: 'contact-+1212121212',
486
+ data: [{ id: 'stale-contact', name: 'Stale Contact' }]
487
+ });
488
+
489
+ const mockConnector = {
490
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
491
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
492
+ findContact: jest.fn().mockResolvedValue({
493
+ successful: false,
494
+ matchedContactInfo: []
495
+ })
496
+ };
497
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
498
+
499
+ // Act
500
+ await contactHandler.findContact({
501
+ platform: 'testCRM',
502
+ userId: 'test-user-id',
503
+ phoneNumber: '+1212121212',
504
+ isForceRefreshAccountData: true
505
+ });
506
+
507
+ // Assert
508
+ const cachedData = await AccountDataModel.findOne({
509
+ where: {
510
+ rcAccountId: 'rc-account-123',
511
+ platformName: 'testCRM',
512
+ dataKey: 'contact-+1212121212'
513
+ }
514
+ });
515
+ expect(cachedData).toBeNull();
516
+ });
517
+
518
+ test('should remove cached contact when force refresh returns only new contact placeholder', async () => {
519
+ // Arrange
520
+ await UserModel.create(mockUser);
521
+ await AccountDataModel.create({
522
+ rcAccountId: 'rc-account-123',
523
+ platformName: 'testCRM',
524
+ dataKey: 'contact-+3434343434',
525
+ data: [{ id: 'stale-contact', name: 'Stale Contact' }]
526
+ });
527
+
528
+ const placeholderContact = [
529
+ { id: 'new-contact', name: 'Create Contact', isNewContact: true }
530
+ ];
531
+ const mockConnector = {
532
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
533
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
534
+ findContact: jest.fn().mockResolvedValue({
535
+ successful: false,
536
+ matchedContactInfo: placeholderContact
537
+ })
538
+ };
539
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
540
+
541
+ // Act
542
+ const result = await contactHandler.findContact({
543
+ platform: 'testCRM',
544
+ userId: 'test-user-id',
545
+ phoneNumber: '+3434343434',
546
+ isForceRefreshAccountData: true
547
+ });
548
+
549
+ // Assert
550
+ expect(result.contact).toEqual(placeholderContact);
551
+ const cachedData = await AccountDataModel.findOne({
552
+ where: {
553
+ rcAccountId: 'rc-account-123',
554
+ platformName: 'testCRM',
555
+ dataKey: 'contact-+3434343434'
556
+ }
557
+ });
558
+ expect(cachedData).toBeNull();
559
+ });
560
+
561
+ test('should keep cached contact when force refresh connector call errors', async () => {
562
+ // Arrange
563
+ await UserModel.create(mockUser);
564
+ await AccountDataModel.create({
565
+ rcAccountId: 'rc-account-123',
566
+ platformName: 'testCRM',
567
+ dataKey: 'contact-+5656565656',
568
+ data: [{ id: 'stale-contact', name: 'Stale Contact' }]
569
+ });
570
+
571
+ const mockConnector = {
572
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
573
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
574
+ findContact: jest.fn().mockRejectedValue({
575
+ response: { status: 500 }
576
+ })
577
+ };
578
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
579
+
580
+ // Act
581
+ const result = await contactHandler.findContact({
582
+ platform: 'testCRM',
583
+ userId: 'test-user-id',
584
+ phoneNumber: '+5656565656',
585
+ isForceRefreshAccountData: true
586
+ });
587
+
588
+ // Assert
589
+ expect(result.successful).toBe(false);
590
+ const cachedData = await AccountDataModel.findOne({
591
+ where: {
592
+ rcAccountId: 'rc-account-123',
593
+ platformName: 'testCRM',
594
+ dataKey: 'contact-+5656565656'
595
+ }
596
+ });
597
+ expect(cachedData).not.toBeNull();
598
+ expect(cachedData.data).toEqual([{ id: 'stale-contact', name: 'Stale Contact' }]);
599
+ });
600
+
439
601
  test('should work with tracer when provided', async () => {
440
602
  // Arrange
441
603
  await UserModel.create(mockUser);
@@ -94,8 +94,9 @@ describe('MCP Tool: logout', () => {
94
94
  });
95
95
  });
96
96
 
97
- test('should handle logout errors gracefully', async () => {
97
+ test('should treat CRM unAuthorize failure as non-fatal and still succeed', async () => {
98
98
  // Arrange
99
+ const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
99
100
  const mockUser = {
100
101
  id: 'test-user-id',
101
102
  platform: 'testCRM'
@@ -120,10 +121,12 @@ describe('MCP Tool: logout', () => {
120
121
  jwtToken: 'mock-jwt-token'
121
122
  });
122
123
 
123
- // Assert
124
- expect(result.success).toBe(false);
125
- expect(result.error).toBe('Logout API failed');
126
- expect(result.errorDetails).toBeDefined();
124
+ // Assert — local session is cleared; CRM revoke errors are logged only
125
+ expect(result.success).toBe(true);
126
+ expect(result.data.message).toContain('IMPORTANT');
127
+ expect(mockConnector.unAuthorize).toHaveBeenCalled();
128
+ expect(consoleSpy).toHaveBeenCalled();
129
+ consoleSpy.mockRestore();
127
130
  });
128
131
 
129
132
  test('should handle invalid JWT token', async () => {
@@ -140,8 +143,9 @@ describe('MCP Tool: logout', () => {
140
143
  expect(result.error).toBeDefined();
141
144
  });
142
145
 
143
- test('should handle missing platform connector', async () => {
146
+ test('should succeed when platform connector is missing (unAuthorize skipped)', async () => {
144
147
  // Arrange
148
+ const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
145
149
  const mockUser = {
146
150
  id: 'test-user-id',
147
151
  platform: 'unknownCRM'
@@ -160,9 +164,11 @@ describe('MCP Tool: logout', () => {
160
164
  jwtToken: 'mock-jwt-token'
161
165
  });
162
166
 
163
- // Assert
164
- expect(result.success).toBe(false);
165
- expect(result.error).toBeDefined();
167
+ // Assert — null connector throws on unAuthorize; error is caught, logout still succeeds locally
168
+ expect(result.success).toBe(true);
169
+ expect(result.data.message).toContain('IMPORTANT');
170
+ expect(consoleSpy).toHaveBeenCalled();
171
+ consoleSpy.mockRestore();
166
172
  });
167
173
  });
168
174
  });