1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
|
package s3api
import (
"context"
"fmt"
"testing"
"time"
"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/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestBucketCreationBehavior tests the S3-compliant bucket creation behavior
func TestBucketCreationBehavior(t *testing.T) {
client := getS3Client(t)
ctx := context.Background()
// Test cases for bucket creation behavior
testCases := []struct {
name string
setupFunc func(t *testing.T, bucketName string) // Setup before test
bucketName string
objectLockEnabled *bool
expectedStatusCode int
expectedError string
cleanupFunc func(t *testing.T, bucketName string) // Cleanup after test
}{
{
name: "Create new bucket - should succeed",
bucketName: "test-new-bucket-" + fmt.Sprintf("%d", time.Now().Unix()),
objectLockEnabled: nil,
expectedStatusCode: 200,
expectedError: "",
},
{
name: "Create existing bucket with same owner - should return BucketAlreadyExists",
setupFunc: func(t *testing.T, bucketName string) {
// Create bucket first
_, err := client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: aws.String(bucketName),
})
require.NoError(t, err, "Setup: failed to create initial bucket")
},
bucketName: "test-same-owner-same-settings-" + fmt.Sprintf("%d", time.Now().Unix()),
objectLockEnabled: nil,
expectedStatusCode: 409, // SeaweedFS now returns BucketAlreadyExists in all cases
expectedError: "BucketAlreadyExists",
},
{
name: "Create bucket with same owner but different Object Lock settings - should fail",
setupFunc: func(t *testing.T, bucketName string) {
// Create bucket without Object Lock first
_, err := client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: aws.String(bucketName),
})
require.NoError(t, err, "Setup: failed to create initial bucket")
},
bucketName: "test-same-owner-diff-settings-" + fmt.Sprintf("%d", time.Now().Unix()),
objectLockEnabled: aws.Bool(true), // Try to enable Object Lock on existing bucket
expectedStatusCode: 409,
expectedError: "BucketAlreadyExists",
},
{
name: "Create bucket with Object Lock enabled - should succeed",
bucketName: "test-object-lock-new-" + fmt.Sprintf("%d", time.Now().Unix()),
objectLockEnabled: aws.Bool(true),
expectedStatusCode: 200,
expectedError: "",
},
{
name: "Create bucket with Object Lock enabled twice - should fail",
setupFunc: func(t *testing.T, bucketName string) {
// Create bucket with Object Lock first
_, err := client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: aws.String(bucketName),
ObjectLockEnabledForBucket: aws.Bool(true),
})
require.NoError(t, err, "Setup: failed to create initial bucket with Object Lock")
},
bucketName: "test-object-lock-duplicate-" + fmt.Sprintf("%d", time.Now().Unix()),
objectLockEnabled: aws.Bool(true),
expectedStatusCode: 409,
expectedError: "BucketAlreadyExists",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Setup
if tc.setupFunc != nil {
tc.setupFunc(t, tc.bucketName)
}
// Cleanup function to ensure bucket is deleted after test
defer func() {
if tc.cleanupFunc != nil {
tc.cleanupFunc(t, tc.bucketName)
} else {
// Default cleanup - delete bucket and all objects
cleanupBucketForCreationTest(t, client, tc.bucketName)
}
}()
// Execute the test - attempt to create bucket
input := &s3.CreateBucketInput{
Bucket: aws.String(tc.bucketName),
}
if tc.objectLockEnabled != nil {
input.ObjectLockEnabledForBucket = tc.objectLockEnabled
}
_, err := client.CreateBucket(ctx, input)
// Verify results
if tc.expectedError == "" {
// Should succeed
assert.NoError(t, err, "Expected bucket creation to succeed")
} else {
// Should fail with specific error
assert.Error(t, err, "Expected bucket creation to fail")
if err != nil {
assert.Contains(t, err.Error(), tc.expectedError,
"Expected error to contain '%s', got: %v", tc.expectedError, err)
}
}
})
}
}
// TestBucketCreationWithDifferentUsers tests bucket creation with different identity contexts
func TestBucketCreationWithDifferentUsers(t *testing.T) {
// This test would require setting up different S3 credentials/identities
// For now, we'll skip this as it requires more complex setup
t.Skip("Different user testing requires IAM setup - implement when IAM is configured")
// TODO: Implement when we have proper IAM/user management in test setup
// Should test:
// 1. User A creates bucket
// 2. User B tries to create same bucket -> should fail with BucketAlreadyExists
}
// TestBucketCreationVersioningInteraction tests interaction between bucket creation and versioning
func TestBucketCreationVersioningInteraction(t *testing.T) {
client := getS3Client(t)
ctx := context.Background()
bucketName := "test-versioning-interaction-" + fmt.Sprintf("%d", time.Now().Unix())
defer cleanupBucketForCreationTest(t, client, bucketName)
// Create bucket with Object Lock (which enables versioning)
_, err := client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: aws.String(bucketName),
ObjectLockEnabledForBucket: aws.Bool(true),
})
require.NoError(t, err, "Failed to create bucket with Object Lock")
// Verify versioning is enabled
versioningOutput, err := client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{
Bucket: aws.String(bucketName),
})
require.NoError(t, err, "Failed to get bucket versioning status")
assert.Equal(t, types.BucketVersioningStatusEnabled, versioningOutput.Status,
"Expected versioning to be enabled when Object Lock is enabled")
// Try to create the same bucket again - should fail
_, err = client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: aws.String(bucketName),
ObjectLockEnabledForBucket: aws.Bool(true),
})
assert.Error(t, err, "Expected second bucket creation to fail")
assert.Contains(t, err.Error(), "BucketAlreadyExists",
"Expected BucketAlreadyExists error, got: %v", err)
}
// TestBucketCreationErrorMessages tests that proper error messages are returned
func TestBucketCreationErrorMessages(t *testing.T) {
client := getS3Client(t)
ctx := context.Background()
bucketName := "test-error-messages-" + fmt.Sprintf("%d", time.Now().Unix())
defer cleanupBucketForCreationTest(t, client, bucketName)
// Create bucket first
_, err := client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: aws.String(bucketName),
})
require.NoError(t, err, "Failed to create initial bucket")
// Try to create again and check error details
_, err = client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: aws.String(bucketName),
})
require.Error(t, err, "Expected bucket creation to fail")
// Check that it's the right type of error
assert.Contains(t, err.Error(), "BucketAlreadyExists",
"Expected BucketAlreadyExists error, got: %v", err)
}
// cleanupBucketForCreationTest removes a bucket and all its contents
func cleanupBucketForCreationTest(t *testing.T, client *s3.Client, bucketName string) {
ctx := context.Background()
// List and delete all objects (including versions)
listInput := &s3.ListObjectVersionsInput{
Bucket: aws.String(bucketName),
}
for {
listOutput, err := client.ListObjectVersions(ctx, listInput)
if err != nil {
// Bucket might not exist, which is fine
break
}
if len(listOutput.Versions) == 0 && len(listOutput.DeleteMarkers) == 0 {
break
}
// Delete all versions
var objectsToDelete []types.ObjectIdentifier
for _, version := range listOutput.Versions {
objectsToDelete = append(objectsToDelete, types.ObjectIdentifier{
Key: version.Key,
VersionId: version.VersionId,
})
}
for _, marker := range listOutput.DeleteMarkers {
objectsToDelete = append(objectsToDelete, types.ObjectIdentifier{
Key: marker.Key,
VersionId: marker.VersionId,
})
}
if len(objectsToDelete) > 0 {
_, err = client.DeleteObjects(ctx, &s3.DeleteObjectsInput{
Bucket: aws.String(bucketName),
Delete: &types.Delete{
Objects: objectsToDelete,
},
})
if err != nil {
t.Logf("Warning: failed to delete objects from bucket %s: %v", bucketName, err)
}
}
// Check if there are more objects
if !aws.ToBool(listOutput.IsTruncated) {
break
}
listInput.KeyMarker = listOutput.NextKeyMarker
listInput.VersionIdMarker = listOutput.NextVersionIdMarker
}
// Delete the bucket
_, err := client.DeleteBucket(ctx, &s3.DeleteBucketInput{
Bucket: aws.String(bucketName),
})
if err != nil {
t.Logf("Warning: failed to delete bucket %s: %v", bucketName, err)
}
}
|