@friggframework/devtools 2.0.0-next.61 → 2.0.0-next.62
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/package.json +14 -7
- package/.eslintrc.json +0 -3
- package/CHANGELOG.md +0 -132
- package/infrastructure/ARCHITECTURE.md +0 -487
- package/infrastructure/CLAUDE.md +0 -481
- package/infrastructure/HEALTH.md +0 -468
- package/infrastructure/README.md +0 -522
- package/infrastructure/__tests__/fixtures/mock-aws-resources.js +0 -391
- package/infrastructure/__tests__/helpers/test-utils.js +0 -277
- package/infrastructure/__tests__/postgres-config.test.js +0 -914
- package/infrastructure/__tests__/template-generation.test.js +0 -687
- package/infrastructure/create-frigg-infrastructure.js +0 -147
- package/infrastructure/docs/POSTGRES-CONFIGURATION.md +0 -630
- package/infrastructure/docs/PRE-DEPLOYMENT-HEALTH-CHECK-SPEC.md +0 -1317
- package/infrastructure/docs/WEBSOCKET-CONFIGURATION.md +0 -105
- package/infrastructure/docs/deployment-instructions.md +0 -268
- package/infrastructure/docs/generate-iam-command.md +0 -278
- package/infrastructure/docs/iam-policy-templates.md +0 -193
- package/infrastructure/domains/database/aurora-builder.js +0 -809
- package/infrastructure/domains/database/aurora-builder.test.js +0 -950
- package/infrastructure/domains/database/aurora-discovery.js +0 -87
- package/infrastructure/domains/database/aurora-discovery.test.js +0 -188
- package/infrastructure/domains/database/aurora-resolver.js +0 -210
- package/infrastructure/domains/database/aurora-resolver.test.js +0 -347
- package/infrastructure/domains/database/migration-builder.js +0 -701
- package/infrastructure/domains/database/migration-builder.test.js +0 -321
- package/infrastructure/domains/database/migration-resolver.js +0 -163
- package/infrastructure/domains/database/migration-resolver.test.js +0 -337
- package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +0 -164
- package/infrastructure/domains/health/application/ports/IResourceDetector.js +0 -129
- package/infrastructure/domains/health/application/ports/IResourceImporter.js +0 -142
- package/infrastructure/domains/health/application/ports/IStackRepository.js +0 -131
- package/infrastructure/domains/health/application/ports/index.js +0 -26
- package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +0 -679
- package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +0 -167
- package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +0 -1130
- package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +0 -221
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +0 -152
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +0 -343
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +0 -535
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +0 -376
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +0 -213
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +0 -441
- package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +0 -267
- package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +0 -324
- package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +0 -386
- package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +0 -1419
- package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +0 -391
- package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +0 -551
- package/infrastructure/domains/health/domain/entities/issue.js +0 -299
- package/infrastructure/domains/health/domain/entities/issue.test.js +0 -528
- package/infrastructure/domains/health/domain/entities/property-mismatch.js +0 -108
- package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +0 -275
- package/infrastructure/domains/health/domain/entities/resource.js +0 -159
- package/infrastructure/domains/health/domain/entities/resource.test.js +0 -432
- package/infrastructure/domains/health/domain/entities/stack-health-report.js +0 -306
- package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +0 -601
- package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +0 -380
- package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +0 -971
- package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +0 -1150
- package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +0 -672
- package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +0 -496
- package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +0 -419
- package/infrastructure/domains/health/domain/services/health-score-calculator.js +0 -248
- package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +0 -504
- package/infrastructure/domains/health/domain/services/import-progress-monitor.js +0 -195
- package/infrastructure/domains/health/domain/services/import-template-generator.js +0 -435
- package/infrastructure/domains/health/domain/services/logical-id-mapper.js +0 -345
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +0 -234
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +0 -431
- package/infrastructure/domains/health/domain/services/property-mutability-config.js +0 -382
- package/infrastructure/domains/health/domain/services/template-parser.js +0 -245
- package/infrastructure/domains/health/domain/services/update-progress-monitor.js +0 -192
- package/infrastructure/domains/health/domain/value-objects/health-score.js +0 -138
- package/infrastructure/domains/health/domain/value-objects/health-score.test.js +0 -267
- package/infrastructure/domains/health/domain/value-objects/property-mutability.js +0 -161
- package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +0 -198
- package/infrastructure/domains/health/domain/value-objects/resource-state.js +0 -167
- package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +0 -196
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +0 -192
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +0 -262
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +0 -312
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +0 -367
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +0 -432
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +0 -784
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +0 -1133
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +0 -565
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +0 -554
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +0 -318
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +0 -398
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +0 -777
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +0 -580
- package/infrastructure/domains/integration/integration-builder.js +0 -404
- package/infrastructure/domains/integration/integration-builder.test.js +0 -690
- package/infrastructure/domains/integration/integration-resolver.js +0 -170
- package/infrastructure/domains/integration/integration-resolver.test.js +0 -369
- package/infrastructure/domains/integration/websocket-builder.js +0 -69
- package/infrastructure/domains/integration/websocket-builder.test.js +0 -195
- package/infrastructure/domains/networking/vpc-builder.js +0 -2051
- package/infrastructure/domains/networking/vpc-builder.test.js +0 -1960
- package/infrastructure/domains/networking/vpc-discovery.js +0 -177
- package/infrastructure/domains/networking/vpc-discovery.test.js +0 -350
- package/infrastructure/domains/networking/vpc-resolver.js +0 -505
- package/infrastructure/domains/networking/vpc-resolver.test.js +0 -801
- package/infrastructure/domains/parameters/ssm-builder.js +0 -79
- package/infrastructure/domains/parameters/ssm-builder.test.js +0 -189
- package/infrastructure/domains/parameters/ssm-discovery.js +0 -84
- package/infrastructure/domains/parameters/ssm-discovery.test.js +0 -210
- package/infrastructure/domains/security/iam-generator.js +0 -816
- package/infrastructure/domains/security/iam-generator.test.js +0 -204
- package/infrastructure/domains/security/kms-builder.js +0 -415
- package/infrastructure/domains/security/kms-builder.test.js +0 -392
- package/infrastructure/domains/security/kms-discovery.js +0 -80
- package/infrastructure/domains/security/kms-discovery.test.js +0 -177
- package/infrastructure/domains/security/kms-resolver.js +0 -96
- package/infrastructure/domains/security/kms-resolver.test.js +0 -216
- package/infrastructure/domains/security/templates/frigg-deployment-iam-stack.yaml +0 -401
- package/infrastructure/domains/security/templates/iam-policy-basic.json +0 -218
- package/infrastructure/domains/security/templates/iam-policy-full.json +0 -288
- package/infrastructure/domains/shared/base-builder.js +0 -112
- package/infrastructure/domains/shared/base-resolver.js +0 -186
- package/infrastructure/domains/shared/base-resolver.test.js +0 -305
- package/infrastructure/domains/shared/builder-orchestrator.js +0 -212
- package/infrastructure/domains/shared/builder-orchestrator.test.js +0 -213
- package/infrastructure/domains/shared/cloudformation-discovery-v2.js +0 -334
- package/infrastructure/domains/shared/cloudformation-discovery.js +0 -672
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +0 -985
- package/infrastructure/domains/shared/environment-builder.js +0 -119
- package/infrastructure/domains/shared/environment-builder.test.js +0 -247
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +0 -579
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +0 -416
- package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +0 -93
- package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +0 -136
- package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +0 -82
- package/infrastructure/domains/shared/providers/provider-factory.js +0 -108
- package/infrastructure/domains/shared/providers/provider-factory.test.js +0 -170
- package/infrastructure/domains/shared/resource-discovery.enhanced.test.js +0 -306
- package/infrastructure/domains/shared/resource-discovery.js +0 -233
- package/infrastructure/domains/shared/resource-discovery.test.js +0 -588
- package/infrastructure/domains/shared/types/app-definition.js +0 -205
- package/infrastructure/domains/shared/types/discovery-result.js +0 -106
- package/infrastructure/domains/shared/types/discovery-result.test.js +0 -258
- package/infrastructure/domains/shared/types/index.js +0 -46
- package/infrastructure/domains/shared/types/resource-ownership.js +0 -108
- package/infrastructure/domains/shared/types/resource-ownership.test.js +0 -101
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +0 -394
- package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +0 -338
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +0 -291
- package/infrastructure/domains/shared/utilities/handler-path-resolver.js +0 -134
- package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +0 -268
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +0 -159
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +0 -444
- package/infrastructure/domains/shared/validation/env-validator.js +0 -78
- package/infrastructure/domains/shared/validation/env-validator.test.js +0 -173
- package/infrastructure/domains/shared/validation/plugin-validator.js +0 -187
- package/infrastructure/domains/shared/validation/plugin-validator.test.js +0 -323
- package/infrastructure/esbuild.config.js +0 -53
- package/infrastructure/index.js +0 -4
- package/infrastructure/infrastructure-composer.js +0 -117
- package/infrastructure/infrastructure-composer.test.js +0 -1895
- package/infrastructure/integration.test.js +0 -383
- package/infrastructure/scripts/build-prisma-layer.js +0 -701
- package/infrastructure/scripts/build-prisma-layer.test.js +0 -170
- package/infrastructure/scripts/build-time-discovery.js +0 -238
- package/infrastructure/scripts/build-time-discovery.test.js +0 -379
- package/infrastructure/scripts/run-discovery.js +0 -110
- package/infrastructure/scripts/verify-prisma-layer.js +0 -72
- package/layers/prisma/.build-complete +0 -3
- package/layers/prisma/nodejs/package.json +0 -8
- package/management-ui/.eslintrc.js +0 -22
- package/management-ui/components.json +0 -21
- package/management-ui/docs/phase2-integration-guide.md +0 -320
- package/management-ui/index.html +0 -13
- package/management-ui/package.json +0 -76
- package/management-ui/packages/devtools/frigg-cli/ui-command/index.js +0 -302
- package/management-ui/postcss.config.js +0 -6
- package/management-ui/server/api/backend.js +0 -256
- package/management-ui/server/api/cli.js +0 -315
- package/management-ui/server/api/codegen.js +0 -663
- package/management-ui/server/api/connections.js +0 -857
- package/management-ui/server/api/discovery.js +0 -185
- package/management-ui/server/api/environment/index.js +0 -1
- package/management-ui/server/api/environment/router.js +0 -378
- package/management-ui/server/api/environment.js +0 -328
- package/management-ui/server/api/integrations.js +0 -876
- package/management-ui/server/api/logs.js +0 -248
- package/management-ui/server/api/monitoring.js +0 -282
- package/management-ui/server/api/open-ide.js +0 -31
- package/management-ui/server/api/project.js +0 -1029
- package/management-ui/server/api/users/sessions.js +0 -371
- package/management-ui/server/api/users/simulation.js +0 -254
- package/management-ui/server/api/users.js +0 -362
- package/management-ui/server/api-contract.md +0 -275
- package/management-ui/server/index.js +0 -873
- package/management-ui/server/middleware/errorHandler.js +0 -93
- package/management-ui/server/middleware/security.js +0 -32
- package/management-ui/server/processManager.js +0 -296
- package/management-ui/server/server.js +0 -346
- package/management-ui/server/services/aws-monitor.js +0 -413
- package/management-ui/server/services/npm-registry.js +0 -347
- package/management-ui/server/services/template-engine.js +0 -538
- package/management-ui/server/utils/cliIntegration.js +0 -220
- package/management-ui/server/utils/environment/auditLogger.js +0 -471
- package/management-ui/server/utils/environment/awsParameterStore.js +0 -275
- package/management-ui/server/utils/environment/encryption.js +0 -278
- package/management-ui/server/utils/environment/envFileManager.js +0 -286
- package/management-ui/server/utils/import-commonjs.js +0 -28
- package/management-ui/server/utils/response.js +0 -83
- package/management-ui/server/websocket/handler.js +0 -325
- package/management-ui/src/App.jsx +0 -25
- package/management-ui/src/assets/FriggLogo.svg +0 -1
- package/management-ui/src/components/AppRouter.jsx +0 -65
- package/management-ui/src/components/Button.jsx +0 -70
- package/management-ui/src/components/Card.jsx +0 -97
- package/management-ui/src/components/EnvironmentCompare.jsx +0 -400
- package/management-ui/src/components/EnvironmentEditor.jsx +0 -372
- package/management-ui/src/components/EnvironmentImportExport.jsx +0 -469
- package/management-ui/src/components/EnvironmentSchema.jsx +0 -491
- package/management-ui/src/components/EnvironmentSecurity.jsx +0 -463
- package/management-ui/src/components/ErrorBoundary.jsx +0 -73
- package/management-ui/src/components/IntegrationCard.jsx +0 -481
- package/management-ui/src/components/IntegrationCardEnhanced.jsx +0 -770
- package/management-ui/src/components/IntegrationExplorer.jsx +0 -379
- package/management-ui/src/components/IntegrationStatus.jsx +0 -336
- package/management-ui/src/components/Layout.jsx +0 -716
- package/management-ui/src/components/LoadingSpinner.jsx +0 -113
- package/management-ui/src/components/RepositoryPicker.jsx +0 -248
- package/management-ui/src/components/SessionMonitor.jsx +0 -350
- package/management-ui/src/components/StatusBadge.jsx +0 -208
- package/management-ui/src/components/UserContextSwitcher.jsx +0 -212
- package/management-ui/src/components/UserSimulation.jsx +0 -327
- package/management-ui/src/components/Welcome.jsx +0 -434
- package/management-ui/src/components/codegen/APIEndpointGenerator.jsx +0 -637
- package/management-ui/src/components/codegen/APIModuleSelector.jsx +0 -227
- package/management-ui/src/components/codegen/CodeGenerationWizard.jsx +0 -247
- package/management-ui/src/components/codegen/CodePreviewEditor.jsx +0 -316
- package/management-ui/src/components/codegen/DynamicModuleForm.jsx +0 -271
- package/management-ui/src/components/codegen/FormBuilder.jsx +0 -737
- package/management-ui/src/components/codegen/IntegrationGenerator.jsx +0 -855
- package/management-ui/src/components/codegen/ProjectScaffoldWizard.jsx +0 -797
- package/management-ui/src/components/codegen/SchemaBuilder.jsx +0 -303
- package/management-ui/src/components/codegen/TemplateSelector.jsx +0 -586
- package/management-ui/src/components/codegen/index.js +0 -10
- package/management-ui/src/components/connections/ConnectionConfigForm.jsx +0 -362
- package/management-ui/src/components/connections/ConnectionHealthMonitor.jsx +0 -182
- package/management-ui/src/components/connections/ConnectionTester.jsx +0 -200
- package/management-ui/src/components/connections/EntityRelationshipMapper.jsx +0 -292
- package/management-ui/src/components/connections/OAuthFlow.jsx +0 -204
- package/management-ui/src/components/connections/index.js +0 -5
- package/management-ui/src/components/index.js +0 -21
- package/management-ui/src/components/monitoring/APIGatewayMetrics.jsx +0 -222
- package/management-ui/src/components/monitoring/LambdaMetrics.jsx +0 -169
- package/management-ui/src/components/monitoring/MetricsChart.jsx +0 -197
- package/management-ui/src/components/monitoring/MonitoringDashboard.jsx +0 -393
- package/management-ui/src/components/monitoring/SQSMetrics.jsx +0 -246
- package/management-ui/src/components/monitoring/index.js +0 -6
- package/management-ui/src/components/monitoring/monitoring.css +0 -218
- package/management-ui/src/components/theme-provider.jsx +0 -52
- package/management-ui/src/components/theme-toggle.jsx +0 -39
- package/management-ui/src/components/ui/badge.tsx +0 -36
- package/management-ui/src/components/ui/button.test.jsx +0 -56
- package/management-ui/src/components/ui/button.tsx +0 -57
- package/management-ui/src/components/ui/card.tsx +0 -76
- package/management-ui/src/components/ui/dropdown-menu.tsx +0 -199
- package/management-ui/src/components/ui/select.tsx +0 -157
- package/management-ui/src/components/ui/skeleton.jsx +0 -15
- package/management-ui/src/hooks/useFrigg.jsx +0 -387
- package/management-ui/src/hooks/useSocket.jsx +0 -58
- package/management-ui/src/index.css +0 -193
- package/management-ui/src/lib/utils.ts +0 -6
- package/management-ui/src/main.jsx +0 -10
- package/management-ui/src/pages/CodeGeneration.jsx +0 -14
- package/management-ui/src/pages/Connections.jsx +0 -252
- package/management-ui/src/pages/ConnectionsEnhanced.jsx +0 -633
- package/management-ui/src/pages/Dashboard.jsx +0 -311
- package/management-ui/src/pages/Environment.jsx +0 -314
- package/management-ui/src/pages/IntegrationConfigure.jsx +0 -669
- package/management-ui/src/pages/IntegrationDiscovery.jsx +0 -567
- package/management-ui/src/pages/IntegrationTest.jsx +0 -742
- package/management-ui/src/pages/Integrations.jsx +0 -253
- package/management-ui/src/pages/Monitoring.jsx +0 -17
- package/management-ui/src/pages/Simulation.jsx +0 -155
- package/management-ui/src/pages/Users.jsx +0 -492
- package/management-ui/src/services/api.js +0 -41
- package/management-ui/src/services/apiModuleService.js +0 -193
- package/management-ui/src/services/websocket-handlers.js +0 -120
- package/management-ui/src/test/api/project.test.js +0 -273
- package/management-ui/src/test/components/Welcome.test.jsx +0 -378
- package/management-ui/src/test/mocks/server.js +0 -178
- package/management-ui/src/test/setup.js +0 -61
- package/management-ui/src/test/utils/test-utils.jsx +0 -134
- package/management-ui/src/utils/repository.js +0 -98
- package/management-ui/src/utils/repository.test.js +0 -118
- package/management-ui/src/workflows/phase2-integration-workflows.js +0 -884
- package/management-ui/tailwind.config.js +0 -63
- package/management-ui/tsconfig.json +0 -37
- package/management-ui/tsconfig.node.json +0 -10
- package/management-ui/vite.config.js +0 -26
- package/management-ui/vitest.config.js +0 -38
- package/test/auther-definition-method-tester.js +0 -45
- package/test/index.js +0 -9
- package/test/integration-validator.js +0 -2
- package/test/mock-api-readme.md +0 -102
- package/test/mock-api.js +0 -284
- package/test/mock-integration.js +0 -78
|
@@ -1,1960 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for VPC Builder
|
|
3
|
-
*
|
|
4
|
-
* Tests VPC infrastructure building with various management modes
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const { VpcBuilder } = require('./vpc-builder');
|
|
8
|
-
const { ValidationResult } = require('../shared/base-builder');
|
|
9
|
-
|
|
10
|
-
describe('VpcBuilder', () => {
|
|
11
|
-
let vpcBuilder;
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
vpcBuilder = new VpcBuilder();
|
|
15
|
-
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe('shouldExecute()', () => {
|
|
23
|
-
it('should return true when VPC is enabled', () => {
|
|
24
|
-
const appDefinition = {
|
|
25
|
-
vpc: { enable: true },
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
expect(vpcBuilder.shouldExecute(appDefinition)).toBe(true);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('should return false when VPC is disabled', () => {
|
|
32
|
-
const appDefinition = {
|
|
33
|
-
vpc: { enable: false },
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
expect(vpcBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('should return false when VPC is not defined', () => {
|
|
40
|
-
const appDefinition = {};
|
|
41
|
-
|
|
42
|
-
expect(vpcBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should return false when FRIGG_SKIP_AWS_DISCOVERY is set (local mode)', () => {
|
|
46
|
-
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
47
|
-
const appDefinition = {
|
|
48
|
-
vpc: { enable: true },
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
expect(vpcBuilder.shouldExecute(appDefinition)).toBe(false);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe('validate()', () => {
|
|
56
|
-
it('should pass validation for valid discover mode config', () => {
|
|
57
|
-
const appDefinition = {
|
|
58
|
-
vpc: {
|
|
59
|
-
enable: true,
|
|
60
|
-
management: 'discover',
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const result = vpcBuilder.validate(appDefinition);
|
|
65
|
-
|
|
66
|
-
expect(result).toBeInstanceOf(ValidationResult);
|
|
67
|
-
expect(result.valid).toBe(true);
|
|
68
|
-
expect(result.errors).toEqual([]);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('should pass validation for valid create-new mode config', () => {
|
|
72
|
-
const appDefinition = {
|
|
73
|
-
vpc: {
|
|
74
|
-
enable: true,
|
|
75
|
-
management: 'create-new',
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const result = vpcBuilder.validate(appDefinition);
|
|
80
|
-
|
|
81
|
-
expect(result.valid).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('should pass validation for valid use-existing mode with vpcId', () => {
|
|
85
|
-
const appDefinition = {
|
|
86
|
-
vpc: {
|
|
87
|
-
enable: true,
|
|
88
|
-
management: 'use-existing',
|
|
89
|
-
vpcId: 'vpc-123456',
|
|
90
|
-
securityGroupIds: ['sg-123'],
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const result = vpcBuilder.validate(appDefinition);
|
|
95
|
-
|
|
96
|
-
expect(result.valid).toBe(true);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('should error if VPC configuration is missing', () => {
|
|
100
|
-
const appDefinition = {};
|
|
101
|
-
|
|
102
|
-
const result = vpcBuilder.validate(appDefinition);
|
|
103
|
-
|
|
104
|
-
expect(result.valid).toBe(false);
|
|
105
|
-
expect(result.errors).toContain('VPC configuration is missing');
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('should error for invalid management mode', () => {
|
|
109
|
-
const appDefinition = {
|
|
110
|
-
vpc: {
|
|
111
|
-
enable: true,
|
|
112
|
-
management: 'invalid-mode',
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const result = vpcBuilder.validate(appDefinition);
|
|
117
|
-
|
|
118
|
-
expect(result.valid).toBe(false);
|
|
119
|
-
expect(result.errors.some(err => err.includes('Invalid vpc.management'))).toBe(true);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('should error when use-existing mode without vpcId', () => {
|
|
123
|
-
const appDefinition = {
|
|
124
|
-
vpc: {
|
|
125
|
-
enable: true,
|
|
126
|
-
management: 'use-existing',
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const result = vpcBuilder.validate(appDefinition);
|
|
131
|
-
|
|
132
|
-
expect(result.valid).toBe(false);
|
|
133
|
-
expect(result.errors).toContain(
|
|
134
|
-
'vpc.vpcId is required when management="use-existing"'
|
|
135
|
-
);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('should warn when use-existing mode without security groups', () => {
|
|
139
|
-
const appDefinition = {
|
|
140
|
-
vpc: {
|
|
141
|
-
enable: true,
|
|
142
|
-
management: 'use-existing',
|
|
143
|
-
vpcId: 'vpc-123',
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const result = vpcBuilder.validate(appDefinition);
|
|
148
|
-
|
|
149
|
-
expect(result.warnings.some(warn => warn.includes('securityGroupIds not provided'))).toBe(true);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('should error for invalid CIDR block format', () => {
|
|
153
|
-
const appDefinition = {
|
|
154
|
-
vpc: {
|
|
155
|
-
enable: true,
|
|
156
|
-
cidrBlock: 'invalid-cidr',
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const result = vpcBuilder.validate(appDefinition);
|
|
161
|
-
|
|
162
|
-
expect(result.valid).toBe(false);
|
|
163
|
-
expect(result.errors.some(err => err.includes('Invalid CIDR block format'))).toBe(true);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('should accept valid CIDR block formats', () => {
|
|
167
|
-
const validCidrs = ['10.0.0.0/16', '172.31.0.0/16', '192.168.0.0/24'];
|
|
168
|
-
|
|
169
|
-
validCidrs.forEach(cidr => {
|
|
170
|
-
const appDefinition = {
|
|
171
|
-
vpc: {
|
|
172
|
-
enable: true,
|
|
173
|
-
cidrBlock: cidr,
|
|
174
|
-
},
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
const result = vpcBuilder.validate(appDefinition);
|
|
178
|
-
expect(result.valid).toBe(true);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('should error when use-existing subnets without subnet IDs', () => {
|
|
183
|
-
const appDefinition = {
|
|
184
|
-
vpc: {
|
|
185
|
-
enable: true,
|
|
186
|
-
subnets: {
|
|
187
|
-
management: 'use-existing',
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
const result = vpcBuilder.validate(appDefinition);
|
|
193
|
-
|
|
194
|
-
expect(result.valid).toBe(false);
|
|
195
|
-
expect(result.errors.some(err => err.includes('At least 2 subnet IDs required'))).toBe(true);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it('should error when use-existing subnets with only 1 subnet', () => {
|
|
199
|
-
const appDefinition = {
|
|
200
|
-
vpc: {
|
|
201
|
-
enable: true,
|
|
202
|
-
subnets: {
|
|
203
|
-
management: 'use-existing',
|
|
204
|
-
ids: ['subnet-1'],
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
const result = vpcBuilder.validate(appDefinition);
|
|
210
|
-
|
|
211
|
-
expect(result.valid).toBe(false);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('should pass when use-existing subnets with 2+ subnets', () => {
|
|
215
|
-
const appDefinition = {
|
|
216
|
-
vpc: {
|
|
217
|
-
enable: true,
|
|
218
|
-
subnets: {
|
|
219
|
-
management: 'use-existing',
|
|
220
|
-
ids: ['subnet-1', 'subnet-2'],
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
const result = vpcBuilder.validate(appDefinition);
|
|
226
|
-
|
|
227
|
-
expect(result.valid).toBe(true);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it('should default to discover mode when management not specified', () => {
|
|
231
|
-
const appDefinition = {
|
|
232
|
-
vpc: {
|
|
233
|
-
enable: true,
|
|
234
|
-
},
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
const result = vpcBuilder.validate(appDefinition);
|
|
238
|
-
|
|
239
|
-
expect(result.valid).toBe(true);
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
describe('build() - discover mode', () => {
|
|
244
|
-
it('should reuse stack-managed subnets when discovered from CloudFormation', async () => {
|
|
245
|
-
const appDefinition = {
|
|
246
|
-
vpc: { enable: true },
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
const discoveredResources = {
|
|
250
|
-
defaultVpcId: 'vpc-discovered',
|
|
251
|
-
privateSubnetId1: 'subnet-stack-private-1',
|
|
252
|
-
privateSubnetId2: 'subnet-stack-private-2',
|
|
253
|
-
publicSubnetId1: 'subnet-stack-public-1',
|
|
254
|
-
publicSubnetId2: 'subnet-stack-public-2',
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
258
|
-
|
|
259
|
-
// Should use discovered VPC
|
|
260
|
-
expect(result.vpcId).toBe('vpc-discovered');
|
|
261
|
-
|
|
262
|
-
// Should reuse stack-managed subnets (not create new ones)
|
|
263
|
-
expect(result.vpcConfig.subnetIds).toEqual([
|
|
264
|
-
'subnet-stack-private-1',
|
|
265
|
-
'subnet-stack-private-2',
|
|
266
|
-
]);
|
|
267
|
-
|
|
268
|
-
// Should NOT create new subnet resources
|
|
269
|
-
expect(result.resources.FriggPrivateSubnet1).toBeUndefined();
|
|
270
|
-
expect(result.resources.FriggPrivateSubnet2).toBeUndefined();
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it('should use discovered VPC but create stage-specific subnets when no stack subnets exist', async () => {
|
|
274
|
-
const appDefinition = {
|
|
275
|
-
vpc: {
|
|
276
|
-
enable: true,
|
|
277
|
-
management: 'discover',
|
|
278
|
-
},
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
const discoveredResources = {
|
|
282
|
-
defaultVpcId: 'vpc-discovered',
|
|
283
|
-
// No stack-managed subnets, so create new ones for stage isolation
|
|
284
|
-
defaultSecurityGroupId: 'sg-discovered',
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
288
|
-
|
|
289
|
-
// Should create new stage-specific subnets for isolation (prevent route table conflicts)
|
|
290
|
-
expect(result.vpcConfig.subnetIds).toEqual([
|
|
291
|
-
{ Ref: 'FriggPrivateSubnet1' },
|
|
292
|
-
{ Ref: 'FriggPrivateSubnet2' },
|
|
293
|
-
]);
|
|
294
|
-
expect(result.resources.FriggPrivateSubnet1).toBeDefined();
|
|
295
|
-
expect(result.resources.FriggPrivateSubnet2).toBeDefined();
|
|
296
|
-
// In discover mode, we create FriggLambdaSecurityGroup in the discovered VPC
|
|
297
|
-
expect(result.vpcConfig.securityGroupIds).toEqual([{ Ref: 'FriggLambdaSecurityGroup' }]);
|
|
298
|
-
expect(result.resources.FriggLambdaSecurityGroup).toBeDefined();
|
|
299
|
-
expect(result.resources.FriggLambdaSecurityGroup.Properties.VpcId).toBe('vpc-discovered');
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
it('should allow sharing discovered subnets when explicitly configured', async () => {
|
|
303
|
-
const appDefinition = {
|
|
304
|
-
vpc: {
|
|
305
|
-
enable: true,
|
|
306
|
-
management: 'discover',
|
|
307
|
-
subnets: {
|
|
308
|
-
management: 'discover', // Explicitly opt-in to subnet sharing
|
|
309
|
-
},
|
|
310
|
-
},
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
const discoveredResources = {
|
|
314
|
-
defaultVpcId: 'vpc-discovered',
|
|
315
|
-
privateSubnetId1: 'subnet-shared-1',
|
|
316
|
-
privateSubnetId2: 'subnet-shared-2',
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
320
|
-
|
|
321
|
-
// OLD BEHAVIOR: When explicitly set to 'discover', reuse discovered subnets
|
|
322
|
-
expect(result.vpcConfig.subnetIds).toEqual(['subnet-shared-1', 'subnet-shared-2']);
|
|
323
|
-
expect(result.resources.FriggPrivateSubnet1).toBeUndefined();
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
it('should create VPC endpoints in discover mode with selfHeal when none exist', async () => {
|
|
327
|
-
const appDefinition = {
|
|
328
|
-
vpc: {
|
|
329
|
-
enable: true,
|
|
330
|
-
management: 'discover',
|
|
331
|
-
selfHeal: true,
|
|
332
|
-
},
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
const discoveredResources = {
|
|
336
|
-
defaultVpcId: 'vpc-discovered',
|
|
337
|
-
privateSubnetId1: 'subnet-private1',
|
|
338
|
-
privateSubnetId2: 'subnet-private2',
|
|
339
|
-
existingNatGatewayId: 'nat-123',
|
|
340
|
-
// No VPC endpoints discovered
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
344
|
-
|
|
345
|
-
// With selfHeal enabled and no VPC endpoints found, they should be created
|
|
346
|
-
expect(result.resources.FriggS3VPCEndpoint).toBeDefined();
|
|
347
|
-
expect(result.resources.FriggDynamoDBVPCEndpoint).toBeDefined();
|
|
348
|
-
expect(result.resources.FriggLambdaRouteTable).toBeDefined();
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it('should NOT create VPC endpoints in discover mode when they already exist', async () => {
|
|
352
|
-
const appDefinition = {
|
|
353
|
-
vpc: {
|
|
354
|
-
enable: true,
|
|
355
|
-
management: 'discover',
|
|
356
|
-
selfHeal: true,
|
|
357
|
-
},
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
const discoveredResources = {
|
|
361
|
-
defaultVpcId: 'vpc-discovered',
|
|
362
|
-
privateSubnetId1: 'subnet-private1',
|
|
363
|
-
privateSubnetId2: 'subnet-private2',
|
|
364
|
-
existingNatGatewayId: 'nat-123',
|
|
365
|
-
// VPC endpoints already exist
|
|
366
|
-
s3VpcEndpointId: 'vpce-s3-123',
|
|
367
|
-
dynamodbVpcEndpointId: 'vpce-dynamodb-456',
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
371
|
-
|
|
372
|
-
// With existing VPC endpoints discovered, they should NOT be recreated
|
|
373
|
-
expect(result.resources.FriggS3VPCEndpoint).toBeUndefined();
|
|
374
|
-
expect(result.resources.FriggDynamoDBVPCEndpoint).toBeUndefined();
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
it('should create VPC endpoints with selfHeal when missing', async () => {
|
|
378
|
-
const appDefinition = {
|
|
379
|
-
vpc: {
|
|
380
|
-
enable: true,
|
|
381
|
-
management: 'discover',
|
|
382
|
-
selfHeal: true,
|
|
383
|
-
},
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
const discoveredResources = {
|
|
387
|
-
defaultVpcId: 'vpc-123',
|
|
388
|
-
privateSubnetId1: 'subnet-1',
|
|
389
|
-
privateSubnetId2: 'subnet-2',
|
|
390
|
-
defaultSecurityGroupId: 'sg-123',
|
|
391
|
-
// No VPC endpoints discovered - selfHeal should create them
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
395
|
-
|
|
396
|
-
expect(result.resources.FriggS3VPCEndpoint).toBeDefined();
|
|
397
|
-
expect(result.resources.FriggS3VPCEndpoint.Type).toBe('AWS::EC2::VPCEndpoint');
|
|
398
|
-
expect(result.resources.FriggS3VPCEndpoint.Properties.VpcId).toBe('vpc-123');
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
it('should add stack-managed security group back to template to prevent deletion', async () => {
|
|
402
|
-
const appDefinition = {
|
|
403
|
-
vpc: { enable: true },
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
const discoveredResources = {
|
|
407
|
-
fromCloudFormationStack: true,
|
|
408
|
-
stackName: 'test-stack',
|
|
409
|
-
existingLogicalIds: ['FriggLambdaSecurityGroup'],
|
|
410
|
-
defaultVpcId: 'vpc-123',
|
|
411
|
-
privateSubnetId1: 'subnet-1',
|
|
412
|
-
privateSubnetId2: 'subnet-2',
|
|
413
|
-
lambdaSecurityGroupId: 'sg-existing-stack', // Existing in stack
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
417
|
-
|
|
418
|
-
// CRITICAL: Must RE-ADD stack-managed SG to template or CloudFormation will DELETE it
|
|
419
|
-
expect(result.resources.FriggLambdaSecurityGroup).toBeDefined();
|
|
420
|
-
expect(result.resources.FriggLambdaSecurityGroup.Type).toBe('AWS::EC2::SecurityGroup');
|
|
421
|
-
expect(result.resources.FriggLambdaSecurityGroup.Properties.VpcId).toBe('vpc-123');
|
|
422
|
-
expect(result.resources.FriggLambdaSecurityGroup.Properties.GroupDescription).toBeDefined();
|
|
423
|
-
|
|
424
|
-
// Should use Ref in Lambda config (not recreating)
|
|
425
|
-
expect(result.vpcConfig.securityGroupIds).toContainEqual({ Ref: 'FriggLambdaSecurityGroup' });
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
it('should add stack-managed subnets back to template to prevent deletion', async () => {
|
|
429
|
-
const appDefinition = {
|
|
430
|
-
vpc: { enable: true },
|
|
431
|
-
};
|
|
432
|
-
|
|
433
|
-
const discoveredResources = {
|
|
434
|
-
fromCloudFormationStack: true,
|
|
435
|
-
stackName: 'test-stack',
|
|
436
|
-
existingLogicalIds: ['FriggPrivateSubnet1', 'FriggPrivateSubnet2'],
|
|
437
|
-
defaultVpcId: 'vpc-123',
|
|
438
|
-
// Subnets exist in stack with specific IDs
|
|
439
|
-
privateSubnetId1: 'subnet-existing-1',
|
|
440
|
-
privateSubnetId2: 'subnet-existing-2',
|
|
441
|
-
};
|
|
442
|
-
|
|
443
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
444
|
-
|
|
445
|
-
// CRITICAL: Must RE-ADD stack-managed subnets to template or CloudFormation will DELETE them
|
|
446
|
-
expect(result.resources.FriggPrivateSubnet1).toBeDefined();
|
|
447
|
-
expect(result.resources.FriggPrivateSubnet1.Type).toBe('AWS::EC2::Subnet');
|
|
448
|
-
expect(result.resources.FriggPrivateSubnet1.Properties.VpcId).toBe('vpc-123');
|
|
449
|
-
|
|
450
|
-
expect(result.resources.FriggPrivateSubnet2).toBeDefined();
|
|
451
|
-
expect(result.resources.FriggPrivateSubnet2.Type).toBe('AWS::EC2::Subnet');
|
|
452
|
-
expect(result.resources.FriggPrivateSubnet2.Properties.VpcId).toBe('vpc-123');
|
|
453
|
-
|
|
454
|
-
// Should use Refs (not external IDs)
|
|
455
|
-
expect(result.vpcConfig.subnetIds).toEqual([
|
|
456
|
-
{ Ref: 'FriggPrivateSubnet1' },
|
|
457
|
-
{ Ref: 'FriggPrivateSubnet2' }
|
|
458
|
-
]);
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
it('should add stack-managed VPC endpoints back to template to prevent deletion', async () => {
|
|
462
|
-
const appDefinition = {
|
|
463
|
-
vpc: { enable: true },
|
|
464
|
-
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
465
|
-
};
|
|
466
|
-
|
|
467
|
-
// Structured discovery from CloudFormation
|
|
468
|
-
const discoveredResources = {
|
|
469
|
-
fromCloudFormationStack: true,
|
|
470
|
-
stackName: 'test-stack',
|
|
471
|
-
existingLogicalIds: [
|
|
472
|
-
'FriggLambdaSecurityGroup',
|
|
473
|
-
'FriggLambdaRouteTable',
|
|
474
|
-
'FriggS3VPCEndpoint',
|
|
475
|
-
'FriggDynamoDBVPCEndpoint',
|
|
476
|
-
'FriggKMSVPCEndpoint',
|
|
477
|
-
'FriggSecretsManagerVPCEndpoint',
|
|
478
|
-
'FriggSQSVPCEndpoint'
|
|
479
|
-
],
|
|
480
|
-
defaultVpcId: 'vpc-123',
|
|
481
|
-
privateSubnetId1: 'subnet-1',
|
|
482
|
-
privateSubnetId2: 'subnet-2',
|
|
483
|
-
routeTableId: 'rtb-123',
|
|
484
|
-
lambdaSecurityGroupId: 'sg-123',
|
|
485
|
-
// VPC endpoints discovered in stack
|
|
486
|
-
s3VpcEndpointId: 'vpce-s3-stack',
|
|
487
|
-
dynamodbVpcEndpointId: 'vpce-ddb-stack',
|
|
488
|
-
kmsVpcEndpointId: 'vpce-kms-stack',
|
|
489
|
-
secretsManagerVpcEndpointId: 'vpce-sm-stack',
|
|
490
|
-
sqsVpcEndpointId: 'vpce-sqs-stack',
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
494
|
-
|
|
495
|
-
// CRITICAL: Must RE-ADD stack-managed endpoints to template or CloudFormation will DELETE them
|
|
496
|
-
expect(result.resources.FriggS3VPCEndpoint).toBeDefined();
|
|
497
|
-
expect(result.resources.FriggS3VPCEndpoint.Type).toBe('AWS::EC2::VPCEndpoint');
|
|
498
|
-
expect(result.resources.FriggS3VPCEndpoint.Properties.VpcEndpointType).toBe('Gateway');
|
|
499
|
-
|
|
500
|
-
expect(result.resources.FriggDynamoDBVPCEndpoint).toBeDefined();
|
|
501
|
-
expect(result.resources.FriggDynamoDBVPCEndpoint.Type).toBe('AWS::EC2::VPCEndpoint');
|
|
502
|
-
expect(result.resources.FriggDynamoDBVPCEndpoint.Properties.VpcEndpointType).toBe('Gateway');
|
|
503
|
-
|
|
504
|
-
expect(result.resources.FriggKMSVPCEndpoint).toBeDefined();
|
|
505
|
-
expect(result.resources.FriggKMSVPCEndpoint.Type).toBe('AWS::EC2::VPCEndpoint');
|
|
506
|
-
expect(result.resources.FriggKMSVPCEndpoint.Properties.VpcEndpointType).toBe('Interface');
|
|
507
|
-
|
|
508
|
-
expect(result.resources.FriggSecretsManagerVPCEndpoint).toBeDefined();
|
|
509
|
-
expect(result.resources.FriggSecretsManagerVPCEndpoint.Type).toBe('AWS::EC2::VPCEndpoint');
|
|
510
|
-
expect(result.resources.FriggSecretsManagerVPCEndpoint.Properties.VpcEndpointType).toBe('Interface');
|
|
511
|
-
|
|
512
|
-
expect(result.resources.FriggSQSVPCEndpoint).toBeDefined();
|
|
513
|
-
expect(result.resources.FriggSQSVPCEndpoint.Type).toBe('AWS::EC2::VPCEndpoint');
|
|
514
|
-
expect(result.resources.FriggSQSVPCEndpoint.Properties.VpcEndpointType).toBe('Interface');
|
|
515
|
-
|
|
516
|
-
// Should create VPC Endpoint Security Group for interface endpoints
|
|
517
|
-
expect(result.resources.FriggVPCEndpointSecurityGroup).toBeDefined();
|
|
518
|
-
expect(result.resources.FriggVPCEndpointSecurityGroup.Type).toBe('AWS::EC2::SecurityGroup');
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
it('should create VPC endpoints when discovered from AWS but not stack', async () => {
|
|
522
|
-
const appDefinition = {
|
|
523
|
-
vpc: { enable: true, enableVPCEndpoints: true, selfHeal: true },
|
|
524
|
-
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
525
|
-
};
|
|
526
|
-
const discoveredResources = {
|
|
527
|
-
defaultVpcId: 'vpc-123',
|
|
528
|
-
privateSubnetId1: 'subnet-1',
|
|
529
|
-
privateSubnetId2: 'subnet-2',
|
|
530
|
-
// No VPC endpoints in stack (would be strings)
|
|
531
|
-
// existingEndpoints will be passed as empty
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
535
|
-
|
|
536
|
-
// Should create CloudFormation resources (not in stack)
|
|
537
|
-
expect(result.resources.FriggS3VPCEndpoint).toBeDefined();
|
|
538
|
-
expect(result.resources.FriggDynamoDBVPCEndpoint).toBeDefined();
|
|
539
|
-
expect(result.resources.FriggKMSVPCEndpoint).toBeDefined();
|
|
540
|
-
expect(result.resources.FriggSecretsManagerVPCEndpoint).toBeDefined();
|
|
541
|
-
expect(result.resources.FriggSQSVPCEndpoint).toBeDefined();
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
it('should skip VPC endpoints when disabled', async () => {
|
|
545
|
-
const appDefinition = {
|
|
546
|
-
vpc: {
|
|
547
|
-
enable: true,
|
|
548
|
-
management: 'discover',
|
|
549
|
-
enableVPCEndpoints: false,
|
|
550
|
-
},
|
|
551
|
-
};
|
|
552
|
-
|
|
553
|
-
const discoveredResources = {
|
|
554
|
-
defaultVpcId: 'vpc-123',
|
|
555
|
-
privateSubnetId1: 'subnet-1',
|
|
556
|
-
privateSubnetId2: 'subnet-2',
|
|
557
|
-
};
|
|
558
|
-
|
|
559
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
560
|
-
|
|
561
|
-
expect(result.resources.FriggS3VPCEndpoint).toBeUndefined();
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
it('should create route table associations when VPC endpoints exist but no NAT Gateway', async () => {
|
|
565
|
-
const appDefinition = {
|
|
566
|
-
vpc: { enable: true, enableVPCEndpoints: true, selfHeal: true },
|
|
567
|
-
};
|
|
568
|
-
const discoveredResources = {
|
|
569
|
-
defaultVpcId: 'vpc-123',
|
|
570
|
-
privateSubnetId1: 'subnet-1',
|
|
571
|
-
privateSubnetId2: 'subnet-2',
|
|
572
|
-
// No NAT Gateway, so associations won't be created by NAT Gateway routing
|
|
573
|
-
};
|
|
574
|
-
|
|
575
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
576
|
-
|
|
577
|
-
// Route table should be created for VPC endpoints
|
|
578
|
-
expect(result.resources.FriggLambdaRouteTable).toBeDefined();
|
|
579
|
-
expect(result.resources.FriggLambdaRouteTable.Type).toBe('AWS::EC2::RouteTable');
|
|
580
|
-
|
|
581
|
-
// Subnet associations should be created (healing)
|
|
582
|
-
expect(result.resources.FriggPrivateSubnet1RouteTableAssociation).toBeDefined();
|
|
583
|
-
expect(result.resources.FriggPrivateSubnet1RouteTableAssociation.Type).toBe('AWS::EC2::SubnetRouteTableAssociation');
|
|
584
|
-
expect(result.resources.FriggPrivateSubnet1RouteTableAssociation.Properties.SubnetId).toBe('subnet-1');
|
|
585
|
-
|
|
586
|
-
expect(result.resources.FriggPrivateSubnet2RouteTableAssociation).toBeDefined();
|
|
587
|
-
expect(result.resources.FriggPrivateSubnet2RouteTableAssociation.Properties.SubnetId).toBe('subnet-2');
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
it('should include IAM permissions for VPC operations', async () => {
|
|
591
|
-
const appDefinition = {
|
|
592
|
-
vpc: {
|
|
593
|
-
enable: true,
|
|
594
|
-
},
|
|
595
|
-
};
|
|
596
|
-
|
|
597
|
-
const discoveredResources = {
|
|
598
|
-
defaultVpcId: 'vpc-123',
|
|
599
|
-
privateSubnetId1: 'subnet-priv1',
|
|
600
|
-
privateSubnetId2: 'subnet-priv2',
|
|
601
|
-
};
|
|
602
|
-
|
|
603
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
604
|
-
|
|
605
|
-
const vpcPermissions = result.iamStatements.find(stmt =>
|
|
606
|
-
stmt.Action.includes('ec2:CreateNetworkInterface')
|
|
607
|
-
);
|
|
608
|
-
|
|
609
|
-
expect(vpcPermissions).toBeDefined();
|
|
610
|
-
expect(vpcPermissions.Action).toContain('ec2:DescribeNetworkInterfaces');
|
|
611
|
-
expect(vpcPermissions.Action).toContain('ec2:DeleteNetworkInterface');
|
|
612
|
-
});
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
describe('build() - create-new mode', () => {
|
|
616
|
-
it('should create complete VPC infrastructure', async () => {
|
|
617
|
-
const appDefinition = {
|
|
618
|
-
vpc: {
|
|
619
|
-
enable: true,
|
|
620
|
-
management: 'create-new',
|
|
621
|
-
},
|
|
622
|
-
};
|
|
623
|
-
|
|
624
|
-
const result = await vpcBuilder.build(appDefinition, {});
|
|
625
|
-
|
|
626
|
-
expect(result.resources.FriggVPC).toBeDefined();
|
|
627
|
-
expect(result.resources.FriggVPC.Type).toBe('AWS::EC2::VPC');
|
|
628
|
-
expect(result.resources.FriggVPC.Properties.CidrBlock).toBe('10.0.0.0/16');
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
it('should create private and public subnets', async () => {
|
|
632
|
-
const appDefinition = {
|
|
633
|
-
vpc: {
|
|
634
|
-
enable: true,
|
|
635
|
-
management: 'create-new',
|
|
636
|
-
},
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
const result = await vpcBuilder.build(appDefinition, {});
|
|
640
|
-
|
|
641
|
-
expect(result.resources.FriggPrivateSubnet1).toBeDefined();
|
|
642
|
-
expect(result.resources.FriggPrivateSubnet2).toBeDefined();
|
|
643
|
-
expect(result.resources.FriggPublicSubnet).toBeDefined();
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
it('should create security group for Lambda functions', async () => {
|
|
647
|
-
const appDefinition = {
|
|
648
|
-
vpc: {
|
|
649
|
-
enable: true,
|
|
650
|
-
management: 'create-new',
|
|
651
|
-
},
|
|
652
|
-
};
|
|
653
|
-
|
|
654
|
-
const result = await vpcBuilder.build(appDefinition, {});
|
|
655
|
-
|
|
656
|
-
expect(result.resources.FriggLambdaSecurityGroup).toBeDefined();
|
|
657
|
-
expect(result.resources.FriggLambdaSecurityGroup.Type).toBe('AWS::EC2::SecurityGroup');
|
|
658
|
-
});
|
|
659
|
-
|
|
660
|
-
it('should use CloudFormation references for new resources', async () => {
|
|
661
|
-
const appDefinition = {
|
|
662
|
-
vpc: {
|
|
663
|
-
enable: true,
|
|
664
|
-
management: 'create-new',
|
|
665
|
-
},
|
|
666
|
-
};
|
|
667
|
-
|
|
668
|
-
const result = await vpcBuilder.build(appDefinition, {});
|
|
669
|
-
|
|
670
|
-
expect(result.vpcConfig.securityGroupIds).toEqual([{ Ref: 'FriggLambdaSecurityGroup' }]);
|
|
671
|
-
expect(result.vpcConfig.subnetIds).toContainEqual({ Ref: 'FriggPrivateSubnet1' });
|
|
672
|
-
expect(result.vpcConfig.subnetIds).toContainEqual({ Ref: 'FriggPrivateSubnet2' });
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
it('should use custom CIDR block if provided', async () => {
|
|
676
|
-
const appDefinition = {
|
|
677
|
-
vpc: {
|
|
678
|
-
enable: true,
|
|
679
|
-
management: 'create-new',
|
|
680
|
-
cidrBlock: '192.168.0.0/16',
|
|
681
|
-
},
|
|
682
|
-
};
|
|
683
|
-
|
|
684
|
-
const result = await vpcBuilder.build(appDefinition, {});
|
|
685
|
-
|
|
686
|
-
expect(result.resources.FriggVPC.Properties.CidrBlock).toBe('192.168.0.0/16');
|
|
687
|
-
});
|
|
688
|
-
});
|
|
689
|
-
|
|
690
|
-
describe('build() - use-existing mode', () => {
|
|
691
|
-
it('should use provided VPC and subnet IDs', async () => {
|
|
692
|
-
const appDefinition = {
|
|
693
|
-
vpc: {
|
|
694
|
-
enable: true,
|
|
695
|
-
management: 'use-existing',
|
|
696
|
-
vpcId: 'vpc-custom',
|
|
697
|
-
subnets: {
|
|
698
|
-
ids: ['subnet-a', 'subnet-b'],
|
|
699
|
-
},
|
|
700
|
-
securityGroupIds: ['sg-custom'],
|
|
701
|
-
},
|
|
702
|
-
};
|
|
703
|
-
|
|
704
|
-
const result = await vpcBuilder.build(appDefinition, {});
|
|
705
|
-
|
|
706
|
-
expect(result.vpcConfig.subnetIds).toEqual(['subnet-a', 'subnet-b']);
|
|
707
|
-
expect(result.vpcConfig.securityGroupIds).toEqual(['sg-custom']);
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
it('should not create VPC resources in use-existing mode', async () => {
|
|
711
|
-
const appDefinition = {
|
|
712
|
-
vpc: {
|
|
713
|
-
enable: true,
|
|
714
|
-
management: 'use-existing',
|
|
715
|
-
vpcId: 'vpc-custom',
|
|
716
|
-
subnets: {
|
|
717
|
-
ids: ['subnet-a', 'subnet-b'],
|
|
718
|
-
},
|
|
719
|
-
},
|
|
720
|
-
};
|
|
721
|
-
|
|
722
|
-
const result = await vpcBuilder.build(appDefinition, {});
|
|
723
|
-
|
|
724
|
-
expect(result.resources.FriggVPC).toBeUndefined();
|
|
725
|
-
expect(result.resources.FriggPrivateSubnet1).toBeUndefined();
|
|
726
|
-
});
|
|
727
|
-
});
|
|
728
|
-
|
|
729
|
-
describe('getDependencies()', () => {
|
|
730
|
-
it('should have no dependencies', () => {
|
|
731
|
-
const deps = vpcBuilder.getDependencies();
|
|
732
|
-
|
|
733
|
-
expect(deps).toEqual([]);
|
|
734
|
-
});
|
|
735
|
-
});
|
|
736
|
-
|
|
737
|
-
describe('getName()', () => {
|
|
738
|
-
it('should return VpcBuilder', () => {
|
|
739
|
-
expect(vpcBuilder.getName()).toBe('VpcBuilder');
|
|
740
|
-
});
|
|
741
|
-
});
|
|
742
|
-
|
|
743
|
-
describe('NAT Gateway handling', () => {
|
|
744
|
-
it('should create NAT gateway when management is createAndManage', async () => {
|
|
745
|
-
const appDefinition = {
|
|
746
|
-
vpc: {
|
|
747
|
-
enable: true,
|
|
748
|
-
management: 'discover',
|
|
749
|
-
natGateway: {
|
|
750
|
-
management: 'createAndManage',
|
|
751
|
-
},
|
|
752
|
-
selfHeal: true,
|
|
753
|
-
},
|
|
754
|
-
};
|
|
755
|
-
|
|
756
|
-
const discoveredResources = {
|
|
757
|
-
defaultVpcId: 'vpc-123',
|
|
758
|
-
publicSubnetId: 'subnet-public',
|
|
759
|
-
};
|
|
760
|
-
|
|
761
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
762
|
-
|
|
763
|
-
expect(result.resources.FriggNATGateway).toBeDefined();
|
|
764
|
-
expect(result.resources.FriggNATGateway.Type).toBe('AWS::EC2::NatGateway');
|
|
765
|
-
});
|
|
766
|
-
|
|
767
|
-
it('should create route table associations for private subnets with NAT Gateway', async () => {
|
|
768
|
-
const appDefinition = {
|
|
769
|
-
vpc: {
|
|
770
|
-
enable: true,
|
|
771
|
-
management: 'discover',
|
|
772
|
-
subnets: { management: 'create' },
|
|
773
|
-
natGateway: {
|
|
774
|
-
management: 'createAndManage',
|
|
775
|
-
},
|
|
776
|
-
selfHeal: true,
|
|
777
|
-
},
|
|
778
|
-
};
|
|
779
|
-
|
|
780
|
-
const discoveredResources = {
|
|
781
|
-
defaultVpcId: 'vpc-123',
|
|
782
|
-
publicSubnetId: 'subnet-public',
|
|
783
|
-
};
|
|
784
|
-
|
|
785
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
786
|
-
|
|
787
|
-
// Verify route table is created
|
|
788
|
-
expect(result.resources.FriggLambdaRouteTable).toBeDefined();
|
|
789
|
-
expect(result.resources.FriggLambdaRouteTable.Type).toBe('AWS::EC2::RouteTable');
|
|
790
|
-
|
|
791
|
-
// Verify route to NAT Gateway
|
|
792
|
-
expect(result.resources.FriggPrivateRoute).toBeDefined();
|
|
793
|
-
expect(result.resources.FriggPrivateRoute.Properties.NatGatewayId).toEqual({ Ref: 'FriggNATGateway' });
|
|
794
|
-
|
|
795
|
-
// Verify subnet route table associations
|
|
796
|
-
expect(result.resources.FriggPrivateSubnet1RouteTableAssociation).toBeDefined();
|
|
797
|
-
expect(result.resources.FriggPrivateSubnet1RouteTableAssociation.Type).toBe('AWS::EC2::SubnetRouteTableAssociation');
|
|
798
|
-
expect(result.resources.FriggPrivateSubnet1RouteTableAssociation.Properties.SubnetId).toEqual({ Ref: 'FriggPrivateSubnet1' });
|
|
799
|
-
expect(result.resources.FriggPrivateSubnet1RouteTableAssociation.Properties.RouteTableId).toEqual({ Ref: 'FriggLambdaRouteTable' });
|
|
800
|
-
|
|
801
|
-
expect(result.resources.FriggPrivateSubnet2RouteTableAssociation).toBeDefined();
|
|
802
|
-
expect(result.resources.FriggPrivateSubnet2RouteTableAssociation.Type).toBe('AWS::EC2::SubnetRouteTableAssociation');
|
|
803
|
-
expect(result.resources.FriggPrivateSubnet2RouteTableAssociation.Properties.SubnetId).toEqual({ Ref: 'FriggPrivateSubnet2' });
|
|
804
|
-
expect(result.resources.FriggPrivateSubnet2RouteTableAssociation.Properties.RouteTableId).toEqual({ Ref: 'FriggLambdaRouteTable' });
|
|
805
|
-
});
|
|
806
|
-
|
|
807
|
-
it('should add UpdateReplacePolicy to force association recreation on updates', async () => {
|
|
808
|
-
const appDefinition = {
|
|
809
|
-
vpc: {
|
|
810
|
-
enable: true,
|
|
811
|
-
management: 'discover',
|
|
812
|
-
subnets: { management: 'discover' },
|
|
813
|
-
natGateway: { management: 'discover' },
|
|
814
|
-
},
|
|
815
|
-
};
|
|
816
|
-
|
|
817
|
-
const discoveredResources = {
|
|
818
|
-
vpcId: 'vpc-123',
|
|
819
|
-
privateSubnetId1: 'subnet-existing-1',
|
|
820
|
-
privateSubnetId2: 'subnet-existing-2',
|
|
821
|
-
natGatewayId: 'nat-existing',
|
|
822
|
-
routeTableId: 'rtb-old',
|
|
823
|
-
existingLogicalIds: ['FriggSubnet1RouteAssociation', 'FriggSubnet2RouteAssociation'],
|
|
824
|
-
};
|
|
825
|
-
|
|
826
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
827
|
-
|
|
828
|
-
// Verify associations have UpdateReplacePolicy to force recreation
|
|
829
|
-
expect(result.resources.FriggSubnet1RouteAssociation.UpdateReplacePolicy).toBe('Delete');
|
|
830
|
-
expect(result.resources.FriggSubnet2RouteAssociation.UpdateReplacePolicy).toBe('Delete');
|
|
831
|
-
|
|
832
|
-
// This forces CloudFormation to delete old associations and create new ones
|
|
833
|
-
// instead of trying to update them in-place (which doesn't work)
|
|
834
|
-
});
|
|
835
|
-
|
|
836
|
-
it('should not create NAT when existing NAT is properly placed', async () => {
|
|
837
|
-
const appDefinition = {
|
|
838
|
-
vpc: {
|
|
839
|
-
enable: true,
|
|
840
|
-
natGateway: {
|
|
841
|
-
management: 'createAndManage',
|
|
842
|
-
},
|
|
843
|
-
selfHeal: true,
|
|
844
|
-
},
|
|
845
|
-
};
|
|
846
|
-
|
|
847
|
-
const discoveredResources = {
|
|
848
|
-
defaultVpcId: 'vpc-123',
|
|
849
|
-
publicSubnetId: 'subnet-public',
|
|
850
|
-
existingNatGatewayId: 'nat-good',
|
|
851
|
-
natGatewayInPrivateSubnet: false,
|
|
852
|
-
};
|
|
853
|
-
|
|
854
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
855
|
-
|
|
856
|
-
expect(result.resources.FriggNATGateway).toBeUndefined();
|
|
857
|
-
});
|
|
858
|
-
|
|
859
|
-
it('should create new NAT when existing is in private subnet', async () => {
|
|
860
|
-
const appDefinition = {
|
|
861
|
-
vpc: {
|
|
862
|
-
enable: true,
|
|
863
|
-
natGateway: {
|
|
864
|
-
management: 'createAndManage',
|
|
865
|
-
},
|
|
866
|
-
selfHeal: true,
|
|
867
|
-
},
|
|
868
|
-
};
|
|
869
|
-
|
|
870
|
-
const discoveredResources = {
|
|
871
|
-
defaultVpcId: 'vpc-123',
|
|
872
|
-
publicSubnetId: 'subnet-public',
|
|
873
|
-
existingNatGatewayId: 'nat-misplaced',
|
|
874
|
-
natGatewayInPrivateSubnet: true, // WRONG placement
|
|
875
|
-
};
|
|
876
|
-
|
|
877
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
878
|
-
|
|
879
|
-
expect(result.resources.FriggNATGateway).toBeDefined();
|
|
880
|
-
});
|
|
881
|
-
|
|
882
|
-
it('should create new NAT Gateway when existing is in private subnet', async () => {
|
|
883
|
-
const appDefinition = {
|
|
884
|
-
vpc: {
|
|
885
|
-
enable: true,
|
|
886
|
-
natGateway: {
|
|
887
|
-
management: 'createAndManage',
|
|
888
|
-
},
|
|
889
|
-
selfHeal: false,
|
|
890
|
-
},
|
|
891
|
-
};
|
|
892
|
-
|
|
893
|
-
const discoveredResources = {
|
|
894
|
-
defaultVpcId: 'vpc-123',
|
|
895
|
-
privateSubnetId1: 'subnet-priv1',
|
|
896
|
-
privateSubnetId2: 'subnet-priv2',
|
|
897
|
-
publicSubnetId: 'subnet-public',
|
|
898
|
-
existingNatGatewayId: 'nat-misplaced',
|
|
899
|
-
natGatewayInPrivateSubnet: true,
|
|
900
|
-
};
|
|
901
|
-
|
|
902
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
903
|
-
|
|
904
|
-
// Should create new NAT Gateway instead of using the misplaced one
|
|
905
|
-
expect(result.resources.FriggNATGateway).toBeDefined();
|
|
906
|
-
expect(result.resources.FriggNATGateway.Type).toBe('AWS::EC2::NatGateway');
|
|
907
|
-
});
|
|
908
|
-
|
|
909
|
-
it('should reuse existing elastic IP allocation', async () => {
|
|
910
|
-
const appDefinition = {
|
|
911
|
-
vpc: {
|
|
912
|
-
enable: true,
|
|
913
|
-
natGateway: {
|
|
914
|
-
management: 'createAndManage',
|
|
915
|
-
},
|
|
916
|
-
selfHeal: true,
|
|
917
|
-
},
|
|
918
|
-
};
|
|
919
|
-
|
|
920
|
-
const discoveredResources = {
|
|
921
|
-
defaultVpcId: 'vpc-123',
|
|
922
|
-
publicSubnetId: 'subnet-public',
|
|
923
|
-
existingElasticIpAllocationId: 'eipalloc-123',
|
|
924
|
-
};
|
|
925
|
-
|
|
926
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
927
|
-
|
|
928
|
-
if (result.resources.FriggNATGateway) {
|
|
929
|
-
// When reusing existing EIP, it should be a CloudFormation reference
|
|
930
|
-
expect(result.resources.FriggNATGateway.Properties.AllocationId).toEqual(
|
|
931
|
-
{ 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] }
|
|
932
|
-
);
|
|
933
|
-
}
|
|
934
|
-
});
|
|
935
|
-
});
|
|
936
|
-
|
|
937
|
-
describe('VPC Endpoints', () => {
|
|
938
|
-
it('should create KMS endpoint when KMS encryption is enabled', async () => {
|
|
939
|
-
const appDefinition = {
|
|
940
|
-
vpc: {
|
|
941
|
-
enable: true,
|
|
942
|
-
management: 'create-new',
|
|
943
|
-
},
|
|
944
|
-
encryption: {
|
|
945
|
-
fieldLevelEncryptionMethod: 'kms',
|
|
946
|
-
},
|
|
947
|
-
};
|
|
948
|
-
|
|
949
|
-
const discoveredResources = {};
|
|
950
|
-
|
|
951
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
952
|
-
|
|
953
|
-
expect(result.resources.FriggKMSVPCEndpoint).toBeDefined();
|
|
954
|
-
});
|
|
955
|
-
|
|
956
|
-
it('should create Secrets Manager endpoint when enabled', async () => {
|
|
957
|
-
const appDefinition = {
|
|
958
|
-
vpc: {
|
|
959
|
-
enable: true,
|
|
960
|
-
management: 'create-new',
|
|
961
|
-
},
|
|
962
|
-
secretsManager: {
|
|
963
|
-
enable: true,
|
|
964
|
-
},
|
|
965
|
-
};
|
|
966
|
-
|
|
967
|
-
const discoveredResources = {};
|
|
968
|
-
|
|
969
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
970
|
-
|
|
971
|
-
expect(result.resources.FriggSecretsManagerVPCEndpoint).toBeDefined();
|
|
972
|
-
});
|
|
973
|
-
|
|
974
|
-
it('should not create KMS endpoint when encryption is AES', async () => {
|
|
975
|
-
const appDefinition = {
|
|
976
|
-
vpc: {
|
|
977
|
-
enable: true,
|
|
978
|
-
management: 'create-new',
|
|
979
|
-
},
|
|
980
|
-
encryption: {
|
|
981
|
-
fieldLevelEncryptionMethod: 'aes',
|
|
982
|
-
},
|
|
983
|
-
};
|
|
984
|
-
|
|
985
|
-
const discoveredResources = {};
|
|
986
|
-
|
|
987
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
988
|
-
|
|
989
|
-
expect(result.resources.FriggKMSVPCEndpoint).toBeUndefined();
|
|
990
|
-
});
|
|
991
|
-
});
|
|
992
|
-
|
|
993
|
-
describe('Self-healing', () => {
|
|
994
|
-
it('should create missing subnets when selfHeal is enabled', async () => {
|
|
995
|
-
const appDefinition = {
|
|
996
|
-
vpc: {
|
|
997
|
-
enable: true,
|
|
998
|
-
management: 'discover',
|
|
999
|
-
selfHeal: true,
|
|
1000
|
-
},
|
|
1001
|
-
};
|
|
1002
|
-
|
|
1003
|
-
const discoveredResources = {
|
|
1004
|
-
defaultVpcId: 'vpc-123',
|
|
1005
|
-
privateSubnetId1: null,
|
|
1006
|
-
privateSubnetId2: null,
|
|
1007
|
-
};
|
|
1008
|
-
|
|
1009
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
1010
|
-
|
|
1011
|
-
expect(result.resources.FriggPrivateSubnet1).toBeDefined();
|
|
1012
|
-
expect(result.resources.FriggPrivateSubnet2).toBeDefined();
|
|
1013
|
-
});
|
|
1014
|
-
|
|
1015
|
-
it('should throw error for missing subnets without selfHeal', async () => {
|
|
1016
|
-
const appDefinition = {
|
|
1017
|
-
vpc: {
|
|
1018
|
-
enable: true,
|
|
1019
|
-
management: 'discover',
|
|
1020
|
-
subnets: { management: 'discover' },
|
|
1021
|
-
selfHeal: false,
|
|
1022
|
-
},
|
|
1023
|
-
};
|
|
1024
|
-
|
|
1025
|
-
const discoveredResources = {
|
|
1026
|
-
defaultVpcId: 'vpc-123',
|
|
1027
|
-
privateSubnetId1: null,
|
|
1028
|
-
privateSubnetId2: null,
|
|
1029
|
-
};
|
|
1030
|
-
|
|
1031
|
-
await expect(vpcBuilder.build(appDefinition, discoveredResources)).rejects.toThrow(
|
|
1032
|
-
'No subnets discovered'
|
|
1033
|
-
);
|
|
1034
|
-
});
|
|
1035
|
-
});
|
|
1036
|
-
|
|
1037
|
-
describe('Management Mode (Simplified API)', () => {
|
|
1038
|
-
it('should reuse stack VPC when managementMode=managed + vpcIsolation=isolated AND stack has VPC', async () => {
|
|
1039
|
-
const appDefinition = {
|
|
1040
|
-
managementMode: 'managed',
|
|
1041
|
-
vpcIsolation: 'isolated',
|
|
1042
|
-
vpc: {
|
|
1043
|
-
enable: true,
|
|
1044
|
-
management: 'create-new', // Should be IGNORED
|
|
1045
|
-
},
|
|
1046
|
-
};
|
|
1047
|
-
|
|
1048
|
-
// CloudFormation stack has VPC (from previous deployment of this stage)
|
|
1049
|
-
const discoveredResources = {
|
|
1050
|
-
defaultVpcId: 'vpc-stack-dev', // CloudFormation discovery sets this
|
|
1051
|
-
privateSubnetId1: 'subnet-private-1',
|
|
1052
|
-
privateSubnetId2: 'subnet-private-2',
|
|
1053
|
-
publicSubnetId1: 'subnet-public-1',
|
|
1054
|
-
publicSubnetId2: 'subnet-public-2',
|
|
1055
|
-
};
|
|
1056
|
-
|
|
1057
|
-
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
1058
|
-
|
|
1059
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
1060
|
-
|
|
1061
|
-
// Should warn about ignored options
|
|
1062
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
1063
|
-
expect.stringContaining("managementMode='managed' ignoring")
|
|
1064
|
-
);
|
|
1065
|
-
|
|
1066
|
-
// Should log reusing stack VPC
|
|
1067
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
1068
|
-
expect.stringContaining("stack has VPC, reusing")
|
|
1069
|
-
);
|
|
1070
|
-
|
|
1071
|
-
// Should keep VPC definition in template (CloudFormation idempotency)
|
|
1072
|
-
// Even though VPC exists, we include the definition - CF won't recreate it
|
|
1073
|
-
expect(result.vpcId).toEqual({ Ref: 'FriggVPC' });
|
|
1074
|
-
expect(result.resources.FriggVPC).toBeDefined();
|
|
1075
|
-
expect(result.resources.FriggVPC.Type).toBe('AWS::EC2::VPC');
|
|
1076
|
-
|
|
1077
|
-
// Should keep subnet definitions in template and use Refs
|
|
1078
|
-
expect(result.vpcConfig.subnetIds).toEqual([
|
|
1079
|
-
{ Ref: 'FriggPrivateSubnet1' },
|
|
1080
|
-
{ Ref: 'FriggPrivateSubnet2' }
|
|
1081
|
-
]);
|
|
1082
|
-
expect(result.resources.FriggPrivateSubnet1).toBeDefined();
|
|
1083
|
-
expect(result.resources.FriggPrivateSubnet2).toBeDefined();
|
|
1084
|
-
|
|
1085
|
-
consoleLogSpy.mockRestore();
|
|
1086
|
-
});
|
|
1087
|
-
|
|
1088
|
-
it('should create new VPC when managementMode=managed + vpcIsolation=isolated AND stack has NO VPC', async () => {
|
|
1089
|
-
const appDefinition = {
|
|
1090
|
-
managementMode: 'managed',
|
|
1091
|
-
vpcIsolation: 'isolated',
|
|
1092
|
-
vpc: {
|
|
1093
|
-
enable: true,
|
|
1094
|
-
management: 'discover', // Should be IGNORED
|
|
1095
|
-
},
|
|
1096
|
-
};
|
|
1097
|
-
|
|
1098
|
-
// No VPC in CloudFormation stack (fresh deployment)
|
|
1099
|
-
// Default VPC might exist in AWS, but not stack-managed
|
|
1100
|
-
const discoveredResources = {
|
|
1101
|
-
// No defaultVpcId means no VPC in CloudFormation stack
|
|
1102
|
-
};
|
|
1103
|
-
|
|
1104
|
-
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
1105
|
-
|
|
1106
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
1107
|
-
|
|
1108
|
-
// Should warn about ignored options
|
|
1109
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
1110
|
-
expect.stringContaining("managementMode='managed' ignoring")
|
|
1111
|
-
);
|
|
1112
|
-
|
|
1113
|
-
// Should log creating new VPC
|
|
1114
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
1115
|
-
expect.stringContaining("no stack VPC, creating new")
|
|
1116
|
-
);
|
|
1117
|
-
|
|
1118
|
-
// Should create new isolated VPC
|
|
1119
|
-
expect(result.vpcId).toEqual({ Ref: 'FriggVPC' });
|
|
1120
|
-
expect(result.resources.FriggVPC).toBeDefined();
|
|
1121
|
-
|
|
1122
|
-
// Subnets should use CloudFormation Fn::Cidr
|
|
1123
|
-
expect(result.resources.FriggPrivateSubnet1.Properties.CidrBlock).toEqual({
|
|
1124
|
-
'Fn::Select': [0, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
|
|
1125
|
-
});
|
|
1126
|
-
|
|
1127
|
-
consoleLogSpy.mockRestore();
|
|
1128
|
-
});
|
|
1129
|
-
|
|
1130
|
-
it('should use managementMode=managed with vpcIsolation=shared to discover VPC', async () => {
|
|
1131
|
-
const appDefinition = {
|
|
1132
|
-
managementMode: 'managed',
|
|
1133
|
-
vpcIsolation: 'shared',
|
|
1134
|
-
vpc: {
|
|
1135
|
-
enable: true,
|
|
1136
|
-
subnets: { management: 'use-existing' }, // Should be IGNORED
|
|
1137
|
-
},
|
|
1138
|
-
};
|
|
1139
|
-
|
|
1140
|
-
const discoveredResources = {
|
|
1141
|
-
defaultVpcId: 'vpc-existing',
|
|
1142
|
-
};
|
|
1143
|
-
|
|
1144
|
-
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
1145
|
-
|
|
1146
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
1147
|
-
|
|
1148
|
-
// Should warn about ignored options
|
|
1149
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
1150
|
-
expect.stringContaining("ignoring")
|
|
1151
|
-
);
|
|
1152
|
-
|
|
1153
|
-
// Should discover existing VPC
|
|
1154
|
-
expect(result.vpcId).toBe('vpc-existing');
|
|
1155
|
-
expect(result.resources.FriggVPC).toBeUndefined();
|
|
1156
|
-
|
|
1157
|
-
// Should create new stage-specific subnets
|
|
1158
|
-
expect(result.resources.FriggPrivateSubnet1).toBeDefined();
|
|
1159
|
-
|
|
1160
|
-
consoleLogSpy.mockRestore();
|
|
1161
|
-
});
|
|
1162
|
-
|
|
1163
|
-
it('should default to discover mode for backwards compatibility', async () => {
|
|
1164
|
-
const appDefinition = {
|
|
1165
|
-
// No managementMode specified
|
|
1166
|
-
vpc: {
|
|
1167
|
-
enable: true,
|
|
1168
|
-
management: 'create-new', // Should be RESPECTED
|
|
1169
|
-
},
|
|
1170
|
-
};
|
|
1171
|
-
|
|
1172
|
-
const result = await vpcBuilder.build(appDefinition, {});
|
|
1173
|
-
|
|
1174
|
-
// Should respect legacy vpc.management
|
|
1175
|
-
expect(result.vpcId).toEqual({ Ref: 'FriggVPC' });
|
|
1176
|
-
expect(result.resources.FriggVPC).toBeDefined();
|
|
1177
|
-
});
|
|
1178
|
-
});
|
|
1179
|
-
|
|
1180
|
-
describe('VPC Sharing Control', () => {
|
|
1181
|
-
it('should share VPC across stages when shareAcrossStages is true (default)', async () => {
|
|
1182
|
-
const appDefinition = {
|
|
1183
|
-
vpc: {
|
|
1184
|
-
enable: true,
|
|
1185
|
-
shareAcrossStages: true, // Explicit opt-in to sharing
|
|
1186
|
-
},
|
|
1187
|
-
};
|
|
1188
|
-
|
|
1189
|
-
const discoveredResources = {
|
|
1190
|
-
defaultVpcId: 'vpc-shared',
|
|
1191
|
-
natGatewayId: 'nat-shared',
|
|
1192
|
-
};
|
|
1193
|
-
|
|
1194
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
1195
|
-
|
|
1196
|
-
// Should use discovered VPC (not create new one)
|
|
1197
|
-
expect(result.vpcId).toBe('vpc-shared');
|
|
1198
|
-
expect(result.resources.FriggVPC).toBeUndefined();
|
|
1199
|
-
|
|
1200
|
-
// Should create stage-specific subnets for isolation
|
|
1201
|
-
expect(result.resources.FriggPrivateSubnet1).toBeDefined();
|
|
1202
|
-
expect(result.resources.FriggPrivateSubnet2).toBeDefined();
|
|
1203
|
-
|
|
1204
|
-
// Should reuse discovered NAT Gateway
|
|
1205
|
-
expect(result.resources.FriggNATGateway).toBeUndefined();
|
|
1206
|
-
});
|
|
1207
|
-
|
|
1208
|
-
it('should create isolated VPC when shareAcrossStages is false', async () => {
|
|
1209
|
-
const appDefinition = {
|
|
1210
|
-
vpc: {
|
|
1211
|
-
enable: true,
|
|
1212
|
-
shareAcrossStages: false, // Explicit opt-out of sharing
|
|
1213
|
-
},
|
|
1214
|
-
};
|
|
1215
|
-
|
|
1216
|
-
const discoveredResources = {
|
|
1217
|
-
defaultVpcId: 'vpc-shared',
|
|
1218
|
-
natGatewayId: 'nat-shared',
|
|
1219
|
-
};
|
|
1220
|
-
|
|
1221
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
1222
|
-
|
|
1223
|
-
// Should create new VPC (ignore discovered resources)
|
|
1224
|
-
expect(result.vpcId).toEqual({ Ref: 'FriggVPC' });
|
|
1225
|
-
expect(result.resources.FriggVPC).toBeDefined();
|
|
1226
|
-
expect(result.resources.FriggVPC.Properties.CidrBlock).toBe('10.0.0.0/16');
|
|
1227
|
-
|
|
1228
|
-
// Should create stage-specific subnets with Fn::Cidr (dynamic from VPC CIDR)
|
|
1229
|
-
expect(result.resources.FriggPrivateSubnet1).toBeDefined();
|
|
1230
|
-
expect(result.resources.FriggPrivateSubnet2).toBeDefined();
|
|
1231
|
-
|
|
1232
|
-
// Subnets should use CloudFormation Fn::Cidr, NOT hardcoded 172.31.x.x
|
|
1233
|
-
expect(result.resources.FriggPrivateSubnet1.Properties.CidrBlock).toEqual({
|
|
1234
|
-
'Fn::Select': [0, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
|
|
1235
|
-
});
|
|
1236
|
-
expect(result.resources.FriggPrivateSubnet2.Properties.CidrBlock).toEqual({
|
|
1237
|
-
'Fn::Select': [1, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
|
|
1238
|
-
});
|
|
1239
|
-
|
|
1240
|
-
// Should create new NAT Gateway
|
|
1241
|
-
expect(result.resources.FriggNATGateway).toBeDefined();
|
|
1242
|
-
});
|
|
1243
|
-
|
|
1244
|
-
it('should default to shared VPC when shareAcrossStages is not specified', async () => {
|
|
1245
|
-
const appDefinition = {
|
|
1246
|
-
vpc: {
|
|
1247
|
-
enable: true,
|
|
1248
|
-
// shareAcrossStages not specified - should default to true
|
|
1249
|
-
},
|
|
1250
|
-
};
|
|
1251
|
-
|
|
1252
|
-
const discoveredResources = {
|
|
1253
|
-
defaultVpcId: 'vpc-discovered',
|
|
1254
|
-
};
|
|
1255
|
-
|
|
1256
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
1257
|
-
|
|
1258
|
-
// Should use discovered VPC by default (backwards compatibility)
|
|
1259
|
-
expect(result.vpcId).toBe('vpc-discovered');
|
|
1260
|
-
expect(result.resources.FriggVPC).toBeUndefined();
|
|
1261
|
-
});
|
|
1262
|
-
});
|
|
1263
|
-
|
|
1264
|
-
describe('generateSubnetCidrs()', () => {
|
|
1265
|
-
it('should use CloudFormation Fn::Cidr for create-new mode', () => {
|
|
1266
|
-
const cidrs = vpcBuilder.generateSubnetCidrs('create-new', {});
|
|
1267
|
-
|
|
1268
|
-
expect(cidrs.private1).toEqual({
|
|
1269
|
-
'Fn::Select': [0, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
|
|
1270
|
-
});
|
|
1271
|
-
expect(cidrs.private2).toEqual({
|
|
1272
|
-
'Fn::Select': [1, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
|
|
1273
|
-
});
|
|
1274
|
-
expect(cidrs.public1).toEqual({
|
|
1275
|
-
'Fn::Select': [2, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
|
|
1276
|
-
});
|
|
1277
|
-
expect(cidrs.public2).toEqual({
|
|
1278
|
-
'Fn::Select': [3, { 'Fn::Cidr': ['10.0.0.0/16', 4, 8] }]
|
|
1279
|
-
});
|
|
1280
|
-
});
|
|
1281
|
-
|
|
1282
|
-
it('should use default static CIDRs when no existing subnets in VPC', () => {
|
|
1283
|
-
const discoveredResources = {
|
|
1284
|
-
subnets: []
|
|
1285
|
-
};
|
|
1286
|
-
|
|
1287
|
-
const cidrs = vpcBuilder.generateSubnetCidrs('discover', discoveredResources);
|
|
1288
|
-
|
|
1289
|
-
expect(cidrs.private1).toBe('172.31.240.0/24');
|
|
1290
|
-
expect(cidrs.private2).toBe('172.31.241.0/24');
|
|
1291
|
-
expect(cidrs.public1).toBe('172.31.250.0/24');
|
|
1292
|
-
expect(cidrs.public2).toBe('172.31.251.0/24');
|
|
1293
|
-
});
|
|
1294
|
-
|
|
1295
|
-
it('should avoid CIDR conflicts with existing subnets', () => {
|
|
1296
|
-
const discoveredResources = {
|
|
1297
|
-
subnets: [
|
|
1298
|
-
{ CidrBlock: '172.31.240.0/24' }, // Conflicts with default private1
|
|
1299
|
-
{ CidrBlock: '172.31.241.0/24' }, // Conflicts with default private2
|
|
1300
|
-
{ CidrBlock: '172.31.0.0/20' }, // Default VPC subnet
|
|
1301
|
-
{ CidrBlock: '172.31.16.0/20' }, // Default VPC subnet
|
|
1302
|
-
]
|
|
1303
|
-
};
|
|
1304
|
-
|
|
1305
|
-
const cidrs = vpcBuilder.generateSubnetCidrs('discover', discoveredResources);
|
|
1306
|
-
|
|
1307
|
-
// Should skip 240 and 241 (already taken), use 242-243 for private, 250-251 for public
|
|
1308
|
-
expect(cidrs.private1).toBe('172.31.242.0/24');
|
|
1309
|
-
expect(cidrs.private2).toBe('172.31.243.0/24');
|
|
1310
|
-
expect(cidrs.public1).toBe('172.31.250.0/24'); // Public range starts at 250
|
|
1311
|
-
expect(cidrs.public2).toBe('172.31.251.0/24');
|
|
1312
|
-
});
|
|
1313
|
-
|
|
1314
|
-
it('should find first available CIDR blocks when some in range are taken', () => {
|
|
1315
|
-
const discoveredResources = {
|
|
1316
|
-
subnets: [
|
|
1317
|
-
{ CidrBlock: '172.31.240.0/24' },
|
|
1318
|
-
{ CidrBlock: '172.31.242.0/24' },
|
|
1319
|
-
{ CidrBlock: '172.31.244.0/24' },
|
|
1320
|
-
]
|
|
1321
|
-
};
|
|
1322
|
-
|
|
1323
|
-
const cidrs = vpcBuilder.generateSubnetCidrs('discover', discoveredResources);
|
|
1324
|
-
|
|
1325
|
-
// Should use 241, 243 for private (filling gaps), 250, 251 for public
|
|
1326
|
-
expect(cidrs.private1).toBe('172.31.241.0/24');
|
|
1327
|
-
expect(cidrs.private2).toBe('172.31.243.0/24');
|
|
1328
|
-
expect(cidrs.public1).toBe('172.31.250.0/24'); // Public range starts at 250
|
|
1329
|
-
expect(cidrs.public2).toBe('172.31.251.0/24');
|
|
1330
|
-
});
|
|
1331
|
-
|
|
1332
|
-
it('should handle missing discoveredResources gracefully', () => {
|
|
1333
|
-
const cidrs = vpcBuilder.generateSubnetCidrs('discover', null);
|
|
1334
|
-
|
|
1335
|
-
// Should fallback to default CIDRs
|
|
1336
|
-
expect(cidrs.private1).toBe('172.31.240.0/24');
|
|
1337
|
-
expect(cidrs.private2).toBe('172.31.241.0/24');
|
|
1338
|
-
});
|
|
1339
|
-
|
|
1340
|
-
it('should handle discoveredResources without subnets array', () => {
|
|
1341
|
-
const discoveredResources = { vpcId: 'vpc-123' };
|
|
1342
|
-
|
|
1343
|
-
const cidrs = vpcBuilder.generateSubnetCidrs('discover', discoveredResources);
|
|
1344
|
-
|
|
1345
|
-
// Should fallback to default CIDRs
|
|
1346
|
-
expect(cidrs.private1).toBe('172.31.240.0/24');
|
|
1347
|
-
expect(cidrs.private2).toBe('172.31.241.0/24');
|
|
1348
|
-
});
|
|
1349
|
-
});
|
|
1350
|
-
|
|
1351
|
-
describe('Outputs', () => {
|
|
1352
|
-
it.skip('should generate VPC ID output', async () => {
|
|
1353
|
-
const appDefinition = {
|
|
1354
|
-
vpc: {
|
|
1355
|
-
enable: true,
|
|
1356
|
-
management: 'create-new',
|
|
1357
|
-
},
|
|
1358
|
-
};
|
|
1359
|
-
|
|
1360
|
-
const result = await vpcBuilder.build(appDefinition, {});
|
|
1361
|
-
|
|
1362
|
-
expect(result.outputs.VpcId).toBeDefined();
|
|
1363
|
-
});
|
|
1364
|
-
|
|
1365
|
-
it.skip('should generate subnet outputs', async () => {
|
|
1366
|
-
const appDefinition = {
|
|
1367
|
-
vpc: {
|
|
1368
|
-
enable: true,
|
|
1369
|
-
management: 'create-new',
|
|
1370
|
-
},
|
|
1371
|
-
};
|
|
1372
|
-
|
|
1373
|
-
const result = await vpcBuilder.build(appDefinition, {});
|
|
1374
|
-
|
|
1375
|
-
expect(result.outputs.PrivateSubnet1Id).toBeDefined();
|
|
1376
|
-
expect(result.outputs.PrivateSubnet2Id).toBeDefined();
|
|
1377
|
-
});
|
|
1378
|
-
});
|
|
1379
|
-
|
|
1380
|
-
describe('External VPC with stack-managed routing infrastructure pattern', () => {
|
|
1381
|
-
it('should correctly handle external VPC with NEW logical IDs (FriggPrivateRoute pattern)', async () => {
|
|
1382
|
-
// This pattern occurs when VPC/subnets/NAT are external but routing (route tables,
|
|
1383
|
-
// VPC endpoints, security groups) are managed by CloudFormation stack
|
|
1384
|
-
// This tests the NEWER naming convention
|
|
1385
|
-
const appDefinition = {
|
|
1386
|
-
vpc: { enable: true },
|
|
1387
|
-
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
1388
|
-
database: {
|
|
1389
|
-
dynamodb: { enable: true } // Enable DynamoDB to create DynamoDB VPC endpoint
|
|
1390
|
-
}
|
|
1391
|
-
};
|
|
1392
|
-
|
|
1393
|
-
// Discovery results from real-world production scenario (newer stack)
|
|
1394
|
-
const discoveredResources = {
|
|
1395
|
-
fromCloudFormationStack: true,
|
|
1396
|
-
stackName: 'test-production-stack',
|
|
1397
|
-
existingLogicalIds: [
|
|
1398
|
-
'FriggLambdaSecurityGroup',
|
|
1399
|
-
'FriggLambdaRouteTable',
|
|
1400
|
-
'FriggPrivateRoute', // NEW naming
|
|
1401
|
-
'FriggPrivateSubnet1RouteTableAssociation', // NEW naming
|
|
1402
|
-
'FriggPrivateSubnet2RouteTableAssociation', // NEW naming
|
|
1403
|
-
'FriggS3VPCEndpoint', // NEW naming
|
|
1404
|
-
'FriggDynamoDBVPCEndpoint', // NEW naming
|
|
1405
|
-
'FriggKMSVPCEndpoint' // NEW naming
|
|
1406
|
-
],
|
|
1407
|
-
// Stack resources (from CloudFormation)
|
|
1408
|
-
lambdaSecurityGroupId: 'sg-01002240c6a446202',
|
|
1409
|
-
routeTableId: 'rtb-08af43bbf0775602d',
|
|
1410
|
-
s3VpcEndpointId: 'vpce-0d1ecb2c53ce9b4b8',
|
|
1411
|
-
dynamodbVpcEndpointId: 'vpce-0fb749b207f1020b0',
|
|
1412
|
-
kmsVpcEndpointId: 'vpce-0e38c25155b86de22',
|
|
1413
|
-
// External resources (discovered via queries)
|
|
1414
|
-
defaultVpcId: 'vpc-0cd17c0e06cb28b28',
|
|
1415
|
-
privateSubnetId1: 'subnet-034f6562dbbc16348',
|
|
1416
|
-
privateSubnetId2: 'subnet-0b8be2b82aeb5cdec',
|
|
1417
|
-
existingNatGatewayId: 'nat-022660c36a47e2d79'
|
|
1418
|
-
};
|
|
1419
|
-
|
|
1420
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
1421
|
-
|
|
1422
|
-
// === ASSERTIONS: Template Structure ===
|
|
1423
|
-
|
|
1424
|
-
// 1. VPC should be external (not in template)
|
|
1425
|
-
expect(result.resources.FriggVPC).toBeUndefined();
|
|
1426
|
-
expect(result.vpcId).toBe('vpc-0cd17c0e06cb28b28');
|
|
1427
|
-
|
|
1428
|
-
// 2. Security Group MUST be in template (stack-managed)
|
|
1429
|
-
expect(result.resources.FriggLambdaSecurityGroup).toBeDefined();
|
|
1430
|
-
expect(result.resources.FriggLambdaSecurityGroup.Type).toBe('AWS::EC2::SecurityGroup');
|
|
1431
|
-
expect(result.vpcConfig.securityGroupIds).toEqual([{ Ref: 'FriggLambdaSecurityGroup' }]);
|
|
1432
|
-
|
|
1433
|
-
// 3. Subnets should be external (use hardcoded IDs, not in template)
|
|
1434
|
-
expect(result.resources.FriggPrivateSubnet1).toBeUndefined();
|
|
1435
|
-
expect(result.resources.FriggPrivateSubnet2).toBeUndefined();
|
|
1436
|
-
expect(result.vpcConfig.subnetIds).toEqual([
|
|
1437
|
-
'subnet-034f6562dbbc16348',
|
|
1438
|
-
'subnet-0b8be2b82aeb5cdec'
|
|
1439
|
-
]);
|
|
1440
|
-
|
|
1441
|
-
// 4. NAT Gateway should be external (not in template)
|
|
1442
|
-
expect(result.resources.FriggNATGateway).toBeUndefined();
|
|
1443
|
-
expect(result.resources.FriggNATGatewayEIP).toBeUndefined();
|
|
1444
|
-
expect(result.natGatewayId).toBe('nat-022660c36a47e2d79');
|
|
1445
|
-
|
|
1446
|
-
// 5. Route table MUST be in template (stack-managed)
|
|
1447
|
-
expect(result.resources.FriggLambdaRouteTable).toBeDefined();
|
|
1448
|
-
expect(result.resources.FriggLambdaRouteTable.Type).toBe('AWS::EC2::RouteTable');
|
|
1449
|
-
|
|
1450
|
-
// 6. Route table associations MUST be in template
|
|
1451
|
-
expect(result.resources.FriggPrivateSubnet1RouteTableAssociation).toBeDefined();
|
|
1452
|
-
expect(result.resources.FriggPrivateSubnet2RouteTableAssociation).toBeDefined();
|
|
1453
|
-
|
|
1454
|
-
// 7. VPC Endpoints MUST be in template (stack-managed, prevents deletion)
|
|
1455
|
-
expect(result.resources.FriggS3VPCEndpoint).toBeDefined();
|
|
1456
|
-
expect(result.resources.FriggS3VPCEndpoint.Properties.VpcEndpointType).toBe('Gateway');
|
|
1457
|
-
|
|
1458
|
-
expect(result.resources.FriggDynamoDBVPCEndpoint).toBeDefined();
|
|
1459
|
-
expect(result.resources.FriggDynamoDBVPCEndpoint.Properties.VpcEndpointType).toBe('Gateway');
|
|
1460
|
-
|
|
1461
|
-
expect(result.resources.FriggKMSVPCEndpoint).toBeDefined();
|
|
1462
|
-
expect(result.resources.FriggKMSVPCEndpoint.Properties.VpcEndpointType).toBe('Interface');
|
|
1463
|
-
|
|
1464
|
-
// 8. VPC Endpoint Security Group needed for interface endpoints
|
|
1465
|
-
expect(result.resources.FriggVPCEndpointSecurityGroup).toBeDefined();
|
|
1466
|
-
|
|
1467
|
-
// === ASSERTIONS: Resource Count ===
|
|
1468
|
-
const resourceKeys = Object.keys(result.resources);
|
|
1469
|
-
const friggResources = resourceKeys.filter(k => k.startsWith('Frigg') || k.startsWith('VPC'));
|
|
1470
|
-
|
|
1471
|
-
// Should have routing infrastructure + endpoints + security groups
|
|
1472
|
-
// NOT full VPC (no FriggVPC, FriggPrivateSubnet1/2, FriggNATGateway)
|
|
1473
|
-
expect(friggResources).toContain('FriggLambdaSecurityGroup');
|
|
1474
|
-
expect(friggResources).toContain('FriggLambdaRouteTable');
|
|
1475
|
-
expect(friggResources).toContain('FriggS3VPCEndpoint');
|
|
1476
|
-
expect(friggResources).toContain('FriggDynamoDBVPCEndpoint');
|
|
1477
|
-
expect(friggResources).toContain('FriggKMSVPCEndpoint');
|
|
1478
|
-
expect(friggResources).not.toContain('FriggVPC');
|
|
1479
|
-
expect(friggResources).not.toContain('FriggPrivateSubnet1');
|
|
1480
|
-
expect(friggResources).not.toContain('FriggNATGateway');
|
|
1481
|
-
});
|
|
1482
|
-
|
|
1483
|
-
it('should use OLD logical IDs for backwards compatibility (FriggNATRoute, VPCEndpointS3 pattern)', async () => {
|
|
1484
|
-
// CRITICAL TEST: Real Frontify production stack uses OLD naming convention
|
|
1485
|
-
// Stack currently has: FriggNATRoute, VPCEndpointS3, VPCEndpointDynamoDB
|
|
1486
|
-
// We MUST use these same logical IDs to avoid AlreadyExists errors
|
|
1487
|
-
const appDefinition = {
|
|
1488
|
-
vpc: {
|
|
1489
|
-
enable: true,
|
|
1490
|
-
ownership: {
|
|
1491
|
-
securityGroup: 'external'
|
|
1492
|
-
},
|
|
1493
|
-
external: {
|
|
1494
|
-
securityGroupIds: ['sg-0c5e0d0e4a2f5efcf']
|
|
1495
|
-
}
|
|
1496
|
-
},
|
|
1497
|
-
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
1498
|
-
database: {
|
|
1499
|
-
dynamodb: { enable: true }
|
|
1500
|
-
}
|
|
1501
|
-
};
|
|
1502
|
-
|
|
1503
|
-
// Discovery results matching ACTUAL Frontify production stack
|
|
1504
|
-
const discoveredResources = {
|
|
1505
|
-
fromCloudFormationStack: true,
|
|
1506
|
-
stackName: 'create-frigg-app-production',
|
|
1507
|
-
existingLogicalIds: [
|
|
1508
|
-
'FriggLambdaRouteTable',
|
|
1509
|
-
'FriggNATRoute', // OLD naming
|
|
1510
|
-
'FriggSubnet1RouteAssociation', // OLD naming
|
|
1511
|
-
'FriggSubnet2RouteAssociation', // OLD naming
|
|
1512
|
-
'VPCEndpointS3', // OLD naming
|
|
1513
|
-
'VPCEndpointDynamoDB' // OLD naming
|
|
1514
|
-
],
|
|
1515
|
-
// Structured discovery (what resolver needs)
|
|
1516
|
-
_structured: {
|
|
1517
|
-
stackManaged: [
|
|
1518
|
-
{ logicalId: 'FriggLambdaRouteTable', physicalId: 'rtb-08af43bbf0775602d', resourceType: 'AWS::EC2::RouteTable' },
|
|
1519
|
-
{ logicalId: 'FriggNATRoute', physicalId: 'rtb-08af43bbf0775602d|0.0.0.0/0', resourceType: 'AWS::EC2::Route' },
|
|
1520
|
-
{ logicalId: 'VPCEndpointS3', physicalId: 'vpce-0352ceac2124c14be', resourceType: 'AWS::EC2::VPCEndpoint' },
|
|
1521
|
-
{ logicalId: 'VPCEndpointDynamoDB', physicalId: 'vpce-0b06c4f631199ea68', resourceType: 'AWS::EC2::VPCEndpoint' }
|
|
1522
|
-
],
|
|
1523
|
-
external: [
|
|
1524
|
-
{ physicalId: 'vpc-01cd124575c683a17', resourceType: 'AWS::EC2::VPC' },
|
|
1525
|
-
{ physicalId: 'sg-0c5e0d0e4a2f5efcf', resourceType: 'AWS::EC2::SecurityGroup' },
|
|
1526
|
-
{ physicalId: 'subnet-0bbca02e9981df72c', resourceType: 'AWS::EC2::Subnet' },
|
|
1527
|
-
{ physicalId: 'subnet-005f7092b91efaaeb', resourceType: 'AWS::EC2::Subnet' },
|
|
1528
|
-
{ physicalId: 'nat-05a536cbe7056325f', resourceType: 'AWS::EC2::NatGateway' }
|
|
1529
|
-
]
|
|
1530
|
-
},
|
|
1531
|
-
// Flat discovery (for backwards compatibility)
|
|
1532
|
-
routeTableId: 'rtb-08af43bbf0775602d',
|
|
1533
|
-
natRoute: 'rtb-08af43bbf0775602d|0.0.0.0/0',
|
|
1534
|
-
s3VpcEndpointId: 'vpce-0352ceac2124c14be',
|
|
1535
|
-
dynamodbVpcEndpointId: 'vpce-0b06c4f631199ea68',
|
|
1536
|
-
// External resources
|
|
1537
|
-
defaultVpcId: 'vpc-01cd124575c683a17',
|
|
1538
|
-
defaultSecurityGroupId: 'sg-0c5e0d0e4a2f5efcf',
|
|
1539
|
-
privateSubnetId1: 'subnet-0bbca02e9981df72c',
|
|
1540
|
-
privateSubnetId2: 'subnet-005f7092b91efaaeb',
|
|
1541
|
-
existingNatGatewayId: 'nat-05a536cbe7056325f'
|
|
1542
|
-
};
|
|
1543
|
-
|
|
1544
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
1545
|
-
|
|
1546
|
-
// CRITICAL: Must use OLD logical IDs to match existing stack
|
|
1547
|
-
expect(result.resources.FriggNATRoute).toBeDefined();
|
|
1548
|
-
expect(result.resources.FriggNATRoute.Type).toBe('AWS::EC2::Route');
|
|
1549
|
-
expect(result.resources.FriggPrivateRoute).toBeUndefined(); // Should NOT create new ID
|
|
1550
|
-
|
|
1551
|
-
expect(result.resources.FriggSubnet1RouteAssociation).toBeDefined();
|
|
1552
|
-
expect(result.resources.FriggSubnet2RouteAssociation).toBeDefined();
|
|
1553
|
-
expect(result.resources.FriggPrivateSubnet1RouteTableAssociation).toBeUndefined();
|
|
1554
|
-
expect(result.resources.FriggPrivateSubnet2RouteTableAssociation).toBeUndefined();
|
|
1555
|
-
|
|
1556
|
-
expect(result.resources.VPCEndpointS3).toBeDefined();
|
|
1557
|
-
expect(result.resources.VPCEndpointS3.Type).toBe('AWS::EC2::VPCEndpoint');
|
|
1558
|
-
expect(result.resources.FriggS3VPCEndpoint).toBeUndefined(); // Should NOT create new ID
|
|
1559
|
-
|
|
1560
|
-
expect(result.resources.VPCEndpointDynamoDB).toBeDefined();
|
|
1561
|
-
expect(result.resources.VPCEndpointDynamoDB.Type).toBe('AWS::EC2::VPCEndpoint');
|
|
1562
|
-
expect(result.resources.FriggDynamoDBVPCEndpoint).toBeUndefined(); // Should NOT create new ID
|
|
1563
|
-
|
|
1564
|
-
// Route table should still be created
|
|
1565
|
-
expect(result.resources.FriggLambdaRouteTable).toBeDefined();
|
|
1566
|
-
});
|
|
1567
|
-
|
|
1568
|
-
it('should convert OLD logical IDs to structured discovery stackManaged array', () => {
|
|
1569
|
-
// TDD test: Verify that VPCEndpointS3 in existingLogicalIds gets added to stackManaged
|
|
1570
|
-
const flatDiscovery = {
|
|
1571
|
-
fromCloudFormationStack: true,
|
|
1572
|
-
stackName: 'create-frigg-app-production',
|
|
1573
|
-
existingLogicalIds: [
|
|
1574
|
-
'VPCEndpointS3', // OLD naming
|
|
1575
|
-
'VPCEndpointDynamoDB', // OLD naming
|
|
1576
|
-
'FriggNATRoute' // OLD naming
|
|
1577
|
-
],
|
|
1578
|
-
s3VpcEndpointId: 'vpce-0352ceac2124c14be',
|
|
1579
|
-
dynamodbVpcEndpointId: 'vpce-0b06c4f631199ea68',
|
|
1580
|
-
natRoute: 'rtb-xxx|0.0.0.0/0'
|
|
1581
|
-
};
|
|
1582
|
-
|
|
1583
|
-
const structured = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
|
|
1584
|
-
|
|
1585
|
-
// CRITICAL: Old logical IDs should be in stackManaged array
|
|
1586
|
-
expect(structured.stackManaged).toContainEqual(
|
|
1587
|
-
expect.objectContaining({
|
|
1588
|
-
logicalId: 'VPCEndpointS3',
|
|
1589
|
-
physicalId: 'vpce-0352ceac2124c14be',
|
|
1590
|
-
resourceType: 'AWS::EC2::VPCEndpoint'
|
|
1591
|
-
})
|
|
1592
|
-
);
|
|
1593
|
-
expect(structured.stackManaged).toContainEqual(
|
|
1594
|
-
expect.objectContaining({
|
|
1595
|
-
logicalId: 'VPCEndpointDynamoDB',
|
|
1596
|
-
physicalId: 'vpce-0b06c4f631199ea68',
|
|
1597
|
-
resourceType: 'AWS::EC2::VPCEndpoint'
|
|
1598
|
-
})
|
|
1599
|
-
);
|
|
1600
|
-
expect(structured.stackManaged).toContainEqual(
|
|
1601
|
-
expect.objectContaining({
|
|
1602
|
-
logicalId: 'FriggNATRoute',
|
|
1603
|
-
physicalId: 'rtb-xxx|0.0.0.0/0',
|
|
1604
|
-
resourceType: 'AWS::EC2::Route'
|
|
1605
|
-
})
|
|
1606
|
-
);
|
|
1607
|
-
});
|
|
1608
|
-
});
|
|
1609
|
-
|
|
1610
|
-
describe('convertFlatDiscoveryToStructured - Direct Properties', () => {
|
|
1611
|
-
it('should copy flat discovery properties to structured discovery for resolver access', () => {
|
|
1612
|
-
const flatDiscovery = {
|
|
1613
|
-
fromCloudFormationStack: true,
|
|
1614
|
-
defaultVpcId: 'vpc-123',
|
|
1615
|
-
defaultSecurityGroupId: 'sg-default-456',
|
|
1616
|
-
lambdaSecurityGroupId: 'sg-lambda-789',
|
|
1617
|
-
privateSubnetId1: 'subnet-1',
|
|
1618
|
-
privateSubnetId2: 'subnet-2',
|
|
1619
|
-
natGatewayId: 'nat-123'
|
|
1620
|
-
};
|
|
1621
|
-
|
|
1622
|
-
const result = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
|
|
1623
|
-
|
|
1624
|
-
// Direct properties should be copied for resolver access
|
|
1625
|
-
expect(result.defaultVpcId).toBe('vpc-123');
|
|
1626
|
-
expect(result.defaultSecurityGroupId).toBe('sg-default-456');
|
|
1627
|
-
expect(result.lambdaSecurityGroupId).toBe('sg-lambda-789');
|
|
1628
|
-
expect(result.privateSubnetId1).toBe('subnet-1');
|
|
1629
|
-
expect(result.privateSubnetId2).toBe('subnet-2');
|
|
1630
|
-
expect(result.natGatewayId).toBe('nat-123');
|
|
1631
|
-
});
|
|
1632
|
-
});
|
|
1633
|
-
|
|
1634
|
-
describe('VPC Endpoint Security Group with External Lambda SG', () => {
|
|
1635
|
-
it('should use external Lambda SG ID (not Ref) for VPC endpoint SG when Lambda SG is external', async () => {
|
|
1636
|
-
const appDefinition = {
|
|
1637
|
-
vpc: {
|
|
1638
|
-
enable: true,
|
|
1639
|
-
enableVPCEndpoints: true,
|
|
1640
|
-
ownership: {
|
|
1641
|
-
securityGroup: 'external' // External Lambda SG
|
|
1642
|
-
}
|
|
1643
|
-
},
|
|
1644
|
-
encryption: { fieldLevelEncryptionMethod: 'kms' }
|
|
1645
|
-
};
|
|
1646
|
-
const discoveredResources = {
|
|
1647
|
-
fromCloudFormationStack: true,
|
|
1648
|
-
defaultVpcId: 'vpc-123',
|
|
1649
|
-
defaultSecurityGroupId: 'sg-default-456', // Default VPC SG
|
|
1650
|
-
lambdaSecurityGroupId: 'sg-stack-789', // Stack-managed SG (will be ignored)
|
|
1651
|
-
privateSubnetId1: 'subnet-1',
|
|
1652
|
-
privateSubnetId2: 'subnet-2',
|
|
1653
|
-
natGatewayId: 'nat-123',
|
|
1654
|
-
existingLogicalIds: ['FriggS3VPCEndpoint', 'FriggKMSVPCEndpoint'],
|
|
1655
|
-
s3VpcEndpointId: 'vpce-s3-stack',
|
|
1656
|
-
kmsVpcEndpointId: 'vpce-kms-stack'
|
|
1657
|
-
};
|
|
1658
|
-
|
|
1659
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
1660
|
-
|
|
1661
|
-
// VPC Endpoint SG should be created
|
|
1662
|
-
expect(result.resources.FriggVPCEndpointSecurityGroup).toBeDefined();
|
|
1663
|
-
|
|
1664
|
-
// CRITICAL: Should use external Lambda SG ID directly, NOT a CloudFormation Ref
|
|
1665
|
-
const ingressRule = result.resources.FriggVPCEndpointSecurityGroup.Properties.SecurityGroupIngress[0];
|
|
1666
|
-
expect(ingressRule.SourceSecurityGroupId).toBe('sg-default-456'); // Direct ID, not { Ref: 'FriggLambdaSecurityGroup' }
|
|
1667
|
-
expect(typeof ingressRule.SourceSecurityGroupId).toBe('string');
|
|
1668
|
-
|
|
1669
|
-
// Verify FriggLambdaSecurityGroup is NOT in the template
|
|
1670
|
-
expect(result.resources.FriggLambdaSecurityGroup).toBeUndefined();
|
|
1671
|
-
});
|
|
1672
|
-
|
|
1673
|
-
it('should use CloudFormation Ref when Lambda SG is stack-managed', async () => {
|
|
1674
|
-
const appDefinition = {
|
|
1675
|
-
vpc: {
|
|
1676
|
-
enable: true,
|
|
1677
|
-
enableVPCEndpoints: true,
|
|
1678
|
-
ownership: {
|
|
1679
|
-
securityGroup: 'stack' // Stack-managed Lambda SG
|
|
1680
|
-
}
|
|
1681
|
-
},
|
|
1682
|
-
encryption: { fieldLevelEncryptionMethod: 'kms' }
|
|
1683
|
-
};
|
|
1684
|
-
const discoveredResources = {
|
|
1685
|
-
defaultVpcId: 'vpc-123',
|
|
1686
|
-
privateSubnetId1: 'subnet-1',
|
|
1687
|
-
privateSubnetId2: 'subnet-2'
|
|
1688
|
-
};
|
|
1689
|
-
|
|
1690
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
1691
|
-
|
|
1692
|
-
// VPC Endpoint SG should be created
|
|
1693
|
-
expect(result.resources.FriggVPCEndpointSecurityGroup).toBeDefined();
|
|
1694
|
-
|
|
1695
|
-
// Should use CloudFormation Ref when Lambda SG is in stack
|
|
1696
|
-
const ingressRule = result.resources.FriggVPCEndpointSecurityGroup.Properties.SecurityGroupIngress[0];
|
|
1697
|
-
expect(ingressRule.SourceSecurityGroupId).toEqual({ Ref: 'FriggLambdaSecurityGroup' });
|
|
1698
|
-
|
|
1699
|
-
// Verify FriggLambdaSecurityGroup IS in the template
|
|
1700
|
-
expect(result.resources.FriggLambdaSecurityGroup).toBeDefined();
|
|
1701
|
-
});
|
|
1702
|
-
});
|
|
1703
|
-
|
|
1704
|
-
describe('convertFlatDiscoveryToStructured - VPC Endpoints from CloudFormation', () => {
|
|
1705
|
-
it('should add VPC endpoints to stackManaged when in existingLogicalIds', () => {
|
|
1706
|
-
const flatDiscovery = {
|
|
1707
|
-
fromCloudFormationStack: true,
|
|
1708
|
-
stackName: 'test-stack',
|
|
1709
|
-
existingLogicalIds: [
|
|
1710
|
-
'FriggS3VPCEndpoint',
|
|
1711
|
-
'FriggDynamoDBVPCEndpoint',
|
|
1712
|
-
'FriggKMSVPCEndpoint'
|
|
1713
|
-
],
|
|
1714
|
-
s3VpcEndpointId: 'vpce-s3-stack',
|
|
1715
|
-
dynamodbVpcEndpointId: 'vpce-ddb-stack',
|
|
1716
|
-
kmsVpcEndpointId: 'vpce-kms-stack'
|
|
1717
|
-
};
|
|
1718
|
-
|
|
1719
|
-
const result = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
|
|
1720
|
-
|
|
1721
|
-
// VPC endpoints should be in stackManaged (not external)
|
|
1722
|
-
expect(result.stackManaged).toContainEqual(
|
|
1723
|
-
expect.objectContaining({
|
|
1724
|
-
logicalId: 'FriggS3VPCEndpoint',
|
|
1725
|
-
physicalId: 'vpce-s3-stack',
|
|
1726
|
-
resourceType: 'AWS::EC2::VPCEndpoint'
|
|
1727
|
-
})
|
|
1728
|
-
);
|
|
1729
|
-
expect(result.stackManaged).toContainEqual(
|
|
1730
|
-
expect.objectContaining({
|
|
1731
|
-
logicalId: 'FriggDynamoDBVPCEndpoint',
|
|
1732
|
-
physicalId: 'vpce-ddb-stack',
|
|
1733
|
-
resourceType: 'AWS::EC2::VPCEndpoint'
|
|
1734
|
-
})
|
|
1735
|
-
);
|
|
1736
|
-
expect(result.stackManaged).toContainEqual(
|
|
1737
|
-
expect.objectContaining({
|
|
1738
|
-
logicalId: 'FriggKMSVPCEndpoint',
|
|
1739
|
-
physicalId: 'vpce-kms-stack',
|
|
1740
|
-
resourceType: 'AWS::EC2::VPCEndpoint'
|
|
1741
|
-
})
|
|
1742
|
-
);
|
|
1743
|
-
|
|
1744
|
-
// Should NOT be in external array
|
|
1745
|
-
expect(result.external.some(r => r.physicalId === 'vpce-s3-stack')).toBe(false);
|
|
1746
|
-
expect(result.external.some(r => r.physicalId === 'vpce-ddb-stack')).toBe(false);
|
|
1747
|
-
expect(result.external.some(r => r.physicalId === 'vpce-kms-stack')).toBe(false);
|
|
1748
|
-
});
|
|
1749
|
-
|
|
1750
|
-
it('should add VPC endpoints to external when NOT in existingLogicalIds', () => {
|
|
1751
|
-
const flatDiscovery = {
|
|
1752
|
-
fromCloudFormationStack: false, // AWS API discovery
|
|
1753
|
-
s3VpcEndpointId: 'vpce-s3-external',
|
|
1754
|
-
dynamodbVpcEndpointId: 'vpce-ddb-external'
|
|
1755
|
-
};
|
|
1756
|
-
|
|
1757
|
-
const result = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
|
|
1758
|
-
|
|
1759
|
-
// Should be in external (AWS discovery)
|
|
1760
|
-
expect(result.external).toContainEqual(
|
|
1761
|
-
expect.objectContaining({
|
|
1762
|
-
physicalId: 'vpce-s3-external',
|
|
1763
|
-
resourceType: 'AWS::EC2::VPCEndpoint',
|
|
1764
|
-
source: 'aws-discovery'
|
|
1765
|
-
})
|
|
1766
|
-
);
|
|
1767
|
-
|
|
1768
|
-
// Should NOT be in stackManaged
|
|
1769
|
-
expect(result.stackManaged.some(r => r.physicalId === 'vpce-s3-external')).toBe(false);
|
|
1770
|
-
});
|
|
1771
|
-
|
|
1772
|
-
it('should preserve existing VPC endpoints and only create missing ones', async () => {
|
|
1773
|
-
const appDefinition = {
|
|
1774
|
-
vpc: { enable: true },
|
|
1775
|
-
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
1776
|
-
};
|
|
1777
|
-
|
|
1778
|
-
const discoveredResources = {
|
|
1779
|
-
fromCloudFormationStack: true,
|
|
1780
|
-
stackName: 'test-stack',
|
|
1781
|
-
existingLogicalIds: [
|
|
1782
|
-
'FriggS3VPCEndpoint', // In stack
|
|
1783
|
-
'FriggDynamoDBVPCEndpoint', // In stack
|
|
1784
|
-
'FriggKMSVPCEndpoint' // In stack
|
|
1785
|
-
// SecretsManager and SQS NOT in stack (were deleted)
|
|
1786
|
-
],
|
|
1787
|
-
defaultVpcId: 'vpc-123',
|
|
1788
|
-
privateSubnetId1: 'subnet-1',
|
|
1789
|
-
privateSubnetId2: 'subnet-2',
|
|
1790
|
-
lambdaSecurityGroupId: 'sg-123',
|
|
1791
|
-
routeTableId: 'rtb-123',
|
|
1792
|
-
// Endpoints in stack
|
|
1793
|
-
s3VpcEndpointId: 'vpce-s3-existing',
|
|
1794
|
-
dynamodbVpcEndpointId: 'vpce-ddb-existing',
|
|
1795
|
-
kmsVpcEndpointId: 'vpce-kms-existing'
|
|
1796
|
-
// secretsManagerVpcEndpointId and sqsVpcEndpointId NOT present
|
|
1797
|
-
};
|
|
1798
|
-
|
|
1799
|
-
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
1800
|
-
|
|
1801
|
-
// Existing endpoints MUST be in template (re-added)
|
|
1802
|
-
expect(result.resources.FriggS3VPCEndpoint).toBeDefined();
|
|
1803
|
-
expect(result.resources.FriggS3VPCEndpoint.Properties.VpcId).toBe('vpc-123');
|
|
1804
|
-
|
|
1805
|
-
expect(result.resources.FriggDynamoDBVPCEndpoint).toBeDefined();
|
|
1806
|
-
expect(result.resources.FriggDynamoDBVPCEndpoint.Properties.VpcId).toBe('vpc-123');
|
|
1807
|
-
|
|
1808
|
-
expect(result.resources.FriggKMSVPCEndpoint).toBeDefined();
|
|
1809
|
-
expect(result.resources.FriggKMSVPCEndpoint.Properties.VpcId).toBe('vpc-123');
|
|
1810
|
-
|
|
1811
|
-
// Missing endpoints should also be created
|
|
1812
|
-
expect(result.resources.FriggSecretsManagerVPCEndpoint).toBeDefined();
|
|
1813
|
-
expect(result.resources.FriggSQSVPCEndpoint).toBeDefined();
|
|
1814
|
-
|
|
1815
|
-
// VPC Endpoint Security Group should be created
|
|
1816
|
-
expect(result.resources.FriggVPCEndpointSecurityGroup).toBeDefined();
|
|
1817
|
-
});
|
|
1818
|
-
});
|
|
1819
|
-
|
|
1820
|
-
describe('convertFlatDiscoveryToStructured - CloudFormation query results', () => {
|
|
1821
|
-
it('should add VPC from CloudFormation query to external array', () => {
|
|
1822
|
-
const flatDiscovery = {
|
|
1823
|
-
fromCloudFormationStack: true,
|
|
1824
|
-
stackName: 'test-stack',
|
|
1825
|
-
existingLogicalIds: ['FriggLambdaRouteTable', 'FriggLambdaSecurityGroup'],
|
|
1826
|
-
// VPC ID was extracted from security group query (NOT a stack resource)
|
|
1827
|
-
defaultVpcId: 'vpc-extracted-from-sg',
|
|
1828
|
-
lambdaSecurityGroupId: 'sg-123',
|
|
1829
|
-
routeTableId: 'rtb-123'
|
|
1830
|
-
};
|
|
1831
|
-
|
|
1832
|
-
const result = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
|
|
1833
|
-
|
|
1834
|
-
// VPC should be in external array (discovered via query, not in stack)
|
|
1835
|
-
const vpcExternal = result.external.find(r => r.resourceType === 'AWS::EC2::VPC');
|
|
1836
|
-
expect(vpcExternal).toBeDefined();
|
|
1837
|
-
expect(vpcExternal.physicalId).toBe('vpc-extracted-from-sg');
|
|
1838
|
-
expect(vpcExternal.source).toBe('cloudformation-query');
|
|
1839
|
-
|
|
1840
|
-
// Security group SHOULD be in stackManaged (is in stack)
|
|
1841
|
-
const sgStack = result.stackManaged.find(r => r.logicalId === 'FriggLambdaSecurityGroup');
|
|
1842
|
-
expect(sgStack).toBeDefined();
|
|
1843
|
-
expect(sgStack.physicalId).toBe('sg-123');
|
|
1844
|
-
});
|
|
1845
|
-
|
|
1846
|
-
it('should add subnets from route table associations to external array', () => {
|
|
1847
|
-
const flatDiscovery = {
|
|
1848
|
-
fromCloudFormationStack: true,
|
|
1849
|
-
stackName: 'test-stack',
|
|
1850
|
-
existingLogicalIds: ['FriggLambdaRouteTable'],
|
|
1851
|
-
routeTableId: 'rtb-123',
|
|
1852
|
-
// Subnets extracted from route table associations (NOT stack resources)
|
|
1853
|
-
privateSubnetId1: 'subnet-1',
|
|
1854
|
-
privateSubnetId2: 'subnet-2'
|
|
1855
|
-
};
|
|
1856
|
-
|
|
1857
|
-
const result = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
|
|
1858
|
-
|
|
1859
|
-
// Subnets should be in external array
|
|
1860
|
-
const subnet1 = result.external.find(r => r.physicalId === 'subnet-1');
|
|
1861
|
-
const subnet2 = result.external.find(r => r.physicalId === 'subnet-2');
|
|
1862
|
-
|
|
1863
|
-
expect(subnet1).toBeDefined();
|
|
1864
|
-
expect(subnet1.resourceType).toBe('AWS::EC2::Subnet');
|
|
1865
|
-
expect(subnet1.source).toBe('cloudformation-query');
|
|
1866
|
-
|
|
1867
|
-
expect(subnet2).toBeDefined();
|
|
1868
|
-
expect(subnet2.resourceType).toBe('AWS::EC2::Subnet');
|
|
1869
|
-
expect(subnet2.source).toBe('cloudformation-query');
|
|
1870
|
-
});
|
|
1871
|
-
|
|
1872
|
-
it('should add NAT Gateway from route table queries to external array', () => {
|
|
1873
|
-
const flatDiscovery = {
|
|
1874
|
-
fromCloudFormationStack: true,
|
|
1875
|
-
stackName: 'test-stack',
|
|
1876
|
-
existingLogicalIds: ['FriggLambdaRouteTable', 'FriggPrivateRoute'],
|
|
1877
|
-
routeTableId: 'rtb-123',
|
|
1878
|
-
// NAT Gateway extracted from route table routes (NOT a stack resource)
|
|
1879
|
-
existingNatGatewayId: 'nat-extracted'
|
|
1880
|
-
};
|
|
1881
|
-
|
|
1882
|
-
const result = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
|
|
1883
|
-
|
|
1884
|
-
// NAT should be in external array
|
|
1885
|
-
const natExternal = result.external.find(r => r.resourceType === 'AWS::EC2::NatGateway');
|
|
1886
|
-
expect(natExternal).toBeDefined();
|
|
1887
|
-
expect(natExternal.physicalId).toBe('nat-extracted');
|
|
1888
|
-
expect(natExternal.source).toBe('cloudformation-query');
|
|
1889
|
-
});
|
|
1890
|
-
|
|
1891
|
-
it('should NOT add resources to external if they are in stack', () => {
|
|
1892
|
-
const flatDiscovery = {
|
|
1893
|
-
fromCloudFormationStack: true,
|
|
1894
|
-
stackName: 'test-stack',
|
|
1895
|
-
existingLogicalIds: ['FriggVPC', 'FriggPrivateSubnet1'],
|
|
1896
|
-
// These ARE in the stack
|
|
1897
|
-
defaultVpcId: 'vpc-in-stack',
|
|
1898
|
-
privateSubnetId1: 'subnet-in-stack'
|
|
1899
|
-
};
|
|
1900
|
-
|
|
1901
|
-
const result = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
|
|
1902
|
-
|
|
1903
|
-
// Should be in stackManaged, NOT external
|
|
1904
|
-
expect(result.stackManaged.some(r => r.logicalId === 'FriggVPC')).toBe(true);
|
|
1905
|
-
expect(result.stackManaged.some(r => r.logicalId === 'FriggPrivateSubnet1')).toBe(true);
|
|
1906
|
-
|
|
1907
|
-
// Should NOT be in external
|
|
1908
|
-
expect(result.external.some(r => r.physicalId === 'vpc-in-stack')).toBe(false);
|
|
1909
|
-
expect(result.external.some(r => r.physicalId === 'subnet-in-stack')).toBe(false);
|
|
1910
|
-
});
|
|
1911
|
-
|
|
1912
|
-
it('should handle external VPC pattern: stack resources + queried external references', () => {
|
|
1913
|
-
const flatDiscovery = {
|
|
1914
|
-
fromCloudFormationStack: true,
|
|
1915
|
-
stackName: 'test-production-stack',
|
|
1916
|
-
existingLogicalIds: [
|
|
1917
|
-
'FriggLambdaSecurityGroup',
|
|
1918
|
-
'FriggLambdaRouteTable',
|
|
1919
|
-
'FriggPrivateRoute',
|
|
1920
|
-
'FriggPrivateSubnet1RouteTableAssociation',
|
|
1921
|
-
'FriggPrivateSubnet2RouteTableAssociation',
|
|
1922
|
-
'FriggS3VPCEndpoint',
|
|
1923
|
-
'FriggDynamoDBVPCEndpoint',
|
|
1924
|
-
'FriggKMSVPCEndpoint'
|
|
1925
|
-
],
|
|
1926
|
-
// Stack resources
|
|
1927
|
-
lambdaSecurityGroupId: 'sg-stack-123',
|
|
1928
|
-
routeTableId: 'rtb-stack-456',
|
|
1929
|
-
s3VpcEndpointId: 'vpce-s3-stack',
|
|
1930
|
-
// External resources (discovered via queries)
|
|
1931
|
-
defaultVpcId: 'vpc-external-123',
|
|
1932
|
-
privateSubnetId1: 'subnet-external-1',
|
|
1933
|
-
privateSubnetId2: 'subnet-external-2',
|
|
1934
|
-
existingNatGatewayId: 'nat-external-789'
|
|
1935
|
-
};
|
|
1936
|
-
|
|
1937
|
-
const result = vpcBuilder.convertFlatDiscoveryToStructured(flatDiscovery);
|
|
1938
|
-
|
|
1939
|
-
// Stack resources should be in stackManaged
|
|
1940
|
-
expect(result.stackManaged).toEqual(
|
|
1941
|
-
expect.arrayContaining([
|
|
1942
|
-
expect.objectContaining({ logicalId: 'FriggLambdaSecurityGroup', physicalId: 'sg-stack-123' }),
|
|
1943
|
-
expect.objectContaining({ logicalId: 'FriggLambdaRouteTable', physicalId: 'rtb-stack-456' }),
|
|
1944
|
-
expect.objectContaining({ logicalId: 'FriggS3VPCEndpoint', physicalId: 'vpce-s3-stack' })
|
|
1945
|
-
])
|
|
1946
|
-
);
|
|
1947
|
-
|
|
1948
|
-
// External resources should be in external array
|
|
1949
|
-
expect(result.external).toEqual(
|
|
1950
|
-
expect.arrayContaining([
|
|
1951
|
-
expect.objectContaining({ physicalId: 'vpc-external-123', resourceType: 'AWS::EC2::VPC', source: 'cloudformation-query' }),
|
|
1952
|
-
expect.objectContaining({ physicalId: 'subnet-external-1', resourceType: 'AWS::EC2::Subnet', source: 'cloudformation-query' }),
|
|
1953
|
-
expect.objectContaining({ physicalId: 'subnet-external-2', resourceType: 'AWS::EC2::Subnet', source: 'cloudformation-query' }),
|
|
1954
|
-
expect.objectContaining({ physicalId: 'nat-external-789', resourceType: 'AWS::EC2::NatGateway', source: 'cloudformation-query' })
|
|
1955
|
-
])
|
|
1956
|
-
);
|
|
1957
|
-
});
|
|
1958
|
-
});
|
|
1959
|
-
});
|
|
1960
|
-
|