You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
101 lines
3.9 KiB
101 lines
3.9 KiB
package s3api
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"strings"
|
|
"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"
|
|
"github.com/aws/smithy-go"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestConditionalWritesWithVersioning verifies that conditional writes (If-Match)
|
|
// work correctly with versioned buckets, specifically ensuring they validate against
|
|
// the LATEST version of the object, not the base object.
|
|
// reproduces issue #8073
|
|
func TestConditionalWritesWithVersioning(t *testing.T) {
|
|
client := getS3Client(t)
|
|
bucketName := getNewBucketName()
|
|
|
|
// Create bucket
|
|
createBucket(t, client, bucketName)
|
|
defer deleteBucket(t, client, bucketName)
|
|
|
|
// Enable versioning
|
|
enableVersioning(t, client, bucketName)
|
|
checkVersioningStatus(t, client, bucketName, types.BucketVersioningStatusEnabled)
|
|
|
|
key := "cond-write-test"
|
|
|
|
// 1. Create Version 1
|
|
v1Resp := putObject(t, client, bucketName, key, "content-v1")
|
|
require.NotNil(t, v1Resp.ETag)
|
|
require.NotNil(t, v1Resp.VersionId)
|
|
v1ETag := *v1Resp.ETag
|
|
t.Logf("Created Version 1: ETag=%s, VersionId=%s", v1ETag, *v1Resp.VersionId)
|
|
|
|
// 2. Create Version 2 (This is now the LATEST version)
|
|
v2Resp := putObject(t, client, bucketName, key, "content-v2")
|
|
require.NotNil(t, v2Resp.ETag)
|
|
require.NotNil(t, v2Resp.VersionId)
|
|
v2ETag := *v2Resp.ETag
|
|
t.Logf("Created Version 2: ETag=%s, VersionId=%s", v2ETag, *v2Resp.VersionId)
|
|
|
|
require.NotEqual(t, v1ETag, v2ETag, "ETags should be different for different content")
|
|
|
|
// 3. Attempt conditional PUT using Version 1's ETag (If-Match: v1ETag)
|
|
// EXPECTATION: Should FAIL with 412 Precondition Failed because the latest version is V2.
|
|
// BUG (Issue #8073): Previously, this might have succeeded if it checked against an old/stale entry or base entry.
|
|
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
|
Bucket: aws.String(bucketName),
|
|
Key: aws.String(key),
|
|
Body: strings.NewReader("content-v3-should-fail"),
|
|
IfMatch: aws.String(v1ETag),
|
|
})
|
|
|
|
require.Error(t, err, "Conditional PUT with stale ETag should have failed")
|
|
|
|
// Verify strict error checking for 412 Precondition Failed using AWS SDK v2 structured errors
|
|
var apiErr smithy.APIError
|
|
if assert.True(t, errors.As(err, &apiErr), "Expected a smithy.APIError, but got %T", err) {
|
|
assert.Equal(t, "PreconditionFailed", apiErr.ErrorCode(), "Expected PreconditionFailed error code")
|
|
t.Logf("Received expected 412 Precondition Failed error: %v", err)
|
|
}
|
|
|
|
// 4. Attempt conditional PUT using Version 2's ETag (If-Match: v2ETag)
|
|
// EXPECTATION: Should SUCCEED because V2 is the latest version.
|
|
v4Resp, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
|
Bucket: aws.String(bucketName),
|
|
Key: aws.String(key),
|
|
Body: strings.NewReader("content-v4-should-succeed"),
|
|
IfMatch: aws.String(v2ETag),
|
|
})
|
|
require.NoError(t, err, "Conditional PUT with correct latest ETag should succeed")
|
|
require.NotNil(t, v4Resp, "PutObject response should not be nil on success")
|
|
require.NotNil(t, v4Resp.ETag, "ETag should not be nil on successful PutObject")
|
|
require.NotNil(t, v4Resp.VersionId, "VersionId should not be nil on successful PutObject")
|
|
t.Logf("Created Version 4: ETag=%s, VersionId=%s", *v4Resp.ETag, *v4Resp.VersionId)
|
|
|
|
// 5. Verify the updates
|
|
// The content should be "content-v4-should-succeed"
|
|
headResp := headObject(t, client, bucketName, key)
|
|
require.NotNil(t, headResp.VersionId, "VersionId should not be nil on HeadObject response")
|
|
assert.Equal(t, *v4Resp.VersionId, *headResp.VersionId)
|
|
|
|
// Verify actual content
|
|
getResp, err := client.GetObject(context.TODO(), &s3.GetObjectInput{
|
|
Bucket: aws.String(bucketName),
|
|
Key: aws.String(key),
|
|
})
|
|
require.NoError(t, err)
|
|
defer getResp.Body.Close()
|
|
body, err := io.ReadAll(getResp.Body)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "content-v4-should-succeed", string(body), "Content should match the successful conditional write")
|
|
}
|