Repository: go-to-k/delstack
Pull request #99 (2023-02-03 10:27:49)

chore: change makefile for coverage


Patch:
diff --git a/.gitignore b/.gitignore
index 803cf47..9f636da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,7 +21,8 @@
 go.work
 
 dist/
-cover_file.html
+cover.out.tmp
+cover.html
 /delstack
 test_root_output.yaml
 testfiles/
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 9110431..e47df64 100644
--- a/Makefile
+++ b/Makefile
@@ -13,14 +13,17 @@ LDFLAGS := -s -w \
 GO_FILES := $(shell find . -type f -name '*.go' -print)
 
 TEST_RESULT := "$$(go test -race -cover -v ./... -coverpkg=./...)"
-TEST_COV_RESULT := "$$(go test -race -cover -v ./... -coverpkg=./... -coverprofile=cover_file.out)"
+TEST_COV_RESULT := "$$(go test -race -cover -v ./... -coverpkg=./... -coverprofile=cover.out.tmp)"
 FAIL_CHECK := "^[^\s\t]*FAIL[^\s\t]*$$"
 
 test:
 	@! echo $(TEST_RESULT) | $(COLORIZE_PASS) | $(COLORIZE_FAIL) | tee /dev/stderr | grep $(FAIL_CHECK) > /dev/null
 test_view:
 	@! echo $(TEST_COV_RESULT) | $(COLORIZE_PASS) | $(COLORIZE_FAIL) | tee /dev/stderr | grep $(FAIL_CHECK) > /dev/null
-	go tool cover -html=cover_file.out -o cover_file.html
+	cat cover.out.tmp | grep -v "**_mock.go" > cover.out
+	rm cover.out.tmp
+	go tool cover -func=cover.out
+	go tool cover -html=cover.out -o cover.html
 mockgen:
 	go generate ./...
 shadow:
diff --git a/README.md b/README.md
index 6a3b833..6db0405 100644
--- a/README.md
+++ b/README.md
@@ -64,9 +64,9 @@ Among the resources that **fail in the normal CloudFormation stack deletion**, t
 
 ### ResourceTypes
 
-If you selected `-i, --interactive` option, **you can select** ResourceTypes **you wish to delete even if DELETE_FAILED** in a prompt. This allows you to protect resources you do not want to delete!!
+The `-i, --interactive` option allows you to select the ResourceTypes **you wish to force delete even if DELETE_FAILED**. This feature allows you to **protect resources** you really do not want to delete by **"do not select the ResourceTypes"**!
 
-However, if resources of the selected ResourceTypes **will not be DELETE_FAILED when the stack is deleted**, the resources will be deleted **even if you selected**. The purpose of this tool is not to protect specific resources from CloudFormation's stack deletion feature, but simply to avoid forcing the deletion of something that really should not be deleted.
+However, if a resource can be deleted **without becoming DELETE_FAILED** by the **normal** CloudFormation stack deletion feature, the resource will be deleted **even if you do not select that resource type**. This tool is not intended to protect specific resources from the normal CloudFormation stack deletion feature, so I implemented this feature with the nuance that **only those resources that really should not be deleted will not be forced to be deleted**.
 
 If the stack contains resources that will be DELETE_FAILED but is not selected, **all DELETE_FAILED resources including the selected or not selected resources and the stack will remain undeleted**.
 
@@ -85,13 +85,13 @@ However, if resources of the selected ResourceTypes will not be DELETE_FAILED wh
 
 ### StackName Selection
 
-If you do not specify a stack name in command options in the interactive mode, you can input a keyword for filtering stack names and select a stack.
+If you do not specify a stack name in command options in the interactive mode, you can search stack names in a **case-insensitive** and select a stack.
 
 It can be **empty**.
 
 ```sh
 ❯ delstack -i
-Filter a keyword of stack names: test-goto
+Filter a keyword of stack names(case-insensitive): test-goto
 ```
 
 Then you select stack names in the UI.
@@ -102,4 +102,7 @@ Then you select stack names in the UI.
 > test-goto-stack-1
   test-goto-stack-2
   test-goto-stack-3
+  TEST-GOTO-stack-4
+  Test-Goto-stack-5
+  TEST-goto-stack-6
 ```
diff --git a/internal/app/app.go b/internal/app/app.go
index c41f8fb..87f7170 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -139,7 +139,7 @@ func (a *App) doInteractiveMode() ([]string, string, bool) {
 	opts := resourcetype.GetResourceTypes()
 
 	if a.StackName == "" {
-		stackNameLabel := "Filter a keyword of stack names: "
+		stackNameLabel := "Filter a keyword of stack names(case-insensitive): "
 		keyword = io.InputKeywordForFilter(stackNameLabel)
 	}
 
diff --git a/internal/operation/cloudformation_stack.go b/internal/operation/cloudformation_stack.go
index 9843502..e533cc1 100644
--- a/internal/operation/cloudformation_stack.go
+++ b/internal/operation/cloudformation_stack.go
@@ -146,7 +146,10 @@ func (o *CloudFormationStackOperator) ListStacksFilteredByKeyword(ctx context.Co
 	}
 
 	for _, stackSummary := range stackSummaries {
-		if strings.Contains(*stackSummary.StackName, *keyword) {
+		// for case-insensitive
+		lowerStackName := strings.ToLower(*stackSummary.StackName)
+		lowerKeyword := strings.ToLower(*keyword)
+		if strings.Contains(lowerStackName, lowerKeyword) {
 			filteredStacks = append(filteredStacks, *stackSummary.StackName)
 		}
 	}
diff --git a/internal/operation/operator_collection.go b/internal/operation/operator_collection.go
index 7294e4d..ccd2954 100644
--- a/internal/operation/operator_collection.go
+++ b/internal/operation/operator_collection.go
@@ -55,18 +55,18 @@ func (c *OperatorCollection) SetOperatorCollection(stackName *string, stackResou
 				c.unsupportedStackResources = append(c.unsupportedStackResources, stackResource)
 			} else {
 				switch *stackResource.ResourceType {
-				case resourcetype.S3_BUCKET:
+				case resourcetype.S3Bucket:
 					s3BucketOperator.AddResource(&stackResource)
-				case resourcetype.IAM_ROLE:
+				case resourcetype.IamRole:
 					iamRoleOperator.AddResource(&stackResource)
-				case resourcetype.ECR_REPOSITORY:
+				case resourcetype.EcrRepository:
 					ecrRepositoryOperator.AddResource(&stackResource)
-				case resourcetype.BACKUP_VAULT:
+				case resourcetype.BackupVault:
 					backupVaultOperator.AddResource(&stackResource)
-				case resourcetype.CLOUDFORMATION_STACK:
+				case resourcetype.CloudformationStack:
 					cloudformationStackOperator.AddResource(&stackResource)
 				default:
-					if strings.Contains(*stackResource.ResourceType, resourcetype.CUSTOM_RESOURCE) {
+					if strings.Contains(*stackResource.ResourceType, resourcetype.CustomResource) {
 						customOperator.AddResource(&stackResource)
 					}
 				}
@@ -84,7 +84,7 @@ func (c *OperatorCollection) SetOperatorCollection(stackName *string, stackResou
 
 func (c *OperatorCollection) containsResourceType(resource string) bool {
 	for _, t := range c.targetResourceTypes {
-		if t == resource || (t == resourcetype.CUSTOM_RESOURCE && strings.Contains(resource, resourcetype.CUSTOM_RESOURCE)) {
+		if t == resource || (t == resourcetype.CustomResource && strings.Contains(resource, resourcetype.CustomResource)) {
 			return true
 		}
 	}
@@ -112,11 +112,11 @@ func (c *OperatorCollection) RaiseUnsupportedResourceError() error {
 
 	supportedStackResourcesHeader := []string{"ResourceType", "Description"}
 	supportedStackResourcesData := [][]string{
-		{resourcetype.S3_BUCKET, "S3 Buckets, including buckets with Non-empty or Versioning enabled and DeletionPolicy not Retain."},
-		{resourcetype.IAM_ROLE, "IAM Roles, including roles with policies from outside the stack."},
-		{resourcetype.ECR_REPOSITORY, "ECR Repositories, including repositories containing images."},
-		{resourcetype.BACKUP_VAULT, "Backup Vaults, including vaults containing recovery points."},
-		{resourcetype.CLOUDFORMATION_STACK, "Nested Child Stacks that failed to delete."},
+		{resourcetype.S3Bucket, "S3 Buckets, including buckets with Non-empty or Versioning enabled and DeletionPolicy not Retain."},
+		{resourcetype.IamRole, "IAM Roles, including roles with policies from outside the stack."},
+		{resourcetype.EcrRepository, "ECR Repositories, including repositories containing images."},
+		{resourcetype.BackupVault, "Backup Vaults, including vaults containing recovery points."},
+		{resourcetype.CloudformationStack, "Nested Child Stacks that failed to delete."},
 		{"Custom::Xxx", "Custom Resources, but they will be deleted on its own."},
 	}
 	supportedStackResources := "\nSupported resources for force deletion of DELETE_FAILED resources are followings.\n" + *io.ToStringAsTableFormat(supportedStackResourcesHeader, supportedStackResourcesData)
diff --git a/internal/resourcetype/resourcetype.go b/internal/resourcetype/resourcetype.go
index 1e6e015..0b5efa4 100644
--- a/internal/resourcetype/resourcetype.go
+++ b/internal/resourcetype/resourcetype.go
@@ -1,21 +1,21 @@
 package resourcetype
 
 const (
-	S3_BUCKET            = "AWS::S3::Bucket"
-	IAM_ROLE             = "AWS::IAM::Role"
-	ECR_REPOSITORY       = "AWS::ECR::Repository"
-	BACKUP_VAULT         = "AWS::Backup::BackupVault"
-	CLOUDFORMATION_STACK = "AWS::CloudFormation::Stack"
-	CUSTOM_RESOURCE      = "Custom::"
+	S3Bucket            = "AWS::S3::Bucket"
+	IamRole             = "AWS::IAM::Role"
+	EcrRepository       = "AWS::ECR::Repository"
+	BackupVault         = "AWS::Backup::BackupVault"
+	CloudformationStack = "AWS::CloudFormation::Stack"
+	CustomResource      = "Custom::"
 )
 
 func GetResourceTypes() []string {
 	return []string{
-		S3_BUCKET,
-		IAM_ROLE,
-		ECR_REPOSITORY,
-		BACKUP_VAULT,
-		CLOUDFORMATION_STACK,
-		CUSTOM_RESOURCE,
+		S3Bucket,
+		IamRole,
+		EcrRepository,
+		BackupVault,
+		CloudformationStack,
+		CustomResource,
 	}
 }


Test patch:
diff --git a/internal/operation/cloudformation_stack_test.go b/internal/operation/cloudformation_stack_test.go
index 8bf61c1..e353f5b 100644
--- a/internal/operation/cloudformation_stack_test.go
+++ b/internal/operation/cloudformation_stack_test.go
@@ -1613,6 +1613,66 @@ func TestCloudFormationStackOperator_ListStacksFilteredByKeyword(t *testing.T) {
 			},
 			wantErr: false,
 		},
+		{
+			name: "list stacks filtered by lower keyword successfully",
+			args: args{
+				ctx:     ctx,
+				keyword: "teststack",
+			},
+			prepareMockCloudFormationFn: func(m *client.MockICloudFormation) {
+				m.EXPECT().ListStacks(gomock.Any()).Return(
+					[]types.StackSummary{
+						{
+							StackName:   aws.String("TestStack1"),
+							StackStatus: types.StackStatusCreateComplete,
+						},
+						{
+							StackName:   aws.String("TestStack2"),
+							StackStatus: types.StackStatusCreateComplete,
+						},
+					},
+					nil,
+				)
+			},
+			want: want{
+				filteredStacks: []string{
+					"TestStack1",
+					"TestStack2",
+				},
+				err: nil,
+			},
+			wantErr: false,
+		},
+		{
+			name: "list stacks filtered by upper keyword successfully",
+			args: args{
+				ctx:     ctx,
+				keyword: "TESTSTACK",
+			},
+			prepareMockCloudFormationFn: func(m *client.MockICloudFormation) {
+				m.EXPECT().ListStacks(gomock.Any()).Return(
+					[]types.StackSummary{
+						{
+							StackName:   aws.String("TestStack1"),
+							StackStatus: types.StackStatusCreateComplete,
+						},
+						{
+							StackName:   aws.String("TestStack2"),
+							StackStatus: types.StackStatusCreateComplete,
+						},
+					},
+					nil,
+				)
+			},
+			want: want{
+				filteredStacks: []string{
+					"TestStack1",
+					"TestStack2",
+				},
+				err: nil,
+			},
+			wantErr: false,
+		},
 		{
 			name: "list stacks filtered by keyword but empty keyword successfully",
 			args: args{
diff --git a/internal/operation/operator_collection_test.go b/internal/operation/operator_collection_test.go
index 6dc8b1a..b913627 100644
--- a/internal/operation/operator_collection_test.go
+++ b/internal/operation/operator_collection_test.go
@@ -145,6 +145,50 @@ func TestOperatorCollection_SetOperatorCollection(t *testing.T) {
 		},
 		{
 			name: "resource counts check 3 for all target resource types",
+			args: args{
+				ctx:                 context.Background(),
+				stackName:           aws.String("test"),
+				targetResourceTypes: targetResourceTypesForAllServices,
+				stackResourceSummaries: []types.StackResourceSummary{
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId1"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::CloudFormation::Stack"),
+						PhysicalResourceId: aws.String("PhysicalResourceId1"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId2"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::CloudFormation::Stack"),
+						PhysicalResourceId: aws.String("PhysicalResourceId2"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId3"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId3"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId4"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId4"),
+					},
+				},
+			},
+			want: want{
+				logicalResourceIdsLength:                   4,
+				unsupportedStackResourcesLength:            2,
+				s3BucketOperatorResourcesLength:            0,
+				iamRoleOperatorResourcesLength:             0,
+				ecrRepositoryOperatorResourcesLength:       0,
+				backupVaultOperatorResourcesLength:         0,
+				cloudformationStackOperatorResourcesLength: 2,
+				customOperatorResourcesLength:              0,
+			},
+		},
+		{
+			name: "resource counts check 4 for all target resource types",
 			args: args{
 				ctx:                 context.Background(),
 				stackName:           aws.String("test"),
@@ -176,7 +220,51 @@ func TestOperatorCollection_SetOperatorCollection(t *testing.T) {
 			},
 		},
 		{
-			name: "resource counts check 4 for all target resource types",
+			name: "resource counts check 5 for all target resource types",
+			args: args{
+				ctx:                 context.Background(),
+				stackName:           aws.String("test"),
+				targetResourceTypes: targetResourceTypesForAllServices,
+				stackResourceSummaries: []types.StackResourceSummary{
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId1"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("AWS::CloudFormation::Stack"),
+						PhysicalResourceId: aws.String("PhysicalResourceId1"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId2"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("AWS::CloudFormation::Stack"),
+						PhysicalResourceId: aws.String("PhysicalResourceId2"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId3"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId3"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId4"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId4"),
+					},
+				},
+			},
+			want: want{
+				logicalResourceIdsLength:                   2,
+				unsupportedStackResourcesLength:            2,
+				s3BucketOperatorResourcesLength:            0,
+				iamRoleOperatorResourcesLength:             0,
+				ecrRepositoryOperatorResourcesLength:       0,
+				backupVaultOperatorResourcesLength:         0,
+				cloudformationStackOperatorResourcesLength: 0,
+				customOperatorResourcesLength:              0,
+			},
+		},
+		{
+			name: "resource counts check 6 for all target resource types",
 			args: args{
 				ctx:                 context.Background(),
 				stackName:           aws.String("test"),
@@ -207,6 +295,50 @@ func TestOperatorCollection_SetOperatorCollection(t *testing.T) {
 				customOperatorResourcesLength:              0,
 			},
 		},
+		{
+			name: "resource counts check 7 for all target resource types",
+			args: args{
+				ctx:                 context.Background(),
+				stackName:           aws.String("test"),
+				targetResourceTypes: targetResourceTypesForAllServices,
+				stackResourceSummaries: []types.StackResourceSummary{
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId1"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::CloudFormation::Stack"),
+						PhysicalResourceId: aws.String("PhysicalResourceId1"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId2"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::CloudFormation::Stack"),
+						PhysicalResourceId: aws.String("PhysicalResourceId2"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId3"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId3"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId4"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId4"),
+					},
+				},
+			},
+			want: want{
+				logicalResourceIdsLength:                   2,
+				unsupportedStackResourcesLength:            0,
+				s3BucketOperatorResourcesLength:            0,
+				iamRoleOperatorResourcesLength:             0,
+				ecrRepositoryOperatorResourcesLength:       0,
+				backupVaultOperatorResourcesLength:         0,
+				cloudformationStackOperatorResourcesLength: 2,
+				customOperatorResourcesLength:              0,
+			},
+		},
 		{
 			name: "resource counts check 1 for partial target resource types",
 			args: args{
@@ -304,21 +436,33 @@ func TestOperatorCollection_SetOperatorCollection(t *testing.T) {
 				stackResourceSummaries: []types.StackResourceSummary{
 					{
 						LogicalResourceId:  aws.String("LogicalResourceId1"),
-						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceStatus:     "DELETE_FAILED",
 						ResourceType:       aws.String("AWS::CloudFormation::Stack"),
 						PhysicalResourceId: aws.String("PhysicalResourceId1"),
 					},
 					{
 						LogicalResourceId:  aws.String("LogicalResourceId2"),
 						ResourceStatus:     "DELETE_FAILED",
-						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						ResourceType:       aws.String("AWS::CloudFormation::Stack"),
 						PhysicalResourceId: aws.String("PhysicalResourceId2"),
 					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId3"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId3"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId4"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId4"),
+					},
 				},
 			},
 			want: want{
-				logicalResourceIdsLength:                   1,
-				unsupportedStackResourcesLength:            1,
+				logicalResourceIdsLength:                   4,
+				unsupportedStackResourcesLength:            4,
 				s3BucketOperatorResourcesLength:            0,
 				iamRoleOperatorResourcesLength:             0,
 				ecrRepositoryOperatorResourcesLength:       0,
@@ -336,13 +480,13 @@ func TestOperatorCollection_SetOperatorCollection(t *testing.T) {
 				stackResourceSummaries: []types.StackResourceSummary{
 					{
 						LogicalResourceId:  aws.String("LogicalResourceId1"),
-						ResourceStatus:     "DELETE_FAILED",
+						ResourceStatus:     "DELETE_COMPLETE",
 						ResourceType:       aws.String("AWS::CloudFormation::Stack"),
 						PhysicalResourceId: aws.String("PhysicalResourceId1"),
 					},
 					{
 						LogicalResourceId:  aws.String("LogicalResourceId2"),
-						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceStatus:     "DELETE_FAILED",
 						ResourceType:       aws.String("AWS::DynamoDB::Table"),
 						PhysicalResourceId: aws.String("PhysicalResourceId2"),
 					},
@@ -368,22 +512,34 @@ func TestOperatorCollection_SetOperatorCollection(t *testing.T) {
 				stackResourceSummaries: []types.StackResourceSummary{
 					{
 						LogicalResourceId:  aws.String("LogicalResourceId1"),
-						ResourceStatus:     "DELETE_FAILED",
-						ResourceType:       aws.String("AWS::S3::Bucket"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("AWS::CloudFormation::Stack"),
 						PhysicalResourceId: aws.String("PhysicalResourceId1"),
 					},
 					{
 						LogicalResourceId:  aws.String("LogicalResourceId2"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("AWS::CloudFormation::Stack"),
+						PhysicalResourceId: aws.String("PhysicalResourceId2"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId3"),
 						ResourceStatus:     "DELETE_FAILED",
 						ResourceType:       aws.String("AWS::DynamoDB::Table"),
-						PhysicalResourceId: aws.String("PhysicalResourceId2"),
+						PhysicalResourceId: aws.String("PhysicalResourceId3"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId4"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId4"),
 					},
 				},
 			},
 			want: want{
 				logicalResourceIdsLength:                   2,
-				unsupportedStackResourcesLength:            1,
-				s3BucketOperatorResourcesLength:            1,
+				unsupportedStackResourcesLength:            2,
+				s3BucketOperatorResourcesLength:            0,
 				iamRoleOperatorResourcesLength:             0,
 				ecrRepositoryOperatorResourcesLength:       0,
 				backupVaultOperatorResourcesLength:         0,
@@ -400,13 +556,13 @@ func TestOperatorCollection_SetOperatorCollection(t *testing.T) {
 				stackResourceSummaries: []types.StackResourceSummary{
 					{
 						LogicalResourceId:  aws.String("LogicalResourceId1"),
-						ResourceStatus:     "DELETE_COMPLETE",
-						ResourceType:       aws.String("AWS::S3::Bucket"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::CloudFormation::Stack"),
 						PhysicalResourceId: aws.String("PhysicalResourceId1"),
 					},
 					{
 						LogicalResourceId:  aws.String("LogicalResourceId2"),
-						ResourceStatus:     "DELETE_FAILED",
+						ResourceStatus:     "DELETE_COMPLETE",
 						ResourceType:       aws.String("AWS::DynamoDB::Table"),
 						PhysicalResourceId: aws.String("PhysicalResourceId2"),
 					},
@@ -433,21 +589,33 @@ func TestOperatorCollection_SetOperatorCollection(t *testing.T) {
 					{
 						LogicalResourceId:  aws.String("LogicalResourceId1"),
 						ResourceStatus:     "DELETE_FAILED",
-						ResourceType:       aws.String("AWS::S3::Bucket"),
+						ResourceType:       aws.String("AWS::CloudFormation::Stack"),
 						PhysicalResourceId: aws.String("PhysicalResourceId1"),
 					},
 					{
 						LogicalResourceId:  aws.String("LogicalResourceId2"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::CloudFormation::Stack"),
+						PhysicalResourceId: aws.String("PhysicalResourceId2"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId3"),
 						ResourceStatus:     "DELETE_COMPLETE",
 						ResourceType:       aws.String("AWS::DynamoDB::Table"),
-						PhysicalResourceId: aws.String("PhysicalResourceId2"),
+						PhysicalResourceId: aws.String("PhysicalResourceId3"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId4"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId4"),
 					},
 				},
 			},
 			want: want{
-				logicalResourceIdsLength:                   1,
-				unsupportedStackResourcesLength:            0,
-				s3BucketOperatorResourcesLength:            1,
+				logicalResourceIdsLength:                   2,
+				unsupportedStackResourcesLength:            2,
+				s3BucketOperatorResourcesLength:            0,
 				iamRoleOperatorResourcesLength:             0,
 				ecrRepositoryOperatorResourcesLength:       0,
 				backupVaultOperatorResourcesLength:         0,
@@ -465,7 +633,7 @@ func TestOperatorCollection_SetOperatorCollection(t *testing.T) {
 					{
 						LogicalResourceId:  aws.String("LogicalResourceId1"),
 						ResourceStatus:     "DELETE_FAILED",
-						ResourceType:       aws.String("Custom::"),
+						ResourceType:       aws.String("AWS::S3::Bucket"),
 						PhysicalResourceId: aws.String("PhysicalResourceId1"),
 					},
 					{
@@ -479,12 +647,12 @@ func TestOperatorCollection_SetOperatorCollection(t *testing.T) {
 			want: want{
 				logicalResourceIdsLength:                   2,
 				unsupportedStackResourcesLength:            1,
-				s3BucketOperatorResourcesLength:            0,
+				s3BucketOperatorResourcesLength:            1,
 				iamRoleOperatorResourcesLength:             0,
 				ecrRepositoryOperatorResourcesLength:       0,
 				backupVaultOperatorResourcesLength:         0,
 				cloudformationStackOperatorResourcesLength: 0,
-				customOperatorResourcesLength:              1,
+				customOperatorResourcesLength:              0,
 			},
 		},
 		{
@@ -496,22 +664,34 @@ func TestOperatorCollection_SetOperatorCollection(t *testing.T) {
 				stackResourceSummaries: []types.StackResourceSummary{
 					{
 						LogicalResourceId:  aws.String("LogicalResourceId1"),
-						ResourceStatus:     "DELETE_COMPLETE",
-						ResourceType:       aws.String("Custom::"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::S3::Bucket"),
 						PhysicalResourceId: aws.String("PhysicalResourceId1"),
 					},
 					{
 						LogicalResourceId:  aws.String("LogicalResourceId2"),
 						ResourceStatus:     "DELETE_FAILED",
-						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						ResourceType:       aws.String("AWS::S3::Bucket"),
 						PhysicalResourceId: aws.String("PhysicalResourceId2"),
 					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId3"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId3"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId4"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId4"),
+					},
 				},
 			},
 			want: want{
-				logicalResourceIdsLength:                   1,
-				unsupportedStackResourcesLength:            1,
-				s3BucketOperatorResourcesLength:            0,
+				logicalResourceIdsLength:                   4,
+				unsupportedStackResourcesLength:            2,
+				s3BucketOperatorResourcesLength:            2,
 				iamRoleOperatorResourcesLength:             0,
 				ecrRepositoryOperatorResourcesLength:       0,
 				backupVaultOperatorResourcesLength:         0,
@@ -528,13 +708,13 @@ func TestOperatorCollection_SetOperatorCollection(t *testing.T) {
 				stackResourceSummaries: []types.StackResourceSummary{
 					{
 						LogicalResourceId:  aws.String("LogicalResourceId1"),
-						ResourceStatus:     "DELETE_FAILED",
-						ResourceType:       aws.String("Custom::"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("AWS::S3::Bucket"),
 						PhysicalResourceId: aws.String("PhysicalResourceId1"),
 					},
 					{
 						LogicalResourceId:  aws.String("LogicalResourceId2"),
-						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceStatus:     "DELETE_FAILED",
 						ResourceType:       aws.String("AWS::DynamoDB::Table"),
 						PhysicalResourceId: aws.String("PhysicalResourceId2"),
 					},
@@ -542,13 +722,361 @@ func TestOperatorCollection_SetOperatorCollection(t *testing.T) {
 			},
 			want: want{
 				logicalResourceIdsLength:                   1,
-				unsupportedStackResourcesLength:            0,
+				unsupportedStackResourcesLength:            1,
 				s3BucketOperatorResourcesLength:            0,
 				iamRoleOperatorResourcesLength:             0,
 				ecrRepositoryOperatorResourcesLength:       0,
 				backupVaultOperatorResourcesLength:         0,
 				cloudformationStackOperatorResourcesLength: 0,
-				customOperatorResourcesLength:              1,
+				customOperatorResourcesLength:              0,
+			},
+		},
+		{
+			name: "resource counts check 11 for partial target resource types",
+			args: args{
+				ctx:                 context.Background(),
+				stackName:           aws.String("test"),
+				targetResourceTypes: targetResourceTypesForPartialServices,
+				stackResourceSummaries: []types.StackResourceSummary{
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId1"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("AWS::S3::Bucket"),
+						PhysicalResourceId: aws.String("PhysicalResourceId1"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId2"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("AWS::S3::Bucket"),
+						PhysicalResourceId: aws.String("PhysicalResourceId2"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId3"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId3"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId4"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId4"),
+					},
+				},
+			},
+			want: want{
+				logicalResourceIdsLength:                   2,
+				unsupportedStackResourcesLength:            2,
+				s3BucketOperatorResourcesLength:            0,
+				iamRoleOperatorResourcesLength:             0,
+				ecrRepositoryOperatorResourcesLength:       0,
+				backupVaultOperatorResourcesLength:         0,
+				cloudformationStackOperatorResourcesLength: 0,
+				customOperatorResourcesLength:              0,
+			},
+		},
+		{
+			name: "resource counts check 12 for partial target resource types",
+			args: args{
+				ctx:                 context.Background(),
+				stackName:           aws.String("test"),
+				targetResourceTypes: targetResourceTypesForPartialServices,
+				stackResourceSummaries: []types.StackResourceSummary{
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId1"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::S3::Bucket"),
+						PhysicalResourceId: aws.String("PhysicalResourceId1"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId2"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId2"),
+					},
+				},
+			},
+			want: want{
+				logicalResourceIdsLength:                   1,
+				unsupportedStackResourcesLength:            0,
+				s3BucketOperatorResourcesLength:            1,
+				iamRoleOperatorResourcesLength:             0,
+				ecrRepositoryOperatorResourcesLength:       0,
+				backupVaultOperatorResourcesLength:         0,
+				cloudformationStackOperatorResourcesLength: 0,
+				customOperatorResourcesLength:              0,
+			},
+		},
+		{
+			name: "resource counts check 13 for partial target resource types",
+			args: args{
+				ctx:                 context.Background(),
+				stackName:           aws.String("test"),
+				targetResourceTypes: targetResourceTypesForPartialServices,
+				stackResourceSummaries: []types.StackResourceSummary{
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId1"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::S3::Bucket"),
+						PhysicalResourceId: aws.String("PhysicalResourceId1"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId2"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::S3::Bucket"),
+						PhysicalResourceId: aws.String("PhysicalResourceId2"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId3"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId3"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId4"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId4"),
+					},
+				},
+			},
+			want: want{
+				logicalResourceIdsLength:                   2,
+				unsupportedStackResourcesLength:            0,
+				s3BucketOperatorResourcesLength:            2,
+				iamRoleOperatorResourcesLength:             0,
+				ecrRepositoryOperatorResourcesLength:       0,
+				backupVaultOperatorResourcesLength:         0,
+				cloudformationStackOperatorResourcesLength: 0,
+				customOperatorResourcesLength:              0,
+			},
+		},
+		{
+			name: "resource counts check 14 for partial target resource types",
+			args: args{
+				ctx:                 context.Background(),
+				stackName:           aws.String("test"),
+				targetResourceTypes: targetResourceTypesForPartialServices,
+				stackResourceSummaries: []types.StackResourceSummary{
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId1"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("Custom::"),
+						PhysicalResourceId: aws.String("PhysicalResourceId1"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId2"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId2"),
+					},
+				},
+			},
+			want: want{
+				logicalResourceIdsLength:                   2,
+				unsupportedStackResourcesLength:            1,
+				s3BucketOperatorResourcesLength:            0,
+				iamRoleOperatorResourcesLength:             0,
+				ecrRepositoryOperatorResourcesLength:       0,
+				backupVaultOperatorResourcesLength:         0,
+				cloudformationStackOperatorResourcesLength: 0,
+				customOperatorResourcesLength:              1,
+			},
+		},
+		{
+			name: "resource counts check 15 for partial target resource types",
+			args: args{
+				ctx:                 context.Background(),
+				stackName:           aws.String("test"),
+				targetResourceTypes: targetResourceTypesForPartialServices,
+				stackResourceSummaries: []types.StackResourceSummary{
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId1"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("Custom::"),
+						PhysicalResourceId: aws.String("PhysicalResourceId1"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId2"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("Custom::"),
+						PhysicalResourceId: aws.String("PhysicalResourceId2"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId3"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId3"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId4"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId4"),
+					},
+				},
+			},
+			want: want{
+				logicalResourceIdsLength:                   4,
+				unsupportedStackResourcesLength:            2,
+				s3BucketOperatorResourcesLength:            0,
+				iamRoleOperatorResourcesLength:             0,
+				ecrRepositoryOperatorResourcesLength:       0,
+				backupVaultOperatorResourcesLength:         0,
+				cloudformationStackOperatorResourcesLength: 0,
+				customOperatorResourcesLength:              2,
+			},
+		},
+		{
+			name: "resource counts check 16 for partial target resource types",
+			args: args{
+				ctx:                 context.Background(),
+				stackName:           aws.String("test"),
+				targetResourceTypes: targetResourceTypesForPartialServices,
+				stackResourceSummaries: []types.StackResourceSummary{
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId1"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("Custom::"),
+						PhysicalResourceId: aws.String("PhysicalResourceId1"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId2"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId2"),
+					},
+				},
+			},
+			want: want{
+				logicalResourceIdsLength:                   1,
+				unsupportedStackResourcesLength:            1,
+				s3BucketOperatorResourcesLength:            0,
+				iamRoleOperatorResourcesLength:             0,
+				ecrRepositoryOperatorResourcesLength:       0,
+				backupVaultOperatorResourcesLength:         0,
+				cloudformationStackOperatorResourcesLength: 0,
+				customOperatorResourcesLength:              0,
+			},
+		},
+		{
+			name: "resource counts check 17 for partial target resource types",
+			args: args{
+				ctx:                 context.Background(),
+				stackName:           aws.String("test"),
+				targetResourceTypes: targetResourceTypesForPartialServices,
+				stackResourceSummaries: []types.StackResourceSummary{
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId1"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("Custom::"),
+						PhysicalResourceId: aws.String("PhysicalResourceId1"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId2"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("Custom::"),
+						PhysicalResourceId: aws.String("PhysicalResourceId2"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId3"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId3"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId4"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId4"),
+					},
+				},
+			},
+			want: want{
+				logicalResourceIdsLength:                   2,
+				unsupportedStackResourcesLength:            2,
+				s3BucketOperatorResourcesLength:            0,
+				iamRoleOperatorResourcesLength:             0,
+				ecrRepositoryOperatorResourcesLength:       0,
+				backupVaultOperatorResourcesLength:         0,
+				cloudformationStackOperatorResourcesLength: 0,
+				customOperatorResourcesLength:              0,
+			},
+		},
+		{
+			name: "resource counts check 18 for partial target resource types",
+			args: args{
+				ctx:                 context.Background(),
+				stackName:           aws.String("test"),
+				targetResourceTypes: targetResourceTypesForPartialServices,
+				stackResourceSummaries: []types.StackResourceSummary{
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId1"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("Custom::"),
+						PhysicalResourceId: aws.String("PhysicalResourceId1"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId2"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId2"),
+					},
+				},
+			},
+			want: want{
+				logicalResourceIdsLength:                   1,
+				unsupportedStackResourcesLength:            0,
+				s3BucketOperatorResourcesLength:            0,
+				iamRoleOperatorResourcesLength:             0,
+				ecrRepositoryOperatorResourcesLength:       0,
+				backupVaultOperatorResourcesLength:         0,
+				cloudformationStackOperatorResourcesLength: 0,
+				customOperatorResourcesLength:              1,
+			},
+		},
+		{
+			name: "resource counts check 19 for partial target resource types",
+			args: args{
+				ctx:                 context.Background(),
+				stackName:           aws.String("test"),
+				targetResourceTypes: targetResourceTypesForPartialServices,
+				stackResourceSummaries: []types.StackResourceSummary{
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId1"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("Custom::"),
+						PhysicalResourceId: aws.String("PhysicalResourceId1"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId2"),
+						ResourceStatus:     "DELETE_FAILED",
+						ResourceType:       aws.String("Custom::"),
+						PhysicalResourceId: aws.String("PhysicalResourceId2"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId3"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId3"),
+					},
+					{
+						LogicalResourceId:  aws.String("LogicalResourceId4"),
+						ResourceStatus:     "DELETE_COMPLETE",
+						ResourceType:       aws.String("AWS::DynamoDB::Table"),
+						PhysicalResourceId: aws.String("PhysicalResourceId4"),
+					},
+				},
+			},
+			want: want{
+				logicalResourceIdsLength:                   2,
+				unsupportedStackResourcesLength:            0,
+				s3BucketOperatorResourcesLength:            0,
+				iamRoleOperatorResourcesLength:             0,
+				ecrRepositoryOperatorResourcesLength:       0,
+				backupVaultOperatorResourcesLength:         0,
+				cloudformationStackOperatorResourcesLength: 0,
+				customOperatorResourcesLength:              2,
 			},
 		},
 	}