@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
@@ -110,41 +110,6 @@ Found 5 modules matching "crm"
110
110
 
111
111
  ---
112
112
 
113
- #### `frigg validate [options]`
114
-
115
- Validate the app definition (`index.js`) before you build or deploy it (ADR-051).
116
-
117
- **Usage:**
118
- ```bash
119
- frigg validate # schema + runtime checks
120
- frigg validate --stage prod # also the stage checks (encryption, deploy-time environment)
121
- frigg validate --json # machine-readable report for CI
122
- ```
123
-
124
- **What it does:**
125
- 1. Loads `index.js` the way `frigg build` does (current directory, or the nearest `backend/`)
126
- 2. Validates it against the app-definition schema: unknown keys (with "did you mean"), types, and the values the infrastructure builders accept
127
- 3. Checks what the schema cannot: integration classes (static `Definition` with a valid, unique `name`, `version`, `modules`, and API module definitions that pass core's checks), an enabled database, management settings that conflict or lack ids, encryption (`aes` keys in `environment`, KMS key source, stages that never encrypt), a user config with which login works, `'ssm'` entries without `ssm.enable`, and keys that have no effect
128
- 4. Prints each problem with its JSON pointer and a fix; exits `1` when there are errors
129
-
130
- **Example output:**
131
- ```
132
- Validating /path/to/backend/index.js (stage: prod)
133
- error /encrpytion Unknown key "encrpytion": nothing in Frigg reads it.
134
- fix: Did you mean "encryption"?
135
- error /database/postgres/management /database/postgres/management is "create-new", which is not an allowed value.
136
- fix: Use one of: "discover", "managed", "use-existing".
137
- warning /custom custom is not read by Frigg: it has no effect.
138
- fix: Remove it.
139
- ✗ 2 error(s), 1 warning(s)
140
- ```
141
-
142
- `--json` prints `{ valid, source, stage, errors, warnings }`; each issue is `{ severity, code, pointer, message, hint }`.
143
-
144
- `frigg build` and `frigg deploy` run the same validation first and stop on errors (`--skip-validate` bypasses it, with a loud warning). `frigg start` runs it and only warns. `frigg init` validates the generated app the same way.
145
-
146
- ---
147
-
148
113
  #### `frigg start [options]`
149
114
 
150
115
  Start local development server with hot reload.
@@ -158,7 +123,7 @@ frigg start --frontend
158
123
  ```
159
124
 
160
125
  **What it does:**
161
- 1. Validates DATABASE_URL environment variable, and the app definition (`frigg validate`; problems are printed as warnings, `--skip-validate` skips it)
126
+ 1. Validates DATABASE_URL environment variable
162
127
  2. Detects database type (MongoDB or PostgreSQL)
163
128
  3. Checks Prisma client generation status
164
129
  4. Tests database connection
@@ -217,12 +182,10 @@ frigg deploy --region us-west-2
217
182
  frigg deploy --force
218
183
  frigg deploy --skip-env-validation
219
184
  frigg deploy --skip-doctor # Skip health check (not recommended)
220
- frigg deploy --skip-validate # Skip app definition validation (not recommended)
221
185
  frigg deploy --no-interactive # CI/CD mode (no prompts, auto-repair safe issues)
222
186
  ```
223
187
 
224
188
  **What it does:**
225
- 0. **Validates the app definition** (`frigg validate`) and stops on errors, unless `--skip-validate`
226
189
  1. **Runs health check** (`frigg doctor`) to detect infrastructure issues
227
190
  2. **Auto-repairs safe issues** (or prompts for confirmation in interactive mode)
228
191
  3. **Fails if critical issues found** (unless `--skip-doctor` flag used)
@@ -14,9 +14,6 @@ jest.mock('child_process', () => ({
14
14
  spawnSync: jest.fn()
15
15
  }));
16
16
 
17
- // The app definition validation step is tested in validate-command/.
18
- jest.mock('../../../validate-command', () => ({ preflightValidation: jest.fn(() => true) }));
19
-
20
17
  // Require after mocks
21
18
  const { spawnSync } = require('child_process');
22
19
  const { buildCommand } = require('../../../build-command');
@@ -10,9 +10,6 @@
10
10
  */
11
11
 
12
12
  // Mock dependencies BEFORE requiring modules
13
- // The app definition validation step is tested in validate-command/.
14
- jest.mock('../../../validate-command', () => ({ preflightValidation: jest.fn(() => true) }));
15
-
16
13
  jest.mock('child_process', () => ({
17
14
  spawn: jest.fn()
18
15
  }));
@@ -1,15 +1,9 @@
1
1
  const { spawnSync } = require('child_process');
2
2
  const path = require('path');
3
- const { preflightValidation } = require('../validate-command');
4
3
 
5
4
  async function buildCommand(options) {
6
5
  console.log('Building the serverless application...');
7
6
 
8
- // Validate the app definition first (ADR-051); errors stop the build.
9
- if (!preflightValidation({ command: 'build', options, failOnErrors: true })) {
10
- return;
11
- }
12
-
13
7
  // Suppress AWS SDK warning message about maintenance mode
14
8
  process.env.AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE = '1';
15
9
 
@@ -4,7 +4,6 @@ const fs = require('fs');
4
4
 
5
5
  // Import doctor command for post-deployment health check
6
6
  const { doctorCommand } = require('../doctor-command');
7
- const { preflightValidation } = require('../validate-command');
8
7
 
9
8
  // Configuration constants
10
9
  const PATHS = {
@@ -313,11 +312,6 @@ async function pushOffloadedParametersOrAbort(appDefinition, options) {
313
312
  async function deployCommand(options) {
314
313
  console.log('Deploying the serverless application...');
315
314
 
316
- // Validate the app definition first (ADR-051); errors stop the deploy.
317
- if (!preflightValidation({ command: 'deploy', options, failOnErrors: true })) {
318
- return;
319
- }
320
-
321
315
  const appDefinition = loadAppDefinition();
322
316
  const environment = validateAndBuildEnvironment(appDefinition, options);
323
317
 
@@ -87,7 +87,6 @@ const { doctorCommand } = require('./doctor-command');
87
87
  const { repairCommand } = require('./repair-command');
88
88
  const { authCommand } = require('./auth-command');
89
89
  const { ssmPushCommand } = require('./ssm-command');
90
- const { validateCommand } = require('./validate-command');
91
90
 
92
91
  const program = new Command();
93
92
 
@@ -109,24 +108,14 @@ program
109
108
  .description('Run the backend and optional frontend')
110
109
  .option('-s, --stage <stage>', 'deployment stage', 'dev')
111
110
  .option('-v, --verbose', 'enable verbose output')
112
- .option('--skip-validate', 'skip app definition validation')
113
111
  .action(startCommand);
114
112
 
115
- program
116
- .command('validate')
117
- .description('Validate the app definition (index.js) against the schema and the runtime rules')
118
- .option('-s, --stage <stage>', 'also run the checks for this stage (encryption, deploy-time environment)')
119
- .option('--json', 'print the report as JSON')
120
- .option('-v, --verbose', 'enable verbose output')
121
- .action(validateCommand);
122
-
123
113
  program
124
114
  .command('build')
125
115
  .description('Build the serverless application')
126
116
  .option('-s, --stage <stage>', 'deployment stage', 'dev')
127
117
  .option('-v, --verbose', 'enable verbose output')
128
118
  .option('-p, --production', 'build for production (enables AWS discovery)')
129
- .option('--skip-validate', 'skip app definition validation (not recommended)')
130
119
  .action(buildCommand);
131
120
 
132
121
  program
@@ -136,7 +125,6 @@ program
136
125
  .option('-v, --verbose', 'enable verbose output')
137
126
  .option('-f, --force', 'force deployment (bypasses caching for layers and functions)')
138
127
  .option('--skip-doctor', 'skip post-deployment health check')
139
- .option('--skip-validate', 'skip app definition validation (not recommended)')
140
128
  .action(deployCommand);
141
129
 
142
130
  program
@@ -232,4 +220,4 @@ authProgram
232
220
 
233
221
  program.parse(process.argv);
234
222
 
235
- module.exports = { initCommand, installCommand, startCommand, buildCommand, deployCommand, validateCommand, generateIamCommand, uiCommand, dbSetupCommand, doctorCommand, repairCommand, authCommand, ssmPushCommand };
223
+ module.exports = { initCommand, installCommand, startCommand, buildCommand, deployCommand, generateIamCommand, uiCommand, dbSetupCommand, doctorCommand, repairCommand, authCommand, ssmPushCommand };
@@ -5,8 +5,7 @@ const { select, confirm, multiselect } = require('@inquirer/prompts');
5
5
  const { execSync } = require('child_process');
6
6
  const spawn = require('cross-spawn');
7
7
  const npmRegistry = require('../utils/npm-registry');
8
- const { runValidation } = require('../validate-command');
9
- const { formatReport } = require('../validate-command/format-report');
8
+ const { validateAppDefinition, formatErrors } = require('@friggframework/schemas');
10
9
 
11
10
  /**
12
11
  * Backend-first template handler that treats frontend as optional demonstration
@@ -648,39 +647,61 @@ To integrate Frigg into your production application:
648
647
  }
649
648
 
650
649
  /**
651
- * Validate the generated app definition with the same code path as
652
- * `frigg validate`, `frigg build` and `frigg deploy` (ADR-051). Problems
653
- * are reported as warnings: they never stop `frigg init`.
650
+ * Validate generated app definition against schema
654
651
  */
655
652
  async validateGeneratedAppDefinition(appDefPath) {
656
653
  try {
657
- const report = runValidation({ cwd: path.dirname(appDefPath) });
658
- const issues = [...report.errors, ...report.warnings];
659
- if (issues.length === 0) {
660
- if (this.options.verbose) {
661
- console.log(chalk.green('✅ App definition passes validation'));
662
- }
663
- return true;
654
+ if (this.options.verbose) {
655
+ console.log(chalk.gray('🔍 Validating app definition against schema...'));
664
656
  }
665
- if (report.errors.length === 1 && report.errors[0].code === 'load-failed') {
666
- // Before `npm install`, index.js cannot load its dependencies yet.
657
+
658
+ // Read the generated index.js file
659
+ const content = await fs.readFile(appDefPath, 'utf8');
660
+
661
+ // Extract the appDefinition object (simplified approach)
662
+ // In a real scenario, we might use AST parsing for more robust extraction
663
+ const appDefinitionMatch = content.match(/const appDefinition = ({[\s\S]*?});/);
664
+ if (!appDefinitionMatch) {
665
+ throw new Error('Could not extract appDefinition from generated file');
666
+ }
667
+
668
+ // Create a minimal representation for validation
669
+ // Since we can't easily execute the file, we'll validate the structure we know we generated
670
+ const appDefinition = {
671
+ integrations: [], // Will be populated based on selected integrations
672
+ user: { password: true },
673
+ encryption: { fieldLevelEncryptionMethod: 'kms' },
674
+ vpc: { enable: true },
675
+ security: {
676
+ cors: {
677
+ origin: 'http://localhost:3000',
678
+ credentials: true
679
+ }
680
+ },
681
+ logging: { level: 'info', retentionInDays: 30 },
682
+ custom: {
683
+ appName: 'My Frigg Application',
684
+ version: '1.0.0',
685
+ environment: 'development'
686
+ }
687
+ };
688
+
689
+ // Validate against schema
690
+ const result = validateAppDefinition(appDefinition);
691
+
692
+ if (result.valid) {
667
693
  if (this.options.verbose) {
668
- console.log(chalk.gray('App definition not validated yet: run `frigg validate` after `npm install`.'));
694
+ console.log(chalk.green('✅ App definition passes schema validation'));
669
695
  }
670
696
  return true;
697
+ } else {
698
+ console.log(chalk.yellow('⚠️ App definition has validation warnings:'));
699
+ console.log(chalk.gray(formatErrors(result.errors)));
700
+ return false;
671
701
  }
672
- console.log(chalk.yellow('⚠️ App definition validation:'));
673
- console.log(
674
- formatReport({
675
- ...report,
676
- errors: [],
677
- warnings: issues.map((issue) => ({ ...issue, severity: 'warning' })),
678
- })
679
- );
680
- return report.errors.length === 0;
681
702
  } catch (error) {
682
703
  if (this.options.verbose) {
683
- console.log(chalk.yellow(`⚠️ Validation skipped: ${error.message}`));
704
+ console.log(chalk.yellow(`⚠️ Schema validation skipped: ${error.message}`));
684
705
  }
685
706
  return true; // Don't fail the process for validation issues
686
707
  }
@@ -12,7 +12,6 @@ const {
12
12
  getDatabaseTypeNotConfiguredError,
13
13
  getPrismaClientNotGeneratedError
14
14
  } = require('../utils/error-messages');
15
- const { preflightValidation } = require('../validate-command');
16
15
 
17
16
  async function startCommand(options) {
18
17
  if (options.verbose) {
@@ -26,9 +25,6 @@ async function startCommand(options) {
26
25
  const envPath = path.join(process.cwd(), '.env');
27
26
  dotenv.config({ path: envPath });
28
27
 
29
- // Validate the app definition (ADR-051); local development only warns.
30
- preflightValidation({ command: 'start', options, failOnErrors: false });
31
-
32
28
  // Pre-flight database checks
33
29
  try {
34
30
  await performDatabaseChecks(options.verbose);
@@ -23,9 +23,6 @@ jest.mock('node:child_process', () => ({
23
23
  }));
24
24
 
25
25
  jest.mock('../utils/database-validator', () => mockValidator);
26
- // The app definition validation step is tested in validate-command/.
27
- jest.mock('../validate-command', () => ({ preflightValidation: jest.fn(() => true) }));
28
-
29
26
  jest.mock('dotenv');
30
27
 
31
28
  const { spawn } = require('node:child_process');
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Management API ↔ API Gateway route coverage
3
+ *
4
+ * The Management API is an Express app (core's createIntegrationRouter)
5
+ * served by the `auth` Lambda. API Gateway HTTP APIs have no implicit
6
+ * catch-all: a request whose path matches no declared route gets a gateway
7
+ * 404 and never reaches Express. So every route the integration router
8
+ * registers must be covered by an httpApi event on the `auth` function, and
9
+ * no more-specific route on another function may shadow it.
10
+ *
11
+ * The Express routes are read from the live router (not a hard-coded list),
12
+ * so a route added to core later fails this test until the gateway routes
13
+ * are updated alongside it.
14
+ */
15
+
16
+ // Avoid AWS calls: hand the builders a fixed discovery result.
17
+ jest.mock('../domains/shared/resource-discovery', () => {
18
+ const actual = jest.requireActual('../domains/shared/resource-discovery');
19
+ return {
20
+ ...actual,
21
+ gatherDiscoveredResources: jest.fn().mockResolvedValue({
22
+ defaultVpcId: 'vpc-123456',
23
+ vpcCidr: '172.31.0.0/16',
24
+ defaultSecurityGroupId: 'sg-123456',
25
+ privateSubnetId1: 'subnet-123456',
26
+ privateSubnetId2: 'subnet-789012',
27
+ publicSubnetId1: 'subnet-public-1',
28
+ publicSubnetId2: 'subnet-public-2',
29
+ defaultRouteTableId: 'rtb-123456',
30
+ defaultKmsKeyId:
31
+ 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012',
32
+ existingNatGatewayId: 'nat-default123',
33
+ auroraClusterEndpoint:
34
+ 'test-cluster.cluster-abc123.us-east-1.rds.amazonaws.com',
35
+ auroraPort: 5432,
36
+ auroraEngine: 'aurora-postgresql',
37
+ }),
38
+ };
39
+ });
40
+
41
+ // createIntegrationRouter loads the app definition from the consuming
42
+ // backend; give it an empty one so the router can be built in isolation.
43
+ jest.mock('@friggframework/core/handlers/app-definition-loader', () => ({
44
+ loadAppDefinition: () => ({ integrations: [], userConfig: {} }),
45
+ }));
46
+
47
+ const { composeServerlessDefinition } = require('../infrastructure-composer');
48
+
49
+ // Routes the auth Lambda's handler (core/handlers/routers/auth.js) adds on
50
+ // top of createIntegrationRouter().
51
+ const AUTH_HANDLER_EXTRA_ROUTES = [
52
+ { method: 'GET', path: '/api/integrations/redirect/:appId' },
53
+ ];
54
+
55
+ function getIntegrationRouterRoutes() {
56
+ const { createIntegrationRouter } = require('@friggframework/core');
57
+ // The repository factories pick an adapter from DB_TYPE (or the backend's
58
+ // app definition, which doesn't exist here). Nothing connects.
59
+ const previousDbType = process.env.DB_TYPE;
60
+ process.env.DB_TYPE = 'postgresql';
61
+ let app;
62
+ try {
63
+ app = createIntegrationRouter();
64
+ } finally {
65
+ if (previousDbType === undefined) delete process.env.DB_TYPE;
66
+ else process.env.DB_TYPE = previousDbType;
67
+ }
68
+ const stack = (app._router || app.router).stack;
69
+ const routes = [];
70
+ for (const layer of stack) {
71
+ if (!layer.route) continue;
72
+ for (const method of Object.keys(layer.route.methods)) {
73
+ routes.push({
74
+ method: method === '_all' ? 'ANY' : method.toUpperCase(),
75
+ path: layer.route.path,
76
+ });
77
+ }
78
+ }
79
+ return routes;
80
+ }
81
+
82
+ function getGatewayRoutes(definition) {
83
+ const routes = [];
84
+ for (const [functionName, fn] of Object.entries(definition.functions)) {
85
+ for (const event of fn.events || []) {
86
+ if (!event.httpApi) continue;
87
+ routes.push({
88
+ functionName,
89
+ method: String(event.httpApi.method).toUpperCase(),
90
+ path: event.httpApi.path,
91
+ });
92
+ }
93
+ }
94
+ return routes;
95
+ }
96
+
97
+ const segmentsOf = (p) => p.split('/').filter(Boolean);
98
+
99
+ // Turn an Express path into a concrete request path, e.g.
100
+ // /api/entities/:entityId/options -> /api/entities/sample-entityId/options
101
+ const toConcretePath = (expressPath) =>
102
+ expressPath.replace(/:([A-Za-z0-9_]+)/g, 'sample-$1');
103
+
104
+ /**
105
+ * HTTP API path matching: literal segments match exactly, {param} matches
106
+ * one segment, a trailing {proxy+} greedily matches one or more segments.
107
+ * Returns null on no match, otherwise a specificity descriptor.
108
+ */
109
+ function matchGatewayPath(gatewayPath, requestPath) {
110
+ const g = segmentsOf(gatewayPath);
111
+ const r = segmentsOf(requestPath);
112
+ let literals = 0;
113
+ for (let i = 0; i < g.length; i++) {
114
+ const seg = g[i];
115
+ if (/^\{[^}]+\+\}$/.test(seg)) {
116
+ if (i !== g.length - 1 || r.length <= i) return null;
117
+ return { greedy: true, literals, prefixLength: i };
118
+ }
119
+ if (i >= r.length) return null;
120
+ if (/^\{[^}]+\}$/.test(seg)) continue;
121
+ if (seg !== r[i]) return null;
122
+ literals++;
123
+ }
124
+ if (g.length !== r.length) return null;
125
+ return { greedy: false, literals, prefixLength: g.length };
126
+ }
127
+
128
+ /**
129
+ * Select the route API Gateway would invoke: a non-greedy match beats a
130
+ * greedy one, then more literal segments, then the longer greedy prefix,
131
+ * then an explicit method beats ANY.
132
+ */
133
+ function selectGatewayRoute(gatewayRoutes, method, requestPath) {
134
+ const candidates = gatewayRoutes
135
+ .filter(
136
+ (r) => r.method === 'ANY' || method === 'ANY' || r.method === method
137
+ )
138
+ .map((r) => ({
139
+ route: r,
140
+ match: matchGatewayPath(r.path, requestPath),
141
+ }))
142
+ .filter((c) => c.match);
143
+ candidates.sort(
144
+ (a, b) =>
145
+ Number(a.match.greedy) - Number(b.match.greedy) ||
146
+ b.match.literals - a.match.literals ||
147
+ b.match.prefixLength - a.match.prefixLength ||
148
+ Number(a.route.method === 'ANY') - Number(b.route.method === 'ANY')
149
+ );
150
+ return candidates.length ? candidates[0].route : null;
151
+ }
152
+
153
+ describe('Management API routes are reachable through API Gateway', () => {
154
+ let gatewayRoutes;
155
+ let managementRoutes;
156
+ const originalArgv = process.argv;
157
+
158
+ beforeAll(async () => {
159
+ process.argv = ['node', 'test'];
160
+ process.env.AWS_REGION = 'us-east-1';
161
+ // The builders narrate their progress; keep the test output quiet.
162
+ const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
163
+ const warnSpy = jest
164
+ .spyOn(console, 'warn')
165
+ .mockImplementation(() => {});
166
+ try {
167
+ const definition = await composeServerlessDefinition({
168
+ name: 'route-coverage-app',
169
+ provider: 'aws',
170
+ usePrismaLambdaLayer: false,
171
+ database: { postgres: { enable: true } },
172
+ websockets: { enable: true },
173
+ adminScripts: [
174
+ class ExampleScript {
175
+ static Definition = { name: 'example-script' };
176
+ },
177
+ ],
178
+ admin: { includeBuiltinReports: true },
179
+ integrations: [
180
+ {
181
+ Definition: {
182
+ name: 'acme',
183
+ webhooks: true,
184
+ extensions: {
185
+ inbound: {
186
+ extension: {
187
+ routes: [
188
+ { path: '/events', method: 'POST' },
189
+ ],
190
+ },
191
+ },
192
+ },
193
+ },
194
+ },
195
+ // Integration names share the /api/ namespace with the
196
+ // Management API; make sure they don't shadow it.
197
+ { Definition: { name: 'entities' } },
198
+ ],
199
+ });
200
+ gatewayRoutes = getGatewayRoutes(definition);
201
+ } finally {
202
+ logSpy.mockRestore();
203
+ warnSpy.mockRestore();
204
+ }
205
+ managementRoutes = [
206
+ ...getIntegrationRouterRoutes(),
207
+ ...AUTH_HANDLER_EXTRA_ROUTES,
208
+ ];
209
+ });
210
+
211
+ afterAll(() => {
212
+ process.argv = originalArgv;
213
+ delete process.env.AWS_REGION;
214
+ });
215
+
216
+ it('reads the Management API routes from the core integration router', () => {
217
+ const paths = managementRoutes.map((r) => r.path);
218
+ expect(paths).toEqual(
219
+ expect.arrayContaining([
220
+ '/api/integrations',
221
+ '/api/authorize',
222
+ '/api/entity',
223
+ '/api/entity/options/:credentialId',
224
+ '/api/entities/:entityId',
225
+ ])
226
+ );
227
+ });
228
+
229
+ it('routes every Management API route to the auth function', () => {
230
+ const unreachable = managementRoutes
231
+ .map(({ method, path: expressPath }) => {
232
+ const requestPath = toConcretePath(expressPath);
233
+ const selected = selectGatewayRoute(
234
+ gatewayRoutes,
235
+ method,
236
+ requestPath
237
+ );
238
+ return {
239
+ route: `${method} ${expressPath}`,
240
+ reachedBy: selected
241
+ ? `${selected.method} ${selected.path} -> ${selected.functionName}`
242
+ : 'no API Gateway route',
243
+ ok: selected && selected.functionName === 'auth',
244
+ };
245
+ })
246
+ .filter((r) => !r.ok)
247
+ .map(({ route, reachedBy }) => `${route} (${reachedBy})`);
248
+
249
+ expect(unreachable).toEqual([]);
250
+ });
251
+
252
+ it.each([
253
+ ['GET', '/api/v2/reports', 'reportRouter'],
254
+ ['POST', '/api/v2/reports/daily/run', 'reportRouter'],
255
+ ['GET', '/admin/scripts/example-script', 'adminScriptRouter'],
256
+ ['POST', '/api/acme-integration/webhooks', 'acmeWebhook'],
257
+ ['POST', '/api/acme-integration/inbound/events', 'acme__inbound'],
258
+ ['GET', '/api/acme-integration/custom', 'acme'],
259
+ ['GET', '/api/entities-integration/custom', 'entities'],
260
+ ['POST', '/user/login', 'user'],
261
+ ['GET', '/health/ready', 'health'],
262
+ ])('leaves %s %s on %s', (method, requestPath, functionName) => {
263
+ const selected = selectGatewayRoute(gatewayRoutes, method, requestPath);
264
+ expect(selected && selected.functionName).toBe(functionName);
265
+ });
266
+
267
+ it('declares no method + path pair on more than one function', () => {
268
+ const seen = new Map();
269
+ const duplicates = [];
270
+ for (const r of gatewayRoutes) {
271
+ const key = `${r.method} ${r.path}`;
272
+ if (seen.has(key) && seen.get(key) !== r.functionName) {
273
+ duplicates.push(
274
+ `${key}: ${seen.get(key)} and ${r.functionName}`
275
+ );
276
+ }
277
+ seen.set(key, r.functionName);
278
+ }
279
+ expect(duplicates).toEqual([]);
280
+ });
281
+ });
@@ -3,7 +3,6 @@ const fs = require('fs-extra');
3
3
  const crypto = require('crypto');
4
4
  const { composeServerlessDefinition } = require('./infrastructure-composer');
5
5
  const { findNearestBackendPackageJson } = require('@friggframework/core');
6
- const { applyAppDefinitionDefaults } = require('@friggframework/schemas');
7
6
 
8
7
  // Filesystem-based cache to persist across osls require cache clears
9
8
  const getCachePath = (backendDir) => {
@@ -103,9 +102,7 @@ async function createFriggInfrastructure() {
103
102
 
104
103
  try {
105
104
  const backend = require(backendFilePath);
106
- // Same defaults step as core's loadAppDefinition (ADR-051), so the
107
- // builders and the runtime see the same definition.
108
- const appDefinition = applyAppDefinitionDefaults(backend.Definition);
105
+ const appDefinition = backend.Definition;
109
106
 
110
107
  const definition = await composeServerlessDefinition(
111
108
  appDefinition,