Repository: go-to-k/delstack
Pull request #100 (2023-02-03 10:50:36)

test: change operator collection tests


Patch:
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)
 		}
 	}


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,
 			},
 		},
 	}