@friggframework/devtools 2.0.0--canary.666.81647c4.0 → 2.0.0--canary.667.6dc4b39.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.
Files changed (29) hide show
  1. package/frigg-cli/README.md +1 -38
  2. package/frigg-cli/__tests__/unit/commands/build.test.js +0 -3
  3. package/frigg-cli/__tests__/unit/commands/deploy.test.js +0 -3
  4. package/frigg-cli/build-command/index.js +0 -6
  5. package/frigg-cli/deploy-command/index.js +0 -6
  6. package/frigg-cli/index.js +1 -13
  7. package/frigg-cli/init-command/backend-first-handler.js +46 -25
  8. package/frigg-cli/start-command/index.js +0 -4
  9. package/frigg-cli/start-command/start-command.test.js +0 -3
  10. package/infrastructure/__tests__/management-api-gateway-routes.test.js +281 -0
  11. package/infrastructure/create-frigg-infrastructure.js +1 -4
  12. package/infrastructure/docs/POSTGRES-CONFIGURATION.md +98 -60
  13. package/infrastructure/domains/shared/utilities/base-definition-factory.js +19 -0
  14. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +13 -1
  15. package/package.json +7 -7
  16. package/frigg-cli/__tests__/unit/commands/validate-wiring.test.js +0 -92
  17. package/frigg-cli/validate-command/__tests__/init-validation.test.js +0 -63
  18. package/frigg-cli/validate-command/__tests__/integrations-checks.test.js +0 -133
  19. package/frigg-cli/validate-command/__tests__/validate-app-definition.test.js +0 -326
  20. package/frigg-cli/validate-command/__tests__/validate-command.test.js +0 -241
  21. package/frigg-cli/validate-command/checks/config.js +0 -468
  22. package/frigg-cli/validate-command/checks/integrations.js +0 -274
  23. package/frigg-cli/validate-command/format-report.js +0 -58
  24. package/frigg-cli/validate-command/index.js +0 -107
  25. package/frigg-cli/validate-command/load-app-definition.js +0 -93
  26. package/frigg-cli/validate-command/schema-issues.js +0 -154
  27. package/frigg-cli/validate-command/validate-app-definition.js +0 -60
  28. package/infrastructure/app-definition-schema-contract.test.js +0 -607
  29. package/infrastructure/create-frigg-infrastructure.test.js +0 -51
@@ -8,10 +8,10 @@ Frigg Framework supports automatic provisioning of Amazon Aurora Serverless v2 P
8
8
 
9
9
  ### Key Features
10
10
 
11
- - **Aurora Serverless v2**: Cost-efficient auto-scaling database (0.5-4 ACU default)
11
+ - **Aurora Serverless v2**: Cost-efficient auto-scaling database (0.5-1.0 ACU default)
12
12
  - **VPC Integration**: Deployed in same private subnets as Lambda functions
13
13
  - **Secrets Manager**: Automatic credential management and rotation
14
- - **Three Management Modes**: discover, managed, use-existing
14
+ - **Three Management Modes**: discover, create-new, use-existing
15
15
  - **Security**: Private subnet deployment with security group isolation
16
16
  - **High Availability**: Multi-AZ deployment with automatic failover
17
17
 
@@ -21,16 +21,12 @@ Frigg Framework supports automatic provisioning of Amazon Aurora Serverless v2 P
21
21
 
22
22
  ### App Definition Structure
23
23
 
24
- These are the keys the Aurora builder reads (see `database.postgres` in
25
- `packages/schemas/schemas/app-definition.schema.json`). `frigg validate`
26
- rejects any other key under `database.postgres`.
27
-
28
24
  ```javascript
29
25
  // backend/index.js
30
26
  const appDefinition = {
31
27
  name: 'my-frigg-app',
32
28
 
33
- // VPC Configuration (required for Aurora)
29
+ // Enable VPC deployment (required for Aurora)
34
30
  vpc: {
35
31
  enable: true,
36
32
  },
@@ -40,24 +36,34 @@ const appDefinition = {
40
36
  postgres: {
41
37
  enable: true,
42
38
 
43
- // 'discover' (also when omitted) | 'managed' | 'use-existing'
44
- // Ignored when managementMode is 'managed' or 'existing'.
39
+ // Management mode: 'discover' | 'create-new' | 'use-existing'
45
40
  management: 'discover',
46
41
 
47
- database: 'frigg', // database name (default 'frigg')
48
- username: 'postgres', // master user (default 'postgres')
49
- engineVersion: '15.13', // Aurora PostgreSQL version (default '15.13')
42
+ // Basic Configuration
43
+ databaseName: 'frigg_db',
44
+ masterUsername: 'frigg_admin',
45
+
46
+ // Engine Configuration
47
+ engine: 'aurora-postgresql',
48
+ engineVersion: '15.3',
49
+
50
+ // Scaling Configuration (Aurora Serverless v2)
51
+ scaling: {
52
+ minCapacity: 0.5, // ACUs (0.5 = ~1GB RAM, ~$43/month)
53
+ maxCapacity: 1.0, // ACUs (1.0 = ~2GB RAM, ~$87/month)
54
+ },
50
55
 
51
- // Aurora Serverless v2 capacity, in ACUs (0.5-128)
52
- minCapacity: 0.5, // default 0.5
53
- maxCapacity: 4, // default 4
56
+ // Backup Configuration
57
+ backupRetentionDays: 7,
58
+ preferredBackupWindow: '03:00-04:00',
54
59
 
55
- publiclyAccessible: false, // default false (private subnets)
56
- autoCreateCredentials: false, // discover only: create a secret for a discovered cluster
60
+ // Security & Advanced
61
+ deletionProtection: true,
62
+ enablePerformanceInsights: false,
57
63
 
58
- // use-existing only
59
- // endpoint: 'mydb.cluster-abc123.us-east-1.rds.amazonaws.com',
60
- // port: 5432,
64
+ // For use-existing mode
65
+ clusterIdentifier: 'my-existing-cluster',
66
+ secretArn: 'arn:aws:secretsmanager:...',
61
67
  }
62
68
  }
63
69
  };
@@ -67,17 +73,13 @@ module.exports = {
67
73
  };
68
74
  ```
69
75
 
70
- Backup retention (7 days), the backup and maintenance windows and deletion
71
- settings are fixed by the builder and are not configurable through the app
72
- definition.
73
-
74
76
  ---
75
77
 
76
78
  ## Management Modes
77
79
 
78
80
  ### 1. Discover Mode (Default)
79
81
 
80
- Discovers an existing Aurora cluster, or creates a new one in the stack if none is found.
82
+ Automatically discovers existing Aurora clusters or creates new one if none found.
81
83
 
82
84
  ```javascript
83
85
  database: {
@@ -98,56 +100,63 @@ database: {
98
100
 
99
101
  ---
100
102
 
101
- ### 2. Managed Mode
103
+ ### 2. Create-New Mode
102
104
 
103
- Creates the cluster, instance, subnet group and secret in the stack.
105
+ Always creates a new Aurora cluster, even if existing clusters are found.
104
106
 
105
107
  ```javascript
106
108
  database: {
107
109
  postgres: {
108
110
  enable: true,
109
- management: 'managed',
110
- minCapacity: 1,
111
- maxCapacity: 2,
111
+ management: 'create-new',
112
+
113
+ // Customization options
114
+ databaseName: 'my_app_db',
115
+ masterUsername: 'admin',
116
+ engineVersion: '15.3',
117
+ scaling: {
118
+ minCapacity: 1.0,
119
+ maxCapacity: 2.0,
120
+ },
121
+ backupRetentionDays: 14,
122
+ deletionProtection: true,
112
123
  }
113
124
  }
114
125
  ```
115
126
 
116
- With the top-level `managementMode: 'managed'` (and `vpcIsolation: 'isolated'`),
117
- Frigg manages the database per stage and `database.postgres.management` is ignored.
118
-
119
127
  **Best For**: Production environments where you want dedicated database resources.
120
128
 
121
129
  ---
122
130
 
123
131
  ### 3. Use-Existing Mode
124
132
 
125
- Connects to an existing PostgreSQL endpoint without creating database resources.
133
+ Uses a specific existing Aurora cluster by identifier.
126
134
 
127
135
  ```javascript
128
136
  database: {
129
137
  postgres: {
130
138
  enable: true,
131
139
  management: 'use-existing',
132
- endpoint: 'my-cluster.cluster-abc123.us-east-1.rds.amazonaws.com', // required
133
- port: 5432,
134
- database: 'frigg_db',
135
- username: 'frigg_admin',
140
+
141
+ // Required: existing cluster identifier
142
+ clusterIdentifier: 'my-existing-aurora-cluster',
143
+
144
+ // Optional: secret ARN (discovered if not provided)
145
+ secretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:my-db-secret',
146
+
147
+ // Database name to connect to
148
+ databaseName: 'frigg_db',
136
149
  }
137
150
  }
138
151
  ```
139
152
 
140
- To use an existing cluster by identifier instead, use the ownership form:
141
- `ownership: { cluster: 'external', instance: 'external' }` with
142
- `external: { clusterIdentifier, instanceIdentifier, secretArn }`.
143
-
144
153
  **Best For**: Shared database scenarios or when you manage Aurora outside of Frigg.
145
154
 
146
155
  ---
147
156
 
148
157
  ## Created AWS Resources
149
158
 
150
- When provisioning Aurora (`managed`, or `discover` mode without an existing cluster), Frigg creates:
159
+ When provisioning Aurora (`create-new` or `discover` mode without existing cluster), Frigg creates:
151
160
 
152
161
  ### 1. RDS DB Subnet Group
153
162
  - **Name**: `{service}-{stage}-db-subnet-group`
@@ -212,15 +221,17 @@ database: {
212
221
  database: {
213
222
  postgres: {
214
223
  enable: true,
215
- minCapacity: 1, // Higher baseline for production
216
- maxCapacity: 4, // Handle traffic spikes
224
+ scaling: {
225
+ minCapacity: 1.0, // Higher baseline for production
226
+ maxCapacity: 4.0, // Handle traffic spikes
227
+ },
228
+ backupRetentionDays: 30, // Longer retention
229
+ enablePerformanceInsights: true, // Monitoring
230
+ deletionProtection: true, // Prevent accidental deletion
217
231
  }
218
232
  }
219
233
  ```
220
234
 
221
- Backup retention, Performance Insights and deletion protection are not
222
- configurable through the app definition; change them on the cluster.
223
-
224
235
  **Estimated Monthly Costs**:
225
236
  - **Baseline**: $87/month (1.0 ACU minimum)
226
237
  - **Peak Traffic**: $348/month (4.0 ACU maximum)
@@ -265,9 +276,15 @@ const users = await prismaClient.user.findMany();
265
276
 
266
277
  ### 4. Deletion Protection
267
278
 
268
- Deletion protection is not configurable through the app definition. Enable it
269
- on the cluster in the AWS console (or with the AWS CLI); you must then disable
270
- it before deleting the stack.
279
+ ```javascript
280
+ database: {
281
+ postgres: {
282
+ deletionProtection: true, // Prevents accidental deletion
283
+ }
284
+ }
285
+ ```
286
+
287
+ **Important**: When enabled, you must manually disable deletion protection in AWS console before stack deletion.
271
288
 
272
289
  ---
273
290
 
@@ -367,7 +384,7 @@ DB_TYPE=postgresql
367
384
  database: {
368
385
  postgres: {
369
386
  enable: true,
370
- management: 'managed',
387
+ management: 'create-new',
371
388
  }
372
389
  }
373
390
  ```
@@ -394,7 +411,7 @@ DB_TYPE=postgresql
394
411
  database: {
395
412
  postgres: {
396
413
  enable: true,
397
- management: 'managed',
414
+ management: 'create-new',
398
415
  }
399
416
  }
400
417
  ```
@@ -439,13 +456,13 @@ DB_TYPE=postgresql
439
456
 
440
457
  **Error**:
441
458
  ```
442
- No Aurora cluster found in discovery mode. Set management to "managed" or provide endpoint with "use-existing".
459
+ No Aurora cluster found in discovery mode. Set management to "create-new"...
443
460
  ```
444
461
 
445
462
  **Solution**:
446
463
  1. Check VPC is enabled: `vpc.enable: true`
447
- 2. Set management mode: `management: 'managed'`
448
- 3. Or connect to a known endpoint: `management: 'use-existing'`, `endpoint: '...'`
464
+ 2. Set management mode: `management: 'create-new'`
465
+ 3. Or provide cluster identifier: `clusterIdentifier: 'my-cluster'`
449
466
 
450
467
  ---
451
468
 
@@ -527,19 +544,40 @@ Cannot create Aurora cluster: InsufficientDBInstanceCapacity
527
544
 
528
545
  ## Advanced Configuration
529
546
 
530
- ### Custom Engine Version
547
+ ### Custom Backup Window
531
548
 
532
549
  ```javascript
533
550
  database: {
534
551
  postgres: {
535
552
  enable: true,
536
- engineVersion: '16.4', // Default: 15.13
553
+ backupRetentionDays: 30,
554
+ preferredBackupWindow: '02:00-03:00', // UTC
537
555
  }
538
556
  }
539
557
  ```
540
558
 
541
- The backup window, backup retention and Performance Insights are set by the
542
- builder and are not configurable through the app definition.
559
+ ### Enhanced Monitoring
560
+
561
+ ```javascript
562
+ database: {
563
+ postgres: {
564
+ enable: true,
565
+ enablePerformanceInsights: true,
566
+ // Performance Insights retention: 7 days (default) or 731 days
567
+ }
568
+ }
569
+ ```
570
+
571
+ ### Custom Engine Version
572
+
573
+ ```javascript
574
+ database: {
575
+ postgres: {
576
+ enable: true,
577
+ engineVersion: '14.6', // Default: 15.3
578
+ }
579
+ }
580
+ ```
543
581
 
544
582
  ### Read Replicas (Not Supported Yet)
545
583
 
@@ -286,6 +286,11 @@ function createBaseDefinition(
286
286
  ...(usePrismaLayer && { layers: [{ Ref: 'PrismaLambdaLayer' }] }),
287
287
  skipEsbuild: true, // Handlers in node_modules don't need bundling
288
288
  package: skipEsbuildPackageConfig,
289
+ // Every path prefix the core integration router
290
+ // (createIntegrationRouter) registers must be routed here;
291
+ // HTTP API has no implicit catch-all, so an unlisted prefix
292
+ // 404s at the gateway before Express sees it. Guarded by
293
+ // __tests__/management-api-gateway-routes.test.js.
289
294
  events: [
290
295
  { httpApi: { path: '/api/integrations', method: 'ANY' } },
291
296
  {
@@ -295,6 +300,20 @@ function createBaseDefinition(
295
300
  },
296
301
  },
297
302
  { httpApi: { path: '/api/authorize', method: 'ANY' } },
303
+ { httpApi: { path: '/api/entity', method: 'ANY' } },
304
+ {
305
+ httpApi: {
306
+ path: '/api/entity/{proxy+}',
307
+ method: 'ANY',
308
+ },
309
+ },
310
+ { httpApi: { path: '/api/entities', method: 'ANY' } },
311
+ {
312
+ httpApi: {
313
+ path: '/api/entities/{proxy+}',
314
+ method: 'ANY',
315
+ },
316
+ },
298
317
  ],
299
318
  },
300
319
  user: {
@@ -94,7 +94,19 @@ describe('Base Definition Factory', () => {
94
94
 
95
95
  expect(result.functions.auth.handler).toBe('node_modules/@friggframework/core/handlers/routers/auth.handler');
96
96
  expect(result.functions.auth.layers).toEqual([{ Ref: 'PrismaLambdaLayer' }]);
97
- expect(result.functions.auth.events).toHaveLength(3);
97
+ expect(
98
+ result.functions.auth.events.map(
99
+ (e) => `${e.httpApi.method} ${e.httpApi.path}`
100
+ )
101
+ ).toEqual([
102
+ 'ANY /api/integrations',
103
+ 'ANY /api/integrations/{proxy+}',
104
+ 'ANY /api/authorize',
105
+ 'ANY /api/entity',
106
+ 'ANY /api/entity/{proxy+}',
107
+ 'ANY /api/entities',
108
+ 'ANY /api/entities/{proxy+}',
109
+ ]);
98
110
  });
99
111
 
100
112
  it('should NOT include legacy dbMigrate function', () => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/devtools",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.666.81647c4.0",
4
+ "version": "2.0.0--canary.667.6dc4b39.0",
5
5
  "bin": {
6
6
  "frigg": "./frigg-cli/index.js"
7
7
  },
@@ -26,9 +26,9 @@
26
26
  "@babel/eslint-parser": "^7.18.9",
27
27
  "@babel/parser": "^7.25.3",
28
28
  "@babel/traverse": "^7.25.3",
29
- "@friggframework/core": "2.0.0--canary.666.81647c4.0",
30
- "@friggframework/schemas": "2.0.0--canary.666.81647c4.0",
31
- "@friggframework/test": "2.0.0--canary.666.81647c4.0",
29
+ "@friggframework/core": "2.0.0--canary.667.6dc4b39.0",
30
+ "@friggframework/schemas": "2.0.0--canary.667.6dc4b39.0",
31
+ "@friggframework/test": "2.0.0--canary.667.6dc4b39.0",
32
32
  "@hapi/boom": "^10.0.1",
33
33
  "@inquirer/prompts": "^5.3.8",
34
34
  "axios": "^1.18.0",
@@ -56,8 +56,8 @@
56
56
  "validate-npm-package-name": "^5.0.0"
57
57
  },
58
58
  "devDependencies": {
59
- "@friggframework/eslint-config": "2.0.0--canary.666.81647c4.0",
60
- "@friggframework/prettier-config": "2.0.0--canary.666.81647c4.0",
59
+ "@friggframework/eslint-config": "2.0.0--canary.667.6dc4b39.0",
60
+ "@friggframework/prettier-config": "2.0.0--canary.667.6dc4b39.0",
61
61
  "aws-sdk-client-mock": "^4.1.0",
62
62
  "aws-sdk-client-mock-jest": "^4.1.0",
63
63
  "jest": "^30.1.3",
@@ -89,5 +89,5 @@
89
89
  "publishConfig": {
90
90
  "access": "public"
91
91
  },
92
- "gitHead": "81647c4f85dd9b1e8e69b2bddcb09b1759341bf8"
92
+ "gitHead": "6dc4b398e9de7b24d84754021d06e948ebcf7431"
93
93
  }
@@ -1,92 +0,0 @@
1
- /**
2
- * build and deploy validate the app definition first and stop on errors;
3
- * start validates and only warns (ADR-051).
4
- */
5
-
6
- // child_process and node:child_process are one module to jest: one mock.
7
- const mockChildProcess = {
8
- spawnSync: jest.fn(() => ({ status: 0 })),
9
- spawn: jest.fn(),
10
- };
11
- jest.mock('child_process', () => mockChildProcess);
12
- jest.mock('node:child_process', () => mockChildProcess);
13
- jest.mock('../../../validate-command', () => ({
14
- preflightValidation: jest.fn(),
15
- }));
16
- jest.mock('../../../doctor-command', () => ({ doctorCommand: jest.fn() }));
17
- jest.mock('../../../utils/database-validator', () => ({
18
- validateDatabaseUrl: jest.fn(() => ({ valid: true })),
19
- getDatabaseType: jest.fn(() => ({ dbType: 'postgresql' })),
20
- checkPrismaClientGenerated: jest.fn(() => ({ generated: true })),
21
- }));
22
- jest.mock('dotenv', () => ({ config: jest.fn() }));
23
-
24
- const childProcess = mockChildProcess;
25
- const nodeChildProcess = mockChildProcess;
26
- const { preflightValidation } = require('../../../validate-command');
27
- const { buildCommand } = require('../../../build-command');
28
- const { deployCommand } = require('../../../deploy-command');
29
- const { startCommand } = require('../../../start-command');
30
-
31
- describe('validation before build, deploy and start', () => {
32
- beforeEach(() => {
33
- jest.clearAllMocks();
34
- jest.spyOn(console, 'log').mockImplementation(() => {});
35
- jest.spyOn(console, 'warn').mockImplementation(() => {});
36
- jest.spyOn(console, 'error').mockImplementation(() => {});
37
- jest.spyOn(process, 'exit').mockImplementation(() => {});
38
- });
39
-
40
- afterEach(() => jest.restoreAllMocks());
41
-
42
- it('build validates first and fails on errors', async () => {
43
- preflightValidation.mockReturnValue(false);
44
-
45
- await buildCommand({ stage: 'prod' });
46
-
47
- expect(preflightValidation).toHaveBeenCalledWith({
48
- command: 'build',
49
- options: { stage: 'prod' },
50
- failOnErrors: true,
51
- });
52
- expect(childProcess.spawnSync).not.toHaveBeenCalled();
53
- });
54
-
55
- it('build packages after a passing validation', async () => {
56
- preflightValidation.mockReturnValue(true);
57
-
58
- await buildCommand({ stage: 'prod' });
59
-
60
- expect(childProcess.spawnSync).toHaveBeenCalled();
61
- });
62
-
63
- it('deploy validates first and fails on errors', async () => {
64
- preflightValidation.mockReturnValue(false);
65
-
66
- await deployCommand({ stage: 'prod', skipValidate: false });
67
-
68
- expect(preflightValidation).toHaveBeenCalledWith({
69
- command: 'deploy',
70
- options: { stage: 'prod', skipValidate: false },
71
- failOnErrors: true,
72
- });
73
- expect(childProcess.spawn).not.toHaveBeenCalled();
74
- });
75
-
76
- it('start validates and only warns', async () => {
77
- preflightValidation.mockReturnValue(true);
78
- nodeChildProcess.spawn.mockReturnValue({ on: jest.fn() });
79
- const previous = process.env.DATABASE_URL;
80
- process.env.DATABASE_URL = 'postgresql://localhost/frigg';
81
-
82
- await startCommand({ stage: 'dev' });
83
-
84
- process.env.DATABASE_URL = previous;
85
- expect(preflightValidation).toHaveBeenCalledWith({
86
- command: 'start',
87
- options: { stage: 'dev' },
88
- failOnErrors: false,
89
- });
90
- expect(nodeChildProcess.spawn).toHaveBeenCalled();
91
- });
92
- });
@@ -1,63 +0,0 @@
1
- /**
2
- * `frigg init` validates the generated app with the same code path as
3
- * `frigg validate`, and never fails because of it (ADR-051).
4
- */
5
- const fs = require('fs');
6
- const os = require('os');
7
- const path = require('path');
8
-
9
- jest.mock('@inquirer/prompts', () => ({}));
10
-
11
- const BackendFirstHandler = require('../../init-command/backend-first-handler');
12
-
13
- function writeApp(source) {
14
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'frigg-init-'));
15
- fs.writeFileSync(path.join(dir, 'package.json'), '{"name":"backend"}');
16
- fs.writeFileSync(path.join(dir, 'index.js'), source);
17
- return dir;
18
- }
19
-
20
- describe('frigg init validation', () => {
21
- const dirs = [];
22
- let log;
23
-
24
- beforeEach(() => {
25
- log = jest.spyOn(console, 'log').mockImplementation(() => {});
26
- });
27
-
28
- afterEach(() => {
29
- jest.restoreAllMocks();
30
- for (const dir of dirs.splice(0))
31
- fs.rmSync(dir, { recursive: true, force: true });
32
- });
33
-
34
- it('validates the real generated definition, not a stand-in', async () => {
35
- const dir = writeApp(
36
- "module.exports = { Definition: { integrations: [], database: { postgres: { enable: true, management: 'create-new' } } } };"
37
- );
38
- dirs.push(dir);
39
- const handler = new BackendFirstHandler(dir, {});
40
-
41
- const ok = await handler.validateGeneratedAppDefinition(
42
- path.join(dir, 'index.js')
43
- );
44
-
45
- expect(ok).toBe(false);
46
- expect(log.mock.calls.flat().join('\n')).toContain(
47
- '/database/postgres/management'
48
- );
49
- });
50
-
51
- it('stays quiet before npm install, when index.js cannot load its dependencies', async () => {
52
- const dir = writeApp(
53
- "require('@friggframework/api-module-does-not-exist');"
54
- );
55
- dirs.push(dir);
56
- const handler = new BackendFirstHandler(dir, {});
57
-
58
- await expect(
59
- handler.validateGeneratedAppDefinition(path.join(dir, 'index.js'))
60
- ).resolves.toBe(true);
61
- expect(log).not.toHaveBeenCalled();
62
- });
63
- });
@@ -1,133 +0,0 @@
1
- const { checkIntegrations } = require('../checks/integrations');
2
-
3
- class FakeApi {
4
- static requesterType = 'oauth2';
5
- }
6
-
7
- const moduleDefinition = () => ({
8
- moduleName: 'acme',
9
- API: FakeApi,
10
- requiredAuthMethods: {
11
- getToken: async () => {},
12
- getEntityDetails: async () => {},
13
- getCredentialDetails: async () => {},
14
- testAuthRequest: async () => {},
15
- apiPropertiesToPersist: { credential: [], entity: [] },
16
- },
17
- });
18
-
19
- function integrationClass(definition) {
20
- return class Integration {
21
- static Definition = definition;
22
- };
23
- }
24
-
25
- const goodIntegration = () =>
26
- integrationClass({
27
- name: 'acme',
28
- version: '1.0.0',
29
- modules: { acme: { definition: moduleDefinition() } },
30
- });
31
-
32
- const codes = (issues) =>
33
- issues.map((i) => `${i.severity}:${i.code}:${i.pointer}`);
34
-
35
- describe('checkIntegrations', () => {
36
- it('accepts a well-formed integration class', () => {
37
- expect(
38
- checkIntegrations({ integrations: [goodIntegration()] })
39
- ).toEqual([]);
40
- });
41
-
42
- it('warns when the app lists no integrations', () => {
43
- expect(codes(checkIntegrations({ integrations: [] }))).toEqual([
44
- 'warning:no-integrations:/integrations',
45
- ]);
46
- });
47
-
48
- it('rejects an entry that is not a class', () => {
49
- expect(
50
- codes(
51
- checkIntegrations({
52
- integrations: [{ Definition: { name: 'x' } }],
53
- })
54
- )
55
- ).toEqual(['error:integration-not-a-class:/integrations/0']);
56
- });
57
-
58
- it('rejects a class without a static Definition', () => {
59
- class Bare {}
60
- expect(codes(checkIntegrations({ integrations: [Bare] }))).toEqual([
61
- 'error:integration-definition-missing:/integrations/0/Definition',
62
- ]);
63
- });
64
-
65
- it('requires a valid name and modules, and warns on a missing version', () => {
66
- const Cls = integrationClass({ name: '1 bad name' });
67
- expect(codes(checkIntegrations({ integrations: [Cls] }))).toEqual([
68
- 'error:integration-name-invalid:/integrations/0/Definition/name',
69
- 'warning:integration-version-missing:/integrations/0/Definition/version',
70
- 'error:integration-modules-missing:/integrations/0/Definition/modules',
71
- ]);
72
- });
73
-
74
- it('reports duplicate integration names', () => {
75
- expect(
76
- codes(
77
- checkIntegrations({
78
- integrations: [goodIntegration(), goodIntegration()],
79
- })
80
- )
81
- ).toEqual([
82
- 'error:integration-name-duplicate:/integrations/1/Definition/name',
83
- ]);
84
- });
85
-
86
- it('checks module definitions the way Module.validateDefinition does', () => {
87
- const definition = moduleDefinition();
88
- delete definition.requiredAuthMethods.getToken;
89
- delete definition.moduleName;
90
- const Cls = integrationClass({
91
- name: 'acme',
92
- version: '1',
93
- modules: { acme: { definition }, other: {} },
94
- });
95
-
96
- expect(codes(checkIntegrations({ integrations: [Cls] }))).toEqual([
97
- 'error:module-name-missing:/integrations/0/Definition/modules/acme/definition/moduleName',
98
- 'error:module-auth-method-missing:/integrations/0/Definition/modules/acme/definition/requiredAuthMethods/getToken',
99
- 'error:module-definition-missing:/integrations/0/Definition/modules/other/definition',
100
- ]);
101
- });
102
-
103
- it('does not require getToken for non-OAuth2 modules', () => {
104
- const definition = moduleDefinition();
105
- definition.API = class {
106
- static requesterType = 'apiKey';
107
- };
108
- delete definition.requiredAuthMethods.getToken;
109
- const Cls = integrationClass({
110
- name: 'acme',
111
- version: '1',
112
- modules: { acme: { definition } },
113
- });
114
-
115
- expect(checkIntegrations({ integrations: [Cls] })).toEqual([]);
116
- });
117
-
118
- it('warns that a module rateLimit is not applied', () => {
119
- const definition = {
120
- ...moduleDefinition(),
121
- rateLimit: { requests: 10, period: 'second' },
122
- };
123
- const Cls = integrationClass({
124
- name: 'acme',
125
- version: '1',
126
- modules: { acme: { definition } },
127
- });
128
-
129
- expect(codes(checkIntegrations({ integrations: [Cls] }))).toEqual([
130
- 'warning:module-rate-limit-ignored:/integrations/0/Definition/modules/acme/definition/rateLimit',
131
- ]);
132
- });
133
- });