Skip to content

Commit 9318d60

Browse files
committed
API consistency: Bucket API / DTOs
Checking AWS APIs for changes and S3Mock for implementations and tests. Fixes #2340
1 parent 1d632ad commit 9318d60

File tree

83 files changed

+2021
-542
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+2021
-542
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ Version 4.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav
150150
* Check AWS API for changes
151151
* Update S3Mock API / DTOs
152152
* Add tests for changed API / DTOs
153+
* List Objects now returns "delimiter"
154+
* List Objects V2 now accepts "fetch-owner" and returns "delimiter"
155+
* List Buckets now accepts all parameters listed in AWS S3 API
153156
* Version updates (deliverable dependencies)
154157
* Bump spring-boot.version from 3.4.4 to 3.4.5
155158
* Bump testcontainers.version from 1.20.6 to 1.21.0

integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/BucketIT.kt

Lines changed: 137 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@ import software.amazon.awssdk.awscore.exception.AwsServiceException
2626
import software.amazon.awssdk.services.s3.S3Client
2727
import software.amazon.awssdk.services.s3.model.AbortIncompleteMultipartUpload
2828
import software.amazon.awssdk.services.s3.model.BucketLifecycleConfiguration
29+
import software.amazon.awssdk.services.s3.model.BucketType
2930
import software.amazon.awssdk.services.s3.model.BucketVersioningStatus
31+
import software.amazon.awssdk.services.s3.model.DataRedundancy
3032
import software.amazon.awssdk.services.s3.model.DeleteBucketRequest
3133
import software.amazon.awssdk.services.s3.model.ExpirationStatus
3234
import software.amazon.awssdk.services.s3.model.GetBucketLifecycleConfigurationRequest
3335
import software.amazon.awssdk.services.s3.model.LifecycleExpiration
3436
import software.amazon.awssdk.services.s3.model.LifecycleRule
3537
import software.amazon.awssdk.services.s3.model.LifecycleRuleFilter
38+
import software.amazon.awssdk.services.s3.model.LocationType
3639
import software.amazon.awssdk.services.s3.model.MFADelete
3740
import software.amazon.awssdk.services.s3.model.MFADeleteStatus
3841
import software.amazon.awssdk.services.s3.model.NoSuchBucketException
@@ -68,6 +71,35 @@ internal class BucketIT : S3TestBase() {
6871
}
6972
}
7073

74+
@Test
75+
@S3VerifiedSuccess(year = 2025)
76+
fun `creating a bucket with configuration is successful`(testInfo: TestInfo) {
77+
val bucketName = bucketName(testInfo)
78+
val createBucketResponse = s3Client.createBucket {
79+
it.bucket(bucketName)
80+
it.createBucketConfiguration {
81+
it.locationConstraint("ap-southeast-5")
82+
it.bucket {
83+
it.dataRedundancy(DataRedundancy.SINGLE_AVAILABILITY_ZONE)
84+
it.type(BucketType.DIRECTORY)
85+
}
86+
it.location {
87+
it.name("SomeName")
88+
it.type(LocationType.AVAILABILITY_ZONE)
89+
}
90+
}
91+
}
92+
assertThat(createBucketResponse.sdkHttpResponse().statusCode()).isEqualTo(200)
93+
assertThat(createBucketResponse.location()).isEqualTo("/$bucketName")
94+
95+
val bucketCreated = s3Client.waiter().waitUntilBucketExists { it.bucket(bucketName) }
96+
val bucketCreatedResponse = bucketCreated.matched().response().get()
97+
assertThat(bucketCreatedResponse).isNotNull
98+
99+
//does not throw exception if bucket exists.
100+
s3Client.headBucket { it.bucket(bucketName) }
101+
}
102+
71103
@Test
72104
@S3VerifiedSuccess(year = 2025)
73105
fun `deleting a non-empty bucket fails`(testInfo: TestInfo) {
@@ -94,20 +126,113 @@ internal class BucketIT : S3TestBase() {
94126
// and account for a clock-skew in the Docker container of up to a minute.
95127
val creationDate = Instant.now().minus(1, ChronoUnit.MINUTES)
96128

97-
s3Client.listBuckets{
129+
s3Client.listBuckets { }.also {
130+
assertThat(it.hasBuckets()).isTrue
131+
it.buckets().also {
132+
assertThat(it.size).isEqualTo(5)
133+
assertThat(it.map { b -> b.name() }).containsExactly(
134+
// the default buckets
135+
"bucket-a",
136+
"bucket-b",
137+
// the buckets we created in this test
138+
"${bucketName}-1",
139+
"${bucketName}-2",
140+
"${bucketName}-3"
141+
)
142+
assertThat(it[2].creationDate()).isAfterOrEqualTo(creationDate)
143+
assertThat(it[3].creationDate()).isAfterOrEqualTo(creationDate)
144+
assertThat(it[4].creationDate()).isAfterOrEqualTo(creationDate)
145+
}
146+
assertThat(it.prefix()).isNull()
147+
assertThat(it.continuationToken()).isNull()
148+
assertThat(it.owner().displayName()).isEqualTo("s3-mock-file-store")
149+
assertThat(it.owner().id()).isEqualTo("79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be")
150+
}
151+
}
152+
153+
@Test
154+
@S3VerifiedFailure(year = 2025,
155+
reason = "Default owner does not exist in S3.")
156+
fun `creating and listing multiple buckets limiting by prefix is successful`(testInfo: TestInfo) {
157+
val bucketName = bucketName(testInfo)
158+
givenBucket("${bucketName}-1")
159+
givenBucket("${bucketName}-2")
160+
givenBucket("${bucketName}-3")
161+
// the returned creation date might strip off the millisecond-part, resulting in rounding down
162+
// and account for a clock-skew in the Docker container of up to a minute.
163+
val creationDate = Instant.now().minus(1, ChronoUnit.MINUTES)
164+
165+
s3Client.listBuckets {
98166
it.prefix(bucketName)
99167
}.also {
100168
assertThat(it.hasBuckets()).isTrue
101-
//TODO: ListBuckets API currently ignores the prefix argument, see #2340
102-
it.buckets()
103-
.filter { b -> b.name().startsWith(bucketName) }.also { filteredBuckets ->
104-
assertThat(filteredBuckets.size).isEqualTo(3)
105-
assertThat(filteredBuckets.map { b -> b.name() })
106-
.containsExactlyInAnyOrder("${bucketName}-1", "${bucketName}-2", "${bucketName}-3")
107-
assertThat(filteredBuckets[0].creationDate()).isAfterOrEqualTo(creationDate)
108-
assertThat(filteredBuckets[1].creationDate()).isAfterOrEqualTo(creationDate)
109-
assertThat(filteredBuckets[2].creationDate()).isAfterOrEqualTo(creationDate)
110-
}
169+
it.buckets().also {
170+
assertThat(it.size).isEqualTo(3)
171+
assertThat(it.map { b -> b.name() }).containsExactly(
172+
// the buckets we created in this test
173+
"${bucketName}-1",
174+
"${bucketName}-2",
175+
"${bucketName}-3"
176+
)
177+
assertThat(it[0].creationDate()).isAfterOrEqualTo(creationDate)
178+
assertThat(it[1].creationDate()).isAfterOrEqualTo(creationDate)
179+
assertThat(it[2].creationDate()).isAfterOrEqualTo(creationDate)
180+
}
181+
assertThat(it.prefix()).isEqualTo(bucketName)
182+
assertThat(it.continuationToken()).isNull()
183+
assertThat(it.owner().displayName()).isEqualTo("s3-mock-file-store")
184+
assertThat(it.owner().id()).isEqualTo("79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be")
185+
}
186+
}
187+
188+
@Test
189+
@S3VerifiedFailure(year = 2025,
190+
reason = "Default owner does not exist in S3.")
191+
fun `creating and listing multiple buckets limiting by maxBuckets is successful`(testInfo: TestInfo) {
192+
val bucketName = bucketName(testInfo)
193+
givenBucket("${bucketName}-1")
194+
givenBucket("${bucketName}-2")
195+
givenBucket("${bucketName}-3")
196+
// the returned creation date might strip off the millisecond-part, resulting in rounding down
197+
// and account for a clock-skew in the Docker container of up to a minute.
198+
val creationDate = Instant.now().minus(1, ChronoUnit.MINUTES)
199+
200+
val continuationToken = s3Client.listBuckets {
201+
it.maxBuckets(4)
202+
}.also {
203+
assertThat(it.hasBuckets()).isTrue
204+
it.buckets().also {
205+
assertThat(it.size).isEqualTo(4)
206+
assertThat(it.map { b -> b.name() }).containsExactly(
207+
// the default buckets
208+
"bucket-a",
209+
"bucket-b",
210+
// the buckets we created in this test
211+
"${bucketName}-1",
212+
"${bucketName}-2"
213+
)
214+
assertThat(it[2].creationDate()).isAfterOrEqualTo(creationDate)
215+
assertThat(it[3].creationDate()).isAfterOrEqualTo(creationDate)
216+
}
217+
assertThat(it.prefix()).isNull()
218+
assertThat(it.continuationToken()).isNotNull
219+
assertThat(it.owner().displayName()).isEqualTo("s3-mock-file-store")
220+
assertThat(it.owner().id()).isEqualTo("79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be")
221+
}.continuationToken()
222+
223+
s3Client.listBuckets {
224+
it.continuationToken(continuationToken)
225+
}.also {
226+
assertThat(it.hasBuckets()).isTrue
227+
it.buckets().also {
228+
assertThat(it.size).isEqualTo(1)
229+
assertThat(it.map { b -> b.name() }).containsExactly(
230+
"${bucketName}-3"
231+
)
232+
assertThat(it[0].creationDate()).isAfterOrEqualTo(creationDate)
233+
}
234+
assertThat(it.prefix()).isNull()
235+
assertThat(it.continuationToken()).isNull()
111236
assertThat(it.owner().displayName()).isEqualTo("s3-mock-file-store")
112237
assertThat(it.owner().id()).isEqualTo("79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be")
113238
}
@@ -121,7 +246,7 @@ internal class BucketIT : S3TestBase() {
121246
assertThat(it.buckets())
122247
.hasSize(2)
123248
.extracting("name")
124-
.containsExactlyInAnyOrder(INITIAL_BUCKET_NAMES.first(), INITIAL_BUCKET_NAMES.last())
249+
.containsExactly(INITIAL_BUCKET_NAMES.first(), INITIAL_BUCKET_NAMES.last())
125250
}
126251
}
127252

0 commit comments

Comments
 (0)