@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,679 @@
1
+ /**
2
+ * ExecuteResourceImportUseCase Tests
3
+ *
4
+ * TDD tests for the application layer use case orchestrating CloudFormation
5
+ * import execution for the `frigg repair --import` command.
6
+ *
7
+ * Application Layer - Use Case Tests
8
+ * Following TDD, DDD, and Hexagonal Architecture principles
9
+ *
10
+ * Based on: SPEC-IMPORT-EXECUTION.md (lines 712-867)
11
+ */
12
+
13
+ const ExecuteResourceImportUseCase = require('../execute-resource-import-use-case');
14
+
15
+ describe('ExecuteResourceImportUseCase', () => {
16
+ let useCase;
17
+ let mockImportTemplateGenerator;
18
+ let mockImportProgressMonitor;
19
+ let mockCloudFormationRepository;
20
+ let mockStackRepository;
21
+
22
+ beforeEach(() => {
23
+ // Mock ImportTemplateGenerator
24
+ mockImportTemplateGenerator = {
25
+ generateImportTemplate: jest.fn(),
26
+ };
27
+
28
+ // Mock ImportProgressMonitor
29
+ mockImportProgressMonitor = {
30
+ monitorImport: jest.fn(),
31
+ };
32
+
33
+ // Mock CloudFormationRepository
34
+ mockCloudFormationRepository = {
35
+ createChangeSet: jest.fn(),
36
+ waitForChangeSet: jest.fn(),
37
+ executeChangeSet: jest.fn(),
38
+ getStackStatus: jest.fn(),
39
+ getStackResources: jest.fn(),
40
+ };
41
+
42
+ // Mock StackRepository
43
+ mockStackRepository = {
44
+ getTemplate: jest.fn(),
45
+ };
46
+
47
+ // Create use case with mocked dependencies
48
+ useCase = new ExecuteResourceImportUseCase({
49
+ importTemplateGenerator: mockImportTemplateGenerator,
50
+ importProgressMonitor: mockImportProgressMonitor,
51
+ cloudFormationRepository: mockCloudFormationRepository,
52
+ stackRepository: mockStackRepository,
53
+ });
54
+ });
55
+
56
+ describe('execute', () => {
57
+ const stackIdentifier = {
58
+ stackName: 'acme-integrations-dev',
59
+ region: 'us-east-1',
60
+ };
61
+
62
+ const resourcesToImport = [
63
+ {
64
+ logicalId: 'FriggVPC',
65
+ physicalId: 'vpc-12345678',
66
+ resourceType: 'AWS::EC2::VPC',
67
+ },
68
+ {
69
+ logicalId: 'FriggPrivateSubnet1',
70
+ physicalId: 'subnet-11111111',
71
+ resourceType: 'AWS::EC2::Subnet',
72
+ },
73
+ {
74
+ logicalId: 'FriggLambdaSecurityGroup',
75
+ physicalId: 'sg-0123456789abcdef0',
76
+ resourceType: 'AWS::EC2::SecurityGroup',
77
+ },
78
+ ];
79
+
80
+ const buildTemplatePath = '/path/to/build-template.json';
81
+
82
+ it('should execute complete import workflow successfully', async () => {
83
+ // Arrange: Mock all dependencies to succeed
84
+ const mockTemplate = {
85
+ Resources: {
86
+ FriggVPC: { Type: 'AWS::EC2::VPC', Properties: { CidrBlock: '10.0.0.0/16' } },
87
+ FriggPrivateSubnet1: { Type: 'AWS::EC2::Subnet', Properties: { VpcId: 'vpc-12345678' } },
88
+ FriggLambdaSecurityGroup: { Type: 'AWS::EC2::SecurityGroup', Properties: { VpcId: 'vpc-12345678' } },
89
+ },
90
+ };
91
+
92
+ const mockResourceIdentifiers = [
93
+ { ResourceType: 'AWS::EC2::VPC', LogicalResourceId: 'FriggVPC', ResourceIdentifier: { VpcId: 'vpc-12345678' } },
94
+ { ResourceType: 'AWS::EC2::Subnet', LogicalResourceId: 'FriggPrivateSubnet1', ResourceIdentifier: { SubnetId: 'subnet-11111111' } },
95
+ { ResourceType: 'AWS::EC2::SecurityGroup', LogicalResourceId: 'FriggLambdaSecurityGroup', ResourceIdentifier: { Id: 'sg-0123456789abcdef0' } },
96
+ ];
97
+
98
+ const mockChangeSet = { Id: 'arn:aws:cloudformation:us-east-1:123456789012:changeSet/import-orphaned-resources-1234567890000/12345678-1234-1234-1234-123456789012' };
99
+
100
+ mockImportTemplateGenerator.generateImportTemplate.mockResolvedValue({
101
+ template: mockTemplate,
102
+ resourceIdentifiers: mockResourceIdentifiers,
103
+ });
104
+
105
+ mockCloudFormationRepository.createChangeSet.mockResolvedValue(mockChangeSet);
106
+ mockCloudFormationRepository.waitForChangeSet.mockResolvedValue(undefined);
107
+ mockCloudFormationRepository.executeChangeSet.mockResolvedValue(undefined);
108
+
109
+ mockImportProgressMonitor.monitorImport.mockResolvedValue({
110
+ success: true,
111
+ importedCount: 3,
112
+ failedCount: 0,
113
+ failedResources: [],
114
+ });
115
+
116
+ const mockStackResources = [
117
+ { LogicalResourceId: 'FriggVPC', PhysicalResourceId: 'vpc-12345678', ResourceType: 'AWS::EC2::VPC' },
118
+ { LogicalResourceId: 'FriggPrivateSubnet1', PhysicalResourceId: 'subnet-11111111', ResourceType: 'AWS::EC2::Subnet' },
119
+ { LogicalResourceId: 'FriggLambdaSecurityGroup', PhysicalResourceId: 'sg-0123456789abcdef0', ResourceType: 'AWS::EC2::SecurityGroup' },
120
+ ];
121
+
122
+ mockCloudFormationRepository.getStackResources.mockResolvedValue(mockStackResources);
123
+ mockCloudFormationRepository.getStackStatus.mockResolvedValue('UPDATE_COMPLETE');
124
+
125
+ const progressCallback = jest.fn();
126
+
127
+ // Act: Execute import
128
+ const result = await useCase.execute({
129
+ stackIdentifier,
130
+ resourcesToImport,
131
+ buildTemplatePath,
132
+ onProgress: progressCallback,
133
+ });
134
+
135
+ // Assert: Check all steps were called in order
136
+ expect(mockImportTemplateGenerator.generateImportTemplate).toHaveBeenCalledWith({
137
+ resourcesToImport,
138
+ buildTemplatePath,
139
+ stackIdentifier,
140
+ });
141
+
142
+ expect(mockCloudFormationRepository.createChangeSet).toHaveBeenCalledWith(
143
+ expect.objectContaining({
144
+ stackIdentifier,
145
+ changeSetType: 'IMPORT',
146
+ template: mockTemplate,
147
+ resourcesToImport: mockResourceIdentifiers,
148
+ })
149
+ );
150
+
151
+ expect(mockCloudFormationRepository.waitForChangeSet).toHaveBeenCalled();
152
+ expect(mockCloudFormationRepository.executeChangeSet).toHaveBeenCalled();
153
+
154
+ expect(mockImportProgressMonitor.monitorImport).toHaveBeenCalledWith({
155
+ stackIdentifier,
156
+ resourceLogicalIds: ['FriggVPC', 'FriggPrivateSubnet1', 'FriggLambdaSecurityGroup'],
157
+ onProgress: expect.any(Function),
158
+ });
159
+
160
+ expect(mockCloudFormationRepository.getStackResources).toHaveBeenCalledWith(stackIdentifier);
161
+ expect(mockCloudFormationRepository.getStackStatus).toHaveBeenCalledWith(stackIdentifier);
162
+
163
+ // Assert: Check result
164
+ expect(result.success).toBe(true);
165
+ expect(result.importedCount).toBe(3);
166
+ expect(result.failedCount).toBe(0);
167
+ expect(result.stackStatus).toBe('UPDATE_COMPLETE');
168
+ expect(result.verifiedResources).toHaveLength(3);
169
+ expect(result.verifiedResources.every(r => r.verified)).toBe(true);
170
+
171
+ // Assert: Check progress callback was called for each step
172
+ expect(progressCallback).toHaveBeenCalledWith({ step: 'generate_template', status: 'in_progress' });
173
+ expect(progressCallback).toHaveBeenCalledWith({ step: 'generate_template', status: 'complete' });
174
+ expect(progressCallback).toHaveBeenCalledWith({ step: 'create_change_set', status: 'in_progress' });
175
+ expect(progressCallback).toHaveBeenCalledWith(expect.objectContaining({ step: 'create_change_set', status: 'complete' }));
176
+ expect(progressCallback).toHaveBeenCalledWith({ step: 'wait_change_set', status: 'in_progress' });
177
+ expect(progressCallback).toHaveBeenCalledWith({ step: 'wait_change_set', status: 'complete' });
178
+ expect(progressCallback).toHaveBeenCalledWith({ step: 'execute_import', status: 'in_progress' });
179
+ expect(progressCallback).toHaveBeenCalledWith({ step: 'execute_import', status: 'complete' });
180
+ expect(progressCallback).toHaveBeenCalledWith({ step: 'verify', status: 'in_progress' });
181
+ expect(progressCallback).toHaveBeenCalledWith({ step: 'verify', status: 'complete' });
182
+ });
183
+
184
+ it('should handle template generation failure gracefully', async () => {
185
+ // Arrange: Mock templateGenerator to throw
186
+ const templateError = new Error('Failed to resolve intrinsic function !Ref VpcCidr');
187
+ mockImportTemplateGenerator.generateImportTemplate.mockRejectedValue(templateError);
188
+
189
+ const progressCallback = jest.fn();
190
+
191
+ // Act: Call execute
192
+ const result = await useCase.execute({
193
+ stackIdentifier,
194
+ resourcesToImport,
195
+ buildTemplatePath,
196
+ onProgress: progressCallback,
197
+ });
198
+
199
+ // Assert: Check error is returned with step info
200
+ expect(result.success).toBe(false);
201
+ expect(result.error).toBe('Failed to resolve intrinsic function !Ref VpcCidr');
202
+ expect(result.step).toBeTruthy();
203
+
204
+ // Verify only template generation was attempted
205
+ expect(mockImportTemplateGenerator.generateImportTemplate).toHaveBeenCalled();
206
+ expect(mockCloudFormationRepository.createChangeSet).not.toHaveBeenCalled();
207
+ expect(mockCloudFormationRepository.executeChangeSet).not.toHaveBeenCalled();
208
+
209
+ // Verify progress callback received in_progress but not complete
210
+ expect(progressCallback).toHaveBeenCalledWith({ step: 'generate_template', status: 'in_progress' });
211
+ expect(progressCallback).not.toHaveBeenCalledWith({ step: 'generate_template', status: 'complete' });
212
+ });
213
+
214
+ it('should handle change set creation failure gracefully', async () => {
215
+ // Arrange: Mock template generation succeeds, change set creation fails
216
+ mockImportTemplateGenerator.generateImportTemplate.mockResolvedValue({
217
+ template: { Resources: {} },
218
+ resourceIdentifiers: [],
219
+ });
220
+
221
+ const changeSetError = new Error('Resource already managed by another stack');
222
+ mockCloudFormationRepository.createChangeSet.mockRejectedValue(changeSetError);
223
+
224
+ const progressCallback = jest.fn();
225
+
226
+ // Act: Call execute
227
+ const result = await useCase.execute({
228
+ stackIdentifier,
229
+ resourcesToImport,
230
+ buildTemplatePath,
231
+ onProgress: progressCallback,
232
+ });
233
+
234
+ // Assert: Check error is returned
235
+ expect(result.success).toBe(false);
236
+ expect(result.error).toBe('Resource already managed by another stack');
237
+
238
+ // Verify template generation succeeded but execution stopped
239
+ expect(mockImportTemplateGenerator.generateImportTemplate).toHaveBeenCalled();
240
+ expect(mockCloudFormationRepository.createChangeSet).toHaveBeenCalled();
241
+ expect(mockCloudFormationRepository.executeChangeSet).not.toHaveBeenCalled();
242
+
243
+ // Verify progress callbacks
244
+ expect(progressCallback).toHaveBeenCalledWith({ step: 'generate_template', status: 'complete' });
245
+ expect(progressCallback).toHaveBeenCalledWith({ step: 'create_change_set', status: 'in_progress' });
246
+ expect(progressCallback).not.toHaveBeenCalledWith(expect.objectContaining({ step: 'create_change_set', status: 'complete' }));
247
+ });
248
+
249
+ it('should handle change set wait timeout gracefully', async () => {
250
+ // Arrange: Mock successful template generation and change set creation, but wait fails
251
+ mockImportTemplateGenerator.generateImportTemplate.mockResolvedValue({
252
+ template: { Resources: {} },
253
+ resourceIdentifiers: [],
254
+ });
255
+
256
+ mockCloudFormationRepository.createChangeSet.mockResolvedValue({ Id: 'changeset-123' });
257
+
258
+ const waitError = new Error('Change set creation timed out after 5 minutes');
259
+ mockCloudFormationRepository.waitForChangeSet.mockRejectedValue(waitError);
260
+
261
+ const progressCallback = jest.fn();
262
+
263
+ // Act: Call execute
264
+ const result = await useCase.execute({
265
+ stackIdentifier,
266
+ resourcesToImport,
267
+ buildTemplatePath,
268
+ onProgress: progressCallback,
269
+ });
270
+
271
+ // Assert: Check error is returned
272
+ expect(result.success).toBe(false);
273
+ expect(result.error).toBe('Change set creation timed out after 5 minutes');
274
+
275
+ // Verify change set was created but execution didn't proceed
276
+ expect(mockCloudFormationRepository.createChangeSet).toHaveBeenCalled();
277
+ expect(mockCloudFormationRepository.waitForChangeSet).toHaveBeenCalled();
278
+ expect(mockCloudFormationRepository.executeChangeSet).not.toHaveBeenCalled();
279
+
280
+ // Verify progress callbacks
281
+ expect(progressCallback).toHaveBeenCalledWith(expect.objectContaining({ step: 'create_change_set', status: 'complete' }));
282
+ expect(progressCallback).toHaveBeenCalledWith({ step: 'wait_change_set', status: 'in_progress' });
283
+ expect(progressCallback).not.toHaveBeenCalledWith({ step: 'wait_change_set', status: 'complete' });
284
+ });
285
+
286
+ it('should handle execution failure during import gracefully', async () => {
287
+ // Arrange: Mock successful setup but import monitoring fails
288
+ mockImportTemplateGenerator.generateImportTemplate.mockResolvedValue({
289
+ template: { Resources: {} },
290
+ resourceIdentifiers: [],
291
+ });
292
+
293
+ mockCloudFormationRepository.createChangeSet.mockResolvedValue({ Id: 'changeset-123' });
294
+ mockCloudFormationRepository.waitForChangeSet.mockResolvedValue(undefined);
295
+ mockCloudFormationRepository.executeChangeSet.mockResolvedValue(undefined);
296
+
297
+ const importError = new Error('Import operation failed and rolled back');
298
+ mockImportProgressMonitor.monitorImport.mockRejectedValue(importError);
299
+
300
+ const progressCallback = jest.fn();
301
+
302
+ // Act: Call execute
303
+ const result = await useCase.execute({
304
+ stackIdentifier,
305
+ resourcesToImport,
306
+ buildTemplatePath,
307
+ onProgress: progressCallback,
308
+ });
309
+
310
+ // Assert: Check error is returned
311
+ expect(result.success).toBe(false);
312
+ expect(result.error).toBe('Import operation failed and rolled back');
313
+
314
+ // Verify execution started but failed during monitoring
315
+ expect(mockCloudFormationRepository.executeChangeSet).toHaveBeenCalled();
316
+ expect(mockImportProgressMonitor.monitorImport).toHaveBeenCalled();
317
+ expect(mockCloudFormationRepository.getStackResources).not.toHaveBeenCalled();
318
+
319
+ // Verify progress callbacks
320
+ expect(progressCallback).toHaveBeenCalledWith({ step: 'execute_import', status: 'in_progress' });
321
+ expect(progressCallback).not.toHaveBeenCalledWith({ step: 'execute_import', status: 'complete' });
322
+ });
323
+
324
+ it('should handle verification failure after import', async () => {
325
+ // Arrange: Mock successful import but verification finds missing resources
326
+ mockImportTemplateGenerator.generateImportTemplate.mockResolvedValue({
327
+ template: { Resources: {} },
328
+ resourceIdentifiers: [],
329
+ });
330
+
331
+ mockCloudFormationRepository.createChangeSet.mockResolvedValue({ Id: 'changeset-123' });
332
+ mockCloudFormationRepository.waitForChangeSet.mockResolvedValue(undefined);
333
+ mockCloudFormationRepository.executeChangeSet.mockResolvedValue(undefined);
334
+
335
+ mockImportProgressMonitor.monitorImport.mockResolvedValue({
336
+ success: true,
337
+ importedCount: 3,
338
+ failedCount: 0,
339
+ failedResources: [],
340
+ });
341
+
342
+ // Mock verification returning only 2 of 3 resources
343
+ const mockStackResources = [
344
+ { LogicalResourceId: 'FriggVPC', PhysicalResourceId: 'vpc-12345678', ResourceType: 'AWS::EC2::VPC' },
345
+ { LogicalResourceId: 'FriggPrivateSubnet1', PhysicalResourceId: 'subnet-11111111', ResourceType: 'AWS::EC2::Subnet' },
346
+ // FriggLambdaSecurityGroup is missing!
347
+ ];
348
+
349
+ mockCloudFormationRepository.getStackResources.mockResolvedValue(mockStackResources);
350
+ mockCloudFormationRepository.getStackStatus.mockResolvedValue('UPDATE_COMPLETE');
351
+
352
+ const progressCallback = jest.fn();
353
+
354
+ // Act: Call execute
355
+ const result = await useCase.execute({
356
+ stackIdentifier,
357
+ resourcesToImport,
358
+ buildTemplatePath,
359
+ onProgress: progressCallback,
360
+ });
361
+
362
+ // Assert: Check result shows partial success
363
+ expect(result.success).toBe(true); // Import succeeded according to CloudFormation
364
+ expect(result.importedCount).toBe(3);
365
+ expect(result.verifiedResources).toHaveLength(3);
366
+
367
+ // Check that one resource failed verification
368
+ const unverifiedResource = result.verifiedResources.find(r => r.logicalId === 'FriggLambdaSecurityGroup');
369
+ expect(unverifiedResource.verified).toBe(false);
370
+ expect(unverifiedResource.physicalId).toBeUndefined();
371
+
372
+ // Check that two resources passed verification
373
+ const verifiedResources = result.verifiedResources.filter(r => r.verified);
374
+ expect(verifiedResources).toHaveLength(2);
375
+
376
+ // Verify all steps completed
377
+ expect(progressCallback).toHaveBeenCalledWith({ step: 'verify', status: 'complete' });
378
+ });
379
+
380
+ it('should call onProgress callback at each step with correct status', async () => {
381
+ // Arrange: Mock successful workflow
382
+ mockImportTemplateGenerator.generateImportTemplate.mockResolvedValue({
383
+ template: { Resources: {} },
384
+ resourceIdentifiers: [
385
+ { ResourceType: 'AWS::EC2::VPC', LogicalResourceId: 'FriggVPC', ResourceIdentifier: { VpcId: 'vpc-123' } },
386
+ ],
387
+ });
388
+
389
+ mockCloudFormationRepository.createChangeSet.mockResolvedValue({ Id: 'changeset-123' });
390
+ mockCloudFormationRepository.waitForChangeSet.mockResolvedValue(undefined);
391
+ mockCloudFormationRepository.executeChangeSet.mockResolvedValue(undefined);
392
+
393
+ mockImportProgressMonitor.monitorImport.mockImplementation(async ({ onProgress }) => {
394
+ // Simulate progress updates during import
395
+ onProgress({ logicalId: 'FriggVPC', status: 'IN_PROGRESS', progress: 0, total: 1 });
396
+ onProgress({ logicalId: 'FriggVPC', status: 'COMPLETE', progress: 1, total: 1 });
397
+ return { success: true, importedCount: 1, failedCount: 0, failedResources: [] };
398
+ });
399
+
400
+ mockCloudFormationRepository.getStackResources.mockResolvedValue([
401
+ { LogicalResourceId: 'FriggVPC', PhysicalResourceId: 'vpc-123', ResourceType: 'AWS::EC2::VPC' },
402
+ ]);
403
+ mockCloudFormationRepository.getStackStatus.mockResolvedValue('UPDATE_COMPLETE');
404
+
405
+ const progressCallback = jest.fn();
406
+
407
+ // Act: Execute import
408
+ await useCase.execute({
409
+ stackIdentifier,
410
+ resourcesToImport: [{ logicalId: 'FriggVPC', physicalId: 'vpc-123', resourceType: 'AWS::EC2::VPC' }],
411
+ buildTemplatePath,
412
+ onProgress: progressCallback,
413
+ });
414
+
415
+ // Assert: Verify progress callback sequence
416
+ const calls = progressCallback.mock.calls.map(call => call[0]);
417
+
418
+ // Step 1: Generate template
419
+ expect(calls).toContainEqual({ step: 'generate_template', status: 'in_progress' });
420
+ expect(calls).toContainEqual({ step: 'generate_template', status: 'complete' });
421
+
422
+ // Step 2: Create change set
423
+ expect(calls).toContainEqual({ step: 'create_change_set', status: 'in_progress' });
424
+ expect(calls).toContainEqual(expect.objectContaining({ step: 'create_change_set', status: 'complete' }));
425
+
426
+ // Step 3: Wait for change set
427
+ expect(calls).toContainEqual({ step: 'wait_change_set', status: 'in_progress' });
428
+ expect(calls).toContainEqual({ step: 'wait_change_set', status: 'complete' });
429
+
430
+ // Step 4: Execute import with resource progress
431
+ expect(calls).toContainEqual({ step: 'execute_import', status: 'in_progress' });
432
+ expect(calls).toContainEqual(expect.objectContaining({
433
+ step: 'execute_import',
434
+ status: 'in_progress',
435
+ resourceProgress: expect.objectContaining({ logicalId: 'FriggVPC', status: 'IN_PROGRESS' }),
436
+ }));
437
+ expect(calls).toContainEqual(expect.objectContaining({
438
+ step: 'execute_import',
439
+ status: 'in_progress',
440
+ resourceProgress: expect.objectContaining({ logicalId: 'FriggVPC', status: 'COMPLETE' }),
441
+ }));
442
+ expect(calls).toContainEqual({ step: 'execute_import', status: 'complete' });
443
+
444
+ // Step 5: Verify
445
+ expect(calls).toContainEqual({ step: 'verify', status: 'in_progress' });
446
+ expect(calls).toContainEqual({ step: 'verify', status: 'complete' });
447
+ });
448
+
449
+ it('should return detailed error information on failure', async () => {
450
+ // Arrange: Mock failure with specific error details
451
+ const detailedError = new Error('Template property mismatch: VPC CidrBlock expected 10.0.0.0/16 but found 172.31.0.0/16');
452
+ detailedError.step = 'generate_template';
453
+ detailedError.resourceType = 'AWS::EC2::VPC';
454
+ detailedError.logicalId = 'FriggVPC';
455
+
456
+ mockImportTemplateGenerator.generateImportTemplate.mockRejectedValue(detailedError);
457
+
458
+ // Act: Call execute
459
+ const result = await useCase.execute({
460
+ stackIdentifier,
461
+ resourcesToImport,
462
+ buildTemplatePath,
463
+ });
464
+
465
+ // Assert: Check detailed error information is preserved
466
+ expect(result.success).toBe(false);
467
+ expect(result.error).toBe('Template property mismatch: VPC CidrBlock expected 10.0.0.0/16 but found 172.31.0.0/16');
468
+ expect(result.step).toBe('generate_template');
469
+ });
470
+
471
+ it('should handle partial import success with failed resources', async () => {
472
+ // Arrange: Mock import that succeeds for some resources but fails for others
473
+ mockImportTemplateGenerator.generateImportTemplate.mockResolvedValue({
474
+ template: { Resources: {} },
475
+ resourceIdentifiers: [],
476
+ });
477
+
478
+ mockCloudFormationRepository.createChangeSet.mockResolvedValue({ Id: 'changeset-123' });
479
+ mockCloudFormationRepository.waitForChangeSet.mockResolvedValue(undefined);
480
+ mockCloudFormationRepository.executeChangeSet.mockResolvedValue(undefined);
481
+
482
+ mockImportProgressMonitor.monitorImport.mockResolvedValue({
483
+ success: false, // Overall failure
484
+ importedCount: 2,
485
+ failedCount: 1,
486
+ failedResources: [
487
+ {
488
+ logicalId: 'FriggLambdaSecurityGroup',
489
+ reason: 'Resource property mismatch',
490
+ },
491
+ ],
492
+ });
493
+
494
+ mockCloudFormationRepository.getStackResources.mockResolvedValue([
495
+ { LogicalResourceId: 'FriggVPC', PhysicalResourceId: 'vpc-12345678', ResourceType: 'AWS::EC2::VPC' },
496
+ { LogicalResourceId: 'FriggPrivateSubnet1', PhysicalResourceId: 'subnet-11111111', ResourceType: 'AWS::EC2::Subnet' },
497
+ ]);
498
+ mockCloudFormationRepository.getStackStatus.mockResolvedValue('UPDATE_ROLLBACK_COMPLETE');
499
+
500
+ // Act: Call execute
501
+ const result = await useCase.execute({
502
+ stackIdentifier,
503
+ resourcesToImport,
504
+ buildTemplatePath,
505
+ });
506
+
507
+ // Assert: Check partial success is reported
508
+ expect(result.success).toBe(true); // Use case completed (didn't throw)
509
+ expect(result.importedCount).toBe(2);
510
+ expect(result.failedCount).toBe(1);
511
+ expect(result.stackStatus).toBe('UPDATE_ROLLBACK_COMPLETE');
512
+ });
513
+
514
+ it('should work without onProgress callback', async () => {
515
+ // Arrange: Mock successful workflow
516
+ mockImportTemplateGenerator.generateImportTemplate.mockResolvedValue({
517
+ template: { Resources: {} },
518
+ resourceIdentifiers: [],
519
+ });
520
+
521
+ mockCloudFormationRepository.createChangeSet.mockResolvedValue({ Id: 'changeset-123' });
522
+ mockCloudFormationRepository.waitForChangeSet.mockResolvedValue(undefined);
523
+ mockCloudFormationRepository.executeChangeSet.mockResolvedValue(undefined);
524
+
525
+ mockImportProgressMonitor.monitorImport.mockResolvedValue({
526
+ success: true,
527
+ importedCount: 1,
528
+ failedCount: 0,
529
+ failedResources: [],
530
+ });
531
+
532
+ mockCloudFormationRepository.getStackResources.mockResolvedValue([
533
+ { LogicalResourceId: 'FriggVPC', PhysicalResourceId: 'vpc-123', ResourceType: 'AWS::EC2::VPC' },
534
+ ]);
535
+ mockCloudFormationRepository.getStackStatus.mockResolvedValue('UPDATE_COMPLETE');
536
+
537
+ // Act: Call execute WITHOUT onProgress callback
538
+ const result = await useCase.execute({
539
+ stackIdentifier,
540
+ resourcesToImport: [{ logicalId: 'FriggVPC', physicalId: 'vpc-123', resourceType: 'AWS::EC2::VPC' }],
541
+ buildTemplatePath,
542
+ // NO onProgress provided
543
+ });
544
+
545
+ // Assert: Should not throw and should succeed
546
+ expect(result.success).toBe(true);
547
+ expect(mockImportTemplateGenerator.generateImportTemplate).toHaveBeenCalled();
548
+ expect(mockCloudFormationRepository.executeChangeSet).toHaveBeenCalled();
549
+ });
550
+
551
+ it('should include change set name in result', async () => {
552
+ // Arrange: Mock successful workflow
553
+ mockImportTemplateGenerator.generateImportTemplate.mockResolvedValue({
554
+ template: { Resources: {} },
555
+ resourceIdentifiers: [],
556
+ });
557
+
558
+ const mockChangeSetId = 'arn:aws:cloudformation:us-east-1:123456789012:changeSet/import-orphaned-resources-1234567890000/12345678-1234-1234-1234-123456789012';
559
+ mockCloudFormationRepository.createChangeSet.mockResolvedValue({ Id: mockChangeSetId });
560
+ mockCloudFormationRepository.waitForChangeSet.mockResolvedValue(undefined);
561
+ mockCloudFormationRepository.executeChangeSet.mockResolvedValue(undefined);
562
+
563
+ mockImportProgressMonitor.monitorImport.mockResolvedValue({
564
+ success: true,
565
+ importedCount: 1,
566
+ failedCount: 0,
567
+ failedResources: [],
568
+ });
569
+
570
+ mockCloudFormationRepository.getStackResources.mockResolvedValue([]);
571
+ mockCloudFormationRepository.getStackStatus.mockResolvedValue('UPDATE_COMPLETE');
572
+
573
+ // Act: Call execute
574
+ const result = await useCase.execute({
575
+ stackIdentifier,
576
+ resourcesToImport: [{ logicalId: 'FriggVPC', physicalId: 'vpc-123', resourceType: 'AWS::EC2::VPC' }],
577
+ buildTemplatePath,
578
+ });
579
+
580
+ // Assert: Change set name should be in result
581
+ expect(result.changeSetName).toMatch(/^import-orphaned-resources-\d+$/);
582
+ });
583
+ });
584
+
585
+ describe('_verifyImportedResources', () => {
586
+ it('should verify all resources are present in stack', async () => {
587
+ // Arrange
588
+ const stackIdentifier = { stackName: 'test-stack', region: 'us-east-1' };
589
+ const resourceLogicalIds = ['FriggVPC', 'FriggPrivateSubnet1', 'FriggLambdaSecurityGroup'];
590
+
591
+ const mockStackResources = [
592
+ { LogicalResourceId: 'FriggVPC', PhysicalResourceId: 'vpc-123', ResourceType: 'AWS::EC2::VPC' },
593
+ { LogicalResourceId: 'FriggPrivateSubnet1', PhysicalResourceId: 'subnet-456', ResourceType: 'AWS::EC2::Subnet' },
594
+ { LogicalResourceId: 'FriggLambdaSecurityGroup', PhysicalResourceId: 'sg-789', ResourceType: 'AWS::EC2::SecurityGroup' },
595
+ ];
596
+
597
+ mockCloudFormationRepository.getStackResources.mockResolvedValue(mockStackResources);
598
+ mockCloudFormationRepository.getStackStatus.mockResolvedValue('UPDATE_COMPLETE');
599
+
600
+ // Act
601
+ const result = await useCase._verifyImportedResources({
602
+ stackIdentifier,
603
+ resourceLogicalIds,
604
+ });
605
+
606
+ // Assert
607
+ expect(result.allVerified).toBe(true);
608
+ expect(result.resources).toHaveLength(3);
609
+ expect(result.resources.every(r => r.verified)).toBe(true);
610
+ expect(result.stackStatus).toBe('UPDATE_COMPLETE');
611
+
612
+ expect(mockCloudFormationRepository.getStackResources).toHaveBeenCalledWith(stackIdentifier);
613
+ expect(mockCloudFormationRepository.getStackStatus).toHaveBeenCalledWith(stackIdentifier);
614
+ });
615
+
616
+ it('should detect missing resources after import', async () => {
617
+ // Arrange
618
+ const stackIdentifier = { stackName: 'test-stack', region: 'us-east-1' };
619
+ const resourceLogicalIds = ['FriggVPC', 'FriggPrivateSubnet1', 'FriggLambdaSecurityGroup'];
620
+
621
+ // Mock stack resources with one missing
622
+ const mockStackResources = [
623
+ { LogicalResourceId: 'FriggVPC', PhysicalResourceId: 'vpc-123', ResourceType: 'AWS::EC2::VPC' },
624
+ { LogicalResourceId: 'FriggPrivateSubnet1', PhysicalResourceId: 'subnet-456', ResourceType: 'AWS::EC2::Subnet' },
625
+ // FriggLambdaSecurityGroup is missing
626
+ ];
627
+
628
+ mockCloudFormationRepository.getStackResources.mockResolvedValue(mockStackResources);
629
+ mockCloudFormationRepository.getStackStatus.mockResolvedValue('UPDATE_COMPLETE');
630
+
631
+ // Act
632
+ const result = await useCase._verifyImportedResources({
633
+ stackIdentifier,
634
+ resourceLogicalIds,
635
+ });
636
+
637
+ // Assert
638
+ expect(result.allVerified).toBe(false);
639
+ expect(result.resources).toHaveLength(3);
640
+
641
+ const verifiedCount = result.resources.filter(r => r.verified).length;
642
+ const unverifiedCount = result.resources.filter(r => !r.verified).length;
643
+
644
+ expect(verifiedCount).toBe(2);
645
+ expect(unverifiedCount).toBe(1);
646
+
647
+ const missingResource = result.resources.find(r => r.logicalId === 'FriggLambdaSecurityGroup');
648
+ expect(missingResource.verified).toBe(false);
649
+ expect(missingResource.physicalId).toBeUndefined();
650
+ });
651
+
652
+ it('should include resource details for verified resources', async () => {
653
+ // Arrange
654
+ const stackIdentifier = { stackName: 'test-stack', region: 'us-east-1' };
655
+ const resourceLogicalIds = ['FriggVPC'];
656
+
657
+ const mockStackResources = [
658
+ { LogicalResourceId: 'FriggVPC', PhysicalResourceId: 'vpc-12345678', ResourceType: 'AWS::EC2::VPC' },
659
+ ];
660
+
661
+ mockCloudFormationRepository.getStackResources.mockResolvedValue(mockStackResources);
662
+ mockCloudFormationRepository.getStackStatus.mockResolvedValue('UPDATE_COMPLETE');
663
+
664
+ // Act
665
+ const result = await useCase._verifyImportedResources({
666
+ stackIdentifier,
667
+ resourceLogicalIds,
668
+ });
669
+
670
+ // Assert
671
+ expect(result.resources[0]).toEqual({
672
+ logicalId: 'FriggVPC',
673
+ verified: true,
674
+ physicalId: 'vpc-12345678',
675
+ resourceType: 'AWS::EC2::VPC',
676
+ });
677
+ });
678
+ });
679
+ });