@friggframework/devtools 2.0.0-next.11 → 2.0.0-next.110
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/frigg-cli/README.md +1289 -0
- package/frigg-cli/__tests__/unit/commands/build.test.js +279 -0
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +649 -0
- package/frigg-cli/__tests__/unit/commands/deploy.test.js +320 -0
- package/frigg-cli/__tests__/unit/commands/doctor.test.js +309 -0
- package/frigg-cli/__tests__/unit/commands/generate-iam.test.js +97 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +400 -0
- package/frigg-cli/__tests__/unit/commands/ui.test.js +346 -0
- package/frigg-cli/__tests__/unit/dependencies.test.js +74 -0
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +397 -0
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +345 -0
- package/frigg-cli/__tests__/unit/version-detection.test.js +171 -0
- package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
- package/frigg-cli/__tests__/utils/prisma-mock.js +194 -0
- package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
- package/frigg-cli/__tests__/utils/test-setup.js +287 -0
- package/frigg-cli/auth-command/CLAUDE.md +293 -0
- package/frigg-cli/auth-command/README.md +450 -0
- package/frigg-cli/auth-command/api-key-flow.js +153 -0
- package/frigg-cli/auth-command/auth-tester.js +344 -0
- package/frigg-cli/auth-command/credential-storage.js +182 -0
- package/frigg-cli/auth-command/index.js +256 -0
- package/frigg-cli/auth-command/json-schema-form.js +67 -0
- package/frigg-cli/auth-command/module-loader.js +172 -0
- package/frigg-cli/auth-command/oauth-callback-server.js +431 -0
- package/frigg-cli/auth-command/oauth-flow.js +195 -0
- package/frigg-cli/auth-command/utils/browser.js +30 -0
- package/frigg-cli/build-command/index.js +45 -12
- package/frigg-cli/db-setup-command/index.js +246 -0
- package/frigg-cli/deploy-command/SPEC-DEPLOY-DRY-RUN.md +981 -0
- package/frigg-cli/deploy-command/index.js +339 -23
- package/frigg-cli/doctor-command/index.js +335 -0
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +301 -0
- package/frigg-cli/generate-command/azure-generator.js +43 -0
- package/frigg-cli/generate-command/gcp-generator.js +47 -0
- package/frigg-cli/generate-command/index.js +333 -0
- package/frigg-cli/generate-command/terraform-generator.js +555 -0
- package/frigg-cli/generate-iam-command.js +119 -0
- package/frigg-cli/index.js +189 -1
- package/frigg-cli/index.test.js +1 -4
- package/frigg-cli/init-command/backend-first-handler.js +756 -0
- package/frigg-cli/init-command/index.js +93 -0
- package/frigg-cli/init-command/template-handler.js +143 -0
- package/frigg-cli/install-command/index.js +1 -4
- package/frigg-cli/jest.config.js +124 -0
- package/frigg-cli/package.json +63 -0
- package/frigg-cli/repair-command/index.js +564 -0
- package/frigg-cli/ssm-command/index.js +308 -0
- package/frigg-cli/ssm-command/index.test.js +318 -0
- package/frigg-cli/start-command/index.js +118 -5
- package/frigg-cli/start-command/start-command.test.js +297 -0
- package/frigg-cli/test/init-command.test.js +180 -0
- package/frigg-cli/test/npm-registry.test.js +319 -0
- package/frigg-cli/ui-command/index.js +154 -0
- package/frigg-cli/utils/app-resolver.js +319 -0
- package/frigg-cli/utils/backend-path.js +16 -17
- package/frigg-cli/utils/database-validator.js +167 -0
- package/frigg-cli/utils/error-messages.js +329 -0
- package/frigg-cli/utils/npm-registry.js +167 -0
- package/frigg-cli/utils/process-manager.js +199 -0
- package/frigg-cli/utils/repo-detection.js +405 -0
- package/infrastructure/ARCHITECTURE.md +487 -0
- package/infrastructure/CLAUDE.md +481 -0
- package/infrastructure/HEALTH.md +468 -0
- package/infrastructure/README.md +540 -0
- package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
- package/infrastructure/__tests__/helpers/test-utils.js +275 -0
- package/infrastructure/__tests__/postgres-config.test.js +914 -0
- package/infrastructure/__tests__/scoped-environment.test.js +126 -0
- package/infrastructure/__tests__/ssm-preload-node-options.test.js +79 -0
- package/infrastructure/__tests__/template-generation.test.js +687 -0
- package/infrastructure/create-frigg-infrastructure.js +129 -20
- package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
- package/infrastructure/docs/PRE-DEPLOYMENT-HEALTH-CHECK-SPEC.md +1317 -0
- package/infrastructure/docs/WEBSOCKET-CONFIGURATION.md +105 -0
- package/infrastructure/docs/deployment-instructions.md +268 -0
- package/infrastructure/docs/generate-iam-command.md +278 -0
- package/infrastructure/docs/iam-policy-templates.md +193 -0
- package/infrastructure/domains/admin-scripts/admin-script-builder.js +567 -0
- package/infrastructure/domains/admin-scripts/admin-script-builder.test.js +1017 -0
- package/infrastructure/domains/admin-scripts/index.js +5 -0
- package/infrastructure/domains/database/aurora-builder.js +857 -0
- package/infrastructure/domains/database/aurora-builder.test.js +960 -0
- package/infrastructure/domains/database/aurora-discovery.js +87 -0
- package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
- package/infrastructure/domains/database/aurora-resolver.js +210 -0
- package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
- package/infrastructure/domains/database/migration-builder.js +757 -0
- package/infrastructure/domains/database/migration-builder.test.js +454 -0
- package/infrastructure/domains/database/migration-resolver.js +163 -0
- package/infrastructure/domains/database/migration-resolver.test.js +337 -0
- package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
- package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
- package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
- package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
- package/infrastructure/domains/health/application/ports/index.js +26 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
- package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
- package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
- package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
- package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
- package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
- package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
- package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
- package/infrastructure/domains/health/domain/entities/issue.js +299 -0
- package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
- package/infrastructure/domains/health/domain/entities/resource.js +159 -0
- package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
- package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
- package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
- package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
- package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
- package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
- package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
- package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
- package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
- package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
- package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
- package/infrastructure/domains/integration/integration-builder.js +656 -0
- package/infrastructure/domains/integration/integration-builder.test.js +1064 -0
- package/infrastructure/domains/integration/integration-resolver.js +170 -0
- package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
- package/infrastructure/domains/integration/websocket-builder.js +69 -0
- package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
- package/infrastructure/domains/networking/vpc-builder.js +2101 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +2001 -0
- package/infrastructure/domains/networking/vpc-discovery.js +184 -0
- package/infrastructure/domains/networking/vpc-discovery.test.js +368 -0
- package/infrastructure/domains/networking/vpc-resolver.js +514 -0
- package/infrastructure/domains/networking/vpc-resolver.test.js +841 -0
- package/infrastructure/domains/parameters/offload-utils.js +190 -0
- package/infrastructure/domains/parameters/offload-utils.test.js +193 -0
- package/infrastructure/domains/parameters/ssm-builder.js +132 -0
- package/infrastructure/domains/parameters/ssm-builder.test.js +346 -0
- package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
- package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
- package/infrastructure/domains/scheduler/scheduler-builder.js +247 -0
- package/infrastructure/domains/scheduler/scheduler-builder.test.js +118 -0
- package/infrastructure/domains/security/iam-generator.js +851 -0
- package/infrastructure/domains/security/iam-generator.test.js +287 -0
- package/infrastructure/domains/security/kms-builder.js +415 -0
- package/infrastructure/domains/security/kms-builder.test.js +392 -0
- package/infrastructure/domains/security/kms-discovery.js +80 -0
- package/infrastructure/domains/security/kms-discovery.test.js +177 -0
- package/infrastructure/domains/security/kms-resolver.js +96 -0
- package/infrastructure/domains/security/kms-resolver.test.js +216 -0
- package/infrastructure/domains/security/templates/frigg-deployment-iam-stack.yaml +418 -0
- package/infrastructure/domains/security/templates/iam-policy-basic.json +218 -0
- package/infrastructure/domains/security/templates/iam-policy-full.json +293 -0
- package/infrastructure/domains/shared/base-builder.js +112 -0
- package/infrastructure/domains/shared/base-resolver.js +186 -0
- package/infrastructure/domains/shared/base-resolver.test.js +305 -0
- package/infrastructure/domains/shared/builder-orchestrator.js +226 -0
- package/infrastructure/domains/shared/builder-orchestrator.test.js +258 -0
- package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
- package/infrastructure/domains/shared/cloudformation-discovery.js +681 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +1320 -0
- package/infrastructure/domains/shared/environment-builder.js +192 -0
- package/infrastructure/domains/shared/environment-builder.test.js +529 -0
- package/infrastructure/domains/shared/function-environments.js +97 -0
- package/infrastructure/domains/shared/function-environments.test.js +146 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +579 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +416 -0
- package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
- package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
- package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
- package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
- package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
- package/infrastructure/domains/shared/resource-discovery.enhanced.test.js +306 -0
- package/infrastructure/domains/shared/resource-discovery.js +256 -0
- package/infrastructure/domains/shared/resource-discovery.test.js +757 -0
- package/infrastructure/domains/shared/types/app-definition.js +225 -0
- package/infrastructure/domains/shared/types/discovery-result.js +106 -0
- package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
- package/infrastructure/domains/shared/types/index.js +46 -0
- package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
- package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +409 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +319 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
- package/infrastructure/domains/shared/utilities/nested-node-modules.js +53 -0
- package/infrastructure/domains/shared/utilities/nested-node-modules.test.js +61 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +159 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +444 -0
- package/infrastructure/domains/shared/validation/env-validator.js +78 -0
- package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
- package/infrastructure/domains/shared/validation/plugin-validator.js +187 -0
- package/infrastructure/domains/shared/validation/plugin-validator.test.js +323 -0
- package/infrastructure/esbuild.config.js +53 -0
- package/infrastructure/infrastructure-composer.js +168 -0
- package/infrastructure/infrastructure-composer.test.js +2042 -0
- package/infrastructure/integration.test.js +381 -0
- package/infrastructure/scripts/build-prisma-layer.js +701 -0
- package/infrastructure/scripts/build-prisma-layer.test.js +170 -0
- package/infrastructure/scripts/build-time-discovery.js +238 -0
- package/infrastructure/scripts/build-time-discovery.test.js +379 -0
- package/infrastructure/scripts/run-discovery.js +110 -0
- package/infrastructure/scripts/verify-prisma-layer.js +72 -0
- package/management-ui/README.md +203 -0
- package/package.json +48 -16
- package/test/index.js +2 -4
- package/test/mock-api.js +1 -3
- package/test/mock-integration.js +4 -14
- package/.eslintrc.json +0 -3
- package/CHANGELOG.md +0 -132
- package/infrastructure/serverless-template.js +0 -283
- package/infrastructure/webpack.config.js +0 -20
- package/test/auther-definition-tester.js +0 -125
|
@@ -0,0 +1,1017 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Admin Script Builder
|
|
3
|
+
*
|
|
4
|
+
* Tests admin script infrastructure generation including:
|
|
5
|
+
* - SQS queue for script execution
|
|
6
|
+
* - Lambda executor function
|
|
7
|
+
* - Lambda router function with HTTP routes
|
|
8
|
+
* - EventBridge Scheduler resources (optional)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { AdminScriptBuilder } = require('./admin-script-builder');
|
|
12
|
+
const { ValidationResult } = require('../shared/base-builder');
|
|
13
|
+
|
|
14
|
+
describe('AdminScriptBuilder', () => {
|
|
15
|
+
let adminScriptBuilder;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
adminScriptBuilder = new AdminScriptBuilder();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('shouldExecute()', () => {
|
|
22
|
+
it('should return false when no adminScripts', () => {
|
|
23
|
+
const appDefinition = {};
|
|
24
|
+
|
|
25
|
+
expect(adminScriptBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return false when adminScripts is empty array', () => {
|
|
29
|
+
const appDefinition = {
|
|
30
|
+
adminScripts: [],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
expect(adminScriptBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should return true when adminScripts has items', () => {
|
|
37
|
+
const appDefinition = {
|
|
38
|
+
adminScripts: [
|
|
39
|
+
{ Definition: { name: 'test-script' } },
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
expect(adminScriptBuilder.shouldExecute(appDefinition)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should return false when adminScripts is not an array', () => {
|
|
47
|
+
const appDefinition = {
|
|
48
|
+
adminScripts: { name: 'test' },
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
expect(adminScriptBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('getDependencies()', () => {
|
|
56
|
+
it('should have no dependencies', () => {
|
|
57
|
+
const deps = adminScriptBuilder.getDependencies();
|
|
58
|
+
|
|
59
|
+
expect(deps).toEqual([]);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('validate()', () => {
|
|
64
|
+
it('should pass validation with valid adminScripts', () => {
|
|
65
|
+
const appDefinition = {
|
|
66
|
+
adminScripts: [
|
|
67
|
+
{ Definition: { name: 'oauth-refresh' } },
|
|
68
|
+
{ Definition: { name: 'health-check' } },
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const result = adminScriptBuilder.validate(appDefinition);
|
|
73
|
+
|
|
74
|
+
expect(result).toBeInstanceOf(ValidationResult);
|
|
75
|
+
expect(result.valid).toBe(true);
|
|
76
|
+
expect(result.errors).toEqual([]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should pass when adminScripts is undefined', () => {
|
|
80
|
+
const appDefinition = {};
|
|
81
|
+
|
|
82
|
+
const result = adminScriptBuilder.validate(appDefinition);
|
|
83
|
+
|
|
84
|
+
expect(result.valid).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should fail when adminScripts is not an array', () => {
|
|
88
|
+
const appDefinition = {
|
|
89
|
+
adminScripts: 'invalid',
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const result = adminScriptBuilder.validate(appDefinition);
|
|
93
|
+
|
|
94
|
+
expect(result.valid).toBe(false);
|
|
95
|
+
expect(result.errors).toContain('adminScripts must be an array');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should fail when script missing Definition.name', () => {
|
|
99
|
+
const appDefinition = {
|
|
100
|
+
adminScripts: [
|
|
101
|
+
{ Definition: {} },
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const result = adminScriptBuilder.validate(appDefinition);
|
|
106
|
+
|
|
107
|
+
expect(result.valid).toBe(false);
|
|
108
|
+
expect(result.errors).toContain(
|
|
109
|
+
'Admin script at index 0 is missing Definition or name'
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should fail when script missing Definition', () => {
|
|
114
|
+
const appDefinition = {
|
|
115
|
+
adminScripts: [
|
|
116
|
+
{ someOtherField: 'value' },
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const result = adminScriptBuilder.validate(appDefinition);
|
|
121
|
+
|
|
122
|
+
expect(result.valid).toBe(false);
|
|
123
|
+
expect(result.errors).toContain(
|
|
124
|
+
'Admin script at index 0 is missing Definition or name'
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should validate all scripts', () => {
|
|
129
|
+
const appDefinition = {
|
|
130
|
+
adminScripts: [
|
|
131
|
+
{ Definition: { name: 'valid' } },
|
|
132
|
+
{ Definition: {} }, // Invalid - no name
|
|
133
|
+
{ someField: 'value' }, // Invalid - no Definition
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const result = adminScriptBuilder.validate(appDefinition);
|
|
138
|
+
|
|
139
|
+
expect(result.valid).toBe(false);
|
|
140
|
+
expect(result.errors).toHaveLength(2);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('build()', () => {
|
|
145
|
+
it('should create AdminScriptQueue resource', async () => {
|
|
146
|
+
const appDefinition = {
|
|
147
|
+
adminScripts: [
|
|
148
|
+
{ Definition: { name: 'test-script' } },
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
153
|
+
|
|
154
|
+
expect(result.resources.AdminScriptQueue).toBeDefined();
|
|
155
|
+
expect(result.resources.AdminScriptQueue.Type).toBe('AWS::SQS::Queue');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should configure AdminScriptQueue with correct retention and timeout', async () => {
|
|
159
|
+
const appDefinition = {
|
|
160
|
+
adminScripts: [
|
|
161
|
+
{ Definition: { name: 'test-script' } },
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
166
|
+
|
|
167
|
+
expect(result.resources.AdminScriptQueue.Properties.MessageRetentionPeriod).toBe(86400); // 1 day
|
|
168
|
+
expect(result.resources.AdminScriptQueue.Properties.VisibilityTimeout).toBe(900); // 15 minutes
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should configure AdminScriptQueue redrive policy to InternalErrorQueue', async () => {
|
|
172
|
+
const appDefinition = {
|
|
173
|
+
adminScripts: [
|
|
174
|
+
{ Definition: { name: 'test-script' } },
|
|
175
|
+
],
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
179
|
+
|
|
180
|
+
expect(result.resources.AdminScriptQueue.Properties.RedrivePolicy).toEqual({
|
|
181
|
+
maxReceiveCount: 3,
|
|
182
|
+
deadLetterTargetArn: {
|
|
183
|
+
'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should add ADMIN_SCRIPT_QUEUE_URL to environment variables', async () => {
|
|
189
|
+
const appDefinition = {
|
|
190
|
+
adminScripts: [
|
|
191
|
+
{ Definition: { name: 'test-script' } },
|
|
192
|
+
],
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
196
|
+
|
|
197
|
+
expect(result.environment.ADMIN_SCRIPT_QUEUE_URL).toEqual({
|
|
198
|
+
Ref: 'AdminScriptQueue',
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should create adminScriptExecutor function', async () => {
|
|
203
|
+
const appDefinition = {
|
|
204
|
+
adminScripts: [
|
|
205
|
+
{ Definition: { name: 'test-script' } },
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
210
|
+
|
|
211
|
+
expect(result.functions.adminScriptExecutor).toBeDefined();
|
|
212
|
+
expect(result.functions.adminScriptExecutor.handler).toBe(
|
|
213
|
+
'node_modules/@friggframework/admin-scripts/src/infrastructure/script-executor-handler.handler'
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should configure adminScriptExecutor with SQS event', async () => {
|
|
218
|
+
const appDefinition = {
|
|
219
|
+
adminScripts: [
|
|
220
|
+
{ Definition: { name: 'test-script' } },
|
|
221
|
+
],
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
225
|
+
|
|
226
|
+
expect(result.functions.adminScriptExecutor.events).toEqual([
|
|
227
|
+
{
|
|
228
|
+
sqs: {
|
|
229
|
+
arn: { 'Fn::GetAtt': ['AdminScriptQueue', 'Arn'] },
|
|
230
|
+
batchSize: 1,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
]);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should set adminScriptExecutor timeout to 900 seconds', async () => {
|
|
237
|
+
const appDefinition = {
|
|
238
|
+
adminScripts: [
|
|
239
|
+
{ Definition: { name: 'test-script' } },
|
|
240
|
+
],
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
244
|
+
|
|
245
|
+
expect(result.functions.adminScriptExecutor.timeout).toBe(900); // 15 minutes (Lambda max)
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should set adminScriptExecutor memory size', async () => {
|
|
249
|
+
const appDefinition = {
|
|
250
|
+
adminScripts: [
|
|
251
|
+
{ Definition: { name: 'test-script' } },
|
|
252
|
+
],
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
256
|
+
|
|
257
|
+
expect(result.functions.adminScriptExecutor.memorySize).toBe(1024);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should exclude aws-sdk/Prisma from the skipEsbuild function packages', async () => {
|
|
261
|
+
const appDefinition = {
|
|
262
|
+
adminScripts: [{ Definition: { name: 'test-script' } }],
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
266
|
+
|
|
267
|
+
// Regression: without a package config the skipEsbuild functions
|
|
268
|
+
// package the whole node_modules and exceed Lambda's 250 MB limit.
|
|
269
|
+
for (const fn of [
|
|
270
|
+
result.functions.adminScriptExecutor,
|
|
271
|
+
result.functions.adminScriptRouter,
|
|
272
|
+
]) {
|
|
273
|
+
expect(fn.package.exclude).toEqual(
|
|
274
|
+
expect.arrayContaining([
|
|
275
|
+
'node_modules/aws-sdk/**',
|
|
276
|
+
'node_modules/@aws-sdk/**',
|
|
277
|
+
'node_modules/@prisma/**',
|
|
278
|
+
])
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should attach Prisma layer to adminScriptExecutor', async () => {
|
|
284
|
+
const appDefinition = {
|
|
285
|
+
adminScripts: [
|
|
286
|
+
{ Definition: { name: 'test-script' } },
|
|
287
|
+
],
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
291
|
+
|
|
292
|
+
expect(result.functions.adminScriptExecutor.layers).toEqual([
|
|
293
|
+
{ Ref: 'PrismaLambdaLayer' }
|
|
294
|
+
]);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should create adminScriptRouter function', async () => {
|
|
298
|
+
const appDefinition = {
|
|
299
|
+
adminScripts: [
|
|
300
|
+
{ Definition: { name: 'test-script' } },
|
|
301
|
+
],
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
305
|
+
|
|
306
|
+
expect(result.functions.adminScriptRouter).toBeDefined();
|
|
307
|
+
expect(result.functions.adminScriptRouter.handler).toBe(
|
|
308
|
+
'node_modules/@friggframework/admin-scripts/src/infrastructure/admin-script-router.handler'
|
|
309
|
+
);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should configure adminScriptRouter with correct HTTP routes', async () => {
|
|
313
|
+
const appDefinition = {
|
|
314
|
+
adminScripts: [
|
|
315
|
+
{ Definition: { name: 'test-script' } },
|
|
316
|
+
],
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
320
|
+
|
|
321
|
+
expect(result.functions.adminScriptRouter.events).toEqual([
|
|
322
|
+
// List scripts
|
|
323
|
+
{ httpApi: { path: '/admin/scripts', method: 'GET' } },
|
|
324
|
+
// Get script details
|
|
325
|
+
{ httpApi: { path: '/admin/scripts/{scriptName}', method: 'GET' } },
|
|
326
|
+
// Execute script (sync or async)
|
|
327
|
+
{ httpApi: { path: '/admin/scripts/{scriptName}', method: 'POST' } },
|
|
328
|
+
// Validate script input (dry-run preview)
|
|
329
|
+
{ httpApi: { path: '/admin/scripts/{scriptName}/validate', method: 'POST' } },
|
|
330
|
+
// List executions for a script
|
|
331
|
+
{ httpApi: { path: '/admin/scripts/{scriptName}/executions', method: 'GET' } },
|
|
332
|
+
// Get a single execution
|
|
333
|
+
{
|
|
334
|
+
httpApi: {
|
|
335
|
+
path: '/admin/scripts/{scriptName}/executions/{executionId}',
|
|
336
|
+
method: 'GET',
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
// Schedule management (Phase 2)
|
|
340
|
+
{ httpApi: { path: '/admin/scripts/{scriptName}/schedule', method: 'GET' } },
|
|
341
|
+
{ httpApi: { path: '/admin/scripts/{scriptName}/schedule', method: 'PUT' } },
|
|
342
|
+
{ httpApi: { path: '/admin/scripts/{scriptName}/schedule', method: 'DELETE' } },
|
|
343
|
+
]);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('should set adminScriptRouter timeout to 30 seconds', async () => {
|
|
347
|
+
const appDefinition = {
|
|
348
|
+
adminScripts: [
|
|
349
|
+
{ Definition: { name: 'test-script' } },
|
|
350
|
+
],
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
354
|
+
|
|
355
|
+
expect(result.functions.adminScriptRouter.timeout).toBe(30);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should attach Prisma layer to adminScriptRouter', async () => {
|
|
359
|
+
const appDefinition = {
|
|
360
|
+
adminScripts: [
|
|
361
|
+
{ Definition: { name: 'test-script' } },
|
|
362
|
+
],
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
366
|
+
|
|
367
|
+
expect(result.functions.adminScriptRouter.layers).toEqual([
|
|
368
|
+
{ Ref: 'PrismaLambdaLayer' }
|
|
369
|
+
]);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('should create scheduler resources when admin.enableScheduling is true', async () => {
|
|
373
|
+
const appDefinition = {
|
|
374
|
+
adminScripts: [
|
|
375
|
+
{ Definition: { name: 'test-script' } },
|
|
376
|
+
],
|
|
377
|
+
admin: {
|
|
378
|
+
enableScheduling: true,
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
383
|
+
|
|
384
|
+
// Check for scheduler IAM role
|
|
385
|
+
expect(result.resources.AdminScriptSchedulerRole).toBeDefined();
|
|
386
|
+
expect(result.resources.AdminScriptSchedulerRole.Type).toBe('AWS::IAM::Role');
|
|
387
|
+
|
|
388
|
+
// Check for schedule group
|
|
389
|
+
expect(result.resources.AdminScriptScheduleGroup).toBeDefined();
|
|
390
|
+
expect(result.resources.AdminScriptScheduleGroup.Type).toBe('AWS::Scheduler::ScheduleGroup');
|
|
391
|
+
|
|
392
|
+
// Scheduler env vars are scoped to the router function (not the
|
|
393
|
+
// shared provider env): resource refs would create CloudFormation
|
|
394
|
+
// circular deps, and SCHEDULER_PROVIDER='aws' would break core's
|
|
395
|
+
// scheduler factory (rejects 'aws') for every other Lambda.
|
|
396
|
+
const routerEnv = result.functions.adminScriptRouter.environment;
|
|
397
|
+
expect(routerEnv.SCHEDULER_PROVIDER).toBe('aws');
|
|
398
|
+
expect(result.environment.SCHEDULER_PROVIDER).toBeUndefined();
|
|
399
|
+
expect(routerEnv.SCHEDULER_ROLE_ARN).toEqual({
|
|
400
|
+
'Fn::GetAtt': ['AdminScriptSchedulerRole', 'Arn'],
|
|
401
|
+
});
|
|
402
|
+
expect(routerEnv.ADMIN_SCRIPT_SCHEDULE_GROUP).toEqual({
|
|
403
|
+
Ref: 'AdminScriptScheduleGroup',
|
|
404
|
+
});
|
|
405
|
+
// Executor referenced by constructed ARN (Fn::Sub), not Fn::GetAtt,
|
|
406
|
+
// so it creates no dependency edge.
|
|
407
|
+
expect(routerEnv.ADMIN_SCRIPT_EXECUTOR_LAMBDA_ARN).toEqual({
|
|
408
|
+
'Fn::Sub':
|
|
409
|
+
'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${self:service}-${self:provider.stage}-adminScriptExecutor',
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// Regression guard: these must NOT leak onto the shared provider env
|
|
413
|
+
// (that is what produced the circular dependency, incl. the executor
|
|
414
|
+
// self-reference).
|
|
415
|
+
expect(result.environment.SCHEDULER_ROLE_ARN).toBeUndefined();
|
|
416
|
+
expect(result.environment.ADMIN_SCRIPT_SCHEDULE_GROUP).toBeUndefined();
|
|
417
|
+
expect(
|
|
418
|
+
result.environment.ADMIN_SCRIPT_EXECUTOR_LAMBDA_ARN
|
|
419
|
+
).toBeUndefined();
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('should not create scheduler resources when enableScheduling is false', async () => {
|
|
423
|
+
const appDefinition = {
|
|
424
|
+
adminScripts: [
|
|
425
|
+
{ Definition: { name: 'test-script' } },
|
|
426
|
+
],
|
|
427
|
+
admin: {
|
|
428
|
+
enableScheduling: false,
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
433
|
+
|
|
434
|
+
expect(result.resources.AdminScriptSchedulerRole).toBeUndefined();
|
|
435
|
+
expect(result.resources.AdminScriptScheduleGroup).toBeUndefined();
|
|
436
|
+
expect(result.environment.SCHEDULER_ROLE_ARN).toBeUndefined();
|
|
437
|
+
expect(result.environment.ADMIN_SCRIPT_SCHEDULE_GROUP).toBeUndefined();
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('should not create scheduler resources when admin config is not provided', async () => {
|
|
441
|
+
const appDefinition = {
|
|
442
|
+
adminScripts: [
|
|
443
|
+
{ Definition: { name: 'test-script' } },
|
|
444
|
+
],
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
448
|
+
|
|
449
|
+
expect(result.resources.AdminScriptSchedulerRole).toBeUndefined();
|
|
450
|
+
expect(result.resources.AdminScriptScheduleGroup).toBeUndefined();
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('should use skipEsbuild for all functions', async () => {
|
|
454
|
+
const appDefinition = {
|
|
455
|
+
adminScripts: [
|
|
456
|
+
{ Definition: { name: 'test-script' } },
|
|
457
|
+
],
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
461
|
+
|
|
462
|
+
expect(result.functions.adminScriptExecutor.skipEsbuild).toBe(true);
|
|
463
|
+
expect(result.functions.adminScriptRouter.skipEsbuild).toBe(true);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('should not attach Prisma layer when usePrismaLambdaLayer=false', async () => {
|
|
467
|
+
const appDefinition = {
|
|
468
|
+
usePrismaLambdaLayer: false,
|
|
469
|
+
adminScripts: [
|
|
470
|
+
{ Definition: { name: 'test-script' } },
|
|
471
|
+
],
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
475
|
+
|
|
476
|
+
expect(result.functions.adminScriptExecutor.layers).toBeUndefined();
|
|
477
|
+
expect(result.functions.adminScriptRouter.layers).toBeUndefined();
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it('should handle multiple admin scripts', async () => {
|
|
481
|
+
const appDefinition = {
|
|
482
|
+
adminScripts: [
|
|
483
|
+
{ Definition: { name: 'oauth-refresh' } },
|
|
484
|
+
{ Definition: { name: 'health-check' } },
|
|
485
|
+
{ Definition: { name: 'attio-healing' } },
|
|
486
|
+
],
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
490
|
+
|
|
491
|
+
// Should still only create one queue and two functions
|
|
492
|
+
expect(result.resources.AdminScriptQueue).toBeDefined();
|
|
493
|
+
expect(result.functions.adminScriptExecutor).toBeDefined();
|
|
494
|
+
expect(result.functions.adminScriptRouter).toBeDefined();
|
|
495
|
+
|
|
496
|
+
// Should not create separate resources per script
|
|
497
|
+
expect(Object.keys(result.resources)).toHaveLength(1); // Only AdminScriptQueue
|
|
498
|
+
expect(Object.keys(result.functions)).toHaveLength(2); // Only executor and router
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it('should configure scheduler role with correct trust policy', async () => {
|
|
502
|
+
const appDefinition = {
|
|
503
|
+
adminScripts: [
|
|
504
|
+
{ Definition: { name: 'test-script' } },
|
|
505
|
+
],
|
|
506
|
+
admin: {
|
|
507
|
+
enableScheduling: true,
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
512
|
+
|
|
513
|
+
const trustPolicy = result.resources.AdminScriptSchedulerRole.Properties.AssumeRolePolicyDocument;
|
|
514
|
+
|
|
515
|
+
expect(trustPolicy.Statement[0]).toEqual({
|
|
516
|
+
Effect: 'Allow',
|
|
517
|
+
Principal: { Service: 'scheduler.amazonaws.com' },
|
|
518
|
+
Action: 'sts:AssumeRole',
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it('should configure scheduler role with Lambda invoke permission', async () => {
|
|
523
|
+
const appDefinition = {
|
|
524
|
+
adminScripts: [
|
|
525
|
+
{ Definition: { name: 'test-script' } },
|
|
526
|
+
],
|
|
527
|
+
admin: {
|
|
528
|
+
enableScheduling: true,
|
|
529
|
+
},
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
533
|
+
|
|
534
|
+
const policies = result.resources.AdminScriptSchedulerRole.Properties.Policies;
|
|
535
|
+
|
|
536
|
+
expect(policies[0].PolicyName).toBe('InvokeLambda');
|
|
537
|
+
// Constructed ARN (Fn::Sub), not Fn::GetAtt — a GetAtt here closes a
|
|
538
|
+
// circular dependency through the shared Lambda execution role.
|
|
539
|
+
expect(policies[0].PolicyDocument.Statement[0]).toEqual({
|
|
540
|
+
Effect: 'Allow',
|
|
541
|
+
Action: 'lambda:InvokeFunction',
|
|
542
|
+
Resource: {
|
|
543
|
+
'Fn::Sub':
|
|
544
|
+
'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${self:service}-${self:provider.stage}-adminScriptExecutor',
|
|
545
|
+
},
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it('should grant the router SendMessage on AdminScriptQueue', async () => {
|
|
550
|
+
const appDefinition = {
|
|
551
|
+
adminScripts: [{ Definition: { name: 'test-script' } }],
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
555
|
+
|
|
556
|
+
const sqsGrant = result.iamStatements.find(
|
|
557
|
+
(s) =>
|
|
558
|
+
Array.isArray(s.Action) &&
|
|
559
|
+
s.Action.includes('sqs:SendMessage')
|
|
560
|
+
);
|
|
561
|
+
expect(sqsGrant).toBeDefined();
|
|
562
|
+
expect(sqsGrant.Action).toContain('sqs:SendMessageBatch');
|
|
563
|
+
expect(sqsGrant.Resource).toEqual({
|
|
564
|
+
'Fn::GetAtt': ['AdminScriptQueue', 'Arn'],
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('should grant scheduler:* + iam:PassRole when scheduling is enabled', async () => {
|
|
569
|
+
const appDefinition = {
|
|
570
|
+
adminScripts: [{ Definition: { name: 'test-script' } }],
|
|
571
|
+
admin: { enableScheduling: true },
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
575
|
+
|
|
576
|
+
const schedulerGrant = result.iamStatements.find(
|
|
577
|
+
(s) =>
|
|
578
|
+
Array.isArray(s.Action) &&
|
|
579
|
+
s.Action.includes('scheduler:CreateSchedule')
|
|
580
|
+
);
|
|
581
|
+
expect(schedulerGrant).toBeDefined();
|
|
582
|
+
expect(schedulerGrant.Action).toEqual(
|
|
583
|
+
expect.arrayContaining([
|
|
584
|
+
'scheduler:CreateSchedule',
|
|
585
|
+
'scheduler:UpdateSchedule',
|
|
586
|
+
'scheduler:DeleteSchedule',
|
|
587
|
+
'scheduler:GetSchedule',
|
|
588
|
+
])
|
|
589
|
+
);
|
|
590
|
+
expect(schedulerGrant.Resource['Fn::Sub'][1]).toEqual({
|
|
591
|
+
GroupName: { Ref: 'AdminScriptScheduleGroup' },
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
const passRole = result.iamStatements.find(
|
|
595
|
+
(s) =>
|
|
596
|
+
Array.isArray(s.Action) && s.Action.includes('iam:PassRole')
|
|
597
|
+
);
|
|
598
|
+
expect(passRole).toBeDefined();
|
|
599
|
+
expect(passRole.Resource).toEqual({
|
|
600
|
+
'Fn::GetAtt': ['AdminScriptSchedulerRole', 'Arn'],
|
|
601
|
+
});
|
|
602
|
+
expect(passRole.Condition.StringEquals['iam:PassedToService']).toBe(
|
|
603
|
+
'scheduler.amazonaws.com'
|
|
604
|
+
);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
it('should add no scheduler IAM statements when scheduling is disabled', async () => {
|
|
608
|
+
const appDefinition = {
|
|
609
|
+
adminScripts: [{ Definition: { name: 'test-script' } }],
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
613
|
+
|
|
614
|
+
const schedulerGrant = result.iamStatements.find(
|
|
615
|
+
(s) =>
|
|
616
|
+
Array.isArray(s.Action) &&
|
|
617
|
+
s.Action.some((a) => a.startsWith('scheduler:'))
|
|
618
|
+
);
|
|
619
|
+
expect(schedulerGrant).toBeUndefined();
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
describe('reports (ReportQueue + reportExecutor)', () => {
|
|
624
|
+
it('creates ReportQueue + reportExecutor and wires REPORT_QUEUE_URL when reports are present', async () => {
|
|
625
|
+
const appDefinition = {
|
|
626
|
+
reports: [{ Definition: { name: 'my-report', version: '1.0.0' } }],
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
630
|
+
|
|
631
|
+
// Queue
|
|
632
|
+
expect(result.resources.ReportQueue).toBeDefined();
|
|
633
|
+
expect(result.resources.ReportQueue.Type).toBe('AWS::SQS::Queue');
|
|
634
|
+
expect(
|
|
635
|
+
result.resources.ReportQueue.Properties.MessageRetentionPeriod
|
|
636
|
+
).toBe(86400);
|
|
637
|
+
expect(
|
|
638
|
+
result.resources.ReportQueue.Properties.VisibilityTimeout
|
|
639
|
+
).toBe(900);
|
|
640
|
+
expect(
|
|
641
|
+
result.resources.ReportQueue.Properties.RedrivePolicy
|
|
642
|
+
).toEqual({
|
|
643
|
+
maxReceiveCount: 3,
|
|
644
|
+
deadLetterTargetArn: {
|
|
645
|
+
'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
|
|
646
|
+
},
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// Executor function
|
|
650
|
+
expect(result.functions.reportExecutor).toBeDefined();
|
|
651
|
+
expect(result.functions.reportExecutor.handler).toBe(
|
|
652
|
+
'node_modules/@friggframework/admin-scripts/src/infrastructure/report-executor-handler.handler'
|
|
653
|
+
);
|
|
654
|
+
expect(result.functions.reportExecutor.timeout).toBe(900);
|
|
655
|
+
expect(result.functions.reportExecutor.memorySize).toBe(1024);
|
|
656
|
+
expect(result.functions.reportExecutor.events).toEqual([
|
|
657
|
+
{
|
|
658
|
+
sqs: {
|
|
659
|
+
arn: { 'Fn::GetAtt': ['ReportQueue', 'Arn'] },
|
|
660
|
+
batchSize: 1,
|
|
661
|
+
},
|
|
662
|
+
},
|
|
663
|
+
]);
|
|
664
|
+
expect(result.functions.reportExecutor.skipEsbuild).toBe(true);
|
|
665
|
+
expect(result.functions.reportExecutor.layers).toEqual([
|
|
666
|
+
{ Ref: 'PrismaLambdaLayer' },
|
|
667
|
+
]);
|
|
668
|
+
|
|
669
|
+
// Env wired app-wide (scoped flag off)
|
|
670
|
+
expect(result.environment.REPORT_QUEUE_URL).toEqual({
|
|
671
|
+
Ref: 'ReportQueue',
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
// IAM SendMessage grant on the queue Arn
|
|
675
|
+
const grant = result.iamStatements.find(
|
|
676
|
+
(s) =>
|
|
677
|
+
Array.isArray(s.Action) &&
|
|
678
|
+
s.Action.includes('sqs:SendMessage') &&
|
|
679
|
+
s.Resource &&
|
|
680
|
+
s.Resource['Fn::GetAtt'] &&
|
|
681
|
+
s.Resource['Fn::GetAtt'][0] === 'ReportQueue'
|
|
682
|
+
);
|
|
683
|
+
expect(grant).toBeDefined();
|
|
684
|
+
expect(grant.Action).toContain('sqs:SendMessageBatch');
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
it('scopes REPORT_QUEUE_URL to reportRouter + reportExecutor when scopedEnvironment is on', async () => {
|
|
688
|
+
const appDefinition = {
|
|
689
|
+
lambda: { scopedEnvironment: true },
|
|
690
|
+
reports: [{ Definition: { name: 'my-report', version: '1.0.0' } }],
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
694
|
+
|
|
695
|
+
expect(result.environment.REPORT_QUEUE_URL).toBeUndefined();
|
|
696
|
+
for (const fnName of ['reportRouter', 'reportExecutor']) {
|
|
697
|
+
expect(
|
|
698
|
+
result.functionEnvironments[fnName].REPORT_QUEUE_URL
|
|
699
|
+
).toEqual({ Ref: 'ReportQueue' });
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it('creates ReportQueue + reportExecutor when only builtin reports are enabled', async () => {
|
|
704
|
+
const appDefinition = {
|
|
705
|
+
admin: { includeBuiltinReports: true },
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
709
|
+
|
|
710
|
+
expect(result.resources.ReportQueue).toBeDefined();
|
|
711
|
+
expect(result.functions.reportExecutor).toBeDefined();
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
it('does NOT create ReportQueue or reportExecutor when only adminScripts are present', async () => {
|
|
715
|
+
const appDefinition = {
|
|
716
|
+
adminScripts: [{ Definition: { name: 'test-script' } }],
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
720
|
+
|
|
721
|
+
expect(result.resources.ReportQueue).toBeUndefined();
|
|
722
|
+
expect(result.functions.reportExecutor).toBeUndefined();
|
|
723
|
+
expect(result.functions.reportRouter).toBeUndefined();
|
|
724
|
+
expect(result.environment.REPORT_QUEUE_URL).toBeUndefined();
|
|
725
|
+
});
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
describe('report artifacts (ReportArtifactBucket for non-JSON output)', () => {
|
|
729
|
+
it('provisions a private encrypted bucket + IAM + env when a report emits non-JSON', async () => {
|
|
730
|
+
const appDefinition = {
|
|
731
|
+
reports: [
|
|
732
|
+
{
|
|
733
|
+
Definition: {
|
|
734
|
+
name: 'sales-csv',
|
|
735
|
+
version: '1.0.0',
|
|
736
|
+
output: { format: 'csv' },
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
],
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
743
|
+
|
|
744
|
+
const bucket = result.resources.ReportArtifactBucket;
|
|
745
|
+
expect(bucket).toBeDefined();
|
|
746
|
+
expect(bucket.Type).toBe('AWS::S3::Bucket');
|
|
747
|
+
expect(
|
|
748
|
+
bucket.Properties.BucketEncryption
|
|
749
|
+
.ServerSideEncryptionConfiguration[0]
|
|
750
|
+
.ServerSideEncryptionByDefault.SSEAlgorithm
|
|
751
|
+
).toBe('AES256');
|
|
752
|
+
expect(bucket.Properties.PublicAccessBlockConfiguration).toEqual({
|
|
753
|
+
BlockPublicAcls: true,
|
|
754
|
+
BlockPublicPolicy: true,
|
|
755
|
+
IgnorePublicAcls: true,
|
|
756
|
+
RestrictPublicBuckets: true,
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
// Env wired app-wide (scoped flag off).
|
|
760
|
+
expect(result.environment.REPORT_ARTIFACT_BUCKET).toEqual({
|
|
761
|
+
Ref: 'ReportArtifactBucket',
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
// IAM: object-level Put/Get scoped to the bucket keys.
|
|
765
|
+
const grant = result.iamStatements.find(
|
|
766
|
+
(s) =>
|
|
767
|
+
Array.isArray(s.Action) &&
|
|
768
|
+
s.Action.includes('s3:PutObject')
|
|
769
|
+
);
|
|
770
|
+
expect(grant).toBeDefined();
|
|
771
|
+
expect(grant.Action).toContain('s3:GetObject');
|
|
772
|
+
expect(grant.Resource['Fn::Sub'][0]).toBe('${BucketArn}/*');
|
|
773
|
+
expect(grant.Resource['Fn::Sub'][1]).toEqual({
|
|
774
|
+
BucketArn: { 'Fn::GetAtt': ['ReportArtifactBucket', 'Arn'] },
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
it('does NOT provision the bucket for JSON-only reports', async () => {
|
|
779
|
+
const appDefinition = {
|
|
780
|
+
reports: [
|
|
781
|
+
{
|
|
782
|
+
Definition: {
|
|
783
|
+
name: 'json-report',
|
|
784
|
+
version: '1.0.0',
|
|
785
|
+
output: { format: 'json' },
|
|
786
|
+
},
|
|
787
|
+
},
|
|
788
|
+
// No output field defaults to JSON.
|
|
789
|
+
{ Definition: { name: 'plain', version: '1.0.0' } },
|
|
790
|
+
],
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
794
|
+
|
|
795
|
+
expect(result.resources.ReportArtifactBucket).toBeUndefined();
|
|
796
|
+
expect(result.environment.REPORT_ARTIFACT_BUCKET).toBeUndefined();
|
|
797
|
+
const grant = result.iamStatements.find(
|
|
798
|
+
(s) =>
|
|
799
|
+
Array.isArray(s.Action) &&
|
|
800
|
+
s.Action.includes('s3:PutObject')
|
|
801
|
+
);
|
|
802
|
+
expect(grant).toBeUndefined();
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
it('scopes REPORT_ARTIFACT_BUCKET to report functions when scopedEnvironment is on', async () => {
|
|
806
|
+
const appDefinition = {
|
|
807
|
+
lambda: { scopedEnvironment: true },
|
|
808
|
+
reports: [
|
|
809
|
+
{
|
|
810
|
+
Definition: {
|
|
811
|
+
name: 'sales-csv',
|
|
812
|
+
version: '1.0.0',
|
|
813
|
+
output: { format: 'csv' },
|
|
814
|
+
},
|
|
815
|
+
},
|
|
816
|
+
],
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
820
|
+
|
|
821
|
+
expect(result.environment.REPORT_ARTIFACT_BUCKET).toBeUndefined();
|
|
822
|
+
for (const fnName of ['reportRouter', 'reportExecutor']) {
|
|
823
|
+
expect(
|
|
824
|
+
result.functionEnvironments[fnName].REPORT_ARTIFACT_BUCKET
|
|
825
|
+
).toEqual({ Ref: 'ReportArtifactBucket' });
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
describe('report scheduling (enableScheduling && reports)', () => {
|
|
831
|
+
it('wires the report scheduler env onto reportRouter targeting the report executor', async () => {
|
|
832
|
+
const appDefinition = {
|
|
833
|
+
reports: [{ Definition: { name: 'my-report', version: '1.0.0' } }],
|
|
834
|
+
admin: { enableScheduling: true },
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
838
|
+
|
|
839
|
+
// Shared scheduler role + group are created even without scripts.
|
|
840
|
+
expect(result.resources.AdminScriptSchedulerRole).toBeDefined();
|
|
841
|
+
expect(result.resources.AdminScriptScheduleGroup).toBeDefined();
|
|
842
|
+
|
|
843
|
+
const routerEnv = result.functions.reportRouter.environment;
|
|
844
|
+
expect(routerEnv.SCHEDULER_PROVIDER).toBe('aws');
|
|
845
|
+
expect(routerEnv.SCHEDULER_ROLE_ARN).toEqual({
|
|
846
|
+
'Fn::GetAtt': ['AdminScriptSchedulerRole', 'Arn'],
|
|
847
|
+
});
|
|
848
|
+
expect(routerEnv.REPORT_SCHEDULE_GROUP).toEqual({
|
|
849
|
+
Ref: 'AdminScriptScheduleGroup',
|
|
850
|
+
});
|
|
851
|
+
// Constructed ARN (Fn::Sub), not Fn::GetAtt — avoids a circular dep.
|
|
852
|
+
expect(routerEnv.REPORT_EXECUTOR_LAMBDA_ARN).toEqual({
|
|
853
|
+
'Fn::Sub':
|
|
854
|
+
'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${self:service}-${self:provider.stage}-reportExecutor',
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
// Must NOT leak onto the shared provider env.
|
|
858
|
+
expect(result.environment.SCHEDULER_PROVIDER).toBeUndefined();
|
|
859
|
+
expect(result.environment.REPORT_EXECUTOR_LAMBDA_ARN).toBeUndefined();
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
it('grants the scheduler role invoke on the report executor (reports-only)', async () => {
|
|
863
|
+
const appDefinition = {
|
|
864
|
+
reports: [{ Definition: { name: 'my-report', version: '1.0.0' } }],
|
|
865
|
+
admin: { enableScheduling: true },
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
869
|
+
|
|
870
|
+
const statement =
|
|
871
|
+
result.resources.AdminScriptSchedulerRole.Properties.Policies[0]
|
|
872
|
+
.PolicyDocument.Statement[0];
|
|
873
|
+
// Only reports present -> single Resource, the report executor ARN.
|
|
874
|
+
expect(statement.Resource).toEqual({
|
|
875
|
+
'Fn::Sub':
|
|
876
|
+
'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${self:service}-${self:provider.stage}-reportExecutor',
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
// scheduler:* + iam:PassRole grants present.
|
|
880
|
+
const schedulerGrant = result.iamStatements.find(
|
|
881
|
+
(s) =>
|
|
882
|
+
Array.isArray(s.Action) &&
|
|
883
|
+
s.Action.includes('scheduler:CreateSchedule')
|
|
884
|
+
);
|
|
885
|
+
expect(schedulerGrant).toBeDefined();
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
it('lets the scheduler role invoke BOTH executors when scripts and reports coexist', async () => {
|
|
889
|
+
const appDefinition = {
|
|
890
|
+
adminScripts: [{ Definition: { name: 'test-script' } }],
|
|
891
|
+
reports: [{ Definition: { name: 'my-report', version: '1.0.0' } }],
|
|
892
|
+
admin: { enableScheduling: true },
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
896
|
+
|
|
897
|
+
const statement =
|
|
898
|
+
result.resources.AdminScriptSchedulerRole.Properties.Policies[0]
|
|
899
|
+
.PolicyDocument.Statement[0];
|
|
900
|
+
expect(statement.Resource).toEqual([
|
|
901
|
+
{
|
|
902
|
+
'Fn::Sub':
|
|
903
|
+
'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${self:service}-${self:provider.stage}-adminScriptExecutor',
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
'Fn::Sub':
|
|
907
|
+
'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${self:service}-${self:provider.stage}-reportExecutor',
|
|
908
|
+
},
|
|
909
|
+
]);
|
|
910
|
+
|
|
911
|
+
// Both routers get their own scheduler env.
|
|
912
|
+
expect(
|
|
913
|
+
result.functions.adminScriptRouter.environment
|
|
914
|
+
.ADMIN_SCRIPT_EXECUTOR_LAMBDA_ARN
|
|
915
|
+
).toBeDefined();
|
|
916
|
+
expect(
|
|
917
|
+
result.functions.reportRouter.environment
|
|
918
|
+
.REPORT_EXECUTOR_LAMBDA_ARN
|
|
919
|
+
).toBeDefined();
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
it('does NOT wire report scheduler env when enableScheduling is off', async () => {
|
|
923
|
+
const appDefinition = {
|
|
924
|
+
reports: [{ Definition: { name: 'my-report', version: '1.0.0' } }],
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
const result = await adminScriptBuilder.build(appDefinition, {});
|
|
928
|
+
|
|
929
|
+
expect(result.resources.AdminScriptSchedulerRole).toBeUndefined();
|
|
930
|
+
expect(
|
|
931
|
+
result.functions.reportRouter.environment
|
|
932
|
+
).toBeUndefined();
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
describe('getName()', () => {
|
|
937
|
+
it('should return AdminScriptBuilder', () => {
|
|
938
|
+
expect(adminScriptBuilder.getName()).toBe('AdminScriptBuilder');
|
|
939
|
+
});
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
describe('scoped environment (lambda.scopedEnvironment)', () => {
|
|
943
|
+
const originalSkipDiscovery = process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
944
|
+
|
|
945
|
+
beforeEach(() => {
|
|
946
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
afterEach(() => {
|
|
950
|
+
if (originalSkipDiscovery === undefined) {
|
|
951
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
952
|
+
} else {
|
|
953
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = originalSkipDiscovery;
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
it('scopes ADMIN_SCRIPT_QUEUE_URL to the admin functions only', async () => {
|
|
958
|
+
const result = await adminScriptBuilder.build(
|
|
959
|
+
{
|
|
960
|
+
lambda: { scopedEnvironment: true },
|
|
961
|
+
adminScripts: [{ Definition: { name: 'fix-things' } }],
|
|
962
|
+
},
|
|
963
|
+
{}
|
|
964
|
+
);
|
|
965
|
+
|
|
966
|
+
expect(result.environment.ADMIN_SCRIPT_QUEUE_URL).toBeUndefined();
|
|
967
|
+
for (const fnName of ['adminScriptRouter', 'adminScriptExecutor']) {
|
|
968
|
+
expect(
|
|
969
|
+
result.functionEnvironments[fnName].ADMIN_SCRIPT_QUEUE_URL
|
|
970
|
+
).toEqual({ Ref: 'AdminScriptQueue' });
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
it('broadcasts app-wide when the flag is off', async () => {
|
|
975
|
+
const result = await adminScriptBuilder.build(
|
|
976
|
+
{ adminScripts: [{ Definition: { name: 'fix-things' } }] },
|
|
977
|
+
{}
|
|
978
|
+
);
|
|
979
|
+
|
|
980
|
+
expect(result.environment.ADMIN_SCRIPT_QUEUE_URL).toEqual({
|
|
981
|
+
Ref: 'AdminScriptQueue',
|
|
982
|
+
});
|
|
983
|
+
expect(result.functionEnvironments).toBeUndefined();
|
|
984
|
+
});
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
describe('AdminScriptBuilder nested node_modules (lambda.keepNestedNodeModules)', () => {
|
|
989
|
+
const appDefinition = (lambda) => ({
|
|
990
|
+
adminScripts: [{ Definition: { name: 'test-script' } }],
|
|
991
|
+
...(lambda && { lambda }),
|
|
992
|
+
});
|
|
993
|
+
const executorAndRouter = (result) => [
|
|
994
|
+
result.functions.adminScriptExecutor,
|
|
995
|
+
result.functions.adminScriptRouter,
|
|
996
|
+
];
|
|
997
|
+
|
|
998
|
+
it('excludes every nested node_modules by default', async () => {
|
|
999
|
+
const result = await new AdminScriptBuilder().build(appDefinition(), {});
|
|
1000
|
+
|
|
1001
|
+
for (const fn of executorAndRouter(result)) {
|
|
1002
|
+
expect(fn.package.exclude).toContain('node_modules/**/node_modules/**');
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
it('keeps nested node_modules when the app opts in, still excluding nested Frigg copies', async () => {
|
|
1007
|
+
const result = await new AdminScriptBuilder().build(
|
|
1008
|
+
appDefinition({ keepNestedNodeModules: true }),
|
|
1009
|
+
{}
|
|
1010
|
+
);
|
|
1011
|
+
|
|
1012
|
+
for (const fn of executorAndRouter(result)) {
|
|
1013
|
+
expect(fn.package.exclude).not.toContain('node_modules/**/node_modules/**');
|
|
1014
|
+
expect(fn.package.exclude).toContain('node_modules/**/node_modules/@friggframework/**');
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
});
|