@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,672 @@
1
+ /**
2
+ * LogicalIdMapper Tests
3
+ *
4
+ * TDD tests for logical ID mapping functionality
5
+ * Domain Layer - Service Tests
6
+ */
7
+
8
+ const { LogicalIdMapper } = require('../logical-id-mapper');
9
+
10
+ describe('LogicalIdMapper', () => {
11
+ let mapper;
12
+ let mockEc2Client;
13
+
14
+ beforeEach(() => {
15
+ // Mock EC2 client
16
+ mockEc2Client = {
17
+ send: jest.fn(),
18
+ };
19
+
20
+ mapper = new LogicalIdMapper({ region: 'us-east-1' });
21
+ mapper.ec2Client = mockEc2Client;
22
+ });
23
+
24
+ describe('mapOrphanedResourcesToLogicalIds', () => {
25
+ it('should map orphaned resources using CloudFormation tags (highest confidence)', async () => {
26
+ // Arrange
27
+ const orphanedResources = [
28
+ {
29
+ physicalId: 'vpc-12345678',
30
+ resourceType: 'AWS::EC2::VPC',
31
+ properties: {
32
+ VpcId: 'vpc-12345678',
33
+ CidrBlock: '10.0.0.0/16',
34
+ tags: {
35
+ 'aws:cloudformation:stack-name': 'acme-integrations-dev',
36
+ 'aws:cloudformation:logical-id': 'FriggVPC',
37
+ },
38
+ },
39
+ },
40
+ ];
41
+
42
+ const buildTemplate = { resources: {} };
43
+ const deployedTemplate = { resources: {} };
44
+
45
+ // Act
46
+ const result = await mapper.mapOrphanedResourcesToLogicalIds({
47
+ orphanedResources,
48
+ buildTemplate,
49
+ deployedTemplate,
50
+ });
51
+
52
+ // Assert
53
+ expect(result).toHaveLength(1);
54
+ expect(result[0]).toEqual({
55
+ logicalId: 'FriggVPC',
56
+ physicalId: 'vpc-12345678',
57
+ resourceType: 'AWS::EC2::VPC',
58
+ matchMethod: 'tag',
59
+ confidence: 'high',
60
+ });
61
+ });
62
+
63
+ it('should map VPC by contained resources when no tag found', async () => {
64
+ // Arrange
65
+ const orphanedResources = [
66
+ {
67
+ physicalId: 'vpc-12345678',
68
+ resourceType: 'AWS::EC2::VPC',
69
+ properties: {
70
+ VpcId: 'vpc-12345678',
71
+ CidrBlock: '10.0.0.0/16',
72
+ tags: {},
73
+ },
74
+ },
75
+ ];
76
+
77
+ const buildTemplate = {
78
+ resources: {
79
+ FriggVPC: { Type: 'AWS::EC2::VPC' },
80
+ MyLambda: {
81
+ Type: 'AWS::Lambda::Function',
82
+ Properties: {
83
+ VpcConfig: {
84
+ SubnetIds: [
85
+ { Ref: 'FriggPrivateSubnet1' },
86
+ { Ref: 'FriggPrivateSubnet2' },
87
+ ],
88
+ },
89
+ },
90
+ },
91
+ },
92
+ };
93
+
94
+ const deployedTemplate = {
95
+ resources: {
96
+ MyLambda: {
97
+ Type: 'AWS::Lambda::Function',
98
+ Properties: {
99
+ VpcConfig: {
100
+ SubnetIds: ['subnet-11111111', 'subnet-22222222'],
101
+ },
102
+ },
103
+ },
104
+ },
105
+ };
106
+
107
+ // Mock EC2 describe-subnets response
108
+ mockEc2Client.send.mockResolvedValueOnce({
109
+ Subnets: [
110
+ { SubnetId: 'subnet-11111111', VpcId: 'vpc-12345678' },
111
+ { SubnetId: 'subnet-22222222', VpcId: 'vpc-12345678' },
112
+ ],
113
+ });
114
+
115
+ // Act
116
+ const result = await mapper.mapOrphanedResourcesToLogicalIds({
117
+ orphanedResources,
118
+ buildTemplate,
119
+ deployedTemplate,
120
+ });
121
+
122
+ // Assert
123
+ expect(result).toHaveLength(1);
124
+ expect(result[0]).toEqual({
125
+ logicalId: 'FriggVPC',
126
+ physicalId: 'vpc-12345678',
127
+ resourceType: 'AWS::EC2::VPC',
128
+ matchMethod: 'contained-resources',
129
+ confidence: 'high',
130
+ });
131
+ });
132
+
133
+ it('should map subnet by VPC usage in Lambda functions', async () => {
134
+ // Arrange
135
+ const orphanedResources = [
136
+ {
137
+ physicalId: 'subnet-11111111',
138
+ resourceType: 'AWS::EC2::Subnet',
139
+ properties: {
140
+ SubnetId: 'subnet-11111111',
141
+ VpcId: 'vpc-12345678',
142
+ tags: {},
143
+ },
144
+ },
145
+ ];
146
+
147
+ const buildTemplate = {
148
+ resources: {
149
+ FriggPrivateSubnet1: { Type: 'AWS::EC2::Subnet' },
150
+ MyLambda: {
151
+ Type: 'AWS::Lambda::Function',
152
+ Properties: {
153
+ VpcConfig: {
154
+ SubnetIds: [{ Ref: 'FriggPrivateSubnet1' }],
155
+ },
156
+ },
157
+ },
158
+ },
159
+ };
160
+
161
+ const deployedTemplate = {
162
+ resources: {
163
+ MyLambda: {
164
+ Type: 'AWS::Lambda::Function',
165
+ Properties: {
166
+ VpcConfig: {
167
+ SubnetIds: ['subnet-11111111'],
168
+ },
169
+ },
170
+ },
171
+ },
172
+ };
173
+
174
+ // Act
175
+ const result = await mapper.mapOrphanedResourcesToLogicalIds({
176
+ orphanedResources,
177
+ buildTemplate,
178
+ deployedTemplate,
179
+ });
180
+
181
+ // Assert
182
+ expect(result).toHaveLength(1);
183
+ expect(result[0]).toEqual({
184
+ logicalId: 'FriggPrivateSubnet1',
185
+ physicalId: 'subnet-11111111',
186
+ resourceType: 'AWS::EC2::Subnet',
187
+ matchMethod: 'vpc-usage',
188
+ confidence: 'high',
189
+ });
190
+ });
191
+
192
+ it('should map security group by usage in Lambda functions', async () => {
193
+ // Arrange
194
+ const orphanedResources = [
195
+ {
196
+ physicalId: 'sg-07c01370e830b6ad6',
197
+ resourceType: 'AWS::EC2::SecurityGroup',
198
+ properties: {
199
+ GroupId: 'sg-07c01370e830b6ad6',
200
+ tags: {},
201
+ },
202
+ },
203
+ ];
204
+
205
+ const buildTemplate = {
206
+ resources: {
207
+ FriggLambdaSecurityGroup: { Type: 'AWS::EC2::SecurityGroup' },
208
+ MyLambda: {
209
+ Type: 'AWS::Lambda::Function',
210
+ Properties: {
211
+ VpcConfig: {
212
+ SecurityGroupIds: [{ Ref: 'FriggLambdaSecurityGroup' }],
213
+ },
214
+ },
215
+ },
216
+ },
217
+ };
218
+
219
+ const deployedTemplate = {
220
+ resources: {
221
+ MyLambda: {
222
+ Type: 'AWS::Lambda::Function',
223
+ Properties: {
224
+ VpcConfig: {
225
+ SecurityGroupIds: ['sg-07c01370e830b6ad6'],
226
+ },
227
+ },
228
+ },
229
+ },
230
+ };
231
+
232
+ // Act
233
+ const result = await mapper.mapOrphanedResourcesToLogicalIds({
234
+ orphanedResources,
235
+ buildTemplate,
236
+ deployedTemplate,
237
+ });
238
+
239
+ // Assert
240
+ expect(result).toHaveLength(1);
241
+ expect(result[0]).toEqual({
242
+ logicalId: 'FriggLambdaSecurityGroup',
243
+ physicalId: 'sg-07c01370e830b6ad6',
244
+ resourceType: 'AWS::EC2::SecurityGroup',
245
+ matchMethod: 'usage',
246
+ confidence: 'medium',
247
+ });
248
+ });
249
+
250
+ it('should return null logical ID if no match found', async () => {
251
+ // Arrange
252
+ const orphanedResources = [
253
+ {
254
+ physicalId: 'vpc-unknown',
255
+ resourceType: 'AWS::EC2::VPC',
256
+ properties: {
257
+ VpcId: 'vpc-unknown',
258
+ tags: {},
259
+ },
260
+ },
261
+ ];
262
+
263
+ const buildTemplate = { resources: {} };
264
+ const deployedTemplate = { resources: {} };
265
+
266
+ // Act
267
+ const result = await mapper.mapOrphanedResourcesToLogicalIds({
268
+ orphanedResources,
269
+ buildTemplate,
270
+ deployedTemplate,
271
+ });
272
+
273
+ // Assert
274
+ expect(result).toHaveLength(1);
275
+ expect(result[0]).toEqual({
276
+ logicalId: null,
277
+ physicalId: 'vpc-unknown',
278
+ resourceType: 'AWS::EC2::VPC',
279
+ matchMethod: 'none',
280
+ confidence: 'none',
281
+ });
282
+ });
283
+
284
+ it('should map multiple orphaned resources with different strategies', async () => {
285
+ // Arrange
286
+ const orphanedResources = [
287
+ {
288
+ physicalId: 'vpc-12345678',
289
+ resourceType: 'AWS::EC2::VPC',
290
+ properties: {
291
+ VpcId: 'vpc-12345678',
292
+ tags: {
293
+ 'aws:cloudformation:logical-id': 'FriggVPC',
294
+ },
295
+ },
296
+ },
297
+ {
298
+ physicalId: 'subnet-11111111',
299
+ resourceType: 'AWS::EC2::Subnet',
300
+ properties: {
301
+ SubnetId: 'subnet-11111111',
302
+ VpcId: 'vpc-12345678',
303
+ tags: {},
304
+ },
305
+ },
306
+ ];
307
+
308
+ const buildTemplate = {
309
+ resources: {
310
+ FriggPrivateSubnet1: { Type: 'AWS::EC2::Subnet' },
311
+ MyLambda: {
312
+ Type: 'AWS::Lambda::Function',
313
+ Properties: {
314
+ VpcConfig: {
315
+ SubnetIds: [{ Ref: 'FriggPrivateSubnet1' }],
316
+ },
317
+ },
318
+ },
319
+ },
320
+ };
321
+
322
+ const deployedTemplate = {
323
+ resources: {
324
+ MyLambda: {
325
+ Type: 'AWS::Lambda::Function',
326
+ Properties: {
327
+ VpcConfig: {
328
+ SubnetIds: ['subnet-11111111'],
329
+ },
330
+ },
331
+ },
332
+ },
333
+ };
334
+
335
+ // Act
336
+ const result = await mapper.mapOrphanedResourcesToLogicalIds({
337
+ orphanedResources,
338
+ buildTemplate,
339
+ deployedTemplate,
340
+ });
341
+
342
+ // Assert
343
+ expect(result).toHaveLength(2);
344
+ expect(result[0].matchMethod).toBe('tag');
345
+ expect(result[0].confidence).toBe('high');
346
+ expect(result[1].matchMethod).toBe('vpc-usage');
347
+ expect(result[1].confidence).toBe('high');
348
+ });
349
+ });
350
+
351
+ describe('_getLogicalIdFromTags', () => {
352
+ it('should extract logical ID from CloudFormation tags', () => {
353
+ // Arrange
354
+ const tags = [
355
+ { Key: 'aws:cloudformation:stack-name', Value: 'acme-integrations-dev' },
356
+ { Key: 'aws:cloudformation:logical-id', Value: 'FriggVPC' },
357
+ { Key: 'Name', Value: 'My VPC' },
358
+ ];
359
+
360
+ // Act
361
+ const result = mapper._getLogicalIdFromTags(tags);
362
+
363
+ // Assert
364
+ expect(result).toBe('FriggVPC');
365
+ });
366
+
367
+ it('should return null if logical-id tag not found', () => {
368
+ // Arrange
369
+ const tags = [
370
+ { Key: 'Name', Value: 'My VPC' },
371
+ { Key: 'Environment', Value: 'dev' },
372
+ ];
373
+
374
+ // Act
375
+ const result = mapper._getLogicalIdFromTags(tags);
376
+
377
+ // Assert
378
+ expect(result).toBeNull();
379
+ });
380
+
381
+ it('should return null if tags is null or undefined', () => {
382
+ // Act
383
+ const resultNull = mapper._getLogicalIdFromTags(null);
384
+ const resultUndefined = mapper._getLogicalIdFromTags(undefined);
385
+
386
+ // Assert
387
+ expect(resultNull).toBeNull();
388
+ expect(resultUndefined).toBeNull();
389
+ });
390
+
391
+ it('should return null if tags is not an array', () => {
392
+ // Act
393
+ const result = mapper._getLogicalIdFromTags('not-an-array');
394
+
395
+ // Assert
396
+ expect(result).toBeNull();
397
+ });
398
+ });
399
+
400
+ describe('_matchVpcByContainedResources', () => {
401
+ it('should match VPC that contains all expected subnets', async () => {
402
+ // Arrange
403
+ const vpc = {
404
+ physicalId: 'vpc-12345678',
405
+ resourceType: 'AWS::EC2::VPC',
406
+ };
407
+
408
+ const buildTemplate = {
409
+ resources: {
410
+ FriggVPC: { Type: 'AWS::EC2::VPC' },
411
+ },
412
+ };
413
+
414
+ const deployedTemplate = {
415
+ resources: {
416
+ MyLambda: {
417
+ Type: 'AWS::Lambda::Function',
418
+ Properties: {
419
+ VpcConfig: {
420
+ SubnetIds: ['subnet-111', 'subnet-222'],
421
+ },
422
+ },
423
+ },
424
+ },
425
+ };
426
+
427
+ // Mock EC2 describe-subnets response
428
+ mockEc2Client.send.mockResolvedValueOnce({
429
+ Subnets: [
430
+ { SubnetId: 'subnet-111', VpcId: 'vpc-12345678' },
431
+ { SubnetId: 'subnet-222', VpcId: 'vpc-12345678' },
432
+ { SubnetId: 'subnet-333', VpcId: 'vpc-12345678' },
433
+ ],
434
+ });
435
+
436
+ // Act
437
+ const result = await mapper._matchVpcByContainedResources(
438
+ vpc,
439
+ buildTemplate,
440
+ deployedTemplate
441
+ );
442
+
443
+ // Assert
444
+ expect(result).toBe('FriggVPC');
445
+ });
446
+
447
+ it('should return null if VPC does not contain expected subnets', async () => {
448
+ // Arrange
449
+ const vpc = {
450
+ physicalId: 'vpc-wrong',
451
+ resourceType: 'AWS::EC2::VPC',
452
+ };
453
+
454
+ const buildTemplate = {
455
+ resources: {
456
+ FriggVPC: { Type: 'AWS::EC2::VPC' },
457
+ },
458
+ };
459
+
460
+ const deployedTemplate = {
461
+ resources: {
462
+ MyLambda: {
463
+ Type: 'AWS::Lambda::Function',
464
+ Properties: {
465
+ VpcConfig: {
466
+ SubnetIds: ['subnet-111', 'subnet-222'],
467
+ },
468
+ },
469
+ },
470
+ },
471
+ };
472
+
473
+ // Mock EC2 describe-subnets response - VPC has different subnets
474
+ mockEc2Client.send.mockResolvedValueOnce({
475
+ Subnets: [
476
+ { SubnetId: 'subnet-999', VpcId: 'vpc-wrong' },
477
+ { SubnetId: 'subnet-888', VpcId: 'vpc-wrong' },
478
+ ],
479
+ });
480
+
481
+ // Act
482
+ const result = await mapper._matchVpcByContainedResources(
483
+ vpc,
484
+ buildTemplate,
485
+ deployedTemplate
486
+ );
487
+
488
+ // Assert
489
+ expect(result).toBeNull();
490
+ });
491
+
492
+ it('should return null if no expected subnets in deployed template', async () => {
493
+ // Arrange
494
+ const vpc = {
495
+ physicalId: 'vpc-12345678',
496
+ resourceType: 'AWS::EC2::VPC',
497
+ };
498
+
499
+ const buildTemplate = { resources: {} };
500
+ const deployedTemplate = { resources: {} };
501
+
502
+ // Act
503
+ const result = await mapper._matchVpcByContainedResources(
504
+ vpc,
505
+ buildTemplate,
506
+ deployedTemplate
507
+ );
508
+
509
+ // Assert
510
+ expect(result).toBeNull();
511
+ });
512
+ });
513
+
514
+ describe('_extractSubnetIdsFromTemplate', () => {
515
+ it('should extract subnet IDs from Lambda VpcConfig', () => {
516
+ // Arrange
517
+ const template = {
518
+ resources: {
519
+ Lambda1: {
520
+ Type: 'AWS::Lambda::Function',
521
+ Properties: {
522
+ VpcConfig: {
523
+ SubnetIds: ['subnet-111', 'subnet-222'],
524
+ },
525
+ },
526
+ },
527
+ Lambda2: {
528
+ Type: 'AWS::Lambda::Function',
529
+ Properties: {
530
+ VpcConfig: {
531
+ SubnetIds: ['subnet-333'],
532
+ },
533
+ },
534
+ },
535
+ },
536
+ };
537
+
538
+ // Act
539
+ const result = mapper._extractSubnetIdsFromTemplate(template);
540
+
541
+ // Assert
542
+ expect(result).toEqual(['subnet-111', 'subnet-222', 'subnet-333']);
543
+ });
544
+
545
+ it('should deduplicate subnet IDs', () => {
546
+ // Arrange
547
+ const template = {
548
+ resources: {
549
+ Lambda1: {
550
+ Type: 'AWS::Lambda::Function',
551
+ Properties: {
552
+ VpcConfig: {
553
+ SubnetIds: ['subnet-111', 'subnet-222'],
554
+ },
555
+ },
556
+ },
557
+ Lambda2: {
558
+ Type: 'AWS::Lambda::Function',
559
+ Properties: {
560
+ VpcConfig: {
561
+ SubnetIds: ['subnet-111', 'subnet-333'],
562
+ },
563
+ },
564
+ },
565
+ },
566
+ };
567
+
568
+ // Act
569
+ const result = mapper._extractSubnetIdsFromTemplate(template);
570
+
571
+ // Assert
572
+ expect(result).toEqual(['subnet-111', 'subnet-222', 'subnet-333']);
573
+ });
574
+
575
+ it('should return empty array if no Lambda VPC configs', () => {
576
+ // Arrange
577
+ const template = {
578
+ resources: {
579
+ MyQueue: { Type: 'AWS::SQS::Queue' },
580
+ },
581
+ };
582
+
583
+ // Act
584
+ const result = mapper._extractSubnetIdsFromTemplate(template);
585
+
586
+ // Assert
587
+ expect(result).toEqual([]);
588
+ });
589
+ });
590
+
591
+ describe('_extractSubnetRefsFromTemplate', () => {
592
+ it('should extract subnet Refs from build template', () => {
593
+ // Arrange
594
+ const template = {
595
+ resources: {
596
+ MyLambda: {
597
+ Type: 'AWS::Lambda::Function',
598
+ Properties: {
599
+ VpcConfig: {
600
+ SubnetIds: [
601
+ { Ref: 'FriggPrivateSubnet1' },
602
+ { Ref: 'FriggPrivateSubnet2' },
603
+ ],
604
+ },
605
+ },
606
+ },
607
+ },
608
+ };
609
+
610
+ // Act
611
+ const result = mapper._extractSubnetRefsFromTemplate(template);
612
+
613
+ // Assert
614
+ expect(result).toEqual(['FriggPrivateSubnet1', 'FriggPrivateSubnet2']);
615
+ });
616
+
617
+ it('should return empty array if no Refs found', () => {
618
+ // Arrange
619
+ const template = {
620
+ resources: {
621
+ MyLambda: {
622
+ Type: 'AWS::Lambda::Function',
623
+ Properties: {
624
+ VpcConfig: {
625
+ SubnetIds: ['subnet-111'],
626
+ },
627
+ },
628
+ },
629
+ },
630
+ };
631
+
632
+ // Act
633
+ const result = mapper._extractSubnetRefsFromTemplate(template);
634
+
635
+ // Assert
636
+ expect(result).toEqual([]);
637
+ });
638
+ });
639
+
640
+ describe('_findVpcLogicalIdInTemplate', () => {
641
+ it('should find VPC logical ID in build template', () => {
642
+ // Arrange
643
+ const template = {
644
+ resources: {
645
+ FriggVPC: { Type: 'AWS::EC2::VPC' },
646
+ MySubnet: { Type: 'AWS::EC2::Subnet' },
647
+ },
648
+ };
649
+
650
+ // Act
651
+ const result = mapper._findVpcLogicalIdInTemplate(template);
652
+
653
+ // Assert
654
+ expect(result).toBe('FriggVPC');
655
+ });
656
+
657
+ it('should return null if no VPC in template', () => {
658
+ // Arrange
659
+ const template = {
660
+ resources: {
661
+ MyLambda: { Type: 'AWS::Lambda::Function' },
662
+ },
663
+ };
664
+
665
+ // Act
666
+ const result = mapper._findVpcLogicalIdInTemplate(template);
667
+
668
+ // Assert
669
+ expect(result).toBeNull();
670
+ });
671
+ });
672
+ });