Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
23c4eee
[AV-127651] Add acceptance tests for legacy backup, backup schedule, …
panigrahisubhrajit May 6, 2026
c90e059
[AV-127651] Validate, discover, or create bucket in acceptance test s…
panigrahisubhrajit May 7, 2026
5f8d8d3
[AV-127651] Fix golangci-lint: errorlint and nestif
panigrahisubhrajit May 7, 2026
d4f5e98
[AV-127651] Add snapshot cluster env vars for forward compat
panigrahisubhrajit May 7, 2026
b7e68b2
[AV-127651] Drop snapshot env-var plumbing from this branch
panigrahisubhrajit May 7, 2026
8c0f461
[AV-127651] Make TestAccDatasourceBackups read-only to avoid 60m timeout
panigrahisubhrajit May 7, 2026
8dbc8c1
[AV-127651] Fold backups data source into TestAccBackupResource
panigrahisubhrajit May 7, 2026
f23d2fd
[AV-127651] Fix 4 P1 issues flagged by factory-droid
panigrahisubhrajit May 8, 2026
aeb08ae
[AV-127651] Recover from backend 500 on cluster creation (AV-129960)
panigrahisubhrajit May 8, 2026
d85bd99
[AV-127651] Read TF_VAR_bucket_name env var in test setup
panigrahisubhrajit May 8, 2026
b23ee1e
Merge origin/main into AV-127651-fix-legacy-backup
panigrahisubhrajit May 11, 2026
8d01bc4
[AV-127651] Track bucket lifecycle; restrict 5xx adoption fallback
panigrahisubhrajit May 11, 2026
b8e6d07
[AV-127651] Assert datasource returns created backup's ID via AttrPair
panigrahisubhrajit May 11, 2026
b0884d6
[AV-127651] Only mark cluster for cleanup when createCluster succeede…
panigrahisubhrajit May 11, 2026
c1696f9
[AV-127651] Reuse existing project on quota error so concurrent CI ru…
panigrahisubhrajit May 11, 2026
5259390
[AV-127651] Scan full backup list to verify created backup is present
panigrahisubhrajit May 11, 2026
95daa2f
[AV-127651] Serialize singleton-schedule tests; assert copy_to_regions
panigrahisubhrajit May 11, 2026
2ab7540
[AV-127651] Fix errcheck lint; restore ParallelTest; assert copy_to_r…
panigrahisubhrajit May 11, 2026
7d2f790
[AV-127651] Set globalBucketCreated before bucketWait to avoid leaks
panigrahisubhrajit May 12, 2026
aee7730
[AV-127651] Bump backup wait timeout to 90m, poll every 30s
panigrahisubhrajit May 12, 2026
56937ab
ci: retrigger CodeQL Default Setup (GitHub codeload.github.com 429)
panigrahisubhrajit May 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
249 changes: 249 additions & 0 deletions acceptance_tests/backup_acceptance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
package acceptance_tests

import (
"context"
"encoding/json"
"fmt"
"net/http"
"regexp"
"strconv"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"

"github.com/couchbasecloud/terraform-provider-couchbase-capella/internal/api"
backupapi "github.com/couchbasecloud/terraform-provider-couchbase-capella/internal/api/backup"
"github.com/couchbasecloud/terraform-provider-couchbase-capella/internal/errors"
providerschema "github.com/couchbasecloud/terraform-provider-couchbase-capella/internal/schema"
)

// TestAccBackupResource creates a backup, verifies its attributes, then
// layers the couchbase-capella_backups data source on top to confirm the
// backups list returns the just-created backup. The data source step
// intentionally reuses the same backup resource (no new backup is created)
// because Capella's legacy bucket backup endpoint serialises manual
// backups per bucket and a back-to-back second backup gets stuck in a
// non-terminal state until the per-bucket spacing window elapses.
func TestAccBackupResource(t *testing.T) {
resourceName := randomStringWithPrefix("tf_acc_backup_")
resourceReference := "couchbase-capella_backup." + resourceName
dsName := randomStringWithPrefix("tf_acc_backups_ds_")
dsReference := "data.couchbase-capella_backups." + dsName

resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: globalProtoV6ProviderFactory,
Steps: []resource.TestStep{
{
Config: testAccBackupResourceConfig(resourceName),
Comment thread
panigrahisubhrajit marked this conversation as resolved.
Check: resource.ComposeAggregateTestCheckFunc(
testAccExistsBackupResource(t, resourceReference),
resource.TestCheckResourceAttr(resourceReference, "organization_id", globalOrgId),
resource.TestCheckResourceAttr(resourceReference, "project_id", globalProjectId),
resource.TestCheckResourceAttr(resourceReference, "cluster_id", globalClusterId),
resource.TestCheckResourceAttr(resourceReference, "bucket_id", globalBucketId),
resource.TestCheckResourceAttrSet(resourceReference, "id"),
resource.TestCheckResourceAttrSet(resourceReference, "cycle_id"),
resource.TestCheckResourceAttrSet(resourceReference, "date"),
resource.TestCheckResourceAttrSet(resourceReference, "status"),
resource.TestCheckResourceAttrSet(resourceReference, "method"),
resource.TestCheckResourceAttrSet(resourceReference, "bucket_name"),
resource.TestCheckResourceAttrSet(resourceReference, "source"),
resource.TestCheckResourceAttrSet(resourceReference, "cloud_provider"),
),
},
{
Config: testAccBackupWithDatasourceConfig(resourceName, dsName),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(dsReference, "organization_id", globalOrgId),
resource.TestCheckResourceAttr(dsReference, "project_id", globalProjectId),
resource.TestCheckResourceAttr(dsReference, "cluster_id", globalClusterId),
resource.TestCheckResourceAttr(dsReference, "bucket_id", globalBucketId),
testAccCheckDataSourceContainsBackup(dsReference, resourceReference),
),
Comment thread
panigrahisubhrajit marked this conversation as resolved.
Comment thread
panigrahisubhrajit marked this conversation as resolved.
Comment thread
panigrahisubhrajit marked this conversation as resolved.
},
{
ResourceName: resourceReference,
ImportStateIdFunc: generateBackupImportIdForResource(resourceReference),
ImportState: true,
},
},
})
}

func TestAccBackupResourceInvalidBucket(t *testing.T) {
resourceName := randomStringWithPrefix("tf_acc_backup_invalid_")

resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: globalProtoV6ProviderFactory,
Steps: []resource.TestStep{
{
Config: testAccBackupResourceConfigWithBucketID(resourceName, "00000000-0000-0000-0000-000000000000"),
ExpectError: regexp.MustCompile("Error getting latest bucket backup|There is an error during backup creation"),
},
},
})
}

func TestAccBackupResourceInvalidProject(t *testing.T) {
resourceName := randomStringWithPrefix("tf_acc_backup_invalid_proj_")

resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: globalProtoV6ProviderFactory,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(`
%[1]s

resource "couchbase-capella_backup" "%[2]s" {
organization_id = "%[3]s"
project_id = "00000000-0000-0000-0000-000000000000"
cluster_id = "%[4]s"
bucket_id = "%[5]s"
}
`, globalProviderBlock, resourceName, globalOrgId, globalClusterId, globalBucketId),
ExpectError: regexp.MustCompile("Error getting latest bucket backup|There is an error during backup creation"),
},
},
})
}

func TestAccBackupResourceRestoreTimesOnCreate(t *testing.T) {
resourceName := randomStringWithPrefix("tf_acc_backup_invalid_rt_")

resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: globalProtoV6ProviderFactory,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(`
%[1]s

resource "couchbase-capella_backup" "%[2]s" {
organization_id = "%[3]s"
project_id = "%[4]s"
cluster_id = "%[5]s"
bucket_id = "%[6]s"
restore_times = 1
}
`, globalProviderBlock, resourceName, globalOrgId, globalProjectId, globalClusterId, globalBucketId),
ExpectError: regexp.MustCompile("restore times must not be set while create backup"),
},
},
})
}

func testAccBackupResourceConfig(resourceName string) string {
return testAccBackupResourceConfigWithBucketID(resourceName, globalBucketId)
}

func testAccBackupResourceConfigWithBucketID(resourceName, bucketID string) string {
return fmt.Sprintf(`
%[1]s

resource "couchbase-capella_backup" "%[2]s" {
organization_id = "%[3]s"
project_id = "%[4]s"
cluster_id = "%[5]s"
bucket_id = "%[6]s"
}
`, globalProviderBlock, resourceName, globalOrgId, globalProjectId, globalClusterId, bucketID)
}

func testAccBackupWithDatasourceConfig(resourceName, dsName string) string {
return fmt.Sprintf(`
%[1]s

resource "couchbase-capella_backup" "%[2]s" {
organization_id = "%[3]s"
project_id = "%[4]s"
cluster_id = "%[5]s"
bucket_id = "%[6]s"
}

data "couchbase-capella_backups" "%[7]s" {
organization_id = "%[3]s"
project_id = "%[4]s"
cluster_id = "%[5]s"
bucket_id = "%[6]s"
depends_on = [couchbase-capella_backup.%[2]s]
}
`, globalProviderBlock, resourceName, globalOrgId, globalProjectId, globalClusterId, globalBucketId, dsName)
}

func generateBackupImportIdForResource(resourceReference string) resource.ImportStateIdFunc {
return func(state *terraform.State) (string, error) {
var rawState map[string]string
for _, m := range state.Modules {
if len(m.Resources) > 0 {
if v, ok := m.Resources[resourceReference]; ok {
rawState = v.Primary.Attributes
}
}
}
return fmt.Sprintf("id=%s,cluster_id=%s,project_id=%s,organization_id=%s",
rawState["id"], rawState["cluster_id"], rawState["project_id"], rawState["organization_id"]), nil
}
}

func retrieveBackupFromServer(data *providerschema.Data, organizationId, projectId, clusterId, backupId string) error {
url := fmt.Sprintf("%s/v4/organizations/%s/projects/%s/clusters/%s/backups/%s",
data.HostURL, organizationId, projectId, clusterId, backupId)
cfg := api.EndpointCfg{Url: url, Method: http.MethodGet, SuccessStatus: http.StatusOK}
response, err := data.ClientV1.ExecuteWithRetry(
context.Background(),
cfg,
nil,
data.Token,
nil,
)
if err != nil {
return err
}
backupResp := backupapi.GetBackupResponse{}
if err := json.Unmarshal(response.Body, &backupResp); err != nil {
return err
}
if backupResp.Id == "" {
return errors.ErrNotFound
}
return nil
}

func testAccExistsBackupResource(t *testing.T, resourceReference string) resource.TestCheckFunc {
return func(s *terraform.State) error {
var rawState map[string]string
for _, m := range s.Modules {
if len(m.Resources) > 0 {
if v, ok := m.Resources[resourceReference]; ok {
rawState = v.Primary.Attributes
}
}
}
data := newTestClient(t)
return retrieveBackupFromServer(data, rawState["organization_id"], rawState["project_id"], rawState["cluster_id"], rawState["id"])
}
}

// testAccCheckDataSourceContainsBackup verifies that the backups data source
// contains the specific backup created by resourceReference, regardless of
// its position in the list (older backups may appear at lower indices).
func testAccCheckDataSourceContainsBackup(dsReference, resourceReference string) resource.TestCheckFunc {
return func(s *terraform.State) error {
ds := s.RootModule().Resources[dsReference]
if ds == nil {
return fmt.Errorf("datasource %s not found in state", dsReference)
}
res := s.RootModule().Resources[resourceReference]
if res == nil {
return fmt.Errorf("resource %s not found in state", resourceReference)
}
expectedID := res.Primary.Attributes["id"]
count, _ := strconv.Atoi(ds.Primary.Attributes["data.#"])
for i := 0; i < count; i++ {
if ds.Primary.Attributes[fmt.Sprintf("data.%d.id", i)] == expectedID {
return nil
}
}
return fmt.Errorf("datasource %s does not contain backup id=%s (checked %d items)", dsReference, expectedID, count)
}
}
Loading
Loading