@friggframework/devtools 2.0.0-next.44 → 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 -2074
  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,496 @@
1
+ /**
2
+ * TemplateParser Tests
3
+ *
4
+ * TDD tests for CloudFormation template parsing functionality
5
+ * Domain Layer - Service Tests
6
+ */
7
+
8
+ const { TemplateParser } = require('../template-parser');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ describe('TemplateParser', () => {
13
+ let parser;
14
+
15
+ beforeEach(() => {
16
+ parser = new TemplateParser();
17
+ });
18
+
19
+ describe('parseTemplate', () => {
20
+ it('should parse template from file path', () => {
21
+ // Arrange
22
+ const mockTemplate = {
23
+ AWSTemplateFormatVersion: '2010-09-09',
24
+ Description: 'Test template',
25
+ Resources: {
26
+ FriggVPC: { Type: 'AWS::EC2::VPC' },
27
+ },
28
+ };
29
+
30
+ const tempFile = path.join(__dirname, 'temp-template.json');
31
+ fs.writeFileSync(tempFile, JSON.stringify(mockTemplate));
32
+
33
+ // Act
34
+ const result = parser.parseTemplate(tempFile);
35
+
36
+ // Assert
37
+ expect(result.resources).toEqual(mockTemplate.Resources);
38
+ expect(result.version).toBe('2010-09-09');
39
+ expect(result.description).toBe('Test template');
40
+
41
+ // Cleanup
42
+ fs.unlinkSync(tempFile);
43
+ });
44
+
45
+ it('should parse template from object', () => {
46
+ // Arrange
47
+ const mockTemplate = {
48
+ AWSTemplateFormatVersion: '2010-09-09',
49
+ Resources: {
50
+ FriggVPC: { Type: 'AWS::EC2::VPC' },
51
+ },
52
+ };
53
+
54
+ // Act
55
+ const result = parser.parseTemplate(mockTemplate);
56
+
57
+ // Assert
58
+ expect(result.resources).toEqual(mockTemplate.Resources);
59
+ expect(result.version).toBe('2010-09-09');
60
+ });
61
+
62
+ it('should throw error if template file not found', () => {
63
+ // Arrange
64
+ const nonExistentPath = '/path/to/nonexistent/template.json';
65
+
66
+ // Act & Assert
67
+ expect(() => parser.parseTemplate(nonExistentPath)).toThrow(
68
+ 'Template not found at path'
69
+ );
70
+ });
71
+
72
+ it('should return empty resources if Resources key missing', () => {
73
+ // Arrange
74
+ const mockTemplate = {
75
+ AWSTemplateFormatVersion: '2010-09-09',
76
+ };
77
+
78
+ // Act
79
+ const result = parser.parseTemplate(mockTemplate);
80
+
81
+ // Assert
82
+ expect(result.resources).toEqual({});
83
+ });
84
+ });
85
+
86
+ describe('getVpcResources', () => {
87
+ it('should extract VPC resources from template', () => {
88
+ // Arrange
89
+ const template = {
90
+ resources: {
91
+ FriggVPC: {
92
+ Type: 'AWS::EC2::VPC',
93
+ Properties: { CidrBlock: '10.0.0.0/16' },
94
+ },
95
+ FriggPrivateSubnet1: {
96
+ Type: 'AWS::EC2::Subnet',
97
+ Properties: { CidrBlock: '10.0.0.0/24' },
98
+ },
99
+ FriggLambdaSecurityGroup: {
100
+ Type: 'AWS::EC2::SecurityGroup',
101
+ Properties: { GroupDescription: 'Lambda SG' },
102
+ },
103
+ SomeOtherResource: {
104
+ Type: 'AWS::Lambda::Function',
105
+ Properties: {},
106
+ },
107
+ },
108
+ };
109
+
110
+ // Act
111
+ const result = parser.getVpcResources(template);
112
+
113
+ // Assert
114
+ expect(result).toHaveLength(3);
115
+ expect(result[0]).toEqual({
116
+ logicalId: 'FriggVPC',
117
+ resourceType: 'AWS::EC2::VPC',
118
+ properties: { CidrBlock: '10.0.0.0/16' },
119
+ });
120
+ expect(result[1]).toEqual({
121
+ logicalId: 'FriggPrivateSubnet1',
122
+ resourceType: 'AWS::EC2::Subnet',
123
+ properties: { CidrBlock: '10.0.0.0/24' },
124
+ });
125
+ expect(result[2]).toEqual({
126
+ logicalId: 'FriggLambdaSecurityGroup',
127
+ resourceType: 'AWS::EC2::SecurityGroup',
128
+ properties: { GroupDescription: 'Lambda SG' },
129
+ });
130
+ });
131
+
132
+ it('should return empty array if no VPC resources', () => {
133
+ // Arrange
134
+ const template = {
135
+ resources: {
136
+ MyLambda: { Type: 'AWS::Lambda::Function' },
137
+ },
138
+ };
139
+
140
+ // Act
141
+ const result = parser.getVpcResources(template);
142
+
143
+ // Assert
144
+ expect(result).toEqual([]);
145
+ });
146
+
147
+ it('should include all VPC-related resource types', () => {
148
+ // Arrange
149
+ const template = {
150
+ resources: {
151
+ MyVPC: { Type: 'AWS::EC2::VPC', Properties: {} },
152
+ MySubnet: { Type: 'AWS::EC2::Subnet', Properties: {} },
153
+ MySG: { Type: 'AWS::EC2::SecurityGroup', Properties: {} },
154
+ MyIGW: { Type: 'AWS::EC2::InternetGateway', Properties: {} },
155
+ MyNAT: { Type: 'AWS::EC2::NatGateway', Properties: {} },
156
+ MyRT: { Type: 'AWS::EC2::RouteTable', Properties: {} },
157
+ MyEndpoint: { Type: 'AWS::EC2::VPCEndpoint', Properties: {} },
158
+ },
159
+ };
160
+
161
+ // Act
162
+ const result = parser.getVpcResources(template);
163
+
164
+ // Assert
165
+ expect(result).toHaveLength(7);
166
+ });
167
+ });
168
+
169
+ describe('extractHardcodedIds', () => {
170
+ it('should extract hardcoded VPC IDs from deployed template', () => {
171
+ // Arrange
172
+ const template = {
173
+ resources: {
174
+ MyLambda: {
175
+ Type: 'AWS::Lambda::Function',
176
+ Properties: {
177
+ VpcConfig: {
178
+ VpcId: 'vpc-0eadd96976d29ede7',
179
+ SubnetIds: ['subnet-00ab9e0502e66aac3', 'subnet-00d085a52937aaf91'],
180
+ SecurityGroupIds: ['sg-07c01370e830b6ad6'],
181
+ },
182
+ },
183
+ },
184
+ },
185
+ };
186
+
187
+ // Act
188
+ const result = parser.extractHardcodedIds(template);
189
+
190
+ // Assert
191
+ expect(result.vpcIds).toEqual(['vpc-0eadd96976d29ede7']);
192
+ expect(result.subnetIds).toEqual([
193
+ 'subnet-00ab9e0502e66aac3',
194
+ 'subnet-00d085a52937aaf91',
195
+ ]);
196
+ expect(result.securityGroupIds).toEqual(['sg-07c01370e830b6ad6']);
197
+ });
198
+
199
+ it('should handle nested VPC configurations', () => {
200
+ // Arrange
201
+ const template = {
202
+ resources: {
203
+ Lambda1: {
204
+ Type: 'AWS::Lambda::Function',
205
+ Properties: {
206
+ VpcConfig: {
207
+ SubnetIds: ['subnet-111', 'subnet-222'],
208
+ },
209
+ },
210
+ },
211
+ Lambda2: {
212
+ Type: 'AWS::Lambda::Function',
213
+ Properties: {
214
+ VpcConfig: {
215
+ SubnetIds: ['subnet-333'],
216
+ SecurityGroupIds: ['sg-444'],
217
+ },
218
+ },
219
+ },
220
+ },
221
+ };
222
+
223
+ // Act
224
+ const result = parser.extractHardcodedIds(template);
225
+
226
+ // Assert
227
+ expect(result.subnetIds).toEqual(['subnet-111', 'subnet-222', 'subnet-333']);
228
+ expect(result.securityGroupIds).toEqual(['sg-444']);
229
+ });
230
+
231
+ it('should return empty arrays if no hardcoded IDs found', () => {
232
+ // Arrange
233
+ const template = {
234
+ resources: {
235
+ MyLambda: {
236
+ Type: 'AWS::Lambda::Function',
237
+ Properties: {},
238
+ },
239
+ },
240
+ };
241
+
242
+ // Act
243
+ const result = parser.extractHardcodedIds(template);
244
+
245
+ // Assert
246
+ expect(result.vpcIds).toEqual([]);
247
+ expect(result.subnetIds).toEqual([]);
248
+ expect(result.securityGroupIds).toEqual([]);
249
+ });
250
+
251
+ it('should deduplicate hardcoded IDs', () => {
252
+ // Arrange
253
+ const template = {
254
+ resources: {
255
+ Lambda1: {
256
+ Type: 'AWS::Lambda::Function',
257
+ Properties: {
258
+ VpcConfig: {
259
+ SubnetIds: ['subnet-111', 'subnet-222'],
260
+ },
261
+ },
262
+ },
263
+ Lambda2: {
264
+ Type: 'AWS::Lambda::Function',
265
+ Properties: {
266
+ VpcConfig: {
267
+ SubnetIds: ['subnet-111', 'subnet-333'],
268
+ },
269
+ },
270
+ },
271
+ },
272
+ };
273
+
274
+ // Act
275
+ const result = parser.extractHardcodedIds(template);
276
+
277
+ // Assert
278
+ expect(result.subnetIds).toEqual(['subnet-111', 'subnet-222', 'subnet-333']);
279
+ });
280
+ });
281
+
282
+ describe('extractRefs', () => {
283
+ it('should extract Refs from build template', () => {
284
+ // Arrange
285
+ const template = {
286
+ resources: {
287
+ FriggVPC: { Type: 'AWS::EC2::VPC' },
288
+ FriggPrivateSubnet1: { Type: 'AWS::EC2::Subnet' },
289
+ MyLambda: {
290
+ Type: 'AWS::Lambda::Function',
291
+ Properties: {
292
+ VpcConfig: {
293
+ SubnetIds: [
294
+ { Ref: 'FriggPrivateSubnet1' },
295
+ { Ref: 'FriggPrivateSubnet2' },
296
+ ],
297
+ SecurityGroupIds: [{ Ref: 'FriggLambdaSecurityGroup' }],
298
+ },
299
+ },
300
+ },
301
+ },
302
+ };
303
+
304
+ // Act
305
+ const result = parser.extractRefs(template);
306
+
307
+ // Assert
308
+ expect(result.subnetRefs).toEqual([
309
+ 'FriggPrivateSubnet1',
310
+ 'FriggPrivateSubnet2',
311
+ ]);
312
+ expect(result.securityGroupRefs).toEqual(['FriggLambdaSecurityGroup']);
313
+ });
314
+
315
+ it('should identify VPC Refs correctly', () => {
316
+ // Arrange
317
+ const template = {
318
+ resources: {
319
+ MySubnet: {
320
+ Type: 'AWS::EC2::Subnet',
321
+ Properties: {
322
+ VpcId: { Ref: 'FriggVPC' },
323
+ },
324
+ },
325
+ },
326
+ };
327
+
328
+ // Act
329
+ const result = parser.extractRefs(template);
330
+
331
+ // Assert
332
+ expect(result.vpcRefs).toEqual(['FriggVPC']);
333
+ });
334
+
335
+ it('should not confuse VPCEndpoint with VPC refs', () => {
336
+ // Arrange
337
+ const template = {
338
+ resources: {
339
+ MyEndpoint: {
340
+ Type: 'AWS::EC2::VPCEndpoint',
341
+ Properties: {
342
+ VpcId: { Ref: 'FriggVPCEndpoint' },
343
+ },
344
+ },
345
+ },
346
+ };
347
+
348
+ // Act
349
+ const result = parser.extractRefs(template);
350
+
351
+ // Assert
352
+ expect(result.vpcRefs).toEqual([]); // VPCEndpoint should be excluded
353
+ });
354
+
355
+ it('should return empty arrays if no Refs found', () => {
356
+ // Arrange
357
+ const template = {
358
+ resources: {
359
+ MyLambda: {
360
+ Type: 'AWS::Lambda::Function',
361
+ Properties: {
362
+ VpcConfig: {
363
+ SubnetIds: ['subnet-111'],
364
+ },
365
+ },
366
+ },
367
+ },
368
+ };
369
+
370
+ // Act
371
+ const result = parser.extractRefs(template);
372
+
373
+ // Assert
374
+ expect(result.vpcRefs).toEqual([]);
375
+ expect(result.subnetRefs).toEqual([]);
376
+ expect(result.securityGroupRefs).toEqual([]);
377
+ });
378
+ });
379
+
380
+ describe('findLogicalIdForPhysicalId', () => {
381
+ it('should match physical ID to logical ID via template comparison', () => {
382
+ // Arrange
383
+ const deployedTemplate = {
384
+ resources: {
385
+ MyLambda: {
386
+ Type: 'AWS::Lambda::Function',
387
+ Properties: {
388
+ VpcConfig: {
389
+ SubnetIds: ['subnet-00ab9e0502e66aac3'],
390
+ },
391
+ },
392
+ },
393
+ },
394
+ };
395
+
396
+ const buildTemplate = {
397
+ resources: {
398
+ FriggPrivateSubnet1: { Type: 'AWS::EC2::Subnet' },
399
+ MyLambda: {
400
+ Type: 'AWS::Lambda::Function',
401
+ Properties: {
402
+ VpcConfig: {
403
+ SubnetIds: [{ Ref: 'FriggPrivateSubnet1' }],
404
+ },
405
+ },
406
+ },
407
+ },
408
+ };
409
+
410
+ // Act
411
+ const result = parser.findLogicalIdForPhysicalId(
412
+ 'subnet-00ab9e0502e66aac3',
413
+ deployedTemplate,
414
+ buildTemplate
415
+ );
416
+
417
+ // Assert
418
+ expect(result).toBe('FriggPrivateSubnet1');
419
+ });
420
+
421
+ it('should return null if no match found', () => {
422
+ // Arrange
423
+ const deployedTemplate = { resources: {} };
424
+ const buildTemplate = { resources: {} };
425
+
426
+ // Act
427
+ const result = parser.findLogicalIdForPhysicalId(
428
+ 'subnet-unknown',
429
+ deployedTemplate,
430
+ buildTemplate
431
+ );
432
+
433
+ // Assert
434
+ expect(result).toBeNull();
435
+ });
436
+ });
437
+
438
+ describe('static methods', () => {
439
+ describe('getBuildTemplatePath', () => {
440
+ it('should return correct path to build template', () => {
441
+ // Act
442
+ const result = TemplateParser.getBuildTemplatePath('/project/root');
443
+
444
+ // Assert
445
+ expect(result).toBe(
446
+ '/project/root/.serverless/cloudformation-template-update-stack.json'
447
+ );
448
+ });
449
+
450
+ it('should use current directory if no path provided', () => {
451
+ // Act
452
+ const result = TemplateParser.getBuildTemplatePath();
453
+
454
+ // Assert
455
+ expect(result).toContain('.serverless/cloudformation-template-update-stack.json');
456
+ });
457
+ });
458
+
459
+ describe('buildTemplateExists', () => {
460
+ it('should return true if build template exists', () => {
461
+ // Arrange
462
+ const tempDir = path.join(__dirname, 'temp-project');
463
+ const serverlessDir = path.join(tempDir, '.serverless');
464
+ const templatePath = path.join(
465
+ serverlessDir,
466
+ 'cloudformation-template-update-stack.json'
467
+ );
468
+
469
+ fs.mkdirSync(serverlessDir, { recursive: true });
470
+ fs.writeFileSync(templatePath, '{}');
471
+
472
+ // Act
473
+ const result = TemplateParser.buildTemplateExists(tempDir);
474
+
475
+ // Assert
476
+ expect(result).toBe(true);
477
+
478
+ // Cleanup
479
+ fs.unlinkSync(templatePath);
480
+ fs.rmdirSync(serverlessDir);
481
+ fs.rmdirSync(tempDir);
482
+ });
483
+
484
+ it('should return false if build template does not exist', () => {
485
+ // Arrange
486
+ const tempDir = path.join(__dirname, 'nonexistent-project');
487
+
488
+ // Act
489
+ const result = TemplateParser.buildTemplateExists(tempDir);
490
+
491
+ // Assert
492
+ expect(result).toBe(false);
493
+ });
494
+ });
495
+ });
496
+ });