@friggframework/devtools 2.0.0-next.45 → 2.0.0-next.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/infrastructure/ARCHITECTURE.md +487 -0
  2. package/infrastructure/HEALTH.md +468 -0
  3. package/infrastructure/README.md +51 -0
  4. package/infrastructure/__tests__/postgres-config.test.js +914 -0
  5. package/infrastructure/__tests__/template-generation.test.js +687 -0
  6. package/infrastructure/create-frigg-infrastructure.js +1 -1
  7. package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
  8. package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
  9. package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
  10. package/infrastructure/domains/database/aurora-builder.js +809 -0
  11. package/infrastructure/domains/database/aurora-builder.test.js +950 -0
  12. package/infrastructure/domains/database/aurora-discovery.js +87 -0
  13. package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
  14. package/infrastructure/domains/database/aurora-resolver.js +210 -0
  15. package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
  16. package/infrastructure/domains/database/migration-builder.js +633 -0
  17. package/infrastructure/domains/database/migration-builder.test.js +294 -0
  18. package/infrastructure/domains/database/migration-resolver.js +163 -0
  19. package/infrastructure/domains/database/migration-resolver.test.js +337 -0
  20. package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
  21. package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
  22. package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
  23. package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
  24. package/infrastructure/domains/health/application/ports/index.js +26 -0
  25. package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
  26. package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
  27. package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
  28. package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
  29. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
  30. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
  31. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
  32. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
  33. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
  34. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
  35. package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
  36. package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
  37. package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
  38. package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
  39. package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
  40. package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
  41. package/infrastructure/domains/health/domain/entities/issue.js +299 -0
  42. package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
  43. package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
  44. package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
  45. package/infrastructure/domains/health/domain/entities/resource.js +159 -0
  46. package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
  47. package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
  48. package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
  49. package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
  50. package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
  51. package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
  52. package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
  53. package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
  54. package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
  55. package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
  56. package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
  57. package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
  58. package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
  59. package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
  60. package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
  61. package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
  62. package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
  63. package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
  64. package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
  65. package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
  66. package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
  67. package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
  68. package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
  69. package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
  70. package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
  71. package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
  72. package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
  73. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
  74. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
  75. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
  76. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
  77. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
  78. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
  79. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
  80. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
  81. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
  82. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
  83. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
  84. package/infrastructure/domains/integration/integration-builder.js +397 -0
  85. package/infrastructure/domains/integration/integration-builder.test.js +593 -0
  86. package/infrastructure/domains/integration/integration-resolver.js +170 -0
  87. package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
  88. package/infrastructure/domains/integration/websocket-builder.js +69 -0
  89. package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
  90. package/infrastructure/domains/networking/vpc-builder.js +1829 -0
  91. package/infrastructure/domains/networking/vpc-builder.test.js +1262 -0
  92. package/infrastructure/domains/networking/vpc-discovery.js +177 -0
  93. package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
  94. package/infrastructure/domains/networking/vpc-resolver.js +324 -0
  95. package/infrastructure/domains/networking/vpc-resolver.test.js +501 -0
  96. package/infrastructure/domains/parameters/ssm-builder.js +79 -0
  97. package/infrastructure/domains/parameters/ssm-builder.test.js +189 -0
  98. package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
  99. package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
  100. package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
  101. package/infrastructure/domains/security/kms-builder.js +366 -0
  102. package/infrastructure/domains/security/kms-builder.test.js +374 -0
  103. package/infrastructure/domains/security/kms-discovery.js +80 -0
  104. package/infrastructure/domains/security/kms-discovery.test.js +177 -0
  105. package/infrastructure/domains/security/kms-resolver.js +96 -0
  106. package/infrastructure/domains/security/kms-resolver.test.js +216 -0
  107. package/infrastructure/domains/shared/base-builder.js +112 -0
  108. package/infrastructure/domains/shared/base-resolver.js +186 -0
  109. package/infrastructure/domains/shared/base-resolver.test.js +305 -0
  110. package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
  111. package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
  112. package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
  113. package/infrastructure/domains/shared/cloudformation-discovery.js +375 -0
  114. package/infrastructure/domains/shared/cloudformation-discovery.test.js +590 -0
  115. package/infrastructure/domains/shared/environment-builder.js +119 -0
  116. package/infrastructure/domains/shared/environment-builder.test.js +247 -0
  117. package/infrastructure/domains/shared/providers/aws-provider-adapter.js +544 -0
  118. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +377 -0
  119. package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
  120. package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
  121. package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
  122. package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
  123. package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
  124. package/infrastructure/domains/shared/resource-discovery.js +192 -0
  125. package/infrastructure/domains/shared/resource-discovery.test.js +552 -0
  126. package/infrastructure/domains/shared/types/app-definition.js +205 -0
  127. package/infrastructure/domains/shared/types/discovery-result.js +106 -0
  128. package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
  129. package/infrastructure/domains/shared/types/index.js +46 -0
  130. package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
  131. package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
  132. package/infrastructure/domains/shared/utilities/base-definition-factory.js +380 -0
  133. package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
  134. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -0
  135. package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
  136. package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
  137. package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
  138. package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +138 -0
  139. package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +2 -1
  140. package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
  141. package/infrastructure/esbuild.config.js +53 -0
  142. package/infrastructure/infrastructure-composer.js +87 -0
  143. package/infrastructure/{serverless-template.test.js → infrastructure-composer.test.js} +115 -24
  144. package/infrastructure/scripts/build-prisma-layer.js +553 -0
  145. package/infrastructure/scripts/build-prisma-layer.test.js +102 -0
  146. package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +80 -48
  147. package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
  148. package/layers/prisma/nodejs/package.json +8 -0
  149. package/management-ui/server/utils/cliIntegration.js +1 -1
  150. package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
  151. package/package.json +11 -11
  152. package/frigg-cli/.eslintrc.js +0 -141
  153. package/frigg-cli/__tests__/unit/commands/build.test.js +0 -251
  154. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +0 -548
  155. package/frigg-cli/__tests__/unit/commands/install.test.js +0 -400
  156. package/frigg-cli/__tests__/unit/commands/ui.test.js +0 -346
  157. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +0 -366
  158. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +0 -304
  159. package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +0 -486
  160. package/frigg-cli/__tests__/utils/mock-factory.js +0 -270
  161. package/frigg-cli/__tests__/utils/prisma-mock.js +0 -194
  162. package/frigg-cli/__tests__/utils/test-fixtures.js +0 -463
  163. package/frigg-cli/__tests__/utils/test-setup.js +0 -287
  164. package/frigg-cli/build-command/index.js +0 -65
  165. package/frigg-cli/db-setup-command/index.js +0 -193
  166. package/frigg-cli/deploy-command/index.js +0 -175
  167. package/frigg-cli/generate-command/__tests__/generate-command.test.js +0 -301
  168. package/frigg-cli/generate-command/azure-generator.js +0 -43
  169. package/frigg-cli/generate-command/gcp-generator.js +0 -47
  170. package/frigg-cli/generate-command/index.js +0 -332
  171. package/frigg-cli/generate-command/terraform-generator.js +0 -555
  172. package/frigg-cli/generate-iam-command.js +0 -118
  173. package/frigg-cli/index.js +0 -75
  174. package/frigg-cli/index.test.js +0 -158
  175. package/frigg-cli/init-command/backend-first-handler.js +0 -756
  176. package/frigg-cli/init-command/index.js +0 -93
  177. package/frigg-cli/init-command/template-handler.js +0 -143
  178. package/frigg-cli/install-command/backend-js.js +0 -33
  179. package/frigg-cli/install-command/commit-changes.js +0 -16
  180. package/frigg-cli/install-command/environment-variables.js +0 -127
  181. package/frigg-cli/install-command/environment-variables.test.js +0 -136
  182. package/frigg-cli/install-command/index.js +0 -54
  183. package/frigg-cli/install-command/install-package.js +0 -13
  184. package/frigg-cli/install-command/integration-file.js +0 -30
  185. package/frigg-cli/install-command/logger.js +0 -12
  186. package/frigg-cli/install-command/template.js +0 -90
  187. package/frigg-cli/install-command/validate-package.js +0 -75
  188. package/frigg-cli/jest.config.js +0 -124
  189. package/frigg-cli/package.json +0 -54
  190. package/frigg-cli/start-command/index.js +0 -149
  191. package/frigg-cli/start-command/start-command.test.js +0 -297
  192. package/frigg-cli/test/init-command.test.js +0 -180
  193. package/frigg-cli/test/npm-registry.test.js +0 -319
  194. package/frigg-cli/ui-command/index.js +0 -154
  195. package/frigg-cli/utils/app-resolver.js +0 -319
  196. package/frigg-cli/utils/backend-path.js +0 -25
  197. package/frigg-cli/utils/database-validator.js +0 -161
  198. package/frigg-cli/utils/error-messages.js +0 -257
  199. package/frigg-cli/utils/npm-registry.js +0 -167
  200. package/frigg-cli/utils/prisma-runner.js +0 -280
  201. package/frigg-cli/utils/process-manager.js +0 -199
  202. package/frigg-cli/utils/repo-detection.js +0 -405
  203. package/infrastructure/aws-discovery.js +0 -1176
  204. package/infrastructure/aws-discovery.test.js +0 -1220
  205. package/infrastructure/serverless-template.js +0 -2094
  206. /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
  207. /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
  208. /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
  209. /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
  210. /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
  211. /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
  212. /package/infrastructure/{run-discovery.js → scripts/run-discovery.js} +0 -0
@@ -0,0 +1,177 @@
1
+ /**
2
+ * VPC Discovery Service
3
+ *
4
+ * Domain Service - Hexagonal Architecture
5
+ *
6
+ * Discovers VPC and networking resources using the cloud provider adapter.
7
+ * Adds domain-specific validation and transformation logic.
8
+ */
9
+
10
+ class VpcDiscovery {
11
+ /**
12
+ * @param {CloudProviderAdapter} provider - Cloud provider adapter instance
13
+ */
14
+ constructor(provider) {
15
+ this.provider = provider;
16
+ }
17
+
18
+ /**
19
+ * Discover VPC and networking resources
20
+ *
21
+ * @param {Object} config - Discovery configuration
22
+ * @param {string} [config.vpcId] - Specific VPC ID to discover
23
+ * @param {string} [config.serviceName] - Service name for tagging/filtering
24
+ * @param {string} [config.stage] - Deployment stage
25
+ * @returns {Promise<Object>} Discovered VPC resources with friendly property names
26
+ */
27
+ async discover(config) {
28
+ console.log('🔍 Discovering VPC resources...');
29
+
30
+ try {
31
+ const rawResources = await this.provider.discoverVpc(config);
32
+
33
+ // Transform to Frigg-friendly format
34
+ const result = {
35
+ defaultVpcId: rawResources.vpcId,
36
+ vpcCidr: rawResources.vpcCidr,
37
+ subnets: rawResources.subnets,
38
+ securityGroups: rawResources.securityGroups,
39
+ routeTables: rawResources.routeTables,
40
+ natGateways: rawResources.natGateways,
41
+ internetGateways: rawResources.internetGateways,
42
+ };
43
+
44
+ // Extract specific subnet types
45
+ const privateSubnets = rawResources.subnets.filter(
46
+ s => !s.MapPublicIpOnLaunch
47
+ );
48
+ const publicSubnets = rawResources.subnets.filter(
49
+ s => s.MapPublicIpOnLaunch
50
+ );
51
+
52
+ // Set subnet IDs for Frigg infrastructure
53
+ if (privateSubnets.length >= 1) {
54
+ result.privateSubnetId1 = privateSubnets[0].SubnetId;
55
+ }
56
+ if (privateSubnets.length >= 2) {
57
+ result.privateSubnetId2 = privateSubnets[1].SubnetId;
58
+ }
59
+ if (publicSubnets.length >= 1) {
60
+ result.publicSubnetId = publicSubnets[0].SubnetId;
61
+ result.publicSubnetId1 = publicSubnets[0].SubnetId;
62
+ }
63
+ if (publicSubnets.length >= 2) {
64
+ result.publicSubnetId2 = publicSubnets[1].SubnetId;
65
+ }
66
+
67
+ // Find default security group
68
+ const defaultSg = rawResources.securityGroups.find(
69
+ sg => sg.GroupName === 'default'
70
+ );
71
+ if (defaultSg) {
72
+ result.defaultSecurityGroupId = defaultSg.GroupId;
73
+ }
74
+
75
+ // Find default route table
76
+ const defaultRt = rawResources.routeTables.find(
77
+ rt => rt.Associations?.some(a => a.Main)
78
+ );
79
+ if (defaultRt) {
80
+ result.defaultRouteTableId = defaultRt.RouteTableId;
81
+ result.privateRouteTableId = defaultRt.RouteTableId;
82
+ }
83
+
84
+ // Find NAT gateway
85
+ const activeNat = rawResources.natGateways.find(
86
+ nat => nat.State === 'available'
87
+ );
88
+ if (activeNat) {
89
+ result.existingNatGatewayId = activeNat.NatGatewayId;
90
+
91
+ // Check if NAT is in private subnet (configuration error)
92
+ const natSubnet = rawResources.subnets.find(
93
+ s => s.SubnetId === activeNat.SubnetId
94
+ );
95
+ result.natGatewayInPrivateSubnet = natSubnet && !natSubnet.MapPublicIpOnLaunch;
96
+
97
+ // Get elastic IP allocation
98
+ if (activeNat.NatGatewayAddresses && activeNat.NatGatewayAddresses.length > 0) {
99
+ result.existingElasticIpAllocationId =
100
+ activeNat.NatGatewayAddresses[0].AllocationId;
101
+ }
102
+ }
103
+
104
+ // Find Internet Gateway
105
+ if (rawResources.internetGateways.length > 0) {
106
+ result.internetGatewayId = rawResources.internetGateways[0].InternetGatewayId;
107
+ }
108
+
109
+ // Find VPC Endpoints
110
+ if (rawResources.vpcEndpoints) {
111
+ const s3Endpoint = rawResources.vpcEndpoints.find(
112
+ ep => ep.ServiceName && ep.ServiceName.includes('.s3')
113
+ );
114
+ const dynamodbEndpoint = rawResources.vpcEndpoints.find(
115
+ ep => ep.ServiceName && ep.ServiceName.includes('.dynamodb')
116
+ );
117
+ const kmsEndpoint = rawResources.vpcEndpoints.find(
118
+ ep => ep.ServiceName && ep.ServiceName.includes('.kms')
119
+ );
120
+ const secretsManagerEndpoint = rawResources.vpcEndpoints.find(
121
+ ep => ep.ServiceName && ep.ServiceName.includes('.secretsmanager')
122
+ );
123
+ const sqsEndpoint = rawResources.vpcEndpoints.find(
124
+ ep => ep.ServiceName && ep.ServiceName.includes('.sqs')
125
+ );
126
+
127
+ if (s3Endpoint) {
128
+ result.s3VpcEndpointId = s3Endpoint.VpcEndpointId;
129
+ }
130
+ if (dynamodbEndpoint) {
131
+ result.dynamodbVpcEndpointId = dynamodbEndpoint.VpcEndpointId;
132
+ }
133
+ if (kmsEndpoint) {
134
+ result.kmsVpcEndpointId = kmsEndpoint.VpcEndpointId;
135
+ }
136
+ if (secretsManagerEndpoint) {
137
+ result.secretsManagerVpcEndpointId = secretsManagerEndpoint.VpcEndpointId;
138
+ }
139
+ if (sqsEndpoint) {
140
+ result.sqsVpcEndpointId = sqsEndpoint.VpcEndpointId;
141
+ }
142
+ }
143
+
144
+ console.log(` ✓ Found VPC: ${result.defaultVpcId}`);
145
+ if (result.privateSubnetId1) {
146
+ console.log(` ✓ Found private subnets: ${result.privateSubnetId1}, ${result.privateSubnetId2 || 'N/A'}`);
147
+ }
148
+ if (result.publicSubnetId) {
149
+ console.log(` ✓ Found public subnet: ${result.publicSubnetId}`);
150
+ }
151
+ if (result.existingNatGatewayId) {
152
+ console.log(` ✓ Found NAT Gateway: ${result.existingNatGatewayId}`);
153
+ }
154
+ if (result.s3VpcEndpointId || result.dynamodbVpcEndpointId || result.kmsVpcEndpointId || result.secretsManagerVpcEndpointId || result.sqsVpcEndpointId) {
155
+ console.log(` ✓ Found VPC Endpoints: S3=${result.s3VpcEndpointId ? 'Yes' : 'No'}, DynamoDB=${result.dynamodbVpcEndpointId ? 'Yes' : 'No'}, KMS=${result.kmsVpcEndpointId ? 'Yes' : 'No'}, SecretsManager=${result.secretsManagerVpcEndpointId ? 'Yes' : 'No'}, SQS=${result.sqsVpcEndpointId ? 'Yes' : 'No'}`);
156
+ }
157
+
158
+ return result;
159
+ } catch (error) {
160
+ console.error(' ✗ VPC discovery failed:', error.message);
161
+ return {
162
+ defaultVpcId: null,
163
+ vpcCidr: null,
164
+ privateSubnetId1: null,
165
+ privateSubnetId2: null,
166
+ publicSubnetId: null,
167
+ defaultSecurityGroupId: null,
168
+ defaultRouteTableId: null,
169
+ };
170
+ }
171
+ }
172
+ }
173
+
174
+ module.exports = {
175
+ VpcDiscovery,
176
+ };
177
+
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Tests for VPC Discovery Service
3
+ *
4
+ * Tests VPC resource discovery with mocked cloud provider
5
+ */
6
+
7
+ const { VpcDiscovery } = require('./vpc-discovery');
8
+
9
+ describe('VpcDiscovery', () => {
10
+ let mockProvider;
11
+ let vpcDiscovery;
12
+
13
+ beforeEach(() => {
14
+ mockProvider = {
15
+ discoverVpc: jest.fn(),
16
+ getName: jest.fn().mockReturnValue('aws'),
17
+ };
18
+ vpcDiscovery = new VpcDiscovery(mockProvider);
19
+ });
20
+
21
+ describe('discover()', () => {
22
+ it('should delegate to provider and transform results', async () => {
23
+ const mockProviderResponse = {
24
+ vpcId: 'vpc-123456',
25
+ vpcCidr: '172.31.0.0/16',
26
+ subnets: [
27
+ {
28
+ SubnetId: 'subnet-private1',
29
+ MapPublicIpOnLaunch: false,
30
+ AvailabilityZone: 'us-east-1a',
31
+ },
32
+ {
33
+ SubnetId: 'subnet-private2',
34
+ MapPublicIpOnLaunch: false,
35
+ AvailabilityZone: 'us-east-1b',
36
+ },
37
+ {
38
+ SubnetId: 'subnet-public1',
39
+ MapPublicIpOnLaunch: true,
40
+ AvailabilityZone: 'us-east-1a',
41
+ },
42
+ ],
43
+ securityGroups: [
44
+ { GroupId: 'sg-default', GroupName: 'default' },
45
+ { GroupId: 'sg-custom', GroupName: 'custom' },
46
+ ],
47
+ routeTables: [
48
+ {
49
+ RouteTableId: 'rtb-123',
50
+ Associations: [{ Main: true }],
51
+ },
52
+ ],
53
+ natGateways: [],
54
+ internetGateways: [
55
+ { InternetGatewayId: 'igw-123' },
56
+ ],
57
+ };
58
+
59
+ mockProvider.discoverVpc.mockResolvedValue(mockProviderResponse);
60
+
61
+ const result = await vpcDiscovery.discover({ vpcId: 'vpc-123456' });
62
+
63
+ expect(mockProvider.discoverVpc).toHaveBeenCalledWith({ vpcId: 'vpc-123456' });
64
+ expect(result.defaultVpcId).toBe('vpc-123456');
65
+ expect(result.vpcCidr).toBe('172.31.0.0/16');
66
+ expect(result.privateSubnetId1).toBe('subnet-private1');
67
+ expect(result.privateSubnetId2).toBe('subnet-private2');
68
+ expect(result.publicSubnetId).toBe('subnet-public1');
69
+ expect(result.publicSubnetId1).toBe('subnet-public1');
70
+ expect(result.defaultSecurityGroupId).toBe('sg-default');
71
+ expect(result.defaultRouteTableId).toBe('rtb-123');
72
+ expect(result.internetGatewayId).toBe('igw-123');
73
+ });
74
+
75
+ it('should handle VPC with only private subnets', async () => {
76
+ mockProvider.discoverVpc.mockResolvedValue({
77
+ vpcId: 'vpc-123',
78
+ vpcCidr: '10.0.0.0/16',
79
+ subnets: [
80
+ { SubnetId: 'subnet-1', MapPublicIpOnLaunch: false },
81
+ ],
82
+ securityGroups: [],
83
+ routeTables: [],
84
+ natGateways: [],
85
+ internetGateways: [],
86
+ });
87
+
88
+ const result = await vpcDiscovery.discover({});
89
+
90
+ expect(result.privateSubnetId1).toBe('subnet-1');
91
+ expect(result.privateSubnetId2).toBeUndefined();
92
+ expect(result.publicSubnetId).toBeUndefined();
93
+ });
94
+
95
+ it('should handle NAT gateway discovery', async () => {
96
+ mockProvider.discoverVpc.mockResolvedValue({
97
+ vpcId: 'vpc-123',
98
+ vpcCidr: '10.0.0.0/16',
99
+ subnets: [
100
+ { SubnetId: 'subnet-public', MapPublicIpOnLaunch: true },
101
+ { SubnetId: 'subnet-private', MapPublicIpOnLaunch: false },
102
+ ],
103
+ securityGroups: [],
104
+ routeTables: [],
105
+ natGateways: [
106
+ {
107
+ NatGatewayId: 'nat-123',
108
+ State: 'available',
109
+ SubnetId: 'subnet-public',
110
+ NatGatewayAddresses: [
111
+ { AllocationId: 'eipalloc-456' },
112
+ ],
113
+ },
114
+ ],
115
+ internetGateways: [],
116
+ });
117
+
118
+ const result = await vpcDiscovery.discover({});
119
+
120
+ expect(result.existingNatGatewayId).toBe('nat-123');
121
+ expect(result.natGatewayInPrivateSubnet).toBe(false);
122
+ expect(result.existingElasticIpAllocationId).toBe('eipalloc-456');
123
+ });
124
+
125
+ it('should detect NAT gateway in private subnet (configuration error)', async () => {
126
+ mockProvider.discoverVpc.mockResolvedValue({
127
+ vpcId: 'vpc-123',
128
+ vpcCidr: '10.0.0.0/16',
129
+ subnets: [
130
+ { SubnetId: 'subnet-private', MapPublicIpOnLaunch: false },
131
+ ],
132
+ securityGroups: [],
133
+ routeTables: [],
134
+ natGateways: [
135
+ {
136
+ NatGatewayId: 'nat-misplaced',
137
+ State: 'available',
138
+ SubnetId: 'subnet-private',
139
+ NatGatewayAddresses: [],
140
+ },
141
+ ],
142
+ internetGateways: [],
143
+ });
144
+
145
+ const result = await vpcDiscovery.discover({});
146
+
147
+ expect(result.existingNatGatewayId).toBe('nat-misplaced');
148
+ expect(result.natGatewayInPrivateSubnet).toBe(true);
149
+ });
150
+
151
+ it('should ignore NAT gateways that are not available', async () => {
152
+ mockProvider.discoverVpc.mockResolvedValue({
153
+ vpcId: 'vpc-123',
154
+ vpcCidr: '10.0.0.0/16',
155
+ subnets: [],
156
+ securityGroups: [],
157
+ routeTables: [],
158
+ natGateways: [
159
+ {
160
+ NatGatewayId: 'nat-pending',
161
+ State: 'pending',
162
+ SubnetId: 'subnet-public',
163
+ },
164
+ {
165
+ NatGatewayId: 'nat-failed',
166
+ State: 'failed',
167
+ SubnetId: 'subnet-public',
168
+ },
169
+ ],
170
+ internetGateways: [],
171
+ });
172
+
173
+ const result = await vpcDiscovery.discover({});
174
+
175
+ expect(result.existingNatGatewayId).toBeUndefined();
176
+ });
177
+
178
+ it('should handle discovery errors gracefully', async () => {
179
+ mockProvider.discoverVpc.mockRejectedValue(new Error('AWS API Error'));
180
+
181
+ const result = await vpcDiscovery.discover({});
182
+
183
+ expect(result.defaultVpcId).toBeNull();
184
+ expect(result.vpcCidr).toBeNull();
185
+ expect(result.privateSubnetId1).toBeNull();
186
+ });
187
+
188
+ it('should find default route table from main association', async () => {
189
+ mockProvider.discoverVpc.mockResolvedValue({
190
+ vpcId: 'vpc-123',
191
+ vpcCidr: '10.0.0.0/16',
192
+ subnets: [],
193
+ securityGroups: [],
194
+ routeTables: [
195
+ {
196
+ RouteTableId: 'rtb-custom',
197
+ Associations: [{ Main: false }],
198
+ },
199
+ {
200
+ RouteTableId: 'rtb-main',
201
+ Associations: [{ Main: true }],
202
+ },
203
+ ],
204
+ natGateways: [],
205
+ internetGateways: [],
206
+ });
207
+
208
+ const result = await vpcDiscovery.discover({});
209
+
210
+ expect(result.defaultRouteTableId).toBe('rtb-main');
211
+ expect(result.privateRouteTableId).toBe('rtb-main');
212
+ });
213
+
214
+ it('should handle multiple public subnets', async () => {
215
+ mockProvider.discoverVpc.mockResolvedValue({
216
+ vpcId: 'vpc-123',
217
+ vpcCidr: '10.0.0.0/16',
218
+ subnets: [
219
+ { SubnetId: 'subnet-public-1', MapPublicIpOnLaunch: true },
220
+ { SubnetId: 'subnet-public-2', MapPublicIpOnLaunch: true },
221
+ ],
222
+ securityGroups: [],
223
+ routeTables: [],
224
+ natGateways: [],
225
+ internetGateways: [],
226
+ });
227
+
228
+ const result = await vpcDiscovery.discover({});
229
+
230
+ expect(result.publicSubnetId).toBe('subnet-public-1');
231
+ expect(result.publicSubnetId1).toBe('subnet-public-1');
232
+ expect(result.publicSubnetId2).toBe('subnet-public-2');
233
+ });
234
+
235
+ it('should pass config to provider', async () => {
236
+ mockProvider.discoverVpc.mockResolvedValue({
237
+ vpcId: 'vpc-custom',
238
+ subnets: [],
239
+ securityGroups: [],
240
+ routeTables: [],
241
+ natGateways: [],
242
+ internetGateways: [],
243
+ });
244
+
245
+ const config = {
246
+ vpcId: 'vpc-custom',
247
+ serviceName: 'test-service',
248
+ stage: 'prod',
249
+ };
250
+
251
+ await vpcDiscovery.discover(config);
252
+
253
+ expect(mockProvider.discoverVpc).toHaveBeenCalledWith(config);
254
+ });
255
+
256
+ it('should discover all VPC endpoints (S3, DynamoDB, KMS, Secrets Manager)', async () => {
257
+ mockProvider.discoverVpc.mockResolvedValue({
258
+ vpcId: 'vpc-123',
259
+ vpcCidr: '10.0.0.0/16',
260
+ subnets: [],
261
+ securityGroups: [],
262
+ routeTables: [],
263
+ natGateways: [],
264
+ internetGateways: [],
265
+ vpcEndpoints: [
266
+ {
267
+ VpcEndpointId: 'vpce-s3-123',
268
+ ServiceName: 'com.amazonaws.us-east-1.s3',
269
+ State: 'available',
270
+ },
271
+ {
272
+ VpcEndpointId: 'vpce-ddb-456',
273
+ ServiceName: 'com.amazonaws.us-east-1.dynamodb',
274
+ State: 'available',
275
+ },
276
+ {
277
+ VpcEndpointId: 'vpce-kms-789',
278
+ ServiceName: 'com.amazonaws.us-east-1.kms',
279
+ State: 'available',
280
+ },
281
+ {
282
+ VpcEndpointId: 'vpce-sm-abc',
283
+ ServiceName: 'com.amazonaws.us-east-1.secretsmanager',
284
+ State: 'available',
285
+ },
286
+ ],
287
+ });
288
+
289
+ const result = await vpcDiscovery.discover({});
290
+
291
+ expect(result.s3VpcEndpointId).toBe('vpce-s3-123');
292
+ expect(result.dynamodbVpcEndpointId).toBe('vpce-ddb-456');
293
+ expect(result.kmsVpcEndpointId).toBe('vpce-kms-789');
294
+ expect(result.secretsManagerVpcEndpointId).toBe('vpce-sm-abc');
295
+ });
296
+
297
+ it('should handle partial VPC endpoint discovery', async () => {
298
+ mockProvider.discoverVpc.mockResolvedValue({
299
+ vpcId: 'vpc-123',
300
+ vpcCidr: '10.0.0.0/16',
301
+ subnets: [],
302
+ securityGroups: [],
303
+ routeTables: [],
304
+ natGateways: [],
305
+ internetGateways: [],
306
+ vpcEndpoints: [
307
+ {
308
+ VpcEndpointId: 'vpce-s3-123',
309
+ ServiceName: 'com.amazonaws.us-east-1.s3',
310
+ State: 'available',
311
+ },
312
+ {
313
+ VpcEndpointId: 'vpce-ddb-456',
314
+ ServiceName: 'com.amazonaws.us-east-1.dynamodb',
315
+ State: 'available',
316
+ },
317
+ // KMS and Secrets Manager are missing
318
+ ],
319
+ });
320
+
321
+ const result = await vpcDiscovery.discover({});
322
+
323
+ expect(result.s3VpcEndpointId).toBe('vpce-s3-123');
324
+ expect(result.dynamodbVpcEndpointId).toBe('vpce-ddb-456');
325
+ expect(result.kmsVpcEndpointId).toBeUndefined();
326
+ expect(result.secretsManagerVpcEndpointId).toBeUndefined();
327
+ });
328
+
329
+ it('should handle no VPC endpoints', async () => {
330
+ mockProvider.discoverVpc.mockResolvedValue({
331
+ vpcId: 'vpc-123',
332
+ vpcCidr: '10.0.0.0/16',
333
+ subnets: [],
334
+ securityGroups: [],
335
+ routeTables: [],
336
+ natGateways: [],
337
+ internetGateways: [],
338
+ vpcEndpoints: [],
339
+ });
340
+
341
+ const result = await vpcDiscovery.discover({});
342
+
343
+ expect(result.s3VpcEndpointId).toBeUndefined();
344
+ expect(result.dynamodbVpcEndpointId).toBeUndefined();
345
+ expect(result.kmsVpcEndpointId).toBeUndefined();
346
+ expect(result.secretsManagerVpcEndpointId).toBeUndefined();
347
+ });
348
+ });
349
+ });
350
+