diff options
Diffstat (limited to 'test/s3/versioning/s3_versioning_object_lock_test.go')
| -rw-r--r-- | test/s3/versioning/s3_versioning_object_lock_test.go | 160 |
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) +} |
