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.
		
		
		
		
		
			
		
			
				
					
					
						
							599 lines
						
					
					
						
							21 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							599 lines
						
					
					
						
							21 KiB
						
					
					
				| package s3api | |
| 
 | |
| import ( | |
| 	"net/http" | |
| 	"net/http/httptest" | |
| 	"strings" | |
| 	"testing" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" | |
| ) | |
| 
 | |
| // TestCheckGovernanceBypassPermissionResourceGeneration tests that the function | |
| // correctly generates resource paths for the permission check | |
| func TestCheckGovernanceBypassPermissionResourceGeneration(t *testing.T) { | |
| 	tests := []struct { | |
| 		name         string | |
| 		bucket       string | |
| 		object       string | |
| 		expectedPath string | |
| 		description  string | |
| 	}{ | |
| 		{ | |
| 			name:         "simple_object", | |
| 			bucket:       "test-bucket", | |
| 			object:       "test-object.txt", | |
| 			expectedPath: "test-bucket/test-object.txt", | |
| 			description:  "Simple bucket and object should be joined with slash", | |
| 		}, | |
| 		{ | |
| 			name:         "object_with_leading_slash", | |
| 			bucket:       "test-bucket", | |
| 			object:       "/test-object.txt", | |
| 			expectedPath: "test-bucket/test-object.txt", | |
| 			description:  "Leading slash should be trimmed from object name", | |
| 		}, | |
| 		{ | |
| 			name:         "nested_object", | |
| 			bucket:       "test-bucket", | |
| 			object:       "/folder/subfolder/test-object.txt", | |
| 			expectedPath: "test-bucket/folder/subfolder/test-object.txt", | |
| 			description:  "Nested object path should be handled correctly", | |
| 		}, | |
| 		{ | |
| 			name:         "empty_object", | |
| 			bucket:       "test-bucket", | |
| 			object:       "", | |
| 			expectedPath: "test-bucket/", | |
| 			description:  "Empty object should result in bucket with trailing slash", | |
| 		}, | |
| 		{ | |
| 			name:         "root_object", | |
| 			bucket:       "test-bucket", | |
| 			object:       "/", | |
| 			expectedPath: "test-bucket/", | |
| 			description:  "Root object should result in bucket with trailing slash", | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, tt := range tests { | |
| 		t.Run(tt.name, func(t *testing.T) { | |
| 			// Test the resource generation logic used in checkGovernanceBypassPermission | |
| 			resource := strings.TrimPrefix(tt.object, "/") | |
| 			actualPath := tt.bucket + "/" + resource | |
| 
 | |
| 			if actualPath != tt.expectedPath { | |
| 				t.Errorf("Resource path generation failed. Expected: %s, Got: %s. %s", | |
| 					tt.expectedPath, actualPath, tt.description) | |
| 			} | |
| 		}) | |
| 	} | |
| } | |
| 
 | |
| // TestCheckGovernanceBypassPermissionActionGeneration tests that the function | |
| // correctly generates action strings for IAM checking | |
| func TestCheckGovernanceBypassPermissionActionGeneration(t *testing.T) { | |
| 	tests := []struct { | |
| 		name                 string | |
| 		bucket               string | |
| 		object               string | |
| 		expectedBypassAction string | |
| 		expectedAdminAction  string | |
| 		description          string | |
| 	}{ | |
| 		{ | |
| 			name:                 "bypass_action_generation", | |
| 			bucket:               "test-bucket", | |
| 			object:               "test-object.txt", | |
| 			expectedBypassAction: "BypassGovernanceRetention:test-bucket/test-object.txt", | |
| 			expectedAdminAction:  "Admin:test-bucket/test-object.txt", | |
| 			description:          "Actions should be properly formatted with resource path", | |
| 		}, | |
| 		{ | |
| 			name:                 "leading_slash_handling", | |
| 			bucket:               "test-bucket", | |
| 			object:               "/test-object.txt", | |
| 			expectedBypassAction: "BypassGovernanceRetention:test-bucket/test-object.txt", | |
| 			expectedAdminAction:  "Admin:test-bucket/test-object.txt", | |
| 			description:          "Leading slash should be trimmed in action generation", | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, tt := range tests { | |
| 		t.Run(tt.name, func(t *testing.T) { | |
| 			// Test the action generation logic used in checkGovernanceBypassPermission | |
| 			resource := strings.TrimPrefix(tt.object, "/") | |
| 			resourcePath := tt.bucket + "/" + resource | |
| 
 | |
| 			bypassAction := s3_constants.ACTION_BYPASS_GOVERNANCE_RETENTION + ":" + resourcePath | |
| 			adminAction := s3_constants.ACTION_ADMIN + ":" + resourcePath | |
| 
 | |
| 			if bypassAction != tt.expectedBypassAction { | |
| 				t.Errorf("Bypass action generation failed. Expected: %s, Got: %s. %s", | |
| 					tt.expectedBypassAction, bypassAction, tt.description) | |
| 			} | |
| 
 | |
| 			if adminAction != tt.expectedAdminAction { | |
| 				t.Errorf("Admin action generation failed. Expected: %s, Got: %s. %s", | |
| 					tt.expectedAdminAction, adminAction, tt.description) | |
| 			} | |
| 		}) | |
| 	} | |
| } | |
| 
 | |
| // TestCheckGovernanceBypassPermissionErrorHandling tests error handling scenarios | |
| func TestCheckGovernanceBypassPermissionErrorHandling(t *testing.T) { | |
| 	// Note: This test demonstrates the expected behavior for different error scenarios | |
| 	// without requiring full IAM setup | |
|  | |
| 	tests := []struct { | |
| 		name        string | |
| 		bucket      string | |
| 		object      string | |
| 		description string | |
| 	}{ | |
| 		{ | |
| 			name:        "empty_bucket", | |
| 			bucket:      "", | |
| 			object:      "test-object.txt", | |
| 			description: "Empty bucket should be handled gracefully", | |
| 		}, | |
| 		{ | |
| 			name:        "special_characters", | |
| 			bucket:      "test-bucket", | |
| 			object:      "test object with spaces.txt", | |
| 			description: "Objects with special characters should be handled", | |
| 		}, | |
| 		{ | |
| 			name:        "unicode_characters", | |
| 			bucket:      "test-bucket", | |
| 			object:      "测试文件.txt", | |
| 			description: "Objects with unicode characters should be handled", | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, tt := range tests { | |
| 		t.Run(tt.name, func(t *testing.T) { | |
| 			// Test that the function doesn't panic with various inputs | |
| 			// This would normally call checkGovernanceBypassPermission | |
| 			// but since we don't have a full S3ApiServer setup, we just test | |
| 			// that the resource generation logic works without panicking | |
| 			resource := strings.TrimPrefix(tt.object, "/") | |
| 			resourcePath := tt.bucket + "/" + resource | |
| 
 | |
| 			// Verify the resource path is generated | |
| 			if resourcePath == "" { | |
| 				t.Errorf("Resource path should not be empty for test case: %s", tt.description) | |
| 			} | |
| 
 | |
| 			t.Logf("Generated resource path for %s: %s", tt.description, resourcePath) | |
| 		}) | |
| 	} | |
| } | |
| 
 | |
| // TestCheckGovernanceBypassPermissionIntegrationBehavior documents the expected behavior | |
| // when integrated with a full IAM system | |
| func TestCheckGovernanceBypassPermissionIntegrationBehavior(t *testing.T) { | |
| 	t.Skip("Documentation test - describes expected behavior with full IAM integration") | |
| 
 | |
| 	// This test documents the expected behavior when checkGovernanceBypassPermission | |
| 	// is called with a full IAM system: | |
| 	// | |
| 	// 1. Function calls s3a.iam.authRequest() with the bypass action | |
| 	// 2. If authRequest returns errCode != s3err.ErrNone, function returns false | |
| 	// 3. If authRequest succeeds, function checks identity.canDo() with the bypass action | |
| 	// 4. If canDo() returns true, function returns true | |
| 	// 5. If bypass permission fails, function checks admin action with identity.canDo() | |
| 	// 6. If admin action succeeds, function returns true and logs admin access | |
| 	// 7. If all checks fail, function returns false | |
| 	// | |
| 	// The function correctly uses: | |
| 	// - s3_constants.ACTION_BYPASS_GOVERNANCE_RETENTION for bypass permission | |
| 	// - s3_constants.ACTION_ADMIN for admin permission | |
| 	// - Proper resource path generation with bucket/object format | |
| 	// - Trimming of leading slashes from object names | |
| } | |
| 
 | |
| // TestGovernanceBypassPermission was removed because it tested the old | |
| // insecure behavior of trusting the AmzIsAdmin header. The new implementation | |
| // uses proper IAM authentication instead of relying on client-provided headers. | |
|  | |
| // Test specifically for users with IAM bypass permission | |
| func TestGovernanceBypassWithIAMPermission(t *testing.T) { | |
| 	// This test demonstrates the expected behavior for non-admin users with bypass permission | |
| 	// In a real implementation, this would integrate with the full IAM system | |
|  | |
| 	t.Skip("Integration test requires full IAM setup - demonstrates expected behavior") | |
| 
 | |
| 	// The expected behavior would be: | |
| 	// 1. Non-admin user makes request with bypass header | |
| 	// 2. checkGovernanceBypassPermission calls s3a.iam.authRequest | |
| 	// 3. authRequest validates user identity and checks permissions | |
| 	// 4. If user has s3:BypassGovernanceRetention permission, return true | |
| 	// 5. Otherwise return false | |
|  | |
| 	// For now, the function correctly returns false for non-admin users | |
| 	// when the IAM system doesn't have the user configured with bypass permission | |
| } | |
| 
 | |
| func TestGovernancePermissionIntegration(t *testing.T) { | |
| 	// Note: This test demonstrates the expected integration behavior | |
| 	// In a real implementation, this would require setting up a proper IAM mock | |
| 	// with identities that have the bypass governance permission | |
|  | |
| 	t.Skip("Integration test requires full IAM setup - demonstrates expected behavior") | |
| 
 | |
| 	// This test would verify: | |
| 	// 1. User with BypassGovernanceRetention permission can bypass governance | |
| 	// 2. User without permission cannot bypass governance | |
| 	// 3. Admin users can always bypass governance | |
| 	// 4. Anonymous users cannot bypass governance | |
| } | |
| 
 | |
| func TestGovernanceBypassHeader(t *testing.T) { | |
| 	tests := []struct { | |
| 		name           string | |
| 		headerValue    string | |
| 		expectedResult bool | |
| 		description    string | |
| 	}{ | |
| 		{ | |
| 			name:           "bypass_header_true", | |
| 			headerValue:    "true", | |
| 			expectedResult: true, | |
| 			description:    "Header with 'true' value should enable bypass", | |
| 		}, | |
| 		{ | |
| 			name:           "bypass_header_false", | |
| 			headerValue:    "false", | |
| 			expectedResult: false, | |
| 			description:    "Header with 'false' value should not enable bypass", | |
| 		}, | |
| 		{ | |
| 			name:           "bypass_header_empty", | |
| 			headerValue:    "", | |
| 			expectedResult: false, | |
| 			description:    "Empty header should not enable bypass", | |
| 		}, | |
| 		{ | |
| 			name:           "bypass_header_invalid", | |
| 			headerValue:    "invalid", | |
| 			expectedResult: false, | |
| 			description:    "Invalid header value should not enable bypass", | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, tt := range tests { | |
| 		t.Run(tt.name, func(t *testing.T) { | |
| 			req := httptest.NewRequest("DELETE", "/bucket/object", nil) | |
| 			if tt.headerValue != "" { | |
| 				req.Header.Set("x-amz-bypass-governance-retention", tt.headerValue) | |
| 			} | |
| 
 | |
| 			result := req.Header.Get("x-amz-bypass-governance-retention") == "true" | |
| 
 | |
| 			if result != tt.expectedResult { | |
| 				t.Errorf("bypass header check = %v, want %v. %s", result, tt.expectedResult, tt.description) | |
| 			} | |
| 		}) | |
| 	} | |
| } | |
| 
 | |
| func TestGovernanceRetentionModeChecking(t *testing.T) { | |
| 	tests := []struct { | |
| 		name              string | |
| 		retentionMode     string | |
| 		bypassGovernance  bool | |
| 		hasPermission     bool | |
| 		expectedError     bool | |
| 		expectedErrorType string | |
| 		description       string | |
| 	}{ | |
| 		{ | |
| 			name:              "compliance_mode_cannot_bypass", | |
| 			retentionMode:     s3_constants.RetentionModeCompliance, | |
| 			bypassGovernance:  true, | |
| 			hasPermission:     true, | |
| 			expectedError:     true, | |
| 			expectedErrorType: "compliance mode", | |
| 			description:       "Compliance mode should not be bypassable even with permission", | |
| 		}, | |
| 		{ | |
| 			name:              "governance_mode_without_bypass", | |
| 			retentionMode:     s3_constants.RetentionModeGovernance, | |
| 			bypassGovernance:  false, | |
| 			hasPermission:     false, | |
| 			expectedError:     true, | |
| 			expectedErrorType: "governance mode", | |
| 			description:       "Governance mode should be blocked without bypass", | |
| 		}, | |
| 		{ | |
| 			name:              "governance_mode_with_bypass_no_permission", | |
| 			retentionMode:     s3_constants.RetentionModeGovernance, | |
| 			bypassGovernance:  true, | |
| 			hasPermission:     false, | |
| 			expectedError:     true, | |
| 			expectedErrorType: "permission", | |
| 			description:       "Governance mode bypass should fail without permission", | |
| 		}, | |
| 		{ | |
| 			name:              "governance_mode_with_bypass_and_permission", | |
| 			retentionMode:     s3_constants.RetentionModeGovernance, | |
| 			bypassGovernance:  true, | |
| 			hasPermission:     true, | |
| 			expectedError:     false, | |
| 			expectedErrorType: "", | |
| 			description:       "Governance mode bypass should succeed with permission", | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, tt := range tests { | |
| 		t.Run(tt.name, func(t *testing.T) { | |
| 			// Test validates the logic without actually needing the full implementation | |
| 			// This demonstrates the expected behavior patterns | |
|  | |
| 			var hasError bool | |
| 			var errorType string | |
| 
 | |
| 			if tt.retentionMode == s3_constants.RetentionModeCompliance { | |
| 				hasError = true | |
| 				errorType = "compliance mode" | |
| 			} else if tt.retentionMode == s3_constants.RetentionModeGovernance { | |
| 				if !tt.bypassGovernance { | |
| 					hasError = true | |
| 					errorType = "governance mode" | |
| 				} else if !tt.hasPermission { | |
| 					hasError = true | |
| 					errorType = "permission" | |
| 				} | |
| 			} | |
| 
 | |
| 			if hasError != tt.expectedError { | |
| 				t.Errorf("expected error: %v, got error: %v. %s", tt.expectedError, hasError, tt.description) | |
| 			} | |
| 
 | |
| 			if tt.expectedError && !strings.Contains(errorType, tt.expectedErrorType) { | |
| 				t.Errorf("expected error type containing '%s', got '%s'. %s", tt.expectedErrorType, errorType, tt.description) | |
| 			} | |
| 		}) | |
| 	} | |
| } | |
| 
 | |
| func TestGovernancePermissionActionGeneration(t *testing.T) { | |
| 	tests := []struct { | |
| 		name           string | |
| 		bucket         string | |
| 		object         string | |
| 		expectedAction string | |
| 		description    string | |
| 	}{ | |
| 		{ | |
| 			name:           "bucket_and_object_action", | |
| 			bucket:         "test-bucket", | |
| 			object:         "/test-object", // Object has "/" prefix from GetBucketAndObject | |
| 			expectedAction: "BypassGovernanceRetention:test-bucket/test-object", | |
| 			description:    "Action should be generated correctly for bucket and object", | |
| 		}, | |
| 		{ | |
| 			name:           "bucket_only_action", | |
| 			bucket:         "test-bucket", | |
| 			object:         "", | |
| 			expectedAction: "BypassGovernanceRetention:test-bucket", | |
| 			description:    "Action should be generated correctly for bucket only", | |
| 		}, | |
| 		{ | |
| 			name:           "nested_object_action", | |
| 			bucket:         "test-bucket", | |
| 			object:         "/folder/subfolder/object", // Object has "/" prefix from GetBucketAndObject | |
| 			expectedAction: "BypassGovernanceRetention:test-bucket/folder/subfolder/object", | |
| 			description:    "Action should be generated correctly for nested objects", | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, tt := range tests { | |
| 		t.Run(tt.name, func(t *testing.T) { | |
| 			action := s3_constants.ACTION_BYPASS_GOVERNANCE_RETENTION + ":" + tt.bucket + tt.object | |
| 
 | |
| 			if action != tt.expectedAction { | |
| 				t.Errorf("generated action: %s, expected: %s. %s", action, tt.expectedAction, tt.description) | |
| 			} | |
| 		}) | |
| 	} | |
| } | |
| 
 | |
| // TestGovernancePermissionEndToEnd tests the complete object lock permission flow | |
| func TestGovernancePermissionEndToEnd(t *testing.T) { | |
| 	t.Skip("End-to-end testing requires full S3 API server setup - demonstrates expected behavior") | |
| 
 | |
| 	// This test demonstrates the end-to-end flow that would be tested in a full integration test | |
| 	// The checkObjectLockPermissions method is called by: | |
| 	// 1. DeleteObjectHandler - when versioning is enabled and object lock is configured | |
| 	// 2. DeleteMultipleObjectsHandler - for each object in versioned buckets | |
| 	// 3. PutObjectHandler - via checkObjectLockPermissionsForPut for versioned buckets | |
| 	// 4. PutObjectRetentionHandler - when setting retention on objects | |
| 	// | |
| 	// Each handler: | |
| 	// - Extracts bypassGovernance from "x-amz-bypass-governance-retention" header | |
| 	// - Calls checkObjectLockPermissions with the appropriate parameters | |
| 	// - Handles the returned errors appropriately (ErrAccessDenied, etc.) | |
| 	// | |
| 	// The method integrates with the IAM system through checkGovernanceBypassPermission | |
| 	// which validates the s3:BypassGovernanceRetention permission | |
| } | |
| 
 | |
| // TestGovernancePermissionHTTPFlow tests the HTTP header processing and method calls | |
| func TestGovernancePermissionHTTPFlow(t *testing.T) { | |
| 	tests := []struct { | |
| 		name                     string | |
| 		headerValue              string | |
| 		expectedBypassGovernance bool | |
| 	}{ | |
| 		{ | |
| 			name:                     "bypass_header_true", | |
| 			headerValue:              "true", | |
| 			expectedBypassGovernance: true, | |
| 		}, | |
| 		{ | |
| 			name:                     "bypass_header_false", | |
| 			headerValue:              "false", | |
| 			expectedBypassGovernance: false, | |
| 		}, | |
| 		{ | |
| 			name:                     "bypass_header_missing", | |
| 			headerValue:              "", | |
| 			expectedBypassGovernance: false, | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, tt := range tests { | |
| 		t.Run(tt.name, func(t *testing.T) { | |
| 			// Create a mock HTTP request | |
| 			req, _ := http.NewRequest("DELETE", "/bucket/test-object", nil) | |
| 			if tt.headerValue != "" { | |
| 				req.Header.Set("x-amz-bypass-governance-retention", tt.headerValue) | |
| 			} | |
| 
 | |
| 			// Test the header processing logic used in handlers | |
| 			bypassGovernance := req.Header.Get("x-amz-bypass-governance-retention") == "true" | |
| 
 | |
| 			if bypassGovernance != tt.expectedBypassGovernance { | |
| 				t.Errorf("Expected bypassGovernance to be %v, got %v", tt.expectedBypassGovernance, bypassGovernance) | |
| 			} | |
| 		}) | |
| 	} | |
| } | |
| 
 | |
| // TestGovernancePermissionMethodCalls tests that the governance permission methods are called correctly | |
| func TestGovernancePermissionMethodCalls(t *testing.T) { | |
| 	// Test that demonstrates the method call pattern used in handlers | |
|  | |
| 	// This is the pattern used in DeleteObjectHandler: | |
| 	t.Run("delete_object_handler_pattern", func(t *testing.T) { | |
| 		req, _ := http.NewRequest("DELETE", "/bucket/test-object", nil) | |
| 		req.Header.Set("x-amz-bypass-governance-retention", "true") | |
| 
 | |
| 		// Extract parameters as done in the handler | |
| 		bucket, object := s3_constants.GetBucketAndObject(req) | |
| 		versionId := req.URL.Query().Get("versionId") | |
| 		bypassGovernance := req.Header.Get("x-amz-bypass-governance-retention") == "true" | |
| 
 | |
| 		// Verify the parameters are extracted correctly | |
| 		// Note: The actual bucket and object extraction depends on the URL structure | |
| 		t.Logf("Extracted bucket: %s, object: %s", bucket, object) | |
| 		if versionId != "" { | |
| 			t.Errorf("Expected versionId to be empty, got %v", versionId) | |
| 		} | |
| 		if !bypassGovernance { | |
| 			t.Errorf("Expected bypassGovernance to be true") | |
| 		} | |
| 	}) | |
| 
 | |
| 	// This is the pattern used in PutObjectHandler: | |
| 	t.Run("put_object_handler_pattern", func(t *testing.T) { | |
| 		req, _ := http.NewRequest("PUT", "/bucket/test-object", nil) | |
| 		req.Header.Set("x-amz-bypass-governance-retention", "true") | |
| 
 | |
| 		// Extract parameters as done in the handler | |
| 		bucket, object := s3_constants.GetBucketAndObject(req) | |
| 		bypassGovernance := req.Header.Get("x-amz-bypass-governance-retention") == "true" | |
| 		versioningEnabled := true // Would be determined by isVersioningEnabled(bucket) | |
|  | |
| 		// Verify the parameters are extracted correctly | |
| 		// Note: The actual bucket and object extraction depends on the URL structure | |
| 		t.Logf("Extracted bucket: %s, object: %s", bucket, object) | |
| 		if !bypassGovernance { | |
| 			t.Errorf("Expected bypassGovernance to be true") | |
| 		} | |
| 		if !versioningEnabled { | |
| 			t.Errorf("Expected versioningEnabled to be true") | |
| 		} | |
| 	}) | |
| } | |
| 
 | |
| // TestGovernanceBypassNotPermittedError tests that ErrGovernanceBypassNotPermitted | |
| // is returned when bypass is requested but the user lacks permission | |
| func TestGovernanceBypassNotPermittedError(t *testing.T) { | |
| 	// Test the error constant itself | |
| 	if ErrGovernanceBypassNotPermitted == nil { | |
| 		t.Error("ErrGovernanceBypassNotPermitted should be defined") | |
| 	} | |
| 
 | |
| 	// Verify the error message | |
| 	expectedMessage := "user does not have permission to bypass governance retention" | |
| 	if ErrGovernanceBypassNotPermitted.Error() != expectedMessage { | |
| 		t.Errorf("expected error message '%s', got '%s'", | |
| 			expectedMessage, ErrGovernanceBypassNotPermitted.Error()) | |
| 	} | |
| 
 | |
| 	// Test the scenario where this error should be returned | |
| 	// This documents the expected behavior when: | |
| 	// 1. Object is under governance retention | |
| 	// 2. bypassGovernance is true | |
| 	// 3. checkGovernanceBypassPermission returns false | |
| 	testCases := []struct { | |
| 		name             string | |
| 		retentionMode    string | |
| 		bypassGovernance bool | |
| 		hasPermission    bool | |
| 		expectedError    error | |
| 		description      string | |
| 	}{ | |
| 		{ | |
| 			name:             "governance_bypass_without_permission", | |
| 			retentionMode:    s3_constants.RetentionModeGovernance, | |
| 			bypassGovernance: true, | |
| 			hasPermission:    false, | |
| 			expectedError:    ErrGovernanceBypassNotPermitted, | |
| 			description:      "Should return ErrGovernanceBypassNotPermitted when bypass is requested but user lacks permission", | |
| 		}, | |
| 		{ | |
| 			name:             "governance_bypass_with_permission", | |
| 			retentionMode:    s3_constants.RetentionModeGovernance, | |
| 			bypassGovernance: true, | |
| 			hasPermission:    true, | |
| 			expectedError:    nil, | |
| 			description:      "Should succeed when bypass is requested and user has permission", | |
| 		}, | |
| 		{ | |
| 			name:             "governance_no_bypass", | |
| 			retentionMode:    s3_constants.RetentionModeGovernance, | |
| 			bypassGovernance: false, | |
| 			hasPermission:    false, | |
| 			expectedError:    ErrGovernanceModeActive, | |
| 			description:      "Should return ErrGovernanceModeActive when bypass is not requested", | |
| 		}, | |
| 	} | |
| 
 | |
| 	for _, tc := range testCases { | |
| 		t.Run(tc.name, func(t *testing.T) { | |
| 			// This test documents the expected behavior pattern | |
| 			// The actual checkObjectLockPermissions method implements this logic: | |
| 			// if retention.Mode == s3_constants.RetentionModeGovernance { | |
| 			//     if !bypassGovernance { | |
| 			//         return ErrGovernanceModeActive | |
| 			//     } | |
| 			//     if !s3a.checkGovernanceBypassPermission(request, bucket, object) { | |
| 			//         return ErrGovernanceBypassNotPermitted | |
| 			//     } | |
| 			// } | |
|  | |
| 			var simulatedError error | |
| 			if tc.retentionMode == s3_constants.RetentionModeGovernance { | |
| 				if !tc.bypassGovernance { | |
| 					simulatedError = ErrGovernanceModeActive | |
| 				} else if !tc.hasPermission { | |
| 					simulatedError = ErrGovernanceBypassNotPermitted | |
| 				} | |
| 			} | |
| 
 | |
| 			if simulatedError != tc.expectedError { | |
| 				t.Errorf("expected error %v, got %v. %s", tc.expectedError, simulatedError, tc.description) | |
| 			} | |
| 
 | |
| 			// Verify ErrGovernanceBypassNotPermitted is returned in the right case | |
| 			if tc.name == "governance_bypass_without_permission" && simulatedError != ErrGovernanceBypassNotPermitted { | |
| 				t.Errorf("Test case should return ErrGovernanceBypassNotPermitted but got %v", simulatedError) | |
| 			} | |
| 		}) | |
| 	} | |
| }
 |