Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0a1a266
Add more unit tests
afranken Aug 30, 2025
0a7adf7
Add more unit tests
afranken Aug 30, 2025
2efa170
Add more unit tests
afranken Aug 30, 2025
a25248b
Add "close stale issues or PRs" action
afranken Aug 30, 2025
f4cd24f
Use new StringUtils API
afranken Aug 30, 2025
77c6a57
Refactor BucketControllerTest to MockMvc 1
afranken Aug 30, 2025
3aa519b
Refactor BucketControllerTest to MockMvc 2
afranken Aug 30, 2025
7b6b60e
Refactor BucketControllerTest to MockMvc 3
afranken Aug 30, 2025
2967c8e
Refactor BucketControllerTest to MockMvc 4
afranken Aug 30, 2025
f881874
Refactor ControllerTest convenience functions
afranken Aug 30, 2025
eae9d50
Refactor ContextPathObjectStoreControllerTest to MockMvc
afranken Aug 30, 2025
67a99ba
Refactor FaviconControllerTest to MockMvc
afranken Aug 30, 2025
76d31eb
Refactor MultipartControllerTest to MockMvc 1
afranken Aug 30, 2025
924673e
Refactor MultipartControllerTest to MockMvc 2
afranken Aug 30, 2025
9df218d
Refactor MultipartControllerTest to MockMvc 3
afranken Aug 30, 2025
52540c7
Refactor MultipartControllerTest to MockMvc 4
afranken Aug 31, 2025
9079f32
Refactor ObjectControllerTest to MockMvc 1
afranken Aug 31, 2025
ad81fbf
Refactor ObjectControllerTest to MockMvc 2
afranken Aug 31, 2025
6953a01
Refactor ObjectControllerTest to MockMvc 3
afranken Aug 31, 2025
a08af5e
Use idiomatic MockMvc assertions
afranken Aug 31, 2025
74e15b5
Use imports
afranken Aug 31, 2025
6d32f31
Idiomatic Kotlin part 1
afranken Aug 31, 2025
f3615f5
Idiomatic Kotlin part 2
afranken Aug 31, 2025
bb6d588
Idiomatic Kotlin part 3
afranken Aug 31, 2025
303ffc1
Idiomatic Kotlin part 4
afranken Aug 31, 2025
7a857d1
Idiomatic Kotlin part 5
afranken Aug 31, 2025
3caf178
Idiomatic Kotlin part 6
afranken Aug 31, 2025
556ca51
Idiomatic Kotlin part 7
afranken Aug 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/stale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: stale.yml
on:
schedule:
- cron: '0 0 * * *' # Run every day at midnight

permissions:
issues: write
pull-requests: write

jobs:
close_stale_prs:
runs-on: ubuntu-latest
steps:
- name: Close stale issues and pull requests
uses: actions/stale@v9.1.0
with:
days-before-stale: 30
days-before-close: 5
stale-issue-message: 'This issue has been flagged as stale due to over 30 days of inactivity. If no updates or comments are made within the next 5 days, the system will automatically close it to maintain repository hygiene.'
close-issue-message: 'This issue has been automatically closed due to prolonged inactivity. It was previously marked as stale after 30 days without activity and has now been closed following an additional 5-day grace period. If you believe this issue should be reopened, please provide a comment with relevant updates or justification.'
stale-pr-message: 'This pull request has been marked as stale due to 30 days of inactivity. If no further updates or comments are made within the next 5 days, it will be automatically closed to maintain repository hygiene and reduce review backlog.'
close-pr-message: 'This pull request has been automatically closed due to extended inactivity. It was previously flagged as stale after 30 days without activity and has now been closed following a 5-day grace period. If you believe this pull request is still relevant, feel free to reopen it or submit a new one with updated context.'
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ internal class AclIT : S3TestBase() {
s3Client.getObjectAcl {
it.bucket(bucketName)
it.key(sourceKey)
}.also {
assertThat(it.sdkHttpResponse().isSuccessful).isTrue()
assertThat(it.owner().id()).isNotBlank()
assertThat(it.owner().displayName()).isNotBlank()
assertThat(it.grants().size).isEqualTo(1)
assertThat(it.grants()[0].permission()).isEqualTo(FULL_CONTROL)
}.also { resp ->
assertThat(resp.sdkHttpResponse().isSuccessful).isTrue()
assertThat(resp.owner().id()).isNotBlank()
assertThat(resp.owner().displayName()).isNotBlank()
assertThat(resp.grants()).hasSize(1)
assertThat(resp.grants().first().permission()).isEqualTo(FULL_CONTROL)
}
}

Expand All @@ -87,13 +87,13 @@ internal class AclIT : S3TestBase() {
assertThat(it).hasSize(1)
}

acl.grants()[0].also {
assertThat(it.permission()).isEqualTo(FULL_CONTROL)
}.grantee().also {
assertThat(it).isNotNull
assertThat(it.id()).isEqualTo(DEFAULT_OWNER.id)
assertThat(it.displayName()).isEqualTo(DEFAULT_OWNER.displayName)
assertThat(it.type()).isEqualTo(CANONICAL_USER)
acl.grants().first().also { grant ->
assertThat(grant.permission()).isEqualTo(FULL_CONTROL)
}.grantee().also { grantee ->
assertThat(grantee).isNotNull
assertThat(grantee.id()).isEqualTo(DEFAULT_OWNER.id)
assertThat(grantee.displayName()).isEqualTo(DEFAULT_OWNER.displayName)
assertThat(grantee.type()).isEqualTo(CANONICAL_USER)
}
}

Expand All @@ -108,7 +108,6 @@ internal class AclIT : S3TestBase() {
val userName = "John Doe"
val granteeId = "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2ef"
val granteeName = "Jane Doe"
"jane@doe.com"
s3Client.putObjectAcl {
it.bucket(bucketName)
it.key(sourceKey)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ internal class AwsChunkedEncodingIT : S3TestBase() {
RequestBody.fromFile(UPLOAD_FILE)
)

putObjectResponse.checksumSHA256().also {
assertThat(it).isNotBlank
assertThat(it).isEqualTo(expectedChecksum)
putObjectResponse.checksumSHA256().also { checksum ->
assertThat(checksum).isNotBlank()
assertThat(checksum).isEqualTo(expectedChecksum)
}

s3Client.getObject {
Expand All @@ -71,9 +71,9 @@ internal class AwsChunkedEncodingIT : S3TestBase() {
assertThat(it.response().eTag()).isEqualTo(expectedEtag)
assertThat(it.response().contentLength()).isEqualTo(UPLOAD_FILE_LENGTH)
assertThat(it.response().contentEncoding()).isNotEqualTo("aws-chunked")
it.response().checksumSHA256().also {
assertThat(it).isNotBlank
assertThat(it).isEqualTo(expectedChecksum)
it.response().checksumSHA256().also { checksum ->
assertThat(checksum).isNotBlank()
assertThat(checksum).isEqualTo(expectedChecksum)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,25 @@ internal class BucketIT : S3TestBase() {
val bucketName = bucketName(testInfo)
s3Client.createBucket { it.bucket(bucketName) }

val bucketCreated = s3Client.waiter().waitUntilBucketExists { it.bucket(bucketName) }
val bucketCreatedResponse = bucketCreated.matched().response().get()
val bucketCreatedResponse = s3Client
.waiter()
.waitUntilBucketExists { it.bucket(bucketName) }
.matched()
.response()
.get()
assertThat(bucketCreatedResponse).isNotNull

//does not throw exception if bucket exists.
s3Client.headBucket { it.bucket(bucketName) }

s3Client.deleteBucket { it.bucket(bucketName) }
val bucketDeleted = s3Client.waiter().waitUntilBucketNotExists { it.bucket(bucketName) }
bucketDeleted.matched().exception().get().also {
assertThat(it).isNotNull
assertThat(it).isInstanceOf(NoSuchBucketException::class.java)
}
val deletionException = s3Client
.waiter()
.waitUntilBucketNotExists { it.bucket(bucketName) }
.matched()
.exception()
.get()
assertThat(deletionException).isInstanceOf(NoSuchBucketException::class.java)
}

/**
Expand All @@ -82,23 +88,27 @@ internal class BucketIT : S3TestBase() {
val bucketName = bucketName(testInfo)
val createBucketResponse = s3Client.createBucket {
it.bucket(bucketName)
it.createBucketConfiguration {
it.locationConstraint("ap-southeast-5")
it.bucket {
it.dataRedundancy(DataRedundancy.SINGLE_AVAILABILITY_ZONE)
it.type(BucketType.DIRECTORY)
it.createBucketConfiguration { cfg ->
cfg.locationConstraint("ap-southeast-5")
cfg.bucket { b ->
b.dataRedundancy(DataRedundancy.SINGLE_AVAILABILITY_ZONE)
b.type(BucketType.DIRECTORY)
}
it.location {
it.name("SomeName")
it.type(LocationType.AVAILABILITY_ZONE)
cfg.location { loc ->
loc.name("SomeName")
loc.type(LocationType.AVAILABILITY_ZONE)
}
}
}
assertThat(createBucketResponse.sdkHttpResponse().statusCode()).isEqualTo(200)
assertThat(createBucketResponse.location()).isEqualTo("/$bucketName")

val bucketCreated = s3Client.waiter().waitUntilBucketExists { it.bucket(bucketName) }
val bucketCreatedResponse = bucketCreated.matched().response().get()
val bucketCreatedResponse = s3Client
.waiter()
.waitUntilBucketExists { it.bucket(bucketName) }
.matched()
.response()
.get()
assertThat(bucketCreatedResponse).isNotNull

//does not throw exception if bucket exists.
Expand Down Expand Up @@ -353,10 +363,13 @@ internal class BucketIT : S3TestBase() {
val bucketName = bucketName(testInfo)
s3Client.createBucket { it.bucket(bucketName) }

val bucketCreated = s3Client.waiter().waitUntilBucketExists { it.bucket(bucketName) }
bucketCreated.matched().response().get().also {
assertThat(it).isNotNull
}
val createdResponse = s3Client
.waiter()
.waitUntilBucketExists { it.bucket(bucketName) }
.matched()
.response()
.get()
assertThat(createdResponse).isNotNull

assertThatThrownBy {
s3Client.createBucket { it.bucket(bucketName) }
Expand Down Expand Up @@ -389,11 +402,13 @@ internal class BucketIT : S3TestBase() {
}

s3Client.deleteBucket { it.bucket(bucketName) }
val bucketDeleted = s3Client.waiter().waitUntilBucketNotExists { it.bucket(bucketName) }
bucketDeleted.matched().exception().get().also {
assertThat(it).isNotNull
assertThat(it).isInstanceOf(NoSuchBucketException::class.java)
}
val deletionException = s3Client
.waiter()
.waitUntilBucketNotExists { it.bucket(bucketName) }
.matched()
.exception()
.get()
assertThat(deletionException).isInstanceOf(NoSuchBucketException::class.java)

assertThatThrownBy {
s3Client.deleteBucket(DeleteBucketRequest.builder().bucket(bucketName).build())
Expand All @@ -412,8 +427,11 @@ internal class BucketIT : S3TestBase() {
val bucketName = bucketName(testInfo)
s3Client.createBucket { it.bucket(bucketName) }

val bucketCreated = s3Client.waiter().waitUntilBucketExists { it.bucket(bucketName) }
val bucketCreatedResponse = bucketCreated.matched().response()!!.get()
val bucketCreatedResponse = s3Client
.waiter()
.waitUntilBucketExists { it.bucket(bucketName) }
.matched()
.response()!!.get()
assertThat(bucketCreatedResponse).isNotNull

assertThatThrownBy {
Expand All @@ -433,10 +451,12 @@ internal class BucketIT : S3TestBase() {
val bucketName = bucketName(testInfo)
s3Client.createBucket { it.bucket(bucketName) }

val bucketCreated = s3Client.waiter().waitUntilBucketExists { it.bucket(bucketName) }
bucketCreated.matched().response()!!.get().also {
assertThat(it).isNotNull
}
val createdResponse = s3Client
.waiter()
.waitUntilBucketExists { it.bucket(bucketName) }
.matched()
.response()!!.get()
assertThat(createdResponse).isNotNull

val configuration = BucketLifecycleConfiguration
.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,18 @@ internal class ConcurrencyIT : S3TestBase() {
)
fun `concurrent bucket puts, gets and deletes are successful`(testInfo: TestInfo) {
val bucketName = givenBucket(testInfo)
val runners = mutableListOf<Runner>()
val runners = (1..100).map { Runner(bucketName, "test/key$it") }
val pool = Executors.newFixedThreadPool(100)
for (i in 1..100) {
runners.add(Runner(bucketName, "test/key$i"))
}
val futures = pool.invokeAll(runners)
assertThat(futures).hasSize(100).allSatisfy {
assertThat(it.get()).isTrue
assertThat(futures).hasSize(100).allSatisfy { future ->
assertThat(future.get()).isTrue
}
assertThat(DONE.get()).isEqualTo(100)
}

companion object {
val LATCH = CountDownLatch(100)
val DONE = AtomicInteger(0)
private val LATCH = CountDownLatch(100)
private val DONE = AtomicInteger(0)
}

inner class Runner(val bucketName: String, val key: String) : Callable<Boolean> {
Expand All @@ -65,8 +62,8 @@ internal class ConcurrencyIT : S3TestBase() {
it.bucket(bucketName)
it.key(key)
}, RequestBody.empty()
).also {
assertThat(it.eTag()).isNotBlank
).let { response ->
assertThat(response.eTag()).isNotBlank
}

s3Client.getObject {
Expand All @@ -79,8 +76,8 @@ internal class ConcurrencyIT : S3TestBase() {
s3Client.deleteObject {
it.bucket(bucketName)
it.key(key)
}.also {
assertThat(it.deleteMarker()).isTrue
}.let { response ->
assertThat(response.deleteMarker()).isTrue
}
DONE.incrementAndGet()
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import software.amazon.awssdk.services.s3.model.MetadataDirective
import software.amazon.awssdk.services.s3.model.S3Exception
import software.amazon.awssdk.services.s3.model.ServerSideEncryption
import software.amazon.awssdk.services.s3.model.StorageClass
import java.io.File
import java.time.Duration
import java.time.Instant
import java.time.temporal.ChronoUnit.SECONDS
Expand Down Expand Up @@ -58,8 +57,8 @@ internal class CopyObjectIT : S3TestBase() {
it.sourceKey(sourceKey)
it.destinationBucket(destinationBucketName)
it.destinationKey(destinationKey)
}.copyObjectResult().eTag().also {
assertThat(it).isEqualTo(putObjectResult.eTag())
}.copyObjectResult().eTag().let { eTag ->
assertThat(eTag).isEqualTo(putObjectResult.eTag())
}

s3Client.getObject {
Expand Down Expand Up @@ -91,8 +90,8 @@ internal class CopyObjectIT : S3TestBase() {
it.sourceKey(sourceKey)
it.destinationBucket(destinationBucketName)
it.destinationKey(destinationKey)
}.copyObjectResult().eTag().also {
assertThat(it).isEqualTo(putObjectResult.eTag())
}.copyObjectResult().eTag().let { eTag ->
assertThat(eTag).isEqualTo(putObjectResult.eTag())
}

s3Client.getObject {
Expand Down Expand Up @@ -120,8 +119,8 @@ internal class CopyObjectIT : S3TestBase() {
it.destinationBucket(destinationBucketName)
it.destinationKey(destinationKey)
it.copySourceIfMatch(matchingEtag)
}.copyObjectResult().eTag().also {
assertThat(it).isEqualTo(putObjectResult.eTag())
}.copyObjectResult().eTag().let { eTag ->
assertThat(eTag).isEqualTo(putObjectResult.eTag())
}

s3Client.getObject {
Expand Down Expand Up @@ -151,8 +150,8 @@ internal class CopyObjectIT : S3TestBase() {
it.destinationKey(destinationKey)
it.copySourceIfMatch(matchingEtag)
it.copySourceIfUnmodifiedSince(now)
}.copyObjectResult().eTag().also {
assertThat(it).isEqualTo(putObjectResult.eTag())
}.copyObjectResult().eTag().let { eTag ->
assertThat(eTag).isEqualTo(putObjectResult.eTag())
}

s3Client.getObject {
Expand Down Expand Up @@ -181,8 +180,8 @@ internal class CopyObjectIT : S3TestBase() {
it.destinationBucket(destinationBucketName)
it.destinationKey(destinationKey)
it.copySourceIfModifiedSince(now)
}.copyObjectResult().eTag().also {
assertThat(it).isEqualTo(putObjectResult.eTag())
}.copyObjectResult().eTag().let { eTag ->
assertThat(eTag).isEqualTo(putObjectResult.eTag())
}

s3Client.getObject {
Expand Down Expand Up @@ -238,8 +237,8 @@ internal class CopyObjectIT : S3TestBase() {
it.destinationBucket(destinationBucketName)
it.destinationKey(destinationKey)
it.copySourceIfUnmodifiedSince(now)
}.copyObjectResult().eTag().also {
assertThat(it).isEqualTo(putObjectResult.eTag())
}.copyObjectResult().eTag().let { eTag ->
assertThat(eTag).isEqualTo(putObjectResult.eTag())
}

s3Client.getObject {
Expand All @@ -266,8 +265,8 @@ internal class CopyObjectIT : S3TestBase() {
it.destinationBucket(destinationBucketName)
it.destinationKey(destinationKey)
it.copySourceIfNoneMatch(noneMatchingEtag)
}.copyObjectResult().eTag().also {
assertThat(it).isEqualTo(putObjectResult.eTag())
}.copyObjectResult().eTag().let { eTag ->
assertThat(eTag).isEqualTo(putObjectResult.eTag())
}

s3Client.getObject {
Expand Down
Loading