Files
seaweedFS/weed/s3api/s3api_object_handlers_delete_test.go
Chris Lu 0adb78bc6b s3api: make conditional mutations atomic and AWS-compatible (#8802)
* s3api: serialize conditional write finalization

* s3api: add conditional delete mutation checks

* s3api: enforce destination conditions for copy

* s3api: revalidate multipart completion under lock

* s3api: rollback failed put finalization hooks

* s3api: report delete-marker version deletions

* s3api: fix copy destination versioning edge cases

* s3api: make versioned multipart completion idempotent

* test/s3: cover conditional mutation regressions

* s3api: rollback failed copy version finalization

* s3api: resolve suspended delete conditions via latest entry

* s3api: remove copy test null-version injection

* s3api: reject out-of-order multipart completions

* s3api: preserve multipart replay version metadata

* s3api: surface copy destination existence errors

* s3api: simplify delete condition target resolution

* test/s3: make conditional delete assertions order independent

* test/s3: add distributed lock gateway integration

* s3api: fail closed multipart versioned completion

* s3api: harden copy metadata and overwrite paths

* s3api: create delete markers for suspended deletes

* s3api: allow duplicate multipart completion parts
2026-03-27 19:22:26 -07:00

120 lines
3.2 KiB
Go

package s3api
import (
"encoding/xml"
"testing"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
)
func TestValidateDeleteIfMatch(t *testing.T) {
s3a := NewS3ApiServerForTest()
existingEntry := &filer_pb.Entry{
Extended: map[string][]byte{
s3_constants.ExtETagKey: []byte("\"abc123\""),
},
}
deleteMarkerEntry := &filer_pb.Entry{
Extended: map[string][]byte{
s3_constants.ExtDeleteMarkerKey: []byte("true"),
},
}
testCases := []struct {
name string
entry *filer_pb.Entry
ifMatch string
missingCode s3err.ErrorCode
expected s3err.ErrorCode
}{
{
name: "matching etag succeeds",
entry: existingEntry,
ifMatch: "\"abc123\"",
missingCode: s3err.ErrPreconditionFailed,
expected: s3err.ErrNone,
},
{
name: "wildcard succeeds for existing entry",
entry: existingEntry,
ifMatch: "*",
missingCode: s3err.ErrPreconditionFailed,
expected: s3err.ErrNone,
},
{
name: "mismatched etag fails",
entry: existingEntry,
ifMatch: "\"other\"",
missingCode: s3err.ErrPreconditionFailed,
expected: s3err.ErrPreconditionFailed,
},
{
name: "missing current object fails single delete",
entry: nil,
ifMatch: "*",
missingCode: s3err.ErrPreconditionFailed,
expected: s3err.ErrPreconditionFailed,
},
{
name: "missing current object returns no such key for batch delete",
entry: nil,
ifMatch: "*",
missingCode: s3err.ErrNoSuchKey,
expected: s3err.ErrNoSuchKey,
},
{
name: "current delete marker behaves like missing object",
entry: normalizeConditionalTargetEntry(deleteMarkerEntry),
ifMatch: "*",
missingCode: s3err.ErrPreconditionFailed,
expected: s3err.ErrPreconditionFailed,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if errCode := s3a.validateDeleteIfMatch(tc.entry, tc.ifMatch, tc.missingCode); errCode != tc.expected {
t.Fatalf("validateDeleteIfMatch() = %v, want %v", errCode, tc.expected)
}
})
}
}
func TestDeleteObjectsRequestUnmarshalConditionalETags(t *testing.T) {
var req DeleteObjectsRequest
body := []byte(`
<Delete>
<Quiet>true</Quiet>
<Object>
<Key>first.txt</Key>
<ETag>*</ETag>
</Object>
<Object>
<Key>second.txt</Key>
<VersionId>3HL4kqCxf3vjVBH40Nrjfkd</VersionId>
<ETag>"abc123"</ETag>
</Object>
</Delete>`)
if err := xml.Unmarshal(body, &req); err != nil {
t.Fatalf("xml.Unmarshal() error = %v", err)
}
if !req.Quiet {
t.Fatalf("expected Quiet=true")
}
if len(req.Objects) != 2 {
t.Fatalf("expected 2 objects, got %d", len(req.Objects))
}
if req.Objects[0].ETag != "*" {
t.Fatalf("expected first object ETag to be '*', got %q", req.Objects[0].ETag)
}
if req.Objects[1].ETag != "\"abc123\"" {
t.Fatalf("expected second object ETag to preserve quotes, got %q", req.Objects[1].ETag)
}
if req.Objects[1].VersionId != "3HL4kqCxf3vjVBH40Nrjfkd" {
t.Fatalf("expected second object VersionId to unmarshal, got %q", req.Objects[1].VersionId)
}
}