aboutsummaryrefslogtreecommitdiff
path: root/weed/s3api/s3api_conditional_headers_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'weed/s3api/s3api_conditional_headers_test.go')
-rw-r--r--weed/s3api/s3api_conditional_headers_test.go81
1 files changed, 81 insertions, 0 deletions
diff --git a/weed/s3api/s3api_conditional_headers_test.go b/weed/s3api/s3api_conditional_headers_test.go
index 9a810c15e..834f57305 100644
--- a/weed/s3api/s3api_conditional_headers_test.go
+++ b/weed/s3api/s3api_conditional_headers_test.go
@@ -2,6 +2,7 @@ package s3api
import (
"bytes"
+ "encoding/hex"
"fmt"
"net/http"
"net/url"
@@ -671,6 +672,86 @@ func TestETagMatching(t *testing.T) {
}
}
+// TestGetObjectETagWithMd5AndChunks tests the fix for issue #7274
+// When an object has both Attributes.Md5 and multiple chunks, getObjectETag should
+// prefer Attributes.Md5 to match the behavior of HeadObject and filer.ETag
+func TestGetObjectETagWithMd5AndChunks(t *testing.T) {
+ s3a := NewS3ApiServerForTest()
+ if s3a == nil {
+ t.Skip("S3ApiServer not available for testing")
+ }
+
+ // Create an object with both Md5 and multiple chunks (like in issue #7274)
+ // Md5: ZjcmMwrCVGNVgb4HoqHe9g== (base64) = 663726330ac254635581be07a2a1def6 (hex)
+ md5HexString := "663726330ac254635581be07a2a1def6"
+ md5Bytes, err := hex.DecodeString(md5HexString)
+ if err != nil {
+ t.Fatalf("failed to decode md5 hex string: %v", err)
+ }
+
+ entry := &filer_pb.Entry{
+ Name: "test-multipart-object",
+ Attributes: &filer_pb.FuseAttributes{
+ Mtime: time.Now().Unix(),
+ FileSize: 5597744,
+ Md5: md5Bytes,
+ },
+ // Two chunks - if we only used ETagChunks, it would return format "hash-2"
+ Chunks: []*filer_pb.FileChunk{
+ {
+ FileId: "chunk1",
+ Offset: 0,
+ Size: 4194304,
+ ETag: "9+yCD2DGwMG5uKwAd+y04Q==",
+ },
+ {
+ FileId: "chunk2",
+ Offset: 4194304,
+ Size: 1403440,
+ ETag: "cs6SVSTgZ8W3IbIrAKmklg==",
+ },
+ },
+ }
+
+ // getObjectETag should return the Md5 in hex with quotes
+ expectedETag := "\"" + md5HexString + "\""
+ actualETag := s3a.getObjectETag(entry)
+
+ if actualETag != expectedETag {
+ t.Errorf("Expected ETag %s, got %s", expectedETag, actualETag)
+ }
+
+ // Now test that conditional headers work with this ETag
+ bucket := "test-bucket"
+ object := "/test-object"
+
+ // Test If-Match with the Md5-based ETag (should succeed)
+ t.Run("IfMatch_WithMd5BasedETag_ShouldSucceed", func(t *testing.T) {
+ getter := createMockEntryGetter(entry)
+ req := createTestGetRequest(bucket, object)
+ // Client sends the ETag from HeadObject (without quotes)
+ req.Header.Set(s3_constants.IfMatch, md5HexString)
+
+ result := s3a.checkConditionalHeadersForReadsWithGetter(getter, req, bucket, object)
+ if result.ErrorCode != s3err.ErrNone {
+ t.Errorf("Expected ErrNone when If-Match uses Md5-based ETag, got %v (ETag was %s)", result.ErrorCode, actualETag)
+ }
+ })
+
+ // Test If-Match with chunk-based ETag format (should fail - this was the old incorrect behavior)
+ t.Run("IfMatch_WithChunkBasedETag_ShouldFail", func(t *testing.T) {
+ getter := createMockEntryGetter(entry)
+ req := createTestGetRequest(bucket, object)
+ // If we incorrectly calculated ETag from chunks, it would be in format "hash-2"
+ req.Header.Set(s3_constants.IfMatch, "123294de680f28bde364b81477549f7d-2")
+
+ result := s3a.checkConditionalHeadersForReadsWithGetter(getter, req, bucket, object)
+ if result.ErrorCode != s3err.ErrPreconditionFailed {
+ t.Errorf("Expected ErrPreconditionFailed when If-Match uses chunk-based ETag format, got %v", result.ErrorCode)
+ }
+ })
+}
+
// TestConditionalHeadersIntegration tests conditional headers with full integration
func TestConditionalHeadersIntegration(t *testing.T) {
// This would be a full integration test that requires a running SeaweedFS instance