@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.
@@ -3,12 +3,38 @@ const connectorRegistry = require('../../connector/registry');
3
3
 
4
4
  // Mock the connector registry
5
5
  jest.mock('../../connector/registry');
6
+ jest.mock('../../lib/oauth');
7
+ jest.mock('../../models/dynamo/connectorSchema', () => ({
8
+ Connector: {
9
+ getProxyConfig: jest.fn()
10
+ }
11
+ }));
12
+ jest.mock('../../lib/ringcentral', () => ({
13
+ RingCentral: jest.fn().mockImplementation(() => ({
14
+ generateToken: jest.fn()
15
+ }))
16
+ }));
17
+ jest.mock('../../handlers/admin', () => ({
18
+ updateAdminRcTokens: jest.fn()
19
+ }));
20
+
21
+ const oauth = require('../../lib/oauth');
22
+ const { Connector } = require('../../models/dynamo/connectorSchema');
23
+ const { RingCentral } = require('../../lib/ringcentral');
24
+ const adminCore = require('../../handlers/admin');
6
25
 
7
26
  describe('Auth Handler', () => {
27
+ const originalEnv = process.env;
28
+
8
29
  beforeEach(() => {
9
30
  // Reset mocks
10
31
  jest.clearAllMocks();
11
32
  global.testUtils.resetConnectorRegistry();
33
+ process.env = { ...originalEnv };
34
+ });
35
+
36
+ afterEach(() => {
37
+ process.env = originalEnv;
12
38
  });
13
39
 
14
40
  describe('onApiKeyLogin', () => {
@@ -230,4 +256,333 @@ describe('Auth Handler', () => {
230
256
  expect(result.successful).toBe(false);
231
257
  });
232
258
  });
259
+
260
+ describe('onOAuthCallback', () => {
261
+ const mockOAuthApp = {
262
+ code: {
263
+ getToken: jest.fn()
264
+ }
265
+ };
266
+
267
+ beforeEach(() => {
268
+ oauth.getOAuthApp.mockReturnValue(mockOAuthApp);
269
+ });
270
+
271
+ test('should handle successful OAuth callback', async () => {
272
+ // Arrange
273
+ const mockUserInfo = {
274
+ successful: true,
275
+ platformUserInfo: {
276
+ id: 'oauth-user-id',
277
+ name: 'OAuth User',
278
+ timezoneName: 'America/New_York',
279
+ timezoneOffset: -300,
280
+ platformAdditionalInfo: {}
281
+ },
282
+ returnMessage: {
283
+ messageType: 'success',
284
+ message: 'Connected successfully'
285
+ }
286
+ };
287
+
288
+ const mockConnector = global.testUtils.createMockConnector({
289
+ getOauthInfo: jest.fn().mockResolvedValue({
290
+ clientId: 'client-id',
291
+ clientSecret: 'client-secret',
292
+ accessTokenUri: 'https://api.example.com/oauth/token',
293
+ authorizationUri: 'https://api.example.com/oauth/authorize'
294
+ }),
295
+ getUserInfo: jest.fn().mockResolvedValue(mockUserInfo)
296
+ });
297
+
298
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
299
+
300
+ mockOAuthApp.code.getToken.mockResolvedValue({
301
+ accessToken: 'new-access-token',
302
+ refreshToken: 'new-refresh-token',
303
+ expires: new Date()
304
+ });
305
+
306
+ const requestData = {
307
+ platform: 'testCRM',
308
+ hostname: 'api.example.com',
309
+ tokenUrl: 'https://api.example.com/oauth/token',
310
+ query: {
311
+ callbackUri: 'https://app.example.com/callback?code=auth-code-123',
312
+ rcAccountId: 'rc-account-123'
313
+ }
314
+ };
315
+
316
+ // Act
317
+ const result = await authHandler.onOAuthCallback(requestData);
318
+
319
+ // Assert
320
+ expect(result.userInfo).toBeDefined();
321
+ expect(result.userInfo.id).toBe('oauth-user-id');
322
+ expect(oauth.getOAuthApp).toHaveBeenCalled();
323
+ expect(mockOAuthApp.code.getToken).toHaveBeenCalled();
324
+ });
325
+
326
+ test('should return fail message when oauthInfo has error', async () => {
327
+ // Arrange
328
+ const mockConnector = global.testUtils.createMockConnector({
329
+ getOauthInfo: jest.fn().mockResolvedValue({
330
+ failMessage: 'OAuth configuration not found'
331
+ })
332
+ });
333
+
334
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
335
+
336
+ const requestData = {
337
+ platform: 'testCRM',
338
+ hostname: 'api.example.com',
339
+ tokenUrl: '',
340
+ query: { callbackUri: 'https://app.example.com/callback', rcAccountId: 'rc-123' }
341
+ };
342
+
343
+ // Act
344
+ const result = await authHandler.onOAuthCallback(requestData);
345
+
346
+ // Assert
347
+ expect(result.userInfo).toBeNull();
348
+ expect(result.returnMessage.messageType).toBe('danger');
349
+ expect(result.returnMessage.message).toBe('OAuth configuration not found');
350
+ });
351
+
352
+ test('should handle failed user info retrieval', async () => {
353
+ // Arrange
354
+ const mockConnector = global.testUtils.createMockConnector({
355
+ getOauthInfo: jest.fn().mockResolvedValue({ clientId: 'id', clientSecret: 'secret' }),
356
+ getUserInfo: jest.fn().mockResolvedValue({
357
+ successful: false,
358
+ returnMessage: { messageType: 'error', message: 'User not authorized' }
359
+ })
360
+ });
361
+
362
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
363
+
364
+ mockOAuthApp.code.getToken.mockResolvedValue({
365
+ accessToken: 'token',
366
+ refreshToken: 'refresh',
367
+ expires: new Date()
368
+ });
369
+
370
+ const requestData = {
371
+ platform: 'testCRM',
372
+ hostname: 'api.example.com',
373
+ tokenUrl: '',
374
+ query: { callbackUri: 'https://app.example.com/callback', rcAccountId: 'rc-123' }
375
+ };
376
+
377
+ // Act
378
+ const result = await authHandler.onOAuthCallback(requestData);
379
+
380
+ // Assert
381
+ expect(result.userInfo).toBeNull();
382
+ expect(result.returnMessage.message).toBe('User not authorized');
383
+ });
384
+
385
+ test('should handle proxyId in OAuth callback', async () => {
386
+ // Arrange
387
+ const proxyConfig = { name: 'Proxy Config', settings: {} };
388
+ Connector.getProxyConfig.mockResolvedValue(proxyConfig);
389
+
390
+ const mockConnector = global.testUtils.createMockConnector({
391
+ getOauthInfo: jest.fn().mockResolvedValue({ clientId: 'id', clientSecret: 'secret' }),
392
+ getUserInfo: jest.fn().mockResolvedValue({
393
+ successful: true,
394
+ platformUserInfo: { id: 'proxy-user', name: 'Proxy User' },
395
+ returnMessage: { messageType: 'success', message: 'OK' }
396
+ })
397
+ });
398
+
399
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
400
+
401
+ mockOAuthApp.code.getToken.mockResolvedValue({
402
+ accessToken: 'token',
403
+ refreshToken: 'refresh',
404
+ expires: new Date()
405
+ });
406
+
407
+ const requestData = {
408
+ platform: 'testCRM',
409
+ hostname: 'api.example.com',
410
+ tokenUrl: '',
411
+ query: {
412
+ callbackUri: 'https://app.example.com/callback',
413
+ proxyId: 'proxy-123',
414
+ rcAccountId: 'rc-123'
415
+ }
416
+ };
417
+
418
+ // Act
419
+ await authHandler.onOAuthCallback(requestData);
420
+
421
+ // Assert
422
+ expect(Connector.getProxyConfig).toHaveBeenCalledWith('proxy-123');
423
+ });
424
+
425
+ test('should call postSaveUserInfo if platform implements it', async () => {
426
+ // Arrange
427
+ const postSaveResult = { id: 'user-id', name: 'User', extra: 'data' };
428
+ const mockConnector = global.testUtils.createMockConnector({
429
+ getOauthInfo: jest.fn().mockResolvedValue({ clientId: 'id', clientSecret: 'secret' }),
430
+ getUserInfo: jest.fn().mockResolvedValue({
431
+ successful: true,
432
+ platformUserInfo: { id: 'user-id', name: 'User' },
433
+ returnMessage: { messageType: 'success', message: 'OK' }
434
+ }),
435
+ postSaveUserInfo: jest.fn().mockResolvedValue(postSaveResult)
436
+ });
437
+
438
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
439
+
440
+ mockOAuthApp.code.getToken.mockResolvedValue({
441
+ accessToken: 'token',
442
+ refreshToken: 'refresh',
443
+ expires: new Date()
444
+ });
445
+
446
+ const requestData = {
447
+ platform: 'testCRM',
448
+ hostname: 'api.example.com',
449
+ tokenUrl: '',
450
+ query: { callbackUri: 'https://app.example.com/callback', rcAccountId: 'rc-123' }
451
+ };
452
+
453
+ // Act
454
+ const result = await authHandler.onOAuthCallback(requestData);
455
+
456
+ // Assert
457
+ expect(mockConnector.postSaveUserInfo).toHaveBeenCalled();
458
+ expect(result.userInfo).toEqual(postSaveResult);
459
+ });
460
+
461
+ test('should use overriding OAuth option if provided', async () => {
462
+ // Arrange
463
+ const overridingOption = { redirect_uri: 'custom-redirect' };
464
+ const mockConnector = global.testUtils.createMockConnector({
465
+ getOauthInfo: jest.fn().mockResolvedValue({ clientId: 'id', clientSecret: 'secret' }),
466
+ getOverridingOAuthOption: jest.fn().mockReturnValue(overridingOption),
467
+ getUserInfo: jest.fn().mockResolvedValue({
468
+ successful: true,
469
+ platformUserInfo: { id: 'user-id', name: 'User' },
470
+ returnMessage: { messageType: 'success', message: 'OK' }
471
+ })
472
+ });
473
+
474
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
475
+
476
+ mockOAuthApp.code.getToken.mockResolvedValue({
477
+ accessToken: 'token',
478
+ refreshToken: 'refresh',
479
+ expires: new Date()
480
+ });
481
+
482
+ const requestData = {
483
+ platform: 'testCRM',
484
+ hostname: 'api.example.com',
485
+ tokenUrl: '',
486
+ query: { callbackUri: 'https://app.example.com/callback?code=code123', rcAccountId: 'rc-123' }
487
+ };
488
+
489
+ // Act
490
+ await authHandler.onOAuthCallback(requestData);
491
+
492
+ // Assert
493
+ expect(mockConnector.getOverridingOAuthOption).toHaveBeenCalledWith({ code: 'code123' });
494
+ expect(mockOAuthApp.code.getToken).toHaveBeenCalledWith(
495
+ expect.any(String),
496
+ overridingOption
497
+ );
498
+ });
499
+ });
500
+
501
+ describe('getLicenseStatus', () => {
502
+ test('should return license status from platform module', async () => {
503
+ // Arrange
504
+ const mockLicenseStatus = {
505
+ isValid: true,
506
+ expiresAt: '2025-12-31',
507
+ features: ['call_logging', 'sms_logging']
508
+ };
509
+
510
+ const mockConnector = global.testUtils.createMockConnector({
511
+ getLicenseStatus: jest.fn().mockResolvedValue(mockLicenseStatus)
512
+ });
513
+
514
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
515
+
516
+ // Act
517
+ const result = await authHandler.getLicenseStatus({
518
+ userId: 'user-123',
519
+ platform: 'testCRM'
520
+ });
521
+
522
+ // Assert
523
+ expect(result).toEqual(mockLicenseStatus);
524
+ expect(mockConnector.getLicenseStatus).toHaveBeenCalledWith({
525
+ userId: 'user-123',
526
+ platform: 'testCRM'
527
+ });
528
+ });
529
+ });
530
+
531
+ describe('onRingcentralOAuthCallback', () => {
532
+ beforeEach(() => {
533
+ process.env.RINGCENTRAL_SERVER = 'https://platform.ringcentral.com';
534
+ process.env.RINGCENTRAL_CLIENT_ID = 'rc-client-id';
535
+ process.env.RINGCENTRAL_CLIENT_SECRET = 'rc-client-secret';
536
+ process.env.APP_SERVER = 'https://app.example.com';
537
+ });
538
+
539
+ test('should handle successful RingCentral OAuth callback', async () => {
540
+ // Arrange
541
+ const mockGenerateToken = jest.fn().mockResolvedValue({
542
+ access_token: 'rc-access-token',
543
+ refresh_token: 'rc-refresh-token',
544
+ expire_time: Date.now() + 3600000
545
+ });
546
+
547
+ RingCentral.mockImplementation(() => ({
548
+ generateToken: mockGenerateToken
549
+ }));
550
+
551
+ // Act
552
+ await authHandler.onRingcentralOAuthCallback({
553
+ code: 'rc-auth-code',
554
+ rcAccountId: 'hashed-rc-account-id'
555
+ });
556
+
557
+ // Assert
558
+ expect(RingCentral).toHaveBeenCalledWith({
559
+ server: 'https://platform.ringcentral.com',
560
+ clientId: 'rc-client-id',
561
+ clientSecret: 'rc-client-secret',
562
+ redirectUri: 'https://app.example.com/ringcentral/oauth/callback'
563
+ });
564
+ expect(mockGenerateToken).toHaveBeenCalledWith({ code: 'rc-auth-code' });
565
+ expect(adminCore.updateAdminRcTokens).toHaveBeenCalledWith({
566
+ hashedRcAccountId: 'hashed-rc-account-id',
567
+ adminAccessToken: 'rc-access-token',
568
+ adminRefreshToken: 'rc-refresh-token',
569
+ adminTokenExpiry: expect.any(Number)
570
+ });
571
+ });
572
+
573
+ test('should return early if environment variables are not set', async () => {
574
+ // Arrange
575
+ delete process.env.RINGCENTRAL_SERVER;
576
+
577
+ // Act
578
+ const result = await authHandler.onRingcentralOAuthCallback({
579
+ code: 'rc-auth-code',
580
+ rcAccountId: 'hashed-rc-account-id'
581
+ });
582
+
583
+ // Assert
584
+ expect(result).toBeUndefined();
585
+ expect(RingCentral).not.toHaveBeenCalled();
586
+ });
587
+ });
233
588
  });