aboutsummaryrefslogtreecommitdiff
path: root/test/s3/versioning/s3_versioning_object_lock_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'test/s3/versioning/s3_versioning_object_lock_test.go')
-rw-r--r--test/s3/versioning/s3_versioning_object_lock_test.go160
1 files changed, 160 insertions, 0 deletions
diff --git a/test/s3/versioning/s3_versioning_object_lock_test.go b/test/s3/versioning/s3_versioning_object_lock_test.go
new file mode 100644
index 000000000..5c2689935
--- /dev/null
+++ b/test/s3/versioning/s3_versioning_object_lock_test.go
@@ -0,0 +1,160 @@
+package s3api
+
+import (
+ "context"
+ "strings"
+ "testing"
+ "time"
+
+ "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"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// TestVersioningWithObjectLockHeaders ensures that versioned objects properly
+// handle object lock headers in PUT requests and return them in HEAD/GET responses.
+// This test would have caught the bug where object lock metadata was not returned
+// in HEAD/GET responses.
+func TestVersioningWithObjectLockHeaders(t *testing.T) {
+ client := getS3Client(t)
+ bucketName := getNewBucketName()
+
+ // Create bucket with object lock and versioning enabled
+ createBucketWithObjectLock(t, client, bucketName)
+ defer deleteBucket(t, client, bucketName)
+
+ key := "versioned-object-with-lock"
+ content1 := "version 1 content"
+ content2 := "version 2 content"
+
+ // PUT first version with object lock headers
+ retainUntilDate1 := time.Now().Add(12 * time.Hour)
+ putResp1, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
+ Bucket: aws.String(bucketName),
+ Key: aws.String(key),
+ Body: strings.NewReader(content1),
+ ObjectLockMode: types.ObjectLockModeGovernance,
+ ObjectLockRetainUntilDate: aws.Time(retainUntilDate1),
+ })
+ require.NoError(t, err)
+ require.NotNil(t, putResp1.VersionId)
+
+ // PUT second version with different object lock settings
+ retainUntilDate2 := time.Now().Add(24 * time.Hour)
+ putResp2, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
+ Bucket: aws.String(bucketName),
+ Key: aws.String(key),
+ Body: strings.NewReader(content2),
+ ObjectLockMode: types.ObjectLockModeCompliance,
+ ObjectLockRetainUntilDate: aws.Time(retainUntilDate2),
+ ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn,
+ })
+ require.NoError(t, err)
+ require.NotNil(t, putResp2.VersionId)
+ require.NotEqual(t, *putResp1.VersionId, *putResp2.VersionId)
+
+ // Test HEAD latest version returns correct object lock metadata
+ t.Run("HEAD latest version", func(t *testing.T) {
+ headResp, err := client.HeadObject(context.TODO(), &s3.HeadObjectInput{
+ Bucket: aws.String(bucketName),
+ Key: aws.String(key),
+ })
+ require.NoError(t, err)
+
+ // Should return metadata for version 2 (latest)
+ assert.Equal(t, types.ObjectLockModeCompliance, headResp.ObjectLockMode)
+ assert.NotNil(t, headResp.ObjectLockRetainUntilDate)
+ assert.WithinDuration(t, retainUntilDate2, *headResp.ObjectLockRetainUntilDate, 5*time.Second)
+ assert.Equal(t, types.ObjectLockLegalHoldStatusOn, headResp.ObjectLockLegalHoldStatus)
+ })
+
+ // Test HEAD specific version returns correct object lock metadata
+ t.Run("HEAD specific version", func(t *testing.T) {
+ headResp, err := client.HeadObject(context.TODO(), &s3.HeadObjectInput{
+ Bucket: aws.String(bucketName),
+ Key: aws.String(key),
+ VersionId: putResp1.VersionId,
+ })
+ require.NoError(t, err)
+
+ // Should return metadata for version 1
+ assert.Equal(t, types.ObjectLockModeGovernance, headResp.ObjectLockMode)
+ assert.NotNil(t, headResp.ObjectLockRetainUntilDate)
+ assert.WithinDuration(t, retainUntilDate1, *headResp.ObjectLockRetainUntilDate, 5*time.Second)
+ // Version 1 was created without legal hold, so AWS S3 defaults it to "OFF"
+ assert.Equal(t, types.ObjectLockLegalHoldStatusOff, headResp.ObjectLockLegalHoldStatus)
+ })
+
+ // Test GET latest version returns correct object lock metadata
+ t.Run("GET latest version", func(t *testing.T) {
+ getResp, err := client.GetObject(context.TODO(), &s3.GetObjectInput{
+ Bucket: aws.String(bucketName),
+ Key: aws.String(key),
+ })
+ require.NoError(t, err)
+ defer getResp.Body.Close()
+
+ // Should return metadata for version 2 (latest)
+ assert.Equal(t, types.ObjectLockModeCompliance, getResp.ObjectLockMode)
+ assert.NotNil(t, getResp.ObjectLockRetainUntilDate)
+ assert.WithinDuration(t, retainUntilDate2, *getResp.ObjectLockRetainUntilDate, 5*time.Second)
+ assert.Equal(t, types.ObjectLockLegalHoldStatusOn, getResp.ObjectLockLegalHoldStatus)
+ })
+
+ // Test GET specific version returns correct object lock metadata
+ t.Run("GET specific version", func(t *testing.T) {
+ getResp, err := client.GetObject(context.TODO(), &s3.GetObjectInput{
+ Bucket: aws.String(bucketName),
+ Key: aws.String(key),
+ VersionId: putResp1.VersionId,
+ })
+ require.NoError(t, err)
+ defer getResp.Body.Close()
+
+ // Should return metadata for version 1
+ assert.Equal(t, types.ObjectLockModeGovernance, getResp.ObjectLockMode)
+ assert.NotNil(t, getResp.ObjectLockRetainUntilDate)
+ assert.WithinDuration(t, retainUntilDate1, *getResp.ObjectLockRetainUntilDate, 5*time.Second)
+ // Version 1 was created without legal hold, so AWS S3 defaults it to "OFF"
+ assert.Equal(t, types.ObjectLockLegalHoldStatusOff, getResp.ObjectLockLegalHoldStatus)
+ })
+}
+
+// waitForVersioningToBeEnabled polls the bucket versioning status until it's enabled
+// This helps avoid race conditions where object lock is configured but versioning
+// isn't immediately available
+func waitForVersioningToBeEnabled(t *testing.T, client *s3.Client, bucketName string) {
+ timeout := time.Now().Add(10 * time.Second)
+ for time.Now().Before(timeout) {
+ resp, err := client.GetBucketVersioning(context.TODO(), &s3.GetBucketVersioningInput{
+ Bucket: aws.String(bucketName),
+ })
+ if err == nil && resp.Status == types.BucketVersioningStatusEnabled {
+ return // Versioning is enabled
+ }
+
+ time.Sleep(100 * time.Millisecond)
+ }
+ t.Fatalf("Timeout waiting for versioning to be enabled on bucket %s", bucketName)
+}
+
+// Helper function for creating buckets with object lock enabled
+func createBucketWithObjectLock(t *testing.T, client *s3.Client, bucketName string) {
+ _, err := client.CreateBucket(context.TODO(), &s3.CreateBucketInput{
+ Bucket: aws.String(bucketName),
+ ObjectLockEnabledForBucket: aws.Bool(true),
+ })
+ require.NoError(t, err)
+
+ // Wait for versioning to be automatically enabled by object lock
+ waitForVersioningToBeEnabled(t, client, bucketName)
+
+ // Verify that object lock was actually enabled
+ t.Logf("Verifying object lock configuration for bucket %s", bucketName)
+ _, err = client.GetObjectLockConfiguration(context.TODO(), &s3.GetObjectLockConfigurationInput{
+ Bucket: aws.String(bucketName),
+ })
+ require.NoError(t, err, "Object lock should be configured for bucket %s", bucketName)
+}