diff options
Diffstat (limited to 'weed/s3api/s3api_object_handlers_copy_test.go')
| -rw-r--r-- | weed/s3api/s3api_object_handlers_copy_test.go | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/weed/s3api/s3api_object_handlers_copy_test.go b/weed/s3api/s3api_object_handlers_copy_test.go index a537b6f3c..018c9f270 100644 --- a/weed/s3api/s3api_object_handlers_copy_test.go +++ b/weed/s3api/s3api_object_handlers_copy_test.go @@ -436,3 +436,206 @@ func transferHeaderToH(data map[string][]string) H { } return m } + +// TestShouldCreateVersionForCopy tests the production function that determines +// whether a version should be created during a copy operation. +// This addresses issue #7505 where copies were incorrectly creating versions for non-versioned buckets. +func TestShouldCreateVersionForCopy(t *testing.T) { + testCases := []struct { + name string + versioningState string + expectedResult bool + description string + }{ + { + name: "VersioningEnabled", + versioningState: s3_constants.VersioningEnabled, + expectedResult: true, + description: "Should create versions in .versions/ directory when versioning is Enabled", + }, + { + name: "VersioningSuspended", + versioningState: s3_constants.VersioningSuspended, + expectedResult: false, + description: "Should NOT create versions when versioning is Suspended", + }, + { + name: "VersioningNotConfigured", + versioningState: "", + expectedResult: false, + description: "Should NOT create versions when versioning is not configured", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Call the actual production function + result := shouldCreateVersionForCopy(tc.versioningState) + + if result != tc.expectedResult { + t.Errorf("Test case %s failed: %s\nExpected shouldCreateVersionForCopy(%q)=%v, got %v", + tc.name, tc.description, tc.versioningState, tc.expectedResult, result) + } + }) + } +} + +// TestCleanupVersioningMetadata tests the production function that removes versioning metadata. +// This ensures objects copied to non-versioned buckets don't carry invalid versioning metadata +// or stale ETag values from the source. +func TestCleanupVersioningMetadata(t *testing.T) { + testCases := []struct { + name string + sourceMetadata map[string][]byte + expectedKeys []string // Keys that should be present after cleanup + removedKeys []string // Keys that should be removed + }{ + { + name: "RemovesAllVersioningMetadata", + sourceMetadata: map[string][]byte{ + s3_constants.ExtVersionIdKey: []byte("version-123"), + s3_constants.ExtDeleteMarkerKey: []byte("false"), + s3_constants.ExtIsLatestKey: []byte("true"), + s3_constants.ExtETagKey: []byte("\"abc123\""), + "X-Amz-Meta-Custom": []byte("value"), + }, + expectedKeys: []string{"X-Amz-Meta-Custom"}, + removedKeys: []string{s3_constants.ExtVersionIdKey, s3_constants.ExtDeleteMarkerKey, s3_constants.ExtIsLatestKey, s3_constants.ExtETagKey}, + }, + { + name: "HandlesEmptyMetadata", + sourceMetadata: map[string][]byte{}, + expectedKeys: []string{}, + removedKeys: []string{s3_constants.ExtVersionIdKey, s3_constants.ExtDeleteMarkerKey, s3_constants.ExtIsLatestKey, s3_constants.ExtETagKey}, + }, + { + name: "PreservesNonVersioningMetadata", + sourceMetadata: map[string][]byte{ + s3_constants.ExtVersionIdKey: []byte("version-456"), + s3_constants.ExtETagKey: []byte("\"def456\""), + "X-Amz-Meta-Custom": []byte("value1"), + "X-Amz-Meta-Another": []byte("value2"), + s3_constants.ExtIsLatestKey: []byte("true"), + }, + expectedKeys: []string{"X-Amz-Meta-Custom", "X-Amz-Meta-Another"}, + removedKeys: []string{s3_constants.ExtVersionIdKey, s3_constants.ExtETagKey, s3_constants.ExtIsLatestKey}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Create a copy of the source metadata + dstMetadata := make(map[string][]byte) + for k, v := range tc.sourceMetadata { + dstMetadata[k] = v + } + + // Call the actual production function + cleanupVersioningMetadata(dstMetadata) + + // Verify expected keys are present + for _, key := range tc.expectedKeys { + if _, exists := dstMetadata[key]; !exists { + t.Errorf("Expected key %s to be present in destination metadata", key) + } + } + + // Verify removed keys are absent + for _, key := range tc.removedKeys { + if _, exists := dstMetadata[key]; exists { + t.Errorf("Expected key %s to be removed from destination metadata, but it's still present", key) + } + } + + // Verify the count matches to ensure no extra keys are present + if len(dstMetadata) != len(tc.expectedKeys) { + t.Errorf("Expected %d metadata keys, but got %d. Extra keys might be present.", len(tc.expectedKeys), len(dstMetadata)) + } + }) + } +} + +// TestCopyVersioningIntegration validates the interaction between +// shouldCreateVersionForCopy and cleanupVersioningMetadata functions. +// This integration test ensures the complete fix for issue #7505. +func TestCopyVersioningIntegration(t *testing.T) { + testCases := []struct { + name string + versioningState string + sourceMetadata map[string][]byte + expectVersionPath bool + expectMetadataKeys []string + }{ + { + name: "EnabledPreservesMetadata", + versioningState: s3_constants.VersioningEnabled, + sourceMetadata: map[string][]byte{ + s3_constants.ExtVersionIdKey: []byte("v123"), + "X-Amz-Meta-Custom": []byte("value"), + }, + expectVersionPath: true, + expectMetadataKeys: []string{ + s3_constants.ExtVersionIdKey, + "X-Amz-Meta-Custom", + }, + }, + { + name: "SuspendedCleansMetadata", + versioningState: s3_constants.VersioningSuspended, + sourceMetadata: map[string][]byte{ + s3_constants.ExtVersionIdKey: []byte("v123"), + "X-Amz-Meta-Custom": []byte("value"), + }, + expectVersionPath: false, + expectMetadataKeys: []string{ + "X-Amz-Meta-Custom", + }, + }, + { + name: "NotConfiguredCleansMetadata", + versioningState: "", + sourceMetadata: map[string][]byte{ + s3_constants.ExtVersionIdKey: []byte("v123"), + s3_constants.ExtDeleteMarkerKey: []byte("false"), + "X-Amz-Meta-Custom": []byte("value"), + }, + expectVersionPath: false, + expectMetadataKeys: []string{ + "X-Amz-Meta-Custom", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Test version creation decision using production function + shouldCreateVersion := shouldCreateVersionForCopy(tc.versioningState) + if shouldCreateVersion != tc.expectVersionPath { + t.Errorf("shouldCreateVersionForCopy(%q) = %v, expected %v", + tc.versioningState, shouldCreateVersion, tc.expectVersionPath) + } + + // Test metadata cleanup using production function + metadata := make(map[string][]byte) + for k, v := range tc.sourceMetadata { + metadata[k] = v + } + + if !shouldCreateVersion { + cleanupVersioningMetadata(metadata) + } + + // Verify only expected keys remain + for _, expectedKey := range tc.expectMetadataKeys { + if _, exists := metadata[expectedKey]; !exists { + t.Errorf("Expected key %q to be present in metadata", expectedKey) + } + } + + // Verify the count matches (no extra keys) + if len(metadata) != len(tc.expectMetadataKeys) { + t.Errorf("Expected %d metadata keys, got %d", len(tc.expectMetadataKeys), len(metadata)) + } + }) + } +} |
