@friggframework/devtools 2.0.0-next.45 → 2.0.0-next.47
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/infrastructure/ARCHITECTURE.md +487 -0
- package/infrastructure/HEALTH.md +468 -0
- package/infrastructure/README.md +51 -0
- package/infrastructure/__tests__/postgres-config.test.js +914 -0
- package/infrastructure/__tests__/template-generation.test.js +687 -0
- package/infrastructure/create-frigg-infrastructure.js +1 -1
- package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
- package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
- package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
- package/infrastructure/domains/database/aurora-builder.js +809 -0
- package/infrastructure/domains/database/aurora-builder.test.js +950 -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 +695 -0
- package/infrastructure/domains/database/migration-builder.test.js +294 -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 +397 -0
- package/infrastructure/domains/integration/integration-builder.test.js +593 -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 +1829 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +1262 -0
- package/infrastructure/domains/networking/vpc-discovery.js +177 -0
- package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
- package/infrastructure/domains/networking/vpc-resolver.js +324 -0
- package/infrastructure/domains/networking/vpc-resolver.test.js +501 -0
- package/infrastructure/domains/parameters/ssm-builder.js +79 -0
- package/infrastructure/domains/parameters/ssm-builder.test.js +189 -0
- package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
- package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
- package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
- package/infrastructure/domains/security/kms-builder.js +366 -0
- package/infrastructure/domains/security/kms-builder.test.js +374 -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/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 +212 -0
- package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
- package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
- package/infrastructure/domains/shared/cloudformation-discovery.js +375 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +590 -0
- package/infrastructure/domains/shared/environment-builder.js +119 -0
- package/infrastructure/domains/shared/environment-builder.test.js +247 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +544 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +377 -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.js +192 -0
- package/infrastructure/domains/shared/resource-discovery.test.js +552 -0
- package/infrastructure/domains/shared/types/app-definition.js +205 -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 +380 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -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/prisma-layer-manager.js +55 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +138 -0
- package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +2 -1
- package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
- package/infrastructure/esbuild.config.js +53 -0
- package/infrastructure/infrastructure-composer.js +87 -0
- package/infrastructure/{serverless-template.test.js → infrastructure-composer.test.js} +115 -24
- package/infrastructure/scripts/build-prisma-layer.js +553 -0
- package/infrastructure/scripts/build-prisma-layer.test.js +102 -0
- package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +80 -48
- package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
- package/layers/prisma/nodejs/package.json +8 -0
- package/management-ui/server/utils/cliIntegration.js +1 -1
- package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
- package/package.json +11 -11
- package/frigg-cli/.eslintrc.js +0 -141
- package/frigg-cli/__tests__/unit/commands/build.test.js +0 -251
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +0 -548
- package/frigg-cli/__tests__/unit/commands/install.test.js +0 -400
- package/frigg-cli/__tests__/unit/commands/ui.test.js +0 -346
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +0 -366
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +0 -304
- package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +0 -486
- package/frigg-cli/__tests__/utils/mock-factory.js +0 -270
- package/frigg-cli/__tests__/utils/prisma-mock.js +0 -194
- package/frigg-cli/__tests__/utils/test-fixtures.js +0 -463
- package/frigg-cli/__tests__/utils/test-setup.js +0 -287
- package/frigg-cli/build-command/index.js +0 -65
- package/frigg-cli/db-setup-command/index.js +0 -193
- package/frigg-cli/deploy-command/index.js +0 -175
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +0 -301
- package/frigg-cli/generate-command/azure-generator.js +0 -43
- package/frigg-cli/generate-command/gcp-generator.js +0 -47
- package/frigg-cli/generate-command/index.js +0 -332
- package/frigg-cli/generate-command/terraform-generator.js +0 -555
- package/frigg-cli/generate-iam-command.js +0 -118
- package/frigg-cli/index.js +0 -75
- package/frigg-cli/index.test.js +0 -158
- package/frigg-cli/init-command/backend-first-handler.js +0 -756
- package/frigg-cli/init-command/index.js +0 -93
- package/frigg-cli/init-command/template-handler.js +0 -143
- package/frigg-cli/install-command/backend-js.js +0 -33
- package/frigg-cli/install-command/commit-changes.js +0 -16
- package/frigg-cli/install-command/environment-variables.js +0 -127
- package/frigg-cli/install-command/environment-variables.test.js +0 -136
- package/frigg-cli/install-command/index.js +0 -54
- package/frigg-cli/install-command/install-package.js +0 -13
- package/frigg-cli/install-command/integration-file.js +0 -30
- package/frigg-cli/install-command/logger.js +0 -12
- package/frigg-cli/install-command/template.js +0 -90
- package/frigg-cli/install-command/validate-package.js +0 -75
- package/frigg-cli/jest.config.js +0 -124
- package/frigg-cli/package.json +0 -54
- package/frigg-cli/start-command/index.js +0 -149
- package/frigg-cli/start-command/start-command.test.js +0 -297
- package/frigg-cli/test/init-command.test.js +0 -180
- package/frigg-cli/test/npm-registry.test.js +0 -319
- package/frigg-cli/ui-command/index.js +0 -154
- package/frigg-cli/utils/app-resolver.js +0 -319
- package/frigg-cli/utils/backend-path.js +0 -25
- package/frigg-cli/utils/database-validator.js +0 -161
- package/frigg-cli/utils/error-messages.js +0 -257
- package/frigg-cli/utils/npm-registry.js +0 -167
- package/frigg-cli/utils/prisma-runner.js +0 -280
- package/frigg-cli/utils/process-manager.js +0 -199
- package/frigg-cli/utils/repo-detection.js +0 -405
- package/infrastructure/aws-discovery.js +0 -1176
- package/infrastructure/aws-discovery.test.js +0 -1220
- package/infrastructure/serverless-template.js +0 -2094
- /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
- /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
- /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
- /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
- /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
- /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
- /package/infrastructure/{run-discovery.js → scripts/run-discovery.js} +0 -0
|
@@ -0,0 +1,950 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Aurora Builder
|
|
3
|
+
*
|
|
4
|
+
* Tests Aurora PostgreSQL cluster configuration
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { AuroraBuilder } = require('./aurora-builder');
|
|
8
|
+
const { ValidationResult } = require('../shared/base-builder');
|
|
9
|
+
|
|
10
|
+
describe('AuroraBuilder', () => {
|
|
11
|
+
let auroraBuilder;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
auroraBuilder = new AuroraBuilder();
|
|
15
|
+
// Clean up env vars
|
|
16
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
// Clean up env vars
|
|
21
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('shouldExecute()', () => {
|
|
25
|
+
it('should return true when Postgres is enabled', () => {
|
|
26
|
+
const appDefinition = {
|
|
27
|
+
database: {
|
|
28
|
+
postgres: { enable: true },
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
expect(auroraBuilder.shouldExecute(appDefinition)).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should return false when Postgres is disabled', () => {
|
|
36
|
+
const appDefinition = {
|
|
37
|
+
database: {
|
|
38
|
+
postgres: { enable: false },
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
expect(auroraBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should return false when database is not defined', () => {
|
|
46
|
+
const appDefinition = {};
|
|
47
|
+
|
|
48
|
+
expect(auroraBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should return false when postgres is not defined', () => {
|
|
52
|
+
const appDefinition = {
|
|
53
|
+
database: {},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
expect(auroraBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return false when FRIGG_SKIP_AWS_DISCOVERY is set (local mode)', () => {
|
|
60
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
61
|
+
|
|
62
|
+
const appDefinition = {
|
|
63
|
+
database: {
|
|
64
|
+
postgres: { enable: true },
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
expect(auroraBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should return true when FRIGG_SKIP_AWS_DISCOVERY is not set and Postgres is enabled', () => {
|
|
72
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
73
|
+
|
|
74
|
+
const appDefinition = {
|
|
75
|
+
database: {
|
|
76
|
+
postgres: { enable: true },
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
expect(auroraBuilder.shouldExecute(appDefinition)).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('getDependencies()', () => {
|
|
85
|
+
it('should depend on VpcBuilder', () => {
|
|
86
|
+
const deps = auroraBuilder.getDependencies();
|
|
87
|
+
|
|
88
|
+
expect(deps).toEqual(['VpcBuilder']);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('validate()', () => {
|
|
93
|
+
it('should pass validation for valid discover mode config', () => {
|
|
94
|
+
const appDefinition = {
|
|
95
|
+
database: {
|
|
96
|
+
postgres: {
|
|
97
|
+
enable: true,
|
|
98
|
+
management: 'discover',
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const result = auroraBuilder.validate(appDefinition);
|
|
104
|
+
|
|
105
|
+
expect(result).toBeInstanceOf(ValidationResult);
|
|
106
|
+
expect(result.valid).toBe(true);
|
|
107
|
+
expect(result.errors).toEqual([]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should pass validation for managed mode', () => {
|
|
111
|
+
const appDefinition = {
|
|
112
|
+
database: {
|
|
113
|
+
postgres: {
|
|
114
|
+
enable: true,
|
|
115
|
+
management: 'managed',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const result = auroraBuilder.validate(appDefinition);
|
|
121
|
+
|
|
122
|
+
expect(result.valid).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should error when database config is missing', () => {
|
|
126
|
+
const appDefinition = {};
|
|
127
|
+
|
|
128
|
+
const result = auroraBuilder.validate(appDefinition);
|
|
129
|
+
|
|
130
|
+
expect(result.valid).toBe(false);
|
|
131
|
+
expect(result.errors).toContain('PostgreSQL database configuration is missing');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should error for invalid management mode', () => {
|
|
135
|
+
const appDefinition = {
|
|
136
|
+
database: {
|
|
137
|
+
postgres: {
|
|
138
|
+
enable: true,
|
|
139
|
+
management: 'invalid-mode',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const result = auroraBuilder.validate(appDefinition);
|
|
145
|
+
|
|
146
|
+
expect(result.valid).toBe(false);
|
|
147
|
+
expect(result.errors.some(e => e.includes('Invalid database.postgres.management'))).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should error when use-existing without endpoint', () => {
|
|
151
|
+
const appDefinition = {
|
|
152
|
+
database: {
|
|
153
|
+
postgres: {
|
|
154
|
+
enable: true,
|
|
155
|
+
management: 'use-existing',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const result = auroraBuilder.validate(appDefinition);
|
|
161
|
+
|
|
162
|
+
expect(result.valid).toBe(false);
|
|
163
|
+
expect(result.errors).toContain(
|
|
164
|
+
'database.postgres.endpoint is required when management="use-existing"'
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should pass when use-existing with endpoint', () => {
|
|
169
|
+
const appDefinition = {
|
|
170
|
+
database: {
|
|
171
|
+
postgres: {
|
|
172
|
+
enable: true,
|
|
173
|
+
management: 'use-existing',
|
|
174
|
+
endpoint: 'db.example.com',
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const result = auroraBuilder.validate(appDefinition);
|
|
180
|
+
|
|
181
|
+
expect(result.valid).toBe(true);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should error when minCapacity is out of range', () => {
|
|
185
|
+
const appDefinition = {
|
|
186
|
+
database: {
|
|
187
|
+
postgres: {
|
|
188
|
+
enable: true,
|
|
189
|
+
minCapacity: 0.25, // Too low
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const result = auroraBuilder.validate(appDefinition);
|
|
195
|
+
|
|
196
|
+
expect(result.valid).toBe(false);
|
|
197
|
+
expect(result.errors.some(e => e.includes('minCapacity must be between 0.5 and 128'))).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should error when maxCapacity is out of range', () => {
|
|
201
|
+
const appDefinition = {
|
|
202
|
+
database: {
|
|
203
|
+
postgres: {
|
|
204
|
+
enable: true,
|
|
205
|
+
maxCapacity: 256, // Too high
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const result = auroraBuilder.validate(appDefinition);
|
|
211
|
+
|
|
212
|
+
expect(result.valid).toBe(false);
|
|
213
|
+
expect(result.errors.some(e => e.includes('maxCapacity must be between 0.5 and 128'))).toBe(true);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should pass with valid capacity values', () => {
|
|
217
|
+
const appDefinition = {
|
|
218
|
+
database: {
|
|
219
|
+
postgres: {
|
|
220
|
+
enable: true,
|
|
221
|
+
minCapacity: 0.5,
|
|
222
|
+
maxCapacity: 16,
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const result = auroraBuilder.validate(appDefinition);
|
|
228
|
+
|
|
229
|
+
expect(result.valid).toBe(true);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should warn about public accessibility', () => {
|
|
233
|
+
const appDefinition = {
|
|
234
|
+
database: {
|
|
235
|
+
postgres: {
|
|
236
|
+
enable: true,
|
|
237
|
+
publiclyAccessible: true,
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const result = auroraBuilder.validate(appDefinition);
|
|
243
|
+
|
|
244
|
+
expect(result.warnings.some(w => w.includes('publiclyAccessible=true is not recommended for production'))).toBe(true);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should not warn when publiclyAccessible is false', () => {
|
|
248
|
+
const appDefinition = {
|
|
249
|
+
database: {
|
|
250
|
+
postgres: {
|
|
251
|
+
enable: true,
|
|
252
|
+
publiclyAccessible: false,
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const result = auroraBuilder.validate(appDefinition);
|
|
258
|
+
|
|
259
|
+
expect(result.warnings).toEqual([]);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
describe('build() - discover mode', () => {
|
|
264
|
+
it('should use discovered database endpoint', async () => {
|
|
265
|
+
const appDefinition = {
|
|
266
|
+
database: {
|
|
267
|
+
postgres: {
|
|
268
|
+
enable: true,
|
|
269
|
+
management: 'discover',
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const discoveredResources = {
|
|
275
|
+
auroraClusterEndpoint: 'cluster.abc.us-east-1.rds.amazonaws.com',
|
|
276
|
+
auroraPort: 5432,
|
|
277
|
+
databaseSecretArn: 'arn:aws:secretsmanager:us-east-1:123:secret:db',
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
281
|
+
|
|
282
|
+
// buildDatabaseUrl returns CloudFormation Fn::Sub object, not plain string
|
|
283
|
+
expect(result.environment.DATABASE_URL).toBeDefined();
|
|
284
|
+
expect(result.environment.DATABASE_URL['Fn::Sub']).toBeDefined();
|
|
285
|
+
expect(result.environment.DATABASE_URL['Fn::Sub'][1].Host).toBe('cluster.abc.us-east-1.rds.amazonaws.com');
|
|
286
|
+
expect(result.environment.DATABASE_URL['Fn::Sub'][1].Port).toBe(5432);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should add IAM permissions for Secrets Manager', async () => {
|
|
290
|
+
const appDefinition = {
|
|
291
|
+
database: {
|
|
292
|
+
postgres: {
|
|
293
|
+
enable: true,
|
|
294
|
+
management: 'discover',
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const discoveredResources = {
|
|
300
|
+
auroraClusterEndpoint: 'cluster.abc.us-east-1.rds.amazonaws.com',
|
|
301
|
+
auroraPort: 5432,
|
|
302
|
+
databaseSecretArn: 'arn:aws:secretsmanager:us-east-1:123:secret:db',
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
306
|
+
|
|
307
|
+
const secretPermission = result.iamStatements.find(stmt =>
|
|
308
|
+
stmt.Action.includes('secretsmanager:GetSecretValue')
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
expect(secretPermission).toBeDefined();
|
|
312
|
+
expect(secretPermission.Resource).toBe('arn:aws:secretsmanager:us-east-1:123:secret:db');
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should add security group ingress rule for Lambda to Aurora connectivity', async () => {
|
|
316
|
+
const appDefinition = {
|
|
317
|
+
database: {
|
|
318
|
+
postgres: {
|
|
319
|
+
enable: true,
|
|
320
|
+
management: 'discover',
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const discoveredResources = {
|
|
326
|
+
auroraClusterEndpoint: 'cluster.abc.us-east-1.rds.amazonaws.com',
|
|
327
|
+
auroraPort: 5432,
|
|
328
|
+
auroraSecurityGroupId: 'sg-aurora123',
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
332
|
+
|
|
333
|
+
expect(result.resources.FriggAuroraIngressRule).toBeDefined();
|
|
334
|
+
expect(result.resources.FriggAuroraIngressRule.Type).toBe('AWS::EC2::SecurityGroupIngress');
|
|
335
|
+
expect(result.resources.FriggAuroraIngressRule.Properties.GroupId).toBe('sg-aurora123');
|
|
336
|
+
expect(result.resources.FriggAuroraIngressRule.Properties.IpProtocol).toBe('tcp');
|
|
337
|
+
expect(result.resources.FriggAuroraIngressRule.Properties.FromPort).toBe(5432);
|
|
338
|
+
expect(result.resources.FriggAuroraIngressRule.Properties.ToPort).toBe(5432);
|
|
339
|
+
expect(result.resources.FriggAuroraIngressRule.Properties.SourceSecurityGroupId).toEqual({ Ref: 'FriggLambdaSecurityGroup' });
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
describe('autoCreateCredentials', () => {
|
|
343
|
+
it('should create Secrets Manager secret and password rotator when autoCreateCredentials is enabled', async () => {
|
|
344
|
+
const appDefinition = {
|
|
345
|
+
database: {
|
|
346
|
+
postgres: {
|
|
347
|
+
enable: true,
|
|
348
|
+
management: 'discover',
|
|
349
|
+
autoCreateCredentials: true,
|
|
350
|
+
username: 'postgres',
|
|
351
|
+
database: 'frigg',
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const discoveredResources = {
|
|
357
|
+
auroraClusterEndpoint: 'quo-aurora-cluster.cluster-abc123.us-east-1.rds.amazonaws.com',
|
|
358
|
+
auroraPort: 5432,
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
362
|
+
|
|
363
|
+
// Check secret creation
|
|
364
|
+
expect(result.resources.FriggDBSecret).toBeDefined();
|
|
365
|
+
expect(result.resources.FriggDBSecret.Type).toBe('AWS::SecretsManager::Secret');
|
|
366
|
+
expect(result.resources.FriggDBSecret.Properties.GenerateSecretString.SecretStringTemplate).toContain('postgres');
|
|
367
|
+
expect(result.resources.FriggDBSecret.Properties.GenerateSecretString.PasswordLength).toBe(32);
|
|
368
|
+
|
|
369
|
+
// Check password rotator Lambda
|
|
370
|
+
expect(result.resources.PasswordRotatorLambda).toBeDefined();
|
|
371
|
+
expect(result.resources.PasswordRotatorLambda.Type).toBe('AWS::Lambda::Function');
|
|
372
|
+
expect(result.resources.PasswordRotatorLambda.Properties.Runtime).toBe('nodejs22.x');
|
|
373
|
+
|
|
374
|
+
// Check custom resource
|
|
375
|
+
expect(result.resources.FriggAuroraPasswordRotator).toBeDefined();
|
|
376
|
+
expect(result.resources.FriggAuroraPasswordRotator.Type).toBe('Custom::AuroraPasswordRotator');
|
|
377
|
+
expect(result.resources.FriggAuroraPasswordRotator.Properties.ClusterIdentifier).toBe('quo-aurora-cluster');
|
|
378
|
+
|
|
379
|
+
// Check IAM role
|
|
380
|
+
expect(result.resources.PasswordRotatorRole).toBeDefined();
|
|
381
|
+
expect(result.resources.PasswordRotatorRole.Type).toBe('AWS::IAM::Role');
|
|
382
|
+
|
|
383
|
+
// Check DATABASE_URL uses the secret
|
|
384
|
+
expect(result.environment.DATABASE_URL).toBeDefined();
|
|
385
|
+
expect(result.environment.DATABASE_URL['Fn::Sub']).toBeDefined();
|
|
386
|
+
|
|
387
|
+
// Username and Password should use nested Fn::Sub to resolve the Ref
|
|
388
|
+
expect(result.environment.DATABASE_URL['Fn::Sub'][1].Username['Fn::Sub']).toBeDefined();
|
|
389
|
+
expect(result.environment.DATABASE_URL['Fn::Sub'][1].Password['Fn::Sub']).toBeDefined();
|
|
390
|
+
|
|
391
|
+
// Should contain secretsmanager resolution
|
|
392
|
+
expect(result.environment.DATABASE_URL['Fn::Sub'][1].Username['Fn::Sub'][0]).toContain('resolve:secretsmanager');
|
|
393
|
+
expect(result.environment.DATABASE_URL['Fn::Sub'][1].Password['Fn::Sub'][0]).toContain('resolve:secretsmanager');
|
|
394
|
+
|
|
395
|
+
// Check IAM permissions for secret access
|
|
396
|
+
const secretPermission = result.iamStatements.find(stmt =>
|
|
397
|
+
stmt.Action.includes('secretsmanager:GetSecretValue')
|
|
398
|
+
);
|
|
399
|
+
expect(secretPermission).toBeDefined();
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('should not create credentials when autoCreateCredentials is false', async () => {
|
|
403
|
+
const appDefinition = {
|
|
404
|
+
database: {
|
|
405
|
+
postgres: {
|
|
406
|
+
enable: true,
|
|
407
|
+
management: 'discover',
|
|
408
|
+
autoCreateCredentials: false,
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const discoveredResources = {
|
|
414
|
+
auroraClusterEndpoint: 'cluster.abc.us-east-1.rds.amazonaws.com',
|
|
415
|
+
auroraPort: 5432,
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
419
|
+
|
|
420
|
+
// Should not create secret or rotator
|
|
421
|
+
expect(result.resources.FriggDBSecret).toBeUndefined();
|
|
422
|
+
expect(result.resources.PasswordRotatorLambda).toBeUndefined();
|
|
423
|
+
expect(result.resources.FriggAuroraPasswordRotator).toBeUndefined();
|
|
424
|
+
expect(result.resources.PasswordRotatorRole).toBeUndefined();
|
|
425
|
+
|
|
426
|
+
// Should set individual environment variables for flexible credential management
|
|
427
|
+
expect(result.environment.DATABASE_HOST).toBe('cluster.abc.us-east-1.rds.amazonaws.com');
|
|
428
|
+
expect(result.environment.DATABASE_PORT).toBe('5432');
|
|
429
|
+
expect(result.environment.DATABASE_NAME).toBe('frigg');
|
|
430
|
+
|
|
431
|
+
// DATABASE_URL should NOT be set (to avoid Serverless variable resolution errors)
|
|
432
|
+
// The application should construct it at runtime from DATABASE_HOST, DATABASE_PORT, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD
|
|
433
|
+
expect(result.environment.DATABASE_URL).toBeUndefined();
|
|
434
|
+
|
|
435
|
+
// DATABASE_USER and DATABASE_PASSWORD should come from appDefinition.environment
|
|
436
|
+
// and will be set by the environment-builder, not here
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should not create credentials when secret is already discovered', async () => {
|
|
440
|
+
const appDefinition = {
|
|
441
|
+
database: {
|
|
442
|
+
postgres: {
|
|
443
|
+
enable: true,
|
|
444
|
+
management: 'discover',
|
|
445
|
+
autoCreateCredentials: true, // Enabled, but secret already exists
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const discoveredResources = {
|
|
451
|
+
auroraClusterEndpoint: 'cluster.abc.us-east-1.rds.amazonaws.com',
|
|
452
|
+
auroraPort: 5432,
|
|
453
|
+
databaseSecretArn: 'arn:aws:secretsmanager:us-east-1:123:secret:existing-secret',
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
457
|
+
|
|
458
|
+
// Should use existing secret, not create new one
|
|
459
|
+
expect(result.resources.FriggDBSecret).toBeUndefined();
|
|
460
|
+
expect(result.resources.PasswordRotatorLambda).toBeUndefined();
|
|
461
|
+
expect(result.environment.DATABASE_SECRET_ARN).toBe('arn:aws:secretsmanager:us-east-1:123:secret:existing-secret');
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('should extract correct cluster identifier from endpoint', async () => {
|
|
465
|
+
const appDefinition = {
|
|
466
|
+
database: {
|
|
467
|
+
postgres: {
|
|
468
|
+
enable: true,
|
|
469
|
+
management: 'discover',
|
|
470
|
+
autoCreateCredentials: true,
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const discoveredResources = {
|
|
476
|
+
auroraClusterEndpoint: 'my-cluster-name.cluster-xyz123.us-west-2.rds.amazonaws.com',
|
|
477
|
+
auroraPort: 5432,
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
481
|
+
|
|
482
|
+
expect(result.resources.FriggAuroraPasswordRotator.Properties.ClusterIdentifier).toBe('my-cluster-name');
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('should set DATABASE_HOST, DATABASE_PORT, DATABASE_NAME when autoCreateCredentials is enabled', async () => {
|
|
486
|
+
const appDefinition = {
|
|
487
|
+
database: {
|
|
488
|
+
postgres: {
|
|
489
|
+
enable: true,
|
|
490
|
+
management: 'discover',
|
|
491
|
+
autoCreateCredentials: true,
|
|
492
|
+
database: 'mydb',
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const discoveredResources = {
|
|
498
|
+
auroraClusterEndpoint: 'cluster.abc.us-east-1.rds.amazonaws.com',
|
|
499
|
+
auroraPort: 5432,
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
503
|
+
|
|
504
|
+
expect(result.environment.DATABASE_HOST).toBe('cluster.abc.us-east-1.rds.amazonaws.com');
|
|
505
|
+
expect(result.environment.DATABASE_PORT).toBe('5432');
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('should generate valid CloudFormation ZipFile code without template literal conflicts', async () => {
|
|
509
|
+
const appDefinition = {
|
|
510
|
+
database: {
|
|
511
|
+
postgres: {
|
|
512
|
+
enable: true,
|
|
513
|
+
management: 'discover',
|
|
514
|
+
autoCreateCredentials: true,
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
const discoveredResources = {
|
|
520
|
+
auroraClusterEndpoint: 'cluster.abc.us-east-1.rds.amazonaws.com',
|
|
521
|
+
auroraPort: 5432,
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
525
|
+
|
|
526
|
+
const zipFileCode = result.resources.PasswordRotatorLambda.Properties.Code.ZipFile;
|
|
527
|
+
|
|
528
|
+
// Should not contain template literals that would conflict with CloudFormation ${} substitution
|
|
529
|
+
// CloudFormation uses ${} for parameter substitution, so we should avoid `${variable}` in ZipFile
|
|
530
|
+
expect(zipFileCode).not.toMatch(/`.*\$\{(?!env:).*\}`/); // No template literals with ${} except ${env:...}
|
|
531
|
+
|
|
532
|
+
// Should use string concatenation instead
|
|
533
|
+
expect(zipFileCode).toContain("'Successfully rotated password for cluster: ' + ClusterIdentifier");
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it('should properly handle Ref objects in buildDatabaseUrl when autoCreateCredentials is enabled', async () => {
|
|
537
|
+
const appDefinition = {
|
|
538
|
+
database: {
|
|
539
|
+
postgres: {
|
|
540
|
+
enable: true,
|
|
541
|
+
management: 'discover',
|
|
542
|
+
autoCreateCredentials: true,
|
|
543
|
+
database: 'testdb',
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
const discoveredResources = {
|
|
549
|
+
auroraClusterEndpoint: 'cluster.abc.us-east-1.rds.amazonaws.com',
|
|
550
|
+
auroraPort: 5432,
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
554
|
+
|
|
555
|
+
const dbUrl = result.environment.DATABASE_URL;
|
|
556
|
+
|
|
557
|
+
// Should use Fn::Sub with nested Fn::Sub to resolve the Ref
|
|
558
|
+
expect(dbUrl['Fn::Sub']).toBeDefined();
|
|
559
|
+
expect(dbUrl['Fn::Sub'][0]).toBe('postgresql://${Username}:${Password}@${Host}:${Port}/${Database}');
|
|
560
|
+
|
|
561
|
+
// The Username and Password should use Fn::Sub to resolve the secret Ref, not literal "[object Object]"
|
|
562
|
+
expect(dbUrl['Fn::Sub'][1].Username['Fn::Sub']).toBeDefined();
|
|
563
|
+
expect(dbUrl['Fn::Sub'][1].Password['Fn::Sub']).toBeDefined();
|
|
564
|
+
|
|
565
|
+
// Should not contain the literal string "[object Object]"
|
|
566
|
+
const jsonOutput = JSON.stringify(dbUrl);
|
|
567
|
+
expect(jsonOutput).not.toContain('[object Object]');
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it('should exclude URL-special characters from password generation for Prisma compatibility', async () => {
|
|
571
|
+
const appDefinition = {
|
|
572
|
+
database: {
|
|
573
|
+
postgres: {
|
|
574
|
+
enable: true,
|
|
575
|
+
management: 'discover',
|
|
576
|
+
autoCreateCredentials: true,
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
const discoveredResources = {
|
|
582
|
+
auroraClusterEndpoint: 'cluster.abc.us-east-1.rds.amazonaws.com',
|
|
583
|
+
auroraPort: 5432,
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
587
|
+
|
|
588
|
+
const excludeChars = result.resources.FriggDBSecret.Properties.GenerateSecretString.ExcludeCharacters;
|
|
589
|
+
|
|
590
|
+
// Must exclude URL-special characters that would break Prisma connection strings
|
|
591
|
+
// Prisma docs: https://www.prisma.io/docs/reference/database-reference/connection-urls#special-characters
|
|
592
|
+
// These characters have special meaning in URLs and must be excluded or the password must be URL-encoded
|
|
593
|
+
// Exclude: " @ : / ? # [ ] % (and \ for JSON escaping)
|
|
594
|
+
expect(excludeChars).toBe('"@:/?#[]%\\\\');
|
|
595
|
+
|
|
596
|
+
// Verify it can be JSON-stringified without errors
|
|
597
|
+
expect(() => JSON.stringify(result.resources.FriggDBSecret)).not.toThrow();
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
describe('build() - managed mode', () => {
|
|
603
|
+
it('should create Aurora cluster resources', async () => {
|
|
604
|
+
const appDefinition = {
|
|
605
|
+
database: {
|
|
606
|
+
postgres: {
|
|
607
|
+
enable: true,
|
|
608
|
+
management: 'managed',
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
const discoveredResources = {
|
|
614
|
+
defaultVpcId: 'vpc-123',
|
|
615
|
+
privateSubnetId1: 'subnet-1',
|
|
616
|
+
privateSubnetId2: 'subnet-2',
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
620
|
+
|
|
621
|
+
expect(result.resources.FriggAuroraCluster).toBeDefined();
|
|
622
|
+
expect(result.resources.FriggAuroraCluster.Type).toBe('AWS::RDS::DBCluster');
|
|
623
|
+
|
|
624
|
+
// PubliclyAccessible is NOT supported on Aurora clusters (only on instances)
|
|
625
|
+
expect(result.resources.FriggAuroraCluster.Properties.PubliclyAccessible).toBeUndefined();
|
|
626
|
+
|
|
627
|
+
// Port should be explicitly set to PostgreSQL standard (5432)
|
|
628
|
+
expect(result.resources.FriggAuroraCluster.Properties.Port).toBe(5432);
|
|
629
|
+
|
|
630
|
+
// Should create self-referencing security group ingress rule
|
|
631
|
+
expect(result.resources.FriggAuroraIngressRule).toBeDefined();
|
|
632
|
+
expect(result.resources.FriggAuroraIngressRule.Type).toBe('AWS::EC2::SecurityGroupIngress');
|
|
633
|
+
expect(result.resources.FriggAuroraIngressRule.Properties.FromPort).toBe(5432);
|
|
634
|
+
expect(result.resources.FriggAuroraIngressRule.Properties.ToPort).toBe(5432);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
it('should create database subnet group', async () => {
|
|
638
|
+
const appDefinition = {
|
|
639
|
+
database: {
|
|
640
|
+
postgres: {
|
|
641
|
+
enable: true,
|
|
642
|
+
management: 'managed',
|
|
643
|
+
},
|
|
644
|
+
},
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
const discoveredResources = {
|
|
648
|
+
defaultVpcId: 'vpc-123',
|
|
649
|
+
privateSubnetId1: 'subnet-1',
|
|
650
|
+
privateSubnetId2: 'subnet-2',
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
654
|
+
|
|
655
|
+
expect(result.resources.FriggDBSubnetGroup).toBeDefined();
|
|
656
|
+
expect(result.resources.FriggDBSubnetGroup.Type).toBe('AWS::RDS::DBSubnetGroup');
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it('should create Secrets Manager secret for credentials', async () => {
|
|
660
|
+
const appDefinition = {
|
|
661
|
+
database: {
|
|
662
|
+
postgres: {
|
|
663
|
+
enable: true,
|
|
664
|
+
management: 'managed',
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
const discoveredResources = {
|
|
670
|
+
defaultVpcId: 'vpc-123',
|
|
671
|
+
privateSubnetId1: 'subnet-1',
|
|
672
|
+
privateSubnetId2: 'subnet-2',
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
676
|
+
|
|
677
|
+
expect(result.resources.FriggDBSecret).toBeDefined();
|
|
678
|
+
expect(result.resources.FriggDBSecret.Type).toBe('AWS::SecretsManager::Secret');
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
it('should configure Aurora Serverless v2', async () => {
|
|
682
|
+
const appDefinition = {
|
|
683
|
+
database: {
|
|
684
|
+
postgres: {
|
|
685
|
+
enable: true,
|
|
686
|
+
management: 'managed',
|
|
687
|
+
},
|
|
688
|
+
},
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
const discoveredResources = {
|
|
692
|
+
defaultVpcId: 'vpc-123',
|
|
693
|
+
privateSubnetId1: 'subnet-1',
|
|
694
|
+
privateSubnetId2: 'subnet-2',
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
698
|
+
|
|
699
|
+
expect(result.resources.FriggAuroraCluster.Properties.EngineMode).toBe('provisioned');
|
|
700
|
+
expect(result.resources.FriggAuroraCluster.Properties.ServerlessV2ScalingConfiguration).toBeDefined();
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it('should use custom capacity settings', async () => {
|
|
704
|
+
const appDefinition = {
|
|
705
|
+
database: {
|
|
706
|
+
postgres: {
|
|
707
|
+
enable: true,
|
|
708
|
+
management: 'managed',
|
|
709
|
+
minCapacity: 1,
|
|
710
|
+
maxCapacity: 8,
|
|
711
|
+
},
|
|
712
|
+
},
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
const discoveredResources = {
|
|
716
|
+
defaultVpcId: 'vpc-123',
|
|
717
|
+
privateSubnetId1: 'subnet-1',
|
|
718
|
+
privateSubnetId2: 'subnet-2',
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
722
|
+
|
|
723
|
+
const scaling = result.resources.FriggAuroraCluster.Properties.ServerlessV2ScalingConfiguration;
|
|
724
|
+
expect(scaling.MinCapacity).toBe(1);
|
|
725
|
+
expect(scaling.MaxCapacity).toBe(8);
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
it('should default to sensible capacity values', async () => {
|
|
729
|
+
const appDefinition = {
|
|
730
|
+
database: {
|
|
731
|
+
postgres: {
|
|
732
|
+
enable: true,
|
|
733
|
+
management: 'managed',
|
|
734
|
+
},
|
|
735
|
+
},
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
const discoveredResources = {
|
|
739
|
+
defaultVpcId: 'vpc-123',
|
|
740
|
+
privateSubnetId1: 'subnet-1',
|
|
741
|
+
privateSubnetId2: 'subnet-2',
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
745
|
+
|
|
746
|
+
const scaling = result.resources.FriggAuroraCluster.Properties.ServerlessV2ScalingConfiguration;
|
|
747
|
+
expect(scaling.MinCapacity).toBeGreaterThanOrEqual(0.5);
|
|
748
|
+
expect(scaling.MaxCapacity).toBeLessThanOrEqual(128);
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
describe('Top-Level Management Mode', () => {
|
|
753
|
+
it('should reuse stack Aurora when managementMode=managed + vpcIsolation=isolated AND stack has Aurora', async () => {
|
|
754
|
+
const appDefinition = {
|
|
755
|
+
managementMode: 'managed',
|
|
756
|
+
vpcIsolation: 'isolated',
|
|
757
|
+
database: {
|
|
758
|
+
postgres: {
|
|
759
|
+
enable: true,
|
|
760
|
+
management: 'managed', // Should be IGNORED
|
|
761
|
+
minCapacity: 0.5,
|
|
762
|
+
maxCapacity: 1,
|
|
763
|
+
},
|
|
764
|
+
},
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
// CloudFormation stack has Aurora (from previous deployment of this stage)
|
|
768
|
+
const discoveredResources = {
|
|
769
|
+
auroraClusterId: 'stack-cluster-dev', // CloudFormation discovery sets this
|
|
770
|
+
auroraClusterEndpoint: 'stack-cluster-dev.us-east-1.rds.amazonaws.com', // For discover mode
|
|
771
|
+
auroraClusterPort: 5432,
|
|
772
|
+
auroraClusterIdentifier: 'stack-cluster-dev',
|
|
773
|
+
privateSubnetId1: 'subnet-1',
|
|
774
|
+
privateSubnetId2: 'subnet-2',
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
778
|
+
|
|
779
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
780
|
+
|
|
781
|
+
// Should warn about ignored options
|
|
782
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
783
|
+
expect.stringContaining("managementMode='managed' ignoring")
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
// Should log reusing stack Aurora
|
|
787
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
788
|
+
expect.stringContaining("stack has Aurora, reusing")
|
|
789
|
+
);
|
|
790
|
+
|
|
791
|
+
// Should keep Aurora definitions in template (CloudFormation idempotency)
|
|
792
|
+
// Even though Aurora exists in stack, we include definitions - CF won't recreate
|
|
793
|
+
expect(result.resources.FriggAuroraCluster).toBeDefined();
|
|
794
|
+
expect(result.resources.FriggAuroraCluster.Type).toBe('AWS::RDS::DBCluster');
|
|
795
|
+
expect(result.resources.FriggAuroraInstance).toBeDefined();
|
|
796
|
+
expect(result.resources.FriggAuroraInstance.Type).toBe('AWS::RDS::DBInstance');
|
|
797
|
+
expect(result.environment.DATABASE_URL).toBeDefined();
|
|
798
|
+
|
|
799
|
+
consoleLogSpy.mockRestore();
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
it('should create new Aurora when managementMode=managed + vpcIsolation=isolated AND stack has NO Aurora', async () => {
|
|
803
|
+
const appDefinition = {
|
|
804
|
+
managementMode: 'managed',
|
|
805
|
+
vpcIsolation: 'isolated',
|
|
806
|
+
database: {
|
|
807
|
+
postgres: {
|
|
808
|
+
enable: true,
|
|
809
|
+
management: 'discover', // Should be IGNORED
|
|
810
|
+
minCapacity: 0.5,
|
|
811
|
+
maxCapacity: 1,
|
|
812
|
+
},
|
|
813
|
+
},
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
// No Aurora in CloudFormation stack (fresh deployment)
|
|
817
|
+
const discoveredResources = {
|
|
818
|
+
privateSubnetId1: 'subnet-1',
|
|
819
|
+
privateSubnetId2: 'subnet-2',
|
|
820
|
+
// No auroraEndpoint
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
824
|
+
|
|
825
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
826
|
+
|
|
827
|
+
// Should warn about ignored options
|
|
828
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
829
|
+
expect.stringContaining("managementMode='managed' ignoring")
|
|
830
|
+
);
|
|
831
|
+
|
|
832
|
+
// Should log creating new Aurora
|
|
833
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
834
|
+
expect.stringContaining("no stack Aurora, creating new")
|
|
835
|
+
);
|
|
836
|
+
|
|
837
|
+
// Should create new Aurora cluster (isolated mode)
|
|
838
|
+
expect(result.resources.FriggAuroraCluster).toBeDefined();
|
|
839
|
+
expect(result.environment.DATABASE_URL).toBeDefined();
|
|
840
|
+
|
|
841
|
+
consoleLogSpy.mockRestore();
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
it('should use managementMode=managed with vpcIsolation=shared to discover Aurora', async () => {
|
|
845
|
+
const appDefinition = {
|
|
846
|
+
managementMode: 'managed',
|
|
847
|
+
vpcIsolation: 'shared',
|
|
848
|
+
database: {
|
|
849
|
+
postgres: {
|
|
850
|
+
enable: true,
|
|
851
|
+
management: 'managed', // Should be IGNORED
|
|
852
|
+
},
|
|
853
|
+
},
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
const discoveredResources = {
|
|
857
|
+
auroraClusterEndpoint: 'existing-cluster.us-east-1.rds.amazonaws.com',
|
|
858
|
+
auroraClusterPort: 5432,
|
|
859
|
+
auroraClusterIdentifier: 'existing-cluster',
|
|
860
|
+
databaseSecretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:shared-db-secret',
|
|
861
|
+
privateSubnetId1: 'subnet-1',
|
|
862
|
+
privateSubnetId2: 'subnet-2',
|
|
863
|
+
};
|
|
864
|
+
|
|
865
|
+
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
866
|
+
|
|
867
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
868
|
+
|
|
869
|
+
// Should warn about ignored options
|
|
870
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
871
|
+
expect.stringContaining("ignoring")
|
|
872
|
+
);
|
|
873
|
+
|
|
874
|
+
// Should discover existing Aurora
|
|
875
|
+
expect(result.resources.FriggAuroraCluster).toBeUndefined();
|
|
876
|
+
expect(result.environment.DATABASE_URL).toBeDefined();
|
|
877
|
+
|
|
878
|
+
consoleLogSpy.mockRestore();
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
it('should respect granular management when no managementMode specified', async () => {
|
|
882
|
+
const appDefinition = {
|
|
883
|
+
// No managementMode
|
|
884
|
+
database: {
|
|
885
|
+
postgres: {
|
|
886
|
+
enable: true,
|
|
887
|
+
management: 'managed', // Should be RESPECTED
|
|
888
|
+
},
|
|
889
|
+
},
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
const discoveredResources = {
|
|
893
|
+
privateSubnetId1: 'subnet-1',
|
|
894
|
+
privateSubnetId2: 'subnet-2',
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
const result = await auroraBuilder.build(appDefinition, discoveredResources);
|
|
898
|
+
|
|
899
|
+
// Should create Aurora cluster
|
|
900
|
+
expect(result.resources.FriggAuroraCluster).toBeDefined();
|
|
901
|
+
});
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
describe('build() - use-existing mode', () => {
|
|
905
|
+
it('should use provided database endpoint', async () => {
|
|
906
|
+
const appDefinition = {
|
|
907
|
+
database: {
|
|
908
|
+
postgres: {
|
|
909
|
+
enable: true,
|
|
910
|
+
management: 'use-existing',
|
|
911
|
+
endpoint: 'custom-db.example.com',
|
|
912
|
+
port: 5432,
|
|
913
|
+
},
|
|
914
|
+
},
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
const result = await auroraBuilder.build(appDefinition, {});
|
|
918
|
+
|
|
919
|
+
// use-existing mode sets individual components, not DATABASE_URL
|
|
920
|
+
expect(result.environment.DATABASE_HOST).toBe('custom-db.example.com');
|
|
921
|
+
expect(result.environment.DATABASE_PORT).toBe('5432');
|
|
922
|
+
expect(result.environment.DATABASE_NAME).toBe('frigg');
|
|
923
|
+
expect(result.environment.DATABASE_USER).toBe('postgres');
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
it('should not create Aurora resources in use-existing mode', async () => {
|
|
927
|
+
const appDefinition = {
|
|
928
|
+
database: {
|
|
929
|
+
postgres: {
|
|
930
|
+
enable: true,
|
|
931
|
+
management: 'use-existing',
|
|
932
|
+
endpoint: 'db.example.com',
|
|
933
|
+
},
|
|
934
|
+
},
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
const result = await auroraBuilder.build(appDefinition, {});
|
|
938
|
+
|
|
939
|
+
expect(result.resources.FriggAuroraCluster).toBeUndefined();
|
|
940
|
+
expect(result.resources.FriggDBSecret).toBeUndefined();
|
|
941
|
+
});
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
describe('getName()', () => {
|
|
945
|
+
it('should return AuroraBuilder', () => {
|
|
946
|
+
expect(auroraBuilder.getName()).toBe('AuroraBuilder');
|
|
947
|
+
});
|
|
948
|
+
});
|
|
949
|
+
});
|
|
950
|
+
|