Skip to content

Commit 12da2c1

Browse files
authored
[AV-129987] Add acceptance tests for ClusterDeletionProtection resource lifecycle (#612)
1 parent 333a4ca commit 12da2c1

1 file changed

Lines changed: 139 additions & 0 deletions

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package acceptance_tests
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"regexp"
8+
"testing"
9+
10+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
11+
"github.com/hashicorp/terraform-plugin-testing/terraform"
12+
13+
"github.com/couchbasecloud/terraform-provider-couchbase-capella/internal/api"
14+
clusterapi "github.com/couchbasecloud/terraform-provider-couchbase-capella/internal/api/cluster"
15+
)
16+
17+
// TestAccClusterDeletionProtectionResource tests the full lifecycle:
18+
// create with protection disabled → update to enabled → import state.
19+
func TestAccClusterDeletionProtectionResource(t *testing.T) {
20+
disableDeletionProtectionOnCleanup(t)
21+
resourceName := randomStringWithPrefix("tf_acc_del_prot_")
22+
resourceReference := "couchbase-capella_cluster_deletion_protection." + resourceName
23+
24+
resource.ParallelTest(t, resource.TestCase{
25+
ProtoV6ProviderFactories: globalProtoV6ProviderFactory,
26+
Steps: []resource.TestStep{
27+
// Step 1: Create with deletion_protection = false
28+
{
29+
Config: testAccClusterDeletionProtectionConfig(resourceName, globalClusterId, false),
30+
Check: resource.ComposeAggregateTestCheckFunc(
31+
resource.TestCheckResourceAttr(resourceReference, "organization_id", globalOrgId),
32+
resource.TestCheckResourceAttr(resourceReference, "project_id", globalProjectId),
33+
resource.TestCheckResourceAttr(resourceReference, "cluster_id", globalClusterId),
34+
resource.TestCheckResourceAttr(resourceReference, "deletion_protection", "false"),
35+
),
36+
},
37+
// Step 2: Update from false to true
38+
{
39+
Config: testAccClusterDeletionProtectionConfig(resourceName, globalClusterId, true),
40+
Check: resource.ComposeAggregateTestCheckFunc(
41+
resource.TestCheckResourceAttr(resourceReference, "organization_id", globalOrgId),
42+
resource.TestCheckResourceAttr(resourceReference, "project_id", globalProjectId),
43+
resource.TestCheckResourceAttr(resourceReference, "cluster_id", globalClusterId),
44+
resource.TestCheckResourceAttr(resourceReference, "deletion_protection", "true"),
45+
),
46+
},
47+
// Step 3: ImportState
48+
{
49+
ResourceName: resourceReference,
50+
ImportStateIdFunc: generateDeletionProtectionImportId(resourceReference),
51+
ImportState: true,
52+
ImportStateVerify: true,
53+
ImportStateVerifyIdentifierAttribute: "cluster_id",
54+
},
55+
// Step 4: Disable to leave cluster in clean state
56+
{
57+
Config: testAccClusterDeletionProtectionConfig(resourceName, globalClusterId, false),
58+
Check: resource.ComposeAggregateTestCheckFunc(
59+
resource.TestCheckResourceAttr(resourceReference, "deletion_protection", "false"),
60+
),
61+
},
62+
},
63+
})
64+
}
65+
66+
// TestAccClusterDeletionProtectionInvalidCluster verifies correct error for nonexistent cluster.
67+
func TestAccClusterDeletionProtectionInvalidCluster(t *testing.T) {
68+
resourceName := randomStringWithPrefix("tf_acc_del_prot_invalid_")
69+
70+
resource.ParallelTest(t, resource.TestCase{
71+
ProtoV6ProviderFactories: globalProtoV6ProviderFactory,
72+
Steps: []resource.TestStep{
73+
{
74+
Config: testAccClusterDeletionProtectionConfig(resourceName, "00000000-0000-0000-0000-000000000000", true),
75+
ExpectError: regexp.MustCompile(`(?s)Error.*cluster.*(not be found|not found|access.*denied)`),
76+
},
77+
},
78+
})
79+
}
80+
81+
// --- Config builders ---
82+
83+
func testAccClusterDeletionProtectionConfig(resourceName, clusterID string, enabled bool) string {
84+
return fmt.Sprintf(`
85+
%[1]s
86+
87+
resource "couchbase-capella_cluster_deletion_protection" "%[2]s" {
88+
organization_id = "%[3]s"
89+
project_id = "%[4]s"
90+
cluster_id = "%[5]s"
91+
deletion_protection = %[6]t
92+
}
93+
`, globalProviderBlock, resourceName, globalOrgId, globalProjectId, clusterID, enabled)
94+
}
95+
96+
// --- Import ID ---
97+
98+
func generateDeletionProtectionImportId(resourceReference string) resource.ImportStateIdFunc {
99+
return func(state *terraform.State) (string, error) {
100+
var rawState map[string]string
101+
for _, m := range state.Modules {
102+
if len(m.Resources) > 0 {
103+
if v, ok := m.Resources[resourceReference]; ok {
104+
rawState = v.Primary.Attributes
105+
}
106+
}
107+
}
108+
if rawState == nil {
109+
return "", fmt.Errorf("resource %s not found in state", resourceReference)
110+
}
111+
return fmt.Sprintf(
112+
"cluster_id=%s,project_id=%s,organization_id=%s",
113+
rawState["cluster_id"], rawState["project_id"], rawState["organization_id"],
114+
), nil
115+
}
116+
}
117+
118+
// disableDeletionProtectionOnCleanup registers a t.Cleanup hook that
119+
// unconditionally disables deletion protection on globalClusterId so that
120+
// TestMain teardown can delete the cluster even if the test fails mid-run.
121+
func disableDeletionProtectionOnCleanup(t *testing.T) {
122+
t.Helper()
123+
t.Cleanup(func() {
124+
ctx := context.Background()
125+
url := fmt.Sprintf("%s/v4/organizations/%s/projects/%s/clusters/%s/deletionProtection",
126+
globalHost, globalOrgId, globalProjectId, globalClusterId)
127+
cfg := api.EndpointCfg{Url: url, Method: http.MethodPut, SuccessStatus: http.StatusNoContent}
128+
_, err := globalClient.ExecuteWithRetry(
129+
ctx,
130+
cfg,
131+
clusterapi.UpdateDeletionProtectionRequest{DeletionProtection: false},
132+
globalToken,
133+
nil,
134+
)
135+
if err != nil {
136+
t.Logf("WARNING: failed to disable deletion protection on cleanup: %v", err)
137+
}
138+
})
139+
}

0 commit comments

Comments
 (0)