@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,583 @@
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('axios');
14
+ jest.mock('../../connector/registry');
15
+ jest.mock('../../lib/oauth');
16
+ jest.mock('../../lib/ringcentral');
17
+ jest.mock('../../models/dynamo/connectorSchema', () => ({
18
+ Connector: {
19
+ getProxyConfig: jest.fn()
20
+ }
21
+ }));
22
+
23
+ const axios = require('axios');
24
+ const adminHandler = require('../../handlers/admin');
25
+ const { AdminConfigModel } = require('../../models/adminConfigModel');
26
+ const { UserModel } = require('../../models/userModel');
27
+ const connectorRegistry = require('../../connector/registry');
28
+ const oauth = require('../../lib/oauth');
29
+ const { RingCentral } = require('../../lib/ringcentral');
30
+ const { Connector } = require('../../models/dynamo/connectorSchema');
31
+ const { sequelize } = require('../../models/sequelize');
32
+
33
+ describe('Admin Handler', () => {
34
+ beforeAll(async () => {
35
+ await AdminConfigModel.sync({ force: true });
36
+ await UserModel.sync({ force: true });
37
+ });
38
+
39
+ afterEach(async () => {
40
+ await AdminConfigModel.destroy({ where: {} });
41
+ await UserModel.destroy({ where: {} });
42
+ jest.clearAllMocks();
43
+ });
44
+
45
+ afterAll(async () => {
46
+ await sequelize.close();
47
+ });
48
+
49
+ describe('validateAdminRole', () => {
50
+ test('should return validated true when user has admin permissions', async () => {
51
+ // Arrange
52
+ axios.get.mockResolvedValue({
53
+ data: {
54
+ permissions: {
55
+ admin: { enabled: true }
56
+ },
57
+ account: { id: 'rc-account-123' },
58
+ id: 'extension-123'
59
+ }
60
+ });
61
+
62
+ // Act
63
+ const result = await adminHandler.validateAdminRole({
64
+ rcAccessToken: 'valid-token'
65
+ });
66
+
67
+ // Assert
68
+ expect(result.isValidated).toBe(true);
69
+ expect(result.rcAccountId).toBe('rc-account-123');
70
+ expect(axios.get).toHaveBeenCalledWith(
71
+ 'https://platform.ringcentral.com/restapi/v1.0/account/~/extension/~',
72
+ { headers: { Authorization: 'Bearer valid-token' } }
73
+ );
74
+ });
75
+
76
+ test('should return validated false when user lacks admin permissions', async () => {
77
+ // Arrange
78
+ axios.get.mockResolvedValue({
79
+ data: {
80
+ permissions: {
81
+ admin: { enabled: false }
82
+ },
83
+ account: { id: 'rc-account-456' },
84
+ id: 'extension-456'
85
+ }
86
+ });
87
+
88
+ // Act
89
+ const result = await adminHandler.validateAdminRole({
90
+ rcAccessToken: 'non-admin-token'
91
+ });
92
+
93
+ // Assert
94
+ expect(result.isValidated).toBe(false);
95
+ expect(result.rcAccountId).toBe('rc-account-456');
96
+ });
97
+
98
+ test('should return validated true for dev pass list extension', async () => {
99
+ // Arrange
100
+ const originalEnv = process.env.ADMIN_EXTENSION_ID_DEV_PASS_LIST;
101
+ process.env.ADMIN_EXTENSION_ID_DEV_PASS_LIST = '999,1000,1001';
102
+
103
+ axios.get.mockResolvedValue({
104
+ data: {
105
+ permissions: {
106
+ admin: { enabled: false }
107
+ },
108
+ account: { id: 'rc-account-dev' },
109
+ id: 1000
110
+ }
111
+ });
112
+
113
+ // Act
114
+ const result = await adminHandler.validateAdminRole({
115
+ rcAccessToken: 'dev-token'
116
+ });
117
+
118
+ // Assert
119
+ expect(result.isValidated).toBe(true);
120
+
121
+ // Cleanup
122
+ process.env.ADMIN_EXTENSION_ID_DEV_PASS_LIST = originalEnv;
123
+ });
124
+ });
125
+
126
+ describe('upsertAdminSettings', () => {
127
+ test('should create new admin config when none exists', async () => {
128
+ // Act
129
+ await adminHandler.upsertAdminSettings({
130
+ hashedRcAccountId: 'hashed-123',
131
+ adminSettings: {
132
+ userSettings: { autoLogCalls: true, autoLogMessages: false }
133
+ }
134
+ });
135
+
136
+ // Assert
137
+ const config = await AdminConfigModel.findByPk('hashed-123');
138
+ expect(config).not.toBeNull();
139
+ expect(config.userSettings).toEqual({ autoLogCalls: true, autoLogMessages: false });
140
+ });
141
+
142
+ test('should update existing admin config', async () => {
143
+ // Arrange
144
+ await AdminConfigModel.create({
145
+ id: 'hashed-existing',
146
+ userSettings: { autoLogCalls: false, autoLogMessages: true }
147
+ });
148
+
149
+ // Act
150
+ await adminHandler.upsertAdminSettings({
151
+ hashedRcAccountId: 'hashed-existing',
152
+ adminSettings: {
153
+ userSettings: { autoLogCalls: true, autoLogMessages: false }
154
+ }
155
+ });
156
+
157
+ // Assert
158
+ const config = await AdminConfigModel.findByPk('hashed-existing');
159
+ expect(config.userSettings).toEqual({ autoLogCalls: true, autoLogMessages: false });
160
+ });
161
+ });
162
+
163
+ describe('getAdminSettings', () => {
164
+ test('should return admin settings when they exist', async () => {
165
+ // Arrange
166
+ await AdminConfigModel.create({
167
+ id: 'hashed-get-test',
168
+ userSettings: { autoLogCalls: true, autoLogMessages: true }
169
+ });
170
+
171
+ // Act
172
+ const result = await adminHandler.getAdminSettings({
173
+ hashedRcAccountId: 'hashed-get-test'
174
+ });
175
+
176
+ // Assert
177
+ expect(result).not.toBeNull();
178
+ expect(result.userSettings).toEqual({ autoLogCalls: true, autoLogMessages: true });
179
+ });
180
+
181
+ test('should return null when settings do not exist', async () => {
182
+ // Act
183
+ const result = await adminHandler.getAdminSettings({
184
+ hashedRcAccountId: 'non-existent'
185
+ });
186
+
187
+ // Assert
188
+ expect(result).toBeNull();
189
+ });
190
+ });
191
+
192
+ describe('updateAdminRcTokens', () => {
193
+ test('should update tokens for existing config', async () => {
194
+ // Arrange
195
+ await AdminConfigModel.create({
196
+ id: 'hashed-token-test',
197
+ adminAccessToken: 'old-access',
198
+ adminRefreshToken: 'old-refresh',
199
+ adminTokenExpiry: new Date('2024-01-01')
200
+ });
201
+
202
+ const newExpiry = new Date('2024-12-31');
203
+
204
+ // Act
205
+ await adminHandler.updateAdminRcTokens({
206
+ hashedRcAccountId: 'hashed-token-test',
207
+ adminAccessToken: 'new-access',
208
+ adminRefreshToken: 'new-refresh',
209
+ adminTokenExpiry: newExpiry
210
+ });
211
+
212
+ // Assert
213
+ const config = await AdminConfigModel.findByPk('hashed-token-test');
214
+ expect(config.adminAccessToken).toBe('new-access');
215
+ expect(config.adminRefreshToken).toBe('new-refresh');
216
+ });
217
+
218
+ test('should create new config with tokens when none exists', async () => {
219
+ // Arrange
220
+ const expiry = new Date('2024-12-31');
221
+
222
+ // Act
223
+ await adminHandler.updateAdminRcTokens({
224
+ hashedRcAccountId: 'hashed-new-token',
225
+ adminAccessToken: 'new-access-token',
226
+ adminRefreshToken: 'new-refresh-token',
227
+ adminTokenExpiry: expiry
228
+ });
229
+
230
+ // Assert
231
+ const config = await AdminConfigModel.findByPk('hashed-new-token');
232
+ expect(config).not.toBeNull();
233
+ expect(config.adminAccessToken).toBe('new-access-token');
234
+ expect(config.adminRefreshToken).toBe('new-refresh-token');
235
+ });
236
+ });
237
+
238
+ describe('getServerLoggingSettings', () => {
239
+ test('should return settings from platform module when available', async () => {
240
+ // Arrange
241
+ const mockUser = { platform: 'testCRM', accessToken: 'token' };
242
+ const mockSettings = { enableAutoLog: true, logLevel: 'debug' };
243
+
244
+ const mockConnector = {
245
+ getServerLoggingSettings: jest.fn().mockResolvedValue(mockSettings)
246
+ };
247
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
248
+
249
+ // Act
250
+ const result = await adminHandler.getServerLoggingSettings({ user: mockUser });
251
+
252
+ // Assert
253
+ expect(result).toEqual(mockSettings);
254
+ expect(mockConnector.getServerLoggingSettings).toHaveBeenCalledWith({ user: mockUser });
255
+ });
256
+
257
+ test('should return empty object when platform module lacks getServerLoggingSettings', async () => {
258
+ // Arrange
259
+ const mockUser = { platform: 'testCRM', accessToken: 'token' };
260
+
261
+ const mockConnector = {};
262
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
263
+
264
+ // Act
265
+ const result = await adminHandler.getServerLoggingSettings({ user: mockUser });
266
+
267
+ // Assert
268
+ expect(result).toEqual({});
269
+ });
270
+ });
271
+
272
+ describe('updateServerLoggingSettings', () => {
273
+ test('should update settings via platform module when available', async () => {
274
+ // Arrange
275
+ const mockUser = { platform: 'testCRM', accessToken: 'token' };
276
+ const additionalFieldValues = { field1: 'value1' };
277
+
278
+ const mockConnector = {
279
+ getOauthInfo: jest.fn().mockResolvedValue({
280
+ clientId: 'id',
281
+ clientSecret: 'secret',
282
+ accessTokenUri: 'https://token.url'
283
+ }),
284
+ updateServerLoggingSettings: jest.fn().mockResolvedValue({
285
+ successful: true,
286
+ returnMessage: { message: 'Settings updated' }
287
+ })
288
+ };
289
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
290
+ oauth.getOAuthApp.mockReturnValue({});
291
+
292
+ // Act
293
+ const result = await adminHandler.updateServerLoggingSettings({
294
+ user: mockUser,
295
+ additionalFieldValues
296
+ });
297
+
298
+ // Assert
299
+ expect(result.successful).toBe(true);
300
+ expect(mockConnector.updateServerLoggingSettings).toHaveBeenCalled();
301
+ });
302
+
303
+ test('should return empty object when platform module lacks updateServerLoggingSettings', async () => {
304
+ // Arrange
305
+ const mockUser = { platform: 'testCRM', accessToken: 'token' };
306
+
307
+ const mockConnector = {
308
+ getOauthInfo: jest.fn().mockResolvedValue({})
309
+ };
310
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
311
+ oauth.getOAuthApp.mockReturnValue({});
312
+
313
+ // Act
314
+ const result = await adminHandler.updateServerLoggingSettings({
315
+ user: mockUser,
316
+ additionalFieldValues: {}
317
+ });
318
+
319
+ // Assert
320
+ expect(result).toEqual({});
321
+ });
322
+ });
323
+
324
+ describe('getAdminReport', () => {
325
+ test('should return empty stats when RC credentials are not configured', async () => {
326
+ // Arrange
327
+ const originalServer = process.env.RINGCENTRAL_SERVER;
328
+ delete process.env.RINGCENTRAL_SERVER;
329
+
330
+ // Act
331
+ const result = await adminHandler.getAdminReport({
332
+ rcAccountId: 'account-123',
333
+ timezone: 'America/Los_Angeles',
334
+ timeFrom: '2024-01-01',
335
+ timeTo: '2024-01-31',
336
+ groupBy: 'Users'
337
+ });
338
+
339
+ // Assert
340
+ expect(result).toEqual({ callLogStats: {} });
341
+
342
+ // Cleanup
343
+ if (originalServer) {
344
+ process.env.RINGCENTRAL_SERVER = originalServer;
345
+ }
346
+ });
347
+
348
+ test('should handle errors and return empty stats', async () => {
349
+ // Arrange
350
+ const originalServer = process.env.RINGCENTRAL_SERVER;
351
+ const originalClientId = process.env.RINGCENTRAL_CLIENT_ID;
352
+ const originalClientSecret = process.env.RINGCENTRAL_CLIENT_SECRET;
353
+
354
+ process.env.RINGCENTRAL_SERVER = 'https://platform.ringcentral.com';
355
+ process.env.RINGCENTRAL_CLIENT_ID = 'test-client-id';
356
+ process.env.RINGCENTRAL_CLIENT_SECRET = 'test-client-secret';
357
+
358
+ // Mock AdminConfigModel.findByPk to throw error
359
+ jest.spyOn(AdminConfigModel, 'findByPk').mockRejectedValueOnce(new Error('Database error'));
360
+
361
+ // Act
362
+ const result = await adminHandler.getAdminReport({
363
+ rcAccountId: 'account-123',
364
+ timezone: 'America/Los_Angeles',
365
+ timeFrom: '2024-01-01',
366
+ timeTo: '2024-01-31',
367
+ groupBy: 'Users'
368
+ });
369
+
370
+ // Assert
371
+ expect(result).toEqual({ callLogStats: {} });
372
+
373
+ // Cleanup
374
+ process.env.RINGCENTRAL_SERVER = originalServer;
375
+ process.env.RINGCENTRAL_CLIENT_ID = originalClientId;
376
+ process.env.RINGCENTRAL_CLIENT_SECRET = originalClientSecret;
377
+ });
378
+ });
379
+
380
+ describe('getUserReport', () => {
381
+ test('should return empty stats when RC credentials are not configured', async () => {
382
+ // Arrange
383
+ const originalServer = process.env.RINGCENTRAL_SERVER;
384
+ delete process.env.RINGCENTRAL_SERVER;
385
+
386
+ // Act
387
+ const result = await adminHandler.getUserReport({
388
+ rcAccountId: 'account-123',
389
+ rcExtensionId: 'extension-123',
390
+ timezone: 'America/Los_Angeles',
391
+ timeFrom: '2024-01-01',
392
+ timeTo: '2024-01-31'
393
+ });
394
+
395
+ // Assert
396
+ expect(result).toEqual({ callLogStats: {} });
397
+
398
+ // Cleanup
399
+ if (originalServer) {
400
+ process.env.RINGCENTRAL_SERVER = originalServer;
401
+ }
402
+ });
403
+
404
+ test('should handle errors and return null', async () => {
405
+ // Arrange
406
+ const originalServer = process.env.RINGCENTRAL_SERVER;
407
+ const originalClientId = process.env.RINGCENTRAL_CLIENT_ID;
408
+ const originalClientSecret = process.env.RINGCENTRAL_CLIENT_SECRET;
409
+
410
+ process.env.RINGCENTRAL_SERVER = 'https://platform.ringcentral.com';
411
+ process.env.RINGCENTRAL_CLIENT_ID = 'test-client-id';
412
+ process.env.RINGCENTRAL_CLIENT_SECRET = 'test-client-secret';
413
+
414
+ // Mock AdminConfigModel.findByPk to throw error
415
+ jest.spyOn(AdminConfigModel, 'findByPk').mockRejectedValueOnce(new Error('Database error'));
416
+
417
+ // Act
418
+ const result = await adminHandler.getUserReport({
419
+ rcAccountId: 'account-123',
420
+ rcExtensionId: 'extension-123',
421
+ timezone: 'America/Los_Angeles',
422
+ timeFrom: '2024-01-01',
423
+ timeTo: '2024-01-31'
424
+ });
425
+
426
+ // Assert
427
+ expect(result).toBeNull();
428
+
429
+ // Cleanup
430
+ process.env.RINGCENTRAL_SERVER = originalServer;
431
+ process.env.RINGCENTRAL_CLIENT_ID = originalClientId;
432
+ process.env.RINGCENTRAL_CLIENT_SECRET = originalClientSecret;
433
+ });
434
+ });
435
+
436
+ describe('getUserMapping', () => {
437
+ test('should return empty array when platform module lacks getUserList', async () => {
438
+ // Arrange
439
+ await UserModel.create({
440
+ id: 'test-user-id',
441
+ platform: 'testCRM',
442
+ accessToken: 'token',
443
+ platformAdditionalInfo: {}
444
+ });
445
+
446
+ const mockConnector = {};
447
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
448
+
449
+ // Act
450
+ const result = await adminHandler.getUserMapping({
451
+ user: { id: 'test-user-id', platform: 'testCRM', platformAdditionalInfo: {} },
452
+ hashedRcAccountId: 'hashed-123',
453
+ rcExtensionList: []
454
+ });
455
+
456
+ // Assert
457
+ expect(result).toEqual([]);
458
+ });
459
+
460
+ test('should return empty array when proxy config lacks getUserList operation', async () => {
461
+ // Arrange
462
+ const user = {
463
+ id: 'test-user-id',
464
+ platform: 'testCRM',
465
+ accessToken: 'token',
466
+ platformAdditionalInfo: { proxyId: 'proxy-123' }
467
+ };
468
+
469
+ Connector.getProxyConfig.mockResolvedValue({
470
+ operations: {}
471
+ });
472
+
473
+ const mockConnector = {
474
+ getUserList: jest.fn()
475
+ };
476
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
477
+
478
+ // Act
479
+ const result = await adminHandler.getUserMapping({
480
+ user,
481
+ hashedRcAccountId: 'hashed-123',
482
+ rcExtensionList: []
483
+ });
484
+
485
+ // Assert
486
+ expect(result).toEqual([]);
487
+ });
488
+
489
+ test('should map CRM users to RC extensions', async () => {
490
+ // Arrange
491
+ await AdminConfigModel.create({
492
+ id: 'hashed-mapping',
493
+ userMappings: []
494
+ });
495
+
496
+ const user = {
497
+ id: 'test-user-id',
498
+ platform: 'testCRM',
499
+ accessToken: 'token',
500
+ platformAdditionalInfo: {}
501
+ };
502
+
503
+ const crmUsers = [
504
+ { id: 'crm-user-1', name: 'John Doe', email: 'john@example.com' },
505
+ { id: 'crm-user-2', name: 'Jane Smith', email: 'jane@example.com' }
506
+ ];
507
+
508
+ const rcExtensions = [
509
+ { id: 'ext-1', firstName: 'John', lastName: 'Doe', email: 'john@example.com', extensionNumber: '101' },
510
+ { id: 'ext-2', firstName: 'Bob', lastName: 'Wilson', email: 'bob@example.com', extensionNumber: '102' }
511
+ ];
512
+
513
+ const mockConnector = {
514
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
515
+ getBasicAuth: jest.fn().mockReturnValue('base64'),
516
+ getUserList: jest.fn().mockResolvedValue(crmUsers)
517
+ };
518
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
519
+
520
+ // Act
521
+ const result = await adminHandler.getUserMapping({
522
+ user,
523
+ hashedRcAccountId: 'hashed-mapping',
524
+ rcExtensionList: rcExtensions
525
+ });
526
+
527
+ // Assert
528
+ expect(result).toHaveLength(2);
529
+ // John should be matched by email
530
+ const johnMapping = result.find(m => m.crmUser.id === 'crm-user-1');
531
+ expect(johnMapping.rcUser).toHaveLength(1);
532
+ expect(johnMapping.rcUser[0].extensionId).toBe('ext-1');
533
+
534
+ // Jane should not be matched
535
+ const janeMapping = result.find(m => m.crmUser.id === 'crm-user-2');
536
+ expect(janeMapping.rcUser).toHaveLength(0);
537
+ });
538
+
539
+ test('should preserve existing mappings', async () => {
540
+ // Arrange
541
+ await AdminConfigModel.create({
542
+ id: 'hashed-existing-mapping',
543
+ userMappings: [
544
+ { crmUserId: 'crm-user-1', rcExtensionId: ['ext-existing'] }
545
+ ]
546
+ });
547
+
548
+ const user = {
549
+ id: 'test-user-id',
550
+ platform: 'testCRM',
551
+ accessToken: 'token',
552
+ platformAdditionalInfo: {}
553
+ };
554
+
555
+ const crmUsers = [
556
+ { id: 'crm-user-1', name: 'John Doe', email: 'john@example.com' }
557
+ ];
558
+
559
+ const rcExtensions = [
560
+ { id: 'ext-existing', firstName: 'John', lastName: 'Doe', email: 'john@example.com', extensionNumber: '101' }
561
+ ];
562
+
563
+ const mockConnector = {
564
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
565
+ getBasicAuth: jest.fn().mockReturnValue('base64'),
566
+ getUserList: jest.fn().mockResolvedValue(crmUsers)
567
+ };
568
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
569
+
570
+ // Act
571
+ const result = await adminHandler.getUserMapping({
572
+ user,
573
+ hashedRcAccountId: 'hashed-existing-mapping',
574
+ rcExtensionList: rcExtensions
575
+ });
576
+
577
+ // Assert
578
+ expect(result).toHaveLength(1);
579
+ expect(result[0].rcUser[0].extensionId).toBe('ext-existing');
580
+ });
581
+ });
582
+ });
583
+