@friggframework/test 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.dba001a.0

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/index.js CHANGED
@@ -5,7 +5,22 @@ const {
5
5
  } = require('./override-environment');
6
6
  const globalTeardown = require('./jest-global-teardown');
7
7
  const globalSetup = require('./jest-global-setup');
8
- const Authenticator = require('./Authenticator')
8
+ const Authenticator = require('./Authenticator');
9
+
10
+ // Router test utilities are loaded lazily to avoid jest.fn() errors during
11
+ // global setup (when Jest globals aren't available yet)
12
+ let _routerTestUtils = null;
13
+
14
+ /**
15
+ * Lazily load router test utilities (only when called from test context)
16
+ * @returns {Object} Router test utilities
17
+ */
18
+ const getRouterTestUtils = () => {
19
+ if (!_routerTestUtils) {
20
+ _routerTestUtils = require('./router-test-utils');
21
+ }
22
+ return _routerTestUtils;
23
+ };
9
24
 
10
25
  module.exports = {
11
26
  TestMongo,
@@ -13,5 +28,8 @@ module.exports = {
13
28
  restoreEnvironment,
14
29
  globalTeardown,
15
30
  globalSetup,
16
- Authenticator
31
+ Authenticator,
32
+ // Router test utilities - use getRouterTestUtils() for lazy loading
33
+ getRouterTestUtils,
34
+ // Direct import path is available: require('@friggframework/test/router-test-utils')
17
35
  };
@@ -1,10 +1,20 @@
1
1
  const { TestMongo } = require('./mongodb');
2
- const {overrideEnvironment} = require('./override-environment');
2
+ const { overrideEnvironment } = require('./override-environment');
3
3
 
4
4
  module.exports = async function () {
5
5
  if (!process.env.STAGE) {
6
6
  overrideEnvironment({ STAGE: 'dev' });
7
7
  }
8
- global.testMongo = new TestMongo();
9
- await global.testMongo.start();
8
+
9
+ // Only start MongoDB for integration tests
10
+ // Unit tests should not depend on MongoDB
11
+ const isIntegrationTest =
12
+ process.argv.includes('--group=integration') || process.env.TEST_TYPE === 'integration';
13
+
14
+ if (isIntegrationTest || (!process.argv.includes('--group=unit') && !process.env.TEST_TYPE)) {
15
+ global.testMongo = new TestMongo();
16
+ await global.testMongo.start();
17
+ } else {
18
+ console.log('Skipping MongoDB setup for unit tests');
19
+ }
10
20
  };
@@ -1,5 +1,10 @@
1
- const { restoreEnvironment } = require('./override-environment')
1
+ const { restoreEnvironment } = require('./override-environment');
2
+
2
3
  module.exports = async function () {
3
4
  restoreEnvironment();
4
- await global.testMongo.stop();
5
+
6
+ // Only stop MongoDB if it was started
7
+ if (global.testMongo) {
8
+ await global.testMongo.stop();
9
+ }
5
10
  };
package/package.json CHANGED
@@ -1,24 +1,35 @@
1
1
  {
2
2
  "name": "@friggframework/test",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.548.c8ae0ca.0",
4
+ "version": "2.0.0--canary.545.dba001a.0",
5
5
  "dependencies": {
6
6
  "@babel/eslint-parser": "^7.18.9",
7
+ "@hapi/boom": "^10.0.1",
7
8
  "eslint": "^8.22.0",
8
9
  "eslint-config-prettier": "^8.5.0",
9
10
  "eslint-plugin-json": "^3.1.0",
10
11
  "eslint-plugin-markdown": "^3.0.0",
11
12
  "eslint-plugin-no-only-tests": "^3.0.0",
12
13
  "eslint-plugin-yaml": "^0.5.0",
14
+ "express": "^4.21.2",
13
15
  "jest-runner-groups": "^2.2.0",
14
16
  "mongodb-memory-server": "^8.9.0",
15
17
  "open": "^8.4.2"
16
18
  },
17
19
  "devDependencies": {
18
- "@friggframework/eslint-config": "2.0.0--canary.548.c8ae0ca.0",
19
- "@friggframework/prettier-config": "2.0.0--canary.548.c8ae0ca.0",
20
+ "@friggframework/eslint-config": "2.0.0--canary.545.dba001a.0",
21
+ "@friggframework/prettier-config": "2.0.0--canary.545.dba001a.0",
20
22
  "jest": "^29.7.0",
21
- "prettier": "^2.7.1"
23
+ "prettier": "^2.7.1",
24
+ "supertest": "^7.2.2"
25
+ },
26
+ "peerDependencies": {
27
+ "supertest": ">=7.0.0"
28
+ },
29
+ "peerDependenciesMeta": {
30
+ "supertest": {
31
+ "optional": true
32
+ }
22
33
  },
23
34
  "scripts": {
24
35
  "lint:fix": "prettier --write --loglevel error . && eslint . --fix",
@@ -39,5 +50,5 @@
39
50
  "publishConfig": {
40
51
  "access": "public"
41
52
  },
42
- "gitHead": "c8ae0ca7837ba8393e3617be15c941607627c16b"
53
+ "gitHead": "dba001affb699d55b0684fa236af8a5f67ddf852"
43
54
  }
@@ -0,0 +1,535 @@
1
+ /**
2
+ * @file Router Test Utilities
3
+ * @description Shared test utilities for Express router testing in Frigg Framework
4
+ *
5
+ * These utilities reduce boilerplate and ensure consistency across router tests.
6
+ * Use these helpers to:
7
+ * - Create mock repositories with standard interfaces
8
+ * - Set up Express apps with authentication middleware
9
+ * - Handle Boom errors properly in test environments
10
+ * - Generate consistent test data
11
+ *
12
+ * @example
13
+ * const { createTestApp, createMockRepositories, mockData } = require('@friggframework/test/router-test-utils');
14
+ *
15
+ * describe('My Router', () => {
16
+ * let app, mocks;
17
+ *
18
+ * beforeEach(() => {
19
+ * mocks = createMockRepositories();
20
+ * app = createTestApp({ router: myRouter, mocks });
21
+ * });
22
+ * });
23
+ */
24
+
25
+ const express = require('express');
26
+ const Boom = require('@hapi/boom');
27
+
28
+ // ============================================================================
29
+ // Mock Data Generators
30
+ // ============================================================================
31
+
32
+ /**
33
+ * Helper to safely get jest.fn() - returns a real mock in test context, noop otherwise
34
+ * @returns {Function} Jest mock function or noop
35
+ */
36
+ const createMockFn = (implementation) => {
37
+ if (typeof jest !== 'undefined' && jest.fn) {
38
+ return implementation ? jest.fn(implementation) : jest.fn();
39
+ }
40
+ // Return a noop function with mock properties for non-Jest environments
41
+ const fn = implementation || (() => {});
42
+ fn.mockReturnValue = (val) => {
43
+ fn._mockReturnValue = val;
44
+ return fn;
45
+ };
46
+ fn.mockResolvedValue = (val) => {
47
+ fn._mockResolvedValue = val;
48
+ return fn;
49
+ };
50
+ fn.mockImplementation = (impl) => {
51
+ fn._mockImplementation = impl;
52
+ return fn;
53
+ };
54
+ return fn;
55
+ };
56
+
57
+ /**
58
+ * Default mock user object
59
+ * @type {Object}
60
+ */
61
+ const createMockUser = (overrides = {}) => ({
62
+ id: 'user-123',
63
+ appUserId: 'app-user-123',
64
+ username: 'testuser',
65
+ email: 'test@example.com',
66
+ getId: createMockFn(() => overrides.id || 'user-123'),
67
+ ...overrides,
68
+ });
69
+
70
+ /**
71
+ * Default mock credential object
72
+ * @type {Object}
73
+ */
74
+ const createMockCredential = (overrides = {}) => ({
75
+ id: 'cred-123',
76
+ type: 'hubspot',
77
+ userId: 'user-123',
78
+ authIsValid: true,
79
+ status: 'AUTHORIZED',
80
+ externalId: 'ext-123',
81
+ entityCount: 2,
82
+ createdAt: '2025-01-25T10:00:00.000Z',
83
+ updatedAt: '2025-01-25T10:00:00.000Z',
84
+ data: {
85
+ access_token: 'test-access-token',
86
+ refresh_token: 'test-refresh-token',
87
+ },
88
+ ...overrides,
89
+ });
90
+
91
+ /**
92
+ * Default mock entity object
93
+ * @type {Object}
94
+ */
95
+ const createMockEntity = (overrides = {}) => ({
96
+ id: 'entity-123',
97
+ entityType: 'ACCOUNT',
98
+ credential: 'cred-123',
99
+ credentialId: 'cred-123',
100
+ userId: 'user-123',
101
+ externalId: 'ext-account-123',
102
+ name: 'Test Account',
103
+ authIsValid: true,
104
+ type: 'hubspot',
105
+ ...overrides,
106
+ });
107
+
108
+ /**
109
+ * Default mock integration object
110
+ * @type {Object}
111
+ */
112
+ const createMockIntegration = (overrides = {}) => ({
113
+ id: 'integration-123',
114
+ name: 'Test Integration',
115
+ userId: 'user-123',
116
+ config: {},
117
+ entities: ['entity-123'],
118
+ createdAt: '2025-01-25T10:00:00.000Z',
119
+ updatedAt: '2025-01-25T10:00:00.000Z',
120
+ ...overrides,
121
+ });
122
+
123
+ /**
124
+ * Default mock module definition object
125
+ * @type {Object}
126
+ */
127
+ const createMockModuleDefinition = (overrides = {}) => {
128
+ const defaults = {
129
+ moduleName: 'hubspot',
130
+ definition: {
131
+ getDisplayName: () => overrides.displayName || 'HubSpot',
132
+ getDescription: () => overrides.description || 'Connect to HubSpot CRM',
133
+ getAuthType: () => overrides.authType || 'oauth2',
134
+ getAuthStepCount: () => overrides.stepCount || 1,
135
+ getCapabilities: () => overrides.capabilities || ['contacts', 'companies', 'deals'],
136
+ getAuthRequirementsForStep: jest.fn().mockResolvedValue({
137
+ type: overrides.authType || 'oauth2',
138
+ data: {
139
+ url: 'https://app.hubspot.com/oauth/authorize?client_id=test',
140
+ scopes: ['crm.objects.contacts.read', 'crm.objects.companies.read'],
141
+ },
142
+ }),
143
+ processAuthorizationStep: jest.fn(),
144
+ },
145
+ apiClass: jest.fn(),
146
+ };
147
+
148
+ return {
149
+ ...defaults,
150
+ ...overrides,
151
+ definition: {
152
+ ...defaults.definition,
153
+ ...(overrides.definition || {}),
154
+ },
155
+ };
156
+ };
157
+
158
+ /**
159
+ * Collection of mock data generators
160
+ */
161
+ const mockData = {
162
+ createMockUser,
163
+ createMockCredential,
164
+ createMockEntity,
165
+ createMockIntegration,
166
+ createMockModuleDefinition,
167
+
168
+ // Pre-created instances for quick use
169
+ user: createMockUser(),
170
+ credential: createMockCredential(),
171
+ entity: createMockEntity(),
172
+ integration: createMockIntegration(),
173
+ };
174
+
175
+ // ============================================================================
176
+ // Mock Repository Factories
177
+ // ============================================================================
178
+
179
+ /**
180
+ * Create a mock user repository with standard interface
181
+ * @param {Object} overrides - Method overrides
182
+ * @returns {Object} Mock user repository
183
+ */
184
+ const createMockUserRepository = (overrides = {}) => ({
185
+ findById: jest.fn().mockResolvedValue(mockData.user),
186
+ findOne: jest.fn().mockResolvedValue(mockData.user),
187
+ findByToken: jest.fn().mockResolvedValue(mockData.user),
188
+ getSessionToken: jest.fn().mockResolvedValue({ user: 'user-123', token: 'valid-token' }),
189
+ findIndividualUserById: jest.fn().mockResolvedValue(mockData.user),
190
+ findOrganizationUserById: jest.fn().mockResolvedValue(null),
191
+ findByEmail: jest.fn().mockResolvedValue(mockData.user),
192
+ findUserById: jest.fn().mockResolvedValue(mockData.user),
193
+ save: jest.fn(),
194
+ update: jest.fn(),
195
+ ...overrides,
196
+ });
197
+
198
+ /**
199
+ * Create a mock credential repository with standard interface
200
+ * @param {Object} overrides - Method overrides
201
+ * @returns {Object} Mock credential repository
202
+ */
203
+ const createMockCredentialRepository = (overrides = {}) => ({
204
+ findById: jest.fn(),
205
+ findByIdForUser: jest.fn(),
206
+ findCredential: jest.fn().mockResolvedValue([]),
207
+ findCredentialById: jest.fn(),
208
+ save: jest.fn(),
209
+ update: jest.fn(),
210
+ updateCredential: jest.fn(),
211
+ deleteCredentialById: jest.fn().mockResolvedValue({ deletedCount: 1 }),
212
+ ...overrides,
213
+ });
214
+
215
+ /**
216
+ * Create a mock module repository with standard interface
217
+ * @param {Object} overrides - Method overrides
218
+ * @returns {Object} Mock module repository
219
+ */
220
+ const createMockModuleRepository = (overrides = {}) => ({
221
+ findById: jest.fn(),
222
+ findByIdForUser: jest.fn(),
223
+ findByUserId: jest.fn().mockResolvedValue([]),
224
+ findByUserIdAndType: jest.fn(),
225
+ findModuleById: jest.fn(),
226
+ save: jest.fn(),
227
+ update: jest.fn(),
228
+ ...overrides,
229
+ });
230
+
231
+ /**
232
+ * Create a mock integration repository with standard interface
233
+ * @param {Object} overrides - Method overrides
234
+ * @returns {Object} Mock integration repository
235
+ */
236
+ const createMockIntegrationRepository = (overrides = {}) => ({
237
+ findById: jest.fn(),
238
+ findByIdForUser: jest.fn(),
239
+ findByUserId: jest.fn().mockResolvedValue([]),
240
+ save: jest.fn(),
241
+ update: jest.fn(),
242
+ delete: jest.fn(),
243
+ ...overrides,
244
+ });
245
+
246
+ /**
247
+ * Create a mock authorization session repository
248
+ * @param {Object} overrides - Method overrides
249
+ * @returns {Object} Mock authorization session repository
250
+ */
251
+ const createMockAuthorizationSessionRepository = (overrides = {}) => ({
252
+ findBySessionId: jest.fn(),
253
+ create: jest.fn(),
254
+ update: jest.fn(),
255
+ delete: jest.fn(),
256
+ ...overrides,
257
+ });
258
+
259
+ /**
260
+ * Create all mock repositories at once
261
+ * @param {Object} overrides - Per-repository overrides
262
+ * @returns {Object} All mock repositories
263
+ */
264
+ const createMockRepositories = (overrides = {}) => ({
265
+ userRepository: createMockUserRepository(overrides.user),
266
+ credentialRepository: createMockCredentialRepository(overrides.credential),
267
+ moduleRepository: createMockModuleRepository(overrides.module),
268
+ integrationRepository: createMockIntegrationRepository(overrides.integration),
269
+ authorizationSessionRepository: createMockAuthorizationSessionRepository(overrides.authorizationSession),
270
+ });
271
+
272
+ // ============================================================================
273
+ // Mock API Requester
274
+ // ============================================================================
275
+
276
+ /**
277
+ * Create a mock API requester for proxy tests
278
+ * @param {Object} overrides - Method overrides
279
+ * @returns {Object} Mock API requester
280
+ */
281
+ const createMockApiRequester = (overrides = {}) => ({
282
+ request: jest.fn().mockResolvedValue({
283
+ status: 200,
284
+ headers: { 'content-type': 'application/json' },
285
+ data: { success: true },
286
+ }),
287
+ _get: jest.fn(),
288
+ _post: jest.fn(),
289
+ _put: jest.fn(),
290
+ _patch: jest.fn(),
291
+ _delete: jest.fn(),
292
+ addAuthHeaders: jest.fn().mockResolvedValue({}),
293
+ ...overrides,
294
+ });
295
+
296
+ /**
297
+ * Create a mock module factory
298
+ * @param {Object} apiRequester - Mock API requester to use
299
+ * @returns {Object} Mock module factory
300
+ */
301
+ const createMockModuleFactory = (apiRequester = createMockApiRequester()) => ({
302
+ getModuleInstance: jest.fn().mockResolvedValue({
303
+ api: apiRequester,
304
+ }),
305
+ });
306
+
307
+ // ============================================================================
308
+ // Express App Setup
309
+ // ============================================================================
310
+
311
+ /**
312
+ * Boom error handler middleware for Express
313
+ * Converts Boom errors to proper HTTP responses
314
+ * @param {Error} err - Error object
315
+ * @param {Object} req - Express request
316
+ * @param {Object} res - Express response
317
+ * @param {Function} next - Next middleware
318
+ */
319
+ const boomErrorHandler = (err, req, res, next) => {
320
+ if (Boom.isBoom(err)) {
321
+ const { statusCode, payload } = err.output;
322
+ return res.status(statusCode).json({
323
+ error: payload.message,
324
+ message: payload.message,
325
+ statusCode: payload.statusCode,
326
+ ...err.data,
327
+ });
328
+ }
329
+
330
+ // Handle non-Boom errors
331
+ console.error('Unhandled error:', err);
332
+ return res.status(500).json({
333
+ error: 'Internal Server Error',
334
+ message: err.message,
335
+ statusCode: 500,
336
+ });
337
+ };
338
+
339
+ /**
340
+ * Create authentication middleware for testing
341
+ * @param {Object} options - Configuration options
342
+ * @param {Object} options.mockUser - User to inject on successful auth
343
+ * @param {string} options.validToken - Token that will be considered valid (default: 'valid-token')
344
+ * @returns {Function} Express middleware
345
+ */
346
+ const createAuthMiddleware = ({ mockUser = mockData.user, validToken = 'valid-token' } = {}) => {
347
+ return (req, res, next) => {
348
+ const authHeader = req.headers.authorization;
349
+
350
+ if (!authHeader) {
351
+ return next(Boom.unauthorized('No authentication provided'));
352
+ }
353
+
354
+ const token = authHeader.replace('Bearer ', '');
355
+
356
+ if (token === validToken) {
357
+ req.user = mockUser;
358
+ return next();
359
+ }
360
+
361
+ return next(Boom.unauthorized('Invalid token'));
362
+ };
363
+ };
364
+
365
+ /**
366
+ * Create a test Express app with common configuration
367
+ * @param {Object} options - Configuration options
368
+ * @param {Object} options.router - Express router to mount
369
+ * @param {string} options.basePath - Base path for router (default: '/')
370
+ * @param {Object} options.mockUser - User to inject on auth (default: mockData.user)
371
+ * @param {boolean} options.useAuth - Whether to use auth middleware (default: true)
372
+ * @param {string} options.validToken - Valid token string (default: 'valid-token')
373
+ * @returns {Object} Express app instance
374
+ */
375
+ const createTestApp = ({
376
+ router,
377
+ basePath = '/',
378
+ mockUser = mockData.user,
379
+ useAuth = true,
380
+ validToken = 'valid-token',
381
+ } = {}) => {
382
+ const app = express();
383
+ app.use(express.json());
384
+
385
+ if (useAuth) {
386
+ app.use(createAuthMiddleware({ mockUser, validToken }));
387
+ }
388
+
389
+ if (router) {
390
+ app.use(basePath, router);
391
+ }
392
+
393
+ // Add Boom error handler (must be after routes)
394
+ app.use(boomErrorHandler);
395
+
396
+ return app;
397
+ };
398
+
399
+ // ============================================================================
400
+ // Database Config Mock
401
+ // ============================================================================
402
+
403
+ /**
404
+ * Standard database config mock object
405
+ * Use this to mock '../database/config' in tests
406
+ */
407
+ const databaseConfigMock = {
408
+ DB_TYPE: 'mongodb',
409
+ getDatabaseType: jest.fn(() => 'mongodb'),
410
+ PRISMA_LOG_LEVEL: 'error,warn',
411
+ PRISMA_QUERY_LOGGING: false,
412
+ };
413
+
414
+ // ============================================================================
415
+ // App Definition Mock
416
+ // ============================================================================
417
+
418
+ /**
419
+ * Create a mock app definition
420
+ * @param {Object} overrides - Override specific properties
421
+ * @returns {Object} Mock app definition
422
+ */
423
+ const createMockAppDefinition = (overrides = {}) => ({
424
+ integrations: overrides.integrations || [
425
+ createMockModuleDefinition({ moduleName: 'hubspot' }),
426
+ createMockModuleDefinition({
427
+ moduleName: 'salesforce',
428
+ displayName: 'Salesforce',
429
+ description: 'Connect to Salesforce CRM',
430
+ capabilities: ['accounts', 'contacts', 'opportunities'],
431
+ }),
432
+ ],
433
+ userConfig: {
434
+ usePassword: true,
435
+ primary: 'individual',
436
+ authModes: {
437
+ friggToken: true,
438
+ },
439
+ ...(overrides.userConfig || {}),
440
+ },
441
+ ...overrides,
442
+ });
443
+
444
+ // ============================================================================
445
+ // Test Helpers
446
+ // ============================================================================
447
+
448
+ /**
449
+ * Helper to make authenticated requests
450
+ * @param {Object} app - Express app
451
+ * @param {string} token - Auth token (default: 'valid-token')
452
+ * @returns {Object} Supertest agent with auth header set
453
+ */
454
+ const authenticatedRequest = (request, token = 'valid-token') => {
455
+ return {
456
+ get: (url) => request.get(url).set('Authorization', `Bearer ${token}`),
457
+ post: (url) => request.post(url).set('Authorization', `Bearer ${token}`),
458
+ put: (url) => request.put(url).set('Authorization', `Bearer ${token}`),
459
+ patch: (url) => request.patch(url).set('Authorization', `Bearer ${token}`),
460
+ delete: (url) => request.delete(url).set('Authorization', `Bearer ${token}`),
461
+ };
462
+ };
463
+
464
+ /**
465
+ * Setup common jest mocks for repository factories
466
+ * Call this at the top of your test file after imports
467
+ * @param {Object} factoryMocks - Map of factory name to mock repository
468
+ */
469
+ const setupRepositoryFactoryMocks = ({
470
+ createUserRepository,
471
+ createCredentialRepository,
472
+ createModuleRepository,
473
+ createIntegrationRepository,
474
+ createAuthorizationSessionRepository,
475
+ loadAppDefinition,
476
+ mocks,
477
+ }) => {
478
+ if (createUserRepository && mocks.userRepository) {
479
+ createUserRepository.mockReturnValue(mocks.userRepository);
480
+ }
481
+ if (createCredentialRepository && mocks.credentialRepository) {
482
+ createCredentialRepository.mockReturnValue(mocks.credentialRepository);
483
+ }
484
+ if (createModuleRepository && mocks.moduleRepository) {
485
+ createModuleRepository.mockReturnValue(mocks.moduleRepository);
486
+ }
487
+ if (createIntegrationRepository && mocks.integrationRepository) {
488
+ createIntegrationRepository.mockReturnValue(mocks.integrationRepository);
489
+ }
490
+ if (createAuthorizationSessionRepository && mocks.authorizationSessionRepository) {
491
+ createAuthorizationSessionRepository.mockReturnValue(mocks.authorizationSessionRepository);
492
+ }
493
+ if (loadAppDefinition) {
494
+ loadAppDefinition.mockReturnValue(createMockAppDefinition());
495
+ }
496
+ };
497
+
498
+ // ============================================================================
499
+ // Exports
500
+ // ============================================================================
501
+
502
+ module.exports = {
503
+ // Mock data generators
504
+ mockData,
505
+ createMockUser,
506
+ createMockCredential,
507
+ createMockEntity,
508
+ createMockIntegration,
509
+ createMockModuleDefinition,
510
+
511
+ // Repository mocks
512
+ createMockUserRepository,
513
+ createMockCredentialRepository,
514
+ createMockModuleRepository,
515
+ createMockIntegrationRepository,
516
+ createMockAuthorizationSessionRepository,
517
+ createMockRepositories,
518
+
519
+ // API/Factory mocks
520
+ createMockApiRequester,
521
+ createMockModuleFactory,
522
+
523
+ // Express app utilities
524
+ createTestApp,
525
+ createAuthMiddleware,
526
+ boomErrorHandler,
527
+
528
+ // Config mocks
529
+ databaseConfigMock,
530
+ createMockAppDefinition,
531
+
532
+ // Test helpers
533
+ authenticatedRequest,
534
+ setupRepositoryFactoryMocks,
535
+ };
@@ -0,0 +1,350 @@
1
+ /**
2
+ * @file Router Test Utilities - Unit Tests
3
+ * @description Tests for the shared router test utilities
4
+ */
5
+
6
+ const request = require('supertest');
7
+ const express = require('express');
8
+ const Boom = require('@hapi/boom');
9
+
10
+ const {
11
+ // Mock data generators
12
+ mockData,
13
+ createMockUser,
14
+ createMockCredential,
15
+ createMockEntity,
16
+ createMockIntegration,
17
+ createMockModuleDefinition,
18
+
19
+ // Repository mocks
20
+ createMockUserRepository,
21
+ createMockCredentialRepository,
22
+ createMockModuleRepository,
23
+ createMockIntegrationRepository,
24
+ createMockAuthorizationSessionRepository,
25
+ createMockRepositories,
26
+
27
+ // API/Factory mocks
28
+ createMockApiRequester,
29
+ createMockModuleFactory,
30
+
31
+ // Express app utilities
32
+ createTestApp,
33
+ createAuthMiddleware,
34
+ boomErrorHandler,
35
+
36
+ // Config mocks
37
+ databaseConfigMock,
38
+ createMockAppDefinition,
39
+ } = require('./index');
40
+
41
+ describe('Router Test Utilities', () => {
42
+ describe('Mock Data Generators', () => {
43
+ describe('createMockUser', () => {
44
+ it('should create user with default values', () => {
45
+ const user = createMockUser();
46
+ expect(user.id).toBe('user-123');
47
+ expect(user.username).toBe('testuser');
48
+ expect(user.email).toBe('test@example.com');
49
+ });
50
+
51
+ it('should allow overriding values', () => {
52
+ const user = createMockUser({ id: 'custom-id', username: 'custom' });
53
+ expect(user.id).toBe('custom-id');
54
+ expect(user.username).toBe('custom');
55
+ expect(user.email).toBe('test@example.com'); // Default preserved
56
+ });
57
+
58
+ it('should include getId function', () => {
59
+ const user = createMockUser({ id: 'test-id' });
60
+ expect(user.getId()).toBe('test-id');
61
+ });
62
+ });
63
+
64
+ describe('createMockCredential', () => {
65
+ it('should create credential with default values', () => {
66
+ const cred = createMockCredential();
67
+ expect(cred.id).toBe('cred-123');
68
+ expect(cred.type).toBe('hubspot');
69
+ expect(cred.authIsValid).toBe(true);
70
+ expect(cred.data.access_token).toBeDefined();
71
+ });
72
+
73
+ it('should allow overriding values', () => {
74
+ const cred = createMockCredential({ type: 'salesforce', authIsValid: false });
75
+ expect(cred.type).toBe('salesforce');
76
+ expect(cred.authIsValid).toBe(false);
77
+ });
78
+ });
79
+
80
+ describe('createMockEntity', () => {
81
+ it('should create entity with default values', () => {
82
+ const entity = createMockEntity();
83
+ expect(entity.id).toBe('entity-123');
84
+ expect(entity.userId).toBe('user-123');
85
+ expect(entity.credentialId).toBe('cred-123');
86
+ });
87
+ });
88
+
89
+ describe('createMockIntegration', () => {
90
+ it('should create integration with default values', () => {
91
+ const integration = createMockIntegration();
92
+ expect(integration.id).toBe('integration-123');
93
+ expect(integration.userId).toBe('user-123');
94
+ });
95
+ });
96
+
97
+ describe('createMockModuleDefinition', () => {
98
+ it('should create module definition with default values', () => {
99
+ const def = createMockModuleDefinition();
100
+ expect(def.moduleName).toBe('hubspot');
101
+ expect(def.definition.getDisplayName()).toBe('HubSpot');
102
+ expect(def.definition.getAuthType()).toBe('oauth2');
103
+ });
104
+
105
+ it('should allow customization', () => {
106
+ const def = createMockModuleDefinition({
107
+ moduleName: 'custom',
108
+ displayName: 'Custom Service',
109
+ authType: 'api-key',
110
+ });
111
+ expect(def.moduleName).toBe('custom');
112
+ expect(def.definition.getDisplayName()).toBe('Custom Service');
113
+ expect(def.definition.getAuthType()).toBe('api-key');
114
+ });
115
+ });
116
+
117
+ describe('mockData convenience object', () => {
118
+ it('should provide pre-created instances', () => {
119
+ expect(mockData.user).toBeDefined();
120
+ expect(mockData.credential).toBeDefined();
121
+ expect(mockData.entity).toBeDefined();
122
+ expect(mockData.integration).toBeDefined();
123
+ });
124
+ });
125
+ });
126
+
127
+ describe('Mock Repository Factories', () => {
128
+ describe('createMockUserRepository', () => {
129
+ it('should create repository with all methods', () => {
130
+ const repo = createMockUserRepository();
131
+ expect(repo.findById).toBeDefined();
132
+ expect(repo.findByToken).toBeDefined();
133
+ expect(repo.getSessionToken).toBeDefined();
134
+ expect(repo.findIndividualUserById).toBeDefined();
135
+ });
136
+
137
+ it('should have working mock implementations', async () => {
138
+ const repo = createMockUserRepository();
139
+ const user = await repo.findById('test');
140
+ expect(user.id).toBe('user-123');
141
+ });
142
+ });
143
+
144
+ describe('createMockCredentialRepository', () => {
145
+ it('should create repository with all methods', () => {
146
+ const repo = createMockCredentialRepository();
147
+ expect(repo.findById).toBeDefined();
148
+ expect(repo.findCredential).toBeDefined();
149
+ expect(repo.deleteCredentialById).toBeDefined();
150
+ });
151
+ });
152
+
153
+ describe('createMockRepositories', () => {
154
+ it('should create all repositories at once', () => {
155
+ const repos = createMockRepositories();
156
+ expect(repos.userRepository).toBeDefined();
157
+ expect(repos.credentialRepository).toBeDefined();
158
+ expect(repos.moduleRepository).toBeDefined();
159
+ expect(repos.integrationRepository).toBeDefined();
160
+ expect(repos.authorizationSessionRepository).toBeDefined();
161
+ });
162
+ });
163
+ });
164
+
165
+ describe('Mock API/Factory Utilities', () => {
166
+ describe('createMockApiRequester', () => {
167
+ it('should create API requester with all methods', () => {
168
+ const api = createMockApiRequester();
169
+ expect(api.request).toBeDefined();
170
+ expect(api._get).toBeDefined();
171
+ expect(api._post).toBeDefined();
172
+ });
173
+
174
+ it('should have working request mock', async () => {
175
+ const api = createMockApiRequester();
176
+ const response = await api.request({ method: 'GET', path: '/test' });
177
+ expect(response.status).toBe(200);
178
+ expect(response.data.success).toBe(true);
179
+ });
180
+ });
181
+
182
+ describe('createMockModuleFactory', () => {
183
+ it('should create factory with getModuleInstance', () => {
184
+ const factory = createMockModuleFactory();
185
+ expect(factory.getModuleInstance).toBeDefined();
186
+ });
187
+
188
+ it('should return module with api property', async () => {
189
+ const factory = createMockModuleFactory();
190
+ const instance = await factory.getModuleInstance('entity-123', 'user-123');
191
+ expect(instance.api).toBeDefined();
192
+ expect(instance.api.request).toBeDefined();
193
+ });
194
+ });
195
+ });
196
+
197
+ describe('Express App Utilities', () => {
198
+ describe('boomErrorHandler', () => {
199
+ it('should handle Boom errors', async () => {
200
+ const app = express();
201
+ app.get('/error', (req, res, next) => {
202
+ next(Boom.notFound('Resource not found'));
203
+ });
204
+ app.use(boomErrorHandler);
205
+
206
+ const response = await request(app).get('/error');
207
+ expect(response.status).toBe(404);
208
+ expect(response.body.error).toBe('Resource not found');
209
+ });
210
+
211
+ it('should handle non-Boom errors', async () => {
212
+ const app = express();
213
+ app.get('/error', () => {
214
+ throw new Error('Something went wrong');
215
+ });
216
+ app.use(boomErrorHandler);
217
+
218
+ const response = await request(app).get('/error');
219
+ expect(response.status).toBe(500);
220
+ expect(response.body.error).toBe('Internal Server Error');
221
+ });
222
+ });
223
+
224
+ describe('createAuthMiddleware', () => {
225
+ it('should authenticate valid token', async () => {
226
+ const app = express();
227
+ app.use(createAuthMiddleware());
228
+ app.get('/test', (req, res) => res.json({ user: req.user }));
229
+ app.use(boomErrorHandler);
230
+
231
+ const response = await request(app)
232
+ .get('/test')
233
+ .set('Authorization', 'Bearer valid-token');
234
+
235
+ expect(response.status).toBe(200);
236
+ expect(response.body.user.id).toBe('user-123');
237
+ });
238
+
239
+ it('should reject missing token', async () => {
240
+ const app = express();
241
+ app.use(createAuthMiddleware());
242
+ app.get('/test', (req, res) => res.json({ user: req.user }));
243
+ app.use(boomErrorHandler);
244
+
245
+ const response = await request(app).get('/test');
246
+ expect(response.status).toBe(401);
247
+ });
248
+
249
+ it('should reject invalid token', async () => {
250
+ const app = express();
251
+ app.use(createAuthMiddleware());
252
+ app.get('/test', (req, res) => res.json({ user: req.user }));
253
+ app.use(boomErrorHandler);
254
+
255
+ const response = await request(app)
256
+ .get('/test')
257
+ .set('Authorization', 'Bearer invalid-token');
258
+
259
+ expect(response.status).toBe(401);
260
+ });
261
+
262
+ it('should use custom valid token', async () => {
263
+ const app = express();
264
+ app.use(createAuthMiddleware({ validToken: 'custom-token' }));
265
+ app.get('/test', (req, res) => res.json({ user: req.user }));
266
+ app.use(boomErrorHandler);
267
+
268
+ const response = await request(app)
269
+ .get('/test')
270
+ .set('Authorization', 'Bearer custom-token');
271
+
272
+ expect(response.status).toBe(200);
273
+ });
274
+ });
275
+
276
+ describe('createTestApp', () => {
277
+ it('should create app with JSON parsing', async () => {
278
+ const router = express.Router();
279
+ router.post('/test', (req, res) => res.json(req.body));
280
+
281
+ const app = createTestApp({ router, useAuth: false });
282
+
283
+ const response = await request(app)
284
+ .post('/test')
285
+ .send({ data: 'test' });
286
+
287
+ expect(response.status).toBe(200);
288
+ expect(response.body.data).toBe('test');
289
+ });
290
+
291
+ it('should include auth middleware by default', async () => {
292
+ const router = express.Router();
293
+ router.get('/test', (req, res) => res.json({ ok: true }));
294
+
295
+ const app = createTestApp({ router });
296
+
297
+ const response = await request(app).get('/test');
298
+ expect(response.status).toBe(401);
299
+ });
300
+
301
+ it('should mount router at custom base path', async () => {
302
+ const router = express.Router();
303
+ router.get('/resource', (req, res) => res.json({ ok: true }));
304
+
305
+ const app = createTestApp({ router, basePath: '/api', useAuth: false });
306
+
307
+ const response = await request(app).get('/api/resource');
308
+ expect(response.status).toBe(200);
309
+ });
310
+
311
+ it('should include Boom error handler', async () => {
312
+ const router = express.Router();
313
+ router.get('/error', (req, res, next) => {
314
+ next(Boom.badRequest('Invalid input'));
315
+ });
316
+
317
+ const app = createTestApp({ router, useAuth: false });
318
+
319
+ const response = await request(app).get('/error');
320
+ expect(response.status).toBe(400);
321
+ expect(response.body.error).toBe('Invalid input');
322
+ });
323
+ });
324
+ });
325
+
326
+ describe('Config Mocks', () => {
327
+ describe('databaseConfigMock', () => {
328
+ it('should have expected properties', () => {
329
+ expect(databaseConfigMock.DB_TYPE).toBe('mongodb');
330
+ expect(databaseConfigMock.getDatabaseType()).toBe('mongodb');
331
+ });
332
+ });
333
+
334
+ describe('createMockAppDefinition', () => {
335
+ it('should create app definition with defaults', () => {
336
+ const def = createMockAppDefinition();
337
+ expect(def.integrations).toBeDefined();
338
+ expect(def.integrations.length).toBeGreaterThan(0);
339
+ expect(def.userConfig).toBeDefined();
340
+ });
341
+
342
+ it('should allow customization', () => {
343
+ const def = createMockAppDefinition({
344
+ userConfig: { usePassword: false },
345
+ });
346
+ expect(def.userConfig.usePassword).toBe(false);
347
+ });
348
+ });
349
+ });
350
+ });