@friggframework/devtools 2.0.0-next.45 → 2.0.0-next.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/infrastructure/ARCHITECTURE.md +487 -0
- package/infrastructure/HEALTH.md +468 -0
- package/infrastructure/README.md +51 -0
- package/infrastructure/__tests__/postgres-config.test.js +914 -0
- package/infrastructure/__tests__/template-generation.test.js +687 -0
- package/infrastructure/create-frigg-infrastructure.js +1 -1
- package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
- package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
- package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
- package/infrastructure/domains/database/aurora-builder.js +809 -0
- package/infrastructure/domains/database/aurora-builder.test.js +950 -0
- package/infrastructure/domains/database/aurora-discovery.js +87 -0
- package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
- package/infrastructure/domains/database/aurora-resolver.js +210 -0
- package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
- package/infrastructure/domains/database/migration-builder.js +695 -0
- package/infrastructure/domains/database/migration-builder.test.js +294 -0
- package/infrastructure/domains/database/migration-resolver.js +163 -0
- package/infrastructure/domains/database/migration-resolver.test.js +337 -0
- package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
- package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
- package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
- package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
- package/infrastructure/domains/health/application/ports/index.js +26 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
- package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
- package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
- package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
- package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
- package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
- package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
- package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
- package/infrastructure/domains/health/domain/entities/issue.js +299 -0
- package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
- package/infrastructure/domains/health/domain/entities/resource.js +159 -0
- package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
- package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
- package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
- package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
- package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
- package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
- package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
- package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
- package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
- package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
- package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
- package/infrastructure/domains/integration/integration-builder.js +397 -0
- package/infrastructure/domains/integration/integration-builder.test.js +593 -0
- package/infrastructure/domains/integration/integration-resolver.js +170 -0
- package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
- package/infrastructure/domains/integration/websocket-builder.js +69 -0
- package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
- package/infrastructure/domains/networking/vpc-builder.js +1829 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +1262 -0
- package/infrastructure/domains/networking/vpc-discovery.js +177 -0
- package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
- package/infrastructure/domains/networking/vpc-resolver.js +324 -0
- package/infrastructure/domains/networking/vpc-resolver.test.js +501 -0
- package/infrastructure/domains/parameters/ssm-builder.js +79 -0
- package/infrastructure/domains/parameters/ssm-builder.test.js +189 -0
- package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
- package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
- package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
- package/infrastructure/domains/security/kms-builder.js +366 -0
- package/infrastructure/domains/security/kms-builder.test.js +374 -0
- package/infrastructure/domains/security/kms-discovery.js +80 -0
- package/infrastructure/domains/security/kms-discovery.test.js +177 -0
- package/infrastructure/domains/security/kms-resolver.js +96 -0
- package/infrastructure/domains/security/kms-resolver.test.js +216 -0
- package/infrastructure/domains/shared/base-builder.js +112 -0
- package/infrastructure/domains/shared/base-resolver.js +186 -0
- package/infrastructure/domains/shared/base-resolver.test.js +305 -0
- package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
- package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
- package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
- package/infrastructure/domains/shared/cloudformation-discovery.js +375 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +590 -0
- package/infrastructure/domains/shared/environment-builder.js +119 -0
- package/infrastructure/domains/shared/environment-builder.test.js +247 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +544 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +377 -0
- package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
- package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
- package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
- package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
- package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
- package/infrastructure/domains/shared/resource-discovery.js +192 -0
- package/infrastructure/domains/shared/resource-discovery.test.js +552 -0
- package/infrastructure/domains/shared/types/app-definition.js +205 -0
- package/infrastructure/domains/shared/types/discovery-result.js +106 -0
- package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
- package/infrastructure/domains/shared/types/index.js +46 -0
- package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
- package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +380 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +138 -0
- package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +2 -1
- package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
- package/infrastructure/esbuild.config.js +53 -0
- package/infrastructure/infrastructure-composer.js +87 -0
- package/infrastructure/{serverless-template.test.js → infrastructure-composer.test.js} +115 -24
- package/infrastructure/scripts/build-prisma-layer.js +553 -0
- package/infrastructure/scripts/build-prisma-layer.test.js +102 -0
- package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +80 -48
- package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
- package/layers/prisma/nodejs/package.json +8 -0
- package/management-ui/server/utils/cliIntegration.js +1 -1
- package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
- package/package.json +11 -11
- package/frigg-cli/.eslintrc.js +0 -141
- package/frigg-cli/__tests__/unit/commands/build.test.js +0 -251
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +0 -548
- package/frigg-cli/__tests__/unit/commands/install.test.js +0 -400
- package/frigg-cli/__tests__/unit/commands/ui.test.js +0 -346
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +0 -366
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +0 -304
- package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +0 -486
- package/frigg-cli/__tests__/utils/mock-factory.js +0 -270
- package/frigg-cli/__tests__/utils/prisma-mock.js +0 -194
- package/frigg-cli/__tests__/utils/test-fixtures.js +0 -463
- package/frigg-cli/__tests__/utils/test-setup.js +0 -287
- package/frigg-cli/build-command/index.js +0 -65
- package/frigg-cli/db-setup-command/index.js +0 -193
- package/frigg-cli/deploy-command/index.js +0 -175
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +0 -301
- package/frigg-cli/generate-command/azure-generator.js +0 -43
- package/frigg-cli/generate-command/gcp-generator.js +0 -47
- package/frigg-cli/generate-command/index.js +0 -332
- package/frigg-cli/generate-command/terraform-generator.js +0 -555
- package/frigg-cli/generate-iam-command.js +0 -118
- package/frigg-cli/index.js +0 -75
- package/frigg-cli/index.test.js +0 -158
- package/frigg-cli/init-command/backend-first-handler.js +0 -756
- package/frigg-cli/init-command/index.js +0 -93
- package/frigg-cli/init-command/template-handler.js +0 -143
- package/frigg-cli/install-command/backend-js.js +0 -33
- package/frigg-cli/install-command/commit-changes.js +0 -16
- package/frigg-cli/install-command/environment-variables.js +0 -127
- package/frigg-cli/install-command/environment-variables.test.js +0 -136
- package/frigg-cli/install-command/index.js +0 -54
- package/frigg-cli/install-command/install-package.js +0 -13
- package/frigg-cli/install-command/integration-file.js +0 -30
- package/frigg-cli/install-command/logger.js +0 -12
- package/frigg-cli/install-command/template.js +0 -90
- package/frigg-cli/install-command/validate-package.js +0 -75
- package/frigg-cli/jest.config.js +0 -124
- package/frigg-cli/package.json +0 -54
- package/frigg-cli/start-command/index.js +0 -149
- package/frigg-cli/start-command/start-command.test.js +0 -297
- package/frigg-cli/test/init-command.test.js +0 -180
- package/frigg-cli/test/npm-registry.test.js +0 -319
- package/frigg-cli/ui-command/index.js +0 -154
- package/frigg-cli/utils/app-resolver.js +0 -319
- package/frigg-cli/utils/backend-path.js +0 -25
- package/frigg-cli/utils/database-validator.js +0 -161
- package/frigg-cli/utils/error-messages.js +0 -257
- package/frigg-cli/utils/npm-registry.js +0 -167
- package/frigg-cli/utils/prisma-runner.js +0 -280
- package/frigg-cli/utils/process-manager.js +0 -199
- package/frigg-cli/utils/repo-detection.js +0 -405
- package/infrastructure/aws-discovery.js +0 -1176
- package/infrastructure/aws-discovery.test.js +0 -1220
- package/infrastructure/serverless-template.js +0 -2094
- /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
- /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
- /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
- /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
- /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
- /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
- /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
|
+
});
|