aboutsummaryrefslogtreecommitdiff
path: root/test/s3/versioning/s3_suspended_versioning_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'test/s3/versioning/s3_suspended_versioning_test.go')
-rw-r--r--test/s3/versioning/s3_suspended_versioning_test.go257
1 files changed, 257 insertions, 0 deletions
diff --git a/test/s3/versioning/s3_suspended_versioning_test.go b/test/s3/versioning/s3_suspended_versioning_test.go
new file mode 100644
index 000000000..c1e8c7277
--- /dev/null
+++ b/test/s3/versioning/s3_suspended_versioning_test.go
@@ -0,0 +1,257 @@
+package s3api
+
+import (
+ "bytes"
+ "context"
+ "testing"
+
+ "github.com/aws/aws-sdk-go-v2/aws"
+ "github.com/aws/aws-sdk-go-v2/service/s3"
+ "github.com/aws/aws-sdk-go-v2/service/s3/types"
+)
+
+// TestSuspendedVersioningNullOverwrite tests the scenario where:
+// 1. Create object before versioning is enabled (pre-versioning object)
+// 2. Enable versioning, then suspend it
+// 3. Overwrite the object (should replace the null version, not create duplicate)
+// 4. List versions should show only 1 version with versionId "null"
+//
+// This test corresponds to: test_versioning_obj_plain_null_version_overwrite_suspended
+func TestSuspendedVersioningNullOverwrite(t *testing.T) {
+ ctx := context.Background()
+ client := getS3Client(t)
+
+ // Create bucket
+ bucketName := getNewBucketName()
+ createBucket(t, client, bucketName)
+ defer deleteBucket(t, client, bucketName)
+
+ objectKey := "testobjbar"
+
+ // Step 1: Put object before versioning is configured (pre-versioning object)
+ content1 := []byte("foooz")
+ _, err := client.PutObject(ctx, &s3.PutObjectInput{
+ Bucket: aws.String(bucketName),
+ Key: aws.String(objectKey),
+ Body: bytes.NewReader(content1),
+ })
+ if err != nil {
+ t.Fatalf("Failed to create pre-versioning object: %v", err)
+ }
+ t.Logf("Created pre-versioning object")
+
+ // Step 2: Enable versioning
+ _, err = client.PutBucketVersioning(ctx, &s3.PutBucketVersioningInput{
+ Bucket: aws.String(bucketName),
+ VersioningConfiguration: &types.VersioningConfiguration{
+ Status: types.BucketVersioningStatusEnabled,
+ },
+ })
+ if err != nil {
+ t.Fatalf("Failed to enable versioning: %v", err)
+ }
+ t.Logf("Enabled versioning")
+
+ // Step 3: Suspend versioning
+ _, err = client.PutBucketVersioning(ctx, &s3.PutBucketVersioningInput{
+ Bucket: aws.String(bucketName),
+ VersioningConfiguration: &types.VersioningConfiguration{
+ Status: types.BucketVersioningStatusSuspended,
+ },
+ })
+ if err != nil {
+ t.Fatalf("Failed to suspend versioning: %v", err)
+ }
+ t.Logf("Suspended versioning")
+
+ // Step 4: Overwrite the object during suspended versioning
+ content2 := []byte("zzz")
+ putResp, err := client.PutObject(ctx, &s3.PutObjectInput{
+ Bucket: aws.String(bucketName),
+ Key: aws.String(objectKey),
+ Body: bytes.NewReader(content2),
+ })
+ if err != nil {
+ t.Fatalf("Failed to overwrite object during suspended versioning: %v", err)
+ }
+
+ // Verify no VersionId is returned for suspended versioning
+ if putResp.VersionId != nil {
+ t.Errorf("Suspended versioning should NOT return VersionId, but got: %s", *putResp.VersionId)
+ }
+ t.Logf("Overwrote object during suspended versioning (no VersionId returned as expected)")
+
+ // Step 5: Verify content is updated
+ getResp, err := client.GetObject(ctx, &s3.GetObjectInput{
+ Bucket: aws.String(bucketName),
+ Key: aws.String(objectKey),
+ })
+ if err != nil {
+ t.Fatalf("Failed to get object: %v", err)
+ }
+ defer getResp.Body.Close()
+
+ gotContent := new(bytes.Buffer)
+ gotContent.ReadFrom(getResp.Body)
+ if !bytes.Equal(gotContent.Bytes(), content2) {
+ t.Errorf("Expected content %q, got %q", content2, gotContent.Bytes())
+ }
+ t.Logf("Object content is correctly updated to: %q", content2)
+
+ // Step 6: List object versions - should have only 1 version
+ listResp, err := client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
+ Bucket: aws.String(bucketName),
+ })
+ if err != nil {
+ t.Fatalf("Failed to list object versions: %v", err)
+ }
+
+ // Count versions (excluding delete markers)
+ versionCount := len(listResp.Versions)
+ deleteMarkerCount := len(listResp.DeleteMarkers)
+
+ t.Logf("List results: %d versions, %d delete markers", versionCount, deleteMarkerCount)
+ for i, v := range listResp.Versions {
+ t.Logf(" Version %d: Key=%s, VersionId=%s, IsLatest=%v, Size=%d",
+ i, *v.Key, *v.VersionId, v.IsLatest, v.Size)
+ }
+
+ // THIS IS THE KEY ASSERTION: Should have exactly 1 version, not 2
+ if versionCount != 1 {
+ t.Errorf("Expected 1 version after suspended versioning overwrite, got %d versions", versionCount)
+ t.Error("BUG: Duplicate null versions detected! The overwrite should have replaced the pre-versioning object.")
+ } else {
+ t.Logf("PASS: Only 1 version found (no duplicate null versions)")
+ }
+
+ if deleteMarkerCount != 0 {
+ t.Errorf("Expected 0 delete markers, got %d", deleteMarkerCount)
+ }
+
+ // Verify the version has versionId "null"
+ if versionCount > 0 {
+ if listResp.Versions[0].VersionId == nil || *listResp.Versions[0].VersionId != "null" {
+ t.Errorf("Expected VersionId to be 'null', got %v", listResp.Versions[0].VersionId)
+ } else {
+ t.Logf("Version ID is 'null' as expected")
+ }
+ }
+
+ // Step 7: Delete the null version
+ _, err = client.DeleteObject(ctx, &s3.DeleteObjectInput{
+ Bucket: aws.String(bucketName),
+ Key: aws.String(objectKey),
+ VersionId: aws.String("null"),
+ })
+ if err != nil {
+ t.Fatalf("Failed to delete null version: %v", err)
+ }
+ t.Logf("Deleted null version")
+
+ // Step 8: Verify object no longer exists
+ _, err = client.GetObject(ctx, &s3.GetObjectInput{
+ Bucket: aws.String(bucketName),
+ Key: aws.String(objectKey),
+ })
+ if err == nil {
+ t.Error("Expected object to not exist after deleting null version")
+ }
+ t.Logf("Object no longer exists after deleting null version")
+
+ // Step 9: Verify no versions remain
+ listResp, err = client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
+ Bucket: aws.String(bucketName),
+ })
+ if err != nil {
+ t.Fatalf("Failed to list object versions: %v", err)
+ }
+
+ if len(listResp.Versions) != 0 || len(listResp.DeleteMarkers) != 0 {
+ t.Errorf("Expected no versions or delete markers, got %d versions and %d delete markers",
+ len(listResp.Versions), len(listResp.DeleteMarkers))
+ } else {
+ t.Logf("No versions remain after deletion")
+ }
+}
+
+// TestEnabledVersioningReturnsVersionId tests that when versioning is ENABLED,
+// every PutObject operation returns a version ID
+//
+// This test corresponds to the create_multiple_versions helper function
+func TestEnabledVersioningReturnsVersionId(t *testing.T) {
+ ctx := context.Background()
+ client := getS3Client(t)
+
+ // Create bucket
+ bucketName := getNewBucketName()
+ createBucket(t, client, bucketName)
+ defer deleteBucket(t, client, bucketName)
+
+ objectKey := "testobj"
+
+ // Enable versioning
+ _, err := client.PutBucketVersioning(ctx, &s3.PutBucketVersioningInput{
+ Bucket: aws.String(bucketName),
+ VersioningConfiguration: &types.VersioningConfiguration{
+ Status: types.BucketVersioningStatusEnabled,
+ },
+ })
+ if err != nil {
+ t.Fatalf("Failed to enable versioning: %v", err)
+ }
+ t.Logf("Enabled versioning")
+
+ // Create multiple versions
+ numVersions := 3
+ versionIds := make([]string, 0, numVersions)
+
+ for i := 0; i < numVersions; i++ {
+ content := []byte("content-" + string(rune('0'+i)))
+ putResp, err := client.PutObject(ctx, &s3.PutObjectInput{
+ Bucket: aws.String(bucketName),
+ Key: aws.String(objectKey),
+ Body: bytes.NewReader(content),
+ })
+ if err != nil {
+ t.Fatalf("Failed to create version %d: %v", i, err)
+ }
+
+ // THIS IS THE KEY ASSERTION: VersionId MUST be returned for enabled versioning
+ if putResp.VersionId == nil {
+ t.Errorf("FAILED: PutObject with enabled versioning MUST return VersionId, but got nil for version %d", i)
+ } else {
+ versionId := *putResp.VersionId
+ if versionId == "" {
+ t.Errorf("FAILED: PutObject returned empty VersionId for version %d", i)
+ } else if versionId == "null" {
+ t.Errorf("FAILED: PutObject with enabled versioning should NOT return 'null' version ID, got: %s", versionId)
+ } else {
+ versionIds = append(versionIds, versionId)
+ t.Logf("Version %d created with VersionId: %s", i, versionId)
+ }
+ }
+ }
+
+ if len(versionIds) != numVersions {
+ t.Errorf("Expected %d version IDs, got %d", numVersions, len(versionIds))
+ }
+
+ // List versions to verify all were created
+ listResp, err := client.ListObjectVersions(ctx, &s3.ListObjectVersionsInput{
+ Bucket: aws.String(bucketName),
+ })
+ if err != nil {
+ t.Fatalf("Failed to list object versions: %v", err)
+ }
+
+ if len(listResp.Versions) != numVersions {
+ t.Errorf("Expected %d versions in list, got %d", numVersions, len(listResp.Versions))
+ } else {
+ t.Logf("All %d versions are listed", numVersions)
+ }
+
+ // Verify all version IDs match
+ for i, v := range listResp.Versions {
+ t.Logf(" Version %d: VersionId=%s, Size=%d, IsLatest=%v", i, *v.VersionId, v.Size, v.IsLatest)
+ }
+}