diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 8c8f71677..c9b2fffc0 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -13,12 +13,10 @@ name: "CodeQL"
on:
push:
- branches: [s3mock-v2, main]
+ branches: [s3mock-v2, s3mock-v3, s3mock-v4, main]
pull_request:
# The branches below must be a subset of the branches above
- branches: [s3mock-v2, main]
- schedule:
- - cron: '43 21 * * 6'
+ branches: [s3mock-v2, s3mock-v3, s3mock-v4, main]
# Declare default permissions as read only.
permissions: read-all
@@ -64,12 +62,12 @@ jobs:
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
- # Set up JDK 17, otherwise autobuild will fail below.
+ # Set up JDK 25, otherwise autobuild will fail below.
- name: Set up JDK
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
with:
- java-version: 21
- distribution: 'temurin'
+ java-version: 25
+ distribution: 'oracle'
cache: 'maven'
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
diff --git a/.github/workflows/maven-ci-and-prb.yml b/.github/workflows/maven-ci-and-prb.yml
index a2292866e..836054af7 100644
--- a/.github/workflows/maven-ci-and-prb.yml
+++ b/.github/workflows/maven-ci-and-prb.yml
@@ -20,9 +20,9 @@ name: Maven Build
on:
push:
- branches: [s3mock-v2, main]
+ branches: [s3mock-v2, s3mock-v3, s3mock-v4, main]
pull_request:
- branches: [s3mock-v2, main]
+ branches: [s3mock-v2, s3mock-v3, s3mock-v4, main]
# Declare default permissions as read only.
permissions: read-all
diff --git a/.github/workflows/maven-release.yml b/.github/workflows/maven-release.yml
index 8f0c3fd75..743a8f466 100644
--- a/.github/workflows/maven-release.yml
+++ b/.github/workflows/maven-release.yml
@@ -40,8 +40,9 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
with:
- java-version: 21
- distribution: 'temurin'
+ java-version: 25
+ distribution: 'oracle'
+ cache: 'maven'
# The release build pushes a Docker image to Docker Hub, so we need to log in
- name: Perform docker login
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9c8b44cb0..41cadc184 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -117,7 +117,7 @@ Whenever a 3rd party library is updated, S3Mock will update it's MINOR version.
# PLANNED - 6.x - RELEASE TBD
Version 6.x is JDK25 LTS bytecode compatible, with Docker integration.
-Probably released with Spring Boot 5.x, updating baselines etc. as Spring Boot 5.x requires.
+Will be released after Spring Boot 5.x, updating baselines etc. as Spring Boot 5.x requires.
Any JUnit / direct Java usage support will most likely be dropped and only supported on a best-effort basis.
(i.e., the modules will be deleted from the code base and not released anymore. It *may* be possible to
@@ -149,7 +149,13 @@ Version 5.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav
## 5.0.0
* Features and fixes
+ * Breaking change (file system): Remove "DisplayName" from Owner. (fixes #2738)
+ * AWS APIs stopped returning "DisplayName" in November 2025.
+ * This is unfortunately a breaking change for clients starting S3Mock on existing file systems.
* Get object with range now returns the same headers as non-range calls.
+ * Docker: Copy "s3mock.jar" to "/opt/", run with absolute path reference to avoid issues when working directory is changed. (fixes #2827)
+ * S3Mock supports ChecksumType.FULL_OBJECT for Multipart uploads (fixes #2843)
+ * Return 412 on if-none-match=true when making CompleteMultipartRequest (fixes #2790)
* Refactorings
* Use Jackson 3 annotations and mappers.
* AWS has deprecated SDK for Java v1 and will remove support EOY 2025.
@@ -159,14 +165,23 @@ Version 5.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav
* Remove legacy properties for S3Mock configuration.
* Move all controller-related code from "com.adobe.testing.s3mock" to "com.adobe.testing.s3mock.controller" package.
* Remove Apache libraries like "commons-compress", "commons-codec" or "commons-lang3" from dependencies. Kotlin and Java standard library provide similar functionality.
-* Version updates
- * Bump Spring Boot version to 4.0.0
+* Version updates (deliverable dependencies)
+ * Bump Spring Boot version to 4.0.1
* Bump Spring Framework version to 7.0.1
- * Bump java version from 17 to 25
- * Compile with Java 25, target Java 17
+ * Bump Java version partially from 17 to 25
+ * Compile with Java 25, target Java 17. [This follows Spring guidance](https://spring.io/blog/2025/11/13/spring-framework-7-0-general-availability)
* Docker container runs Java 25
* Bump TestContainers to 2.0.2
- * Bump Maven to 4.0.0
+ * Bump kotlin.version from 2.2.21 to 2.3.0
+ * Compile with Kotlin 2.3, target Kotlin 2.2. [This follows Spring guidance](https://spring.io/blog/2025/12/18/next-level-kotlin-support-in-spring-boot-4#kotlin-2-baseline)
+* Version updates (build dependencies)
+ * Bump Maven to 4.0.0-rc5 (TODO: update to 4.0.0)
+ * Bump org.apache.maven.plugins:maven-release-plugin from 3.3.0 to 3.3.1
+ * Bump com.puppycrawl.tools:checkstyle from 12.2.0 to 12.3.0
+ * Bump actions/upload-artifact from 5.0.0 to 6.0.0
+ * Bump github/codeql-action from 4.31.6 to 4.31.9
+ * Bump actions/setup-java from 5.0.0 to 5.1.0
+ * Bump step-security/harden-runner from 2.13.3 to 2.14.0
# DEPRECATED - 4.x
Version 4.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Java integration.
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 1ae3c5143..b935c7546 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -59,7 +59,7 @@ ENV JAVA_HOME=/opt/java-minimal
ENV PATH="$PATH:$JAVA_HOME/bin"
COPY --from=staging_area "$JAVA_HOME" "$JAVA_HOME"
-COPY ./target/s3mock-exec.jar s3mock.jar
+COPY ./target/s3mock-exec.jar /opt/s3mock.jar
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
@@ -69,4 +69,4 @@ ENV root=/s3mockroot
EXPOSE 9090 9191
# run the app on startup
-ENTRYPOINT ["java", "--illegal-access=warn", "-Djava.security.egd=file:/dev/./urandom", "-XX:+UseZGC", "-XX:+ZGenerational", "-jar", "s3mock.jar" ]
+ENTRYPOINT ["java", "--illegal-access=warn", "-Djava.security.egd=file:/dev/./urandom", "-XX:+UseZGC", "-XX:+ZGenerational", "-jar", "/opt/s3mock.jar" ]
diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/AclIT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/AclIT.kt
index d05ec86a7..530f89067 100644
--- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/AclIT.kt
+++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/AclIT.kt
@@ -63,7 +63,6 @@ internal class AclIT : S3TestBase() {
}.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)
}
@@ -86,7 +85,6 @@ internal class AclIT : S3TestBase() {
acl.owner().also { owner ->
assertThat(owner.id()).isEqualTo(DEFAULT_OWNER.id)
- assertThat(owner.displayName()).isEqualTo(DEFAULT_OWNER.displayName)
}
acl.grants().also {
@@ -102,7 +100,6 @@ internal class AclIT : S3TestBase() {
.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)
}
}
@@ -126,7 +123,6 @@ internal class AclIT : S3TestBase() {
it.accessControlPolicy {
it.owner {
it.id(userId)
- it.displayName(userName)
}
it
.grants(
@@ -150,7 +146,6 @@ internal class AclIT : S3TestBase() {
acl.owner().also {
assertThat(it).isNotNull
assertThat(it.id()).isEqualTo(userId)
- assertThat(it.displayName()).isEqualTo(userName)
}
assertThat(acl.grants()).hasSize(1)
diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/BucketIT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/BucketIT.kt
index 723370e39..9b87e896b 100644
--- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/BucketIT.kt
+++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/BucketIT.kt
@@ -167,7 +167,6 @@ internal class BucketIT : S3TestBase() {
}
assertThat(it.prefix()).isNull()
assertThat(it.continuationToken()).isNull()
- assertThat(it.owner().displayName()).isEqualTo("s3-mock-file-store")
assertThat(it.owner().id()).isEqualTo("79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be")
}
}
@@ -205,7 +204,6 @@ internal class BucketIT : S3TestBase() {
}
assertThat(it.prefix()).isEqualTo(bucketName)
assertThat(it.continuationToken()).isNull()
- assertThat(it.owner().displayName()).isEqualTo("s3-mock-file-store")
assertThat(it.owner().id()).isEqualTo("79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be")
}
}
@@ -245,7 +243,6 @@ internal class BucketIT : S3TestBase() {
}
assertThat(it.prefix()).isNull()
assertThat(it.continuationToken()).isNotNull
- assertThat(it.owner().displayName()).isEqualTo("s3-mock-file-store")
assertThat(it.owner().id()).isEqualTo("79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be")
}.continuationToken()
@@ -263,7 +260,6 @@ internal class BucketIT : S3TestBase() {
}
assertThat(it.prefix()).isNull()
assertThat(it.continuationToken()).isNull()
- assertThat(it.owner().displayName()).isEqualTo("s3-mock-file-store")
assertThat(it.owner().id()).isEqualTo("79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be")
}
}
diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/GetPutDeleteObjectIT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/GetPutDeleteObjectIT.kt
index db31228b5..9cf631f75 100644
--- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/GetPutDeleteObjectIT.kt
+++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/GetPutDeleteObjectIT.kt
@@ -536,7 +536,7 @@ internal class GetPutDeleteObjectIT : S3TestBase() {
)
}.also {
assertThat(it.eTag()).isEqualTo(eTag.trim('"'))
- // default storageClass is STANDARD, which is never returned from APIs except by GetObjectAttributes
+ // GetObjectAttributes returns the default storageClass "STANDARD", even though other APIs may not.
assertThat(it.storageClass()).isEqualTo(StorageClass.STANDARD)
assertThat(it.objectSize()).isEqualTo(UPLOAD_FILE_LENGTH)
assertThat(it.checksum().checksumSHA1()).isEqualTo(expectedChecksum)
diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectsIT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectsIT.kt
index 766e1e7c6..d4af7fbb9 100644
--- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectsIT.kt
+++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectsIT.kt
@@ -29,6 +29,7 @@ import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm
import software.amazon.awssdk.services.s3.model.CommonPrefix
import software.amazon.awssdk.services.s3.model.EncodingType
import software.amazon.awssdk.services.s3.model.NoSuchBucketException
+import software.amazon.awssdk.services.s3.model.ObjectStorageClass
import software.amazon.awssdk.services.s3.model.S3Object
import software.amazon.awssdk.utils.http.SdkHttpUtils
@@ -103,6 +104,14 @@ internal class ListObjectsIT : S3TestBase() {
Tuple(arrayListOf(ChecksumAlgorithm.SHA256)),
Tuple(arrayListOf(ChecksumAlgorithm.SHA256)),
)
+ // ListObjects returns the default storageClass "STANDARD", even though other APIs may not.
+ assertThat(it.contents())
+ .hasSize(2)
+ .extracting(S3Object::storageClass)
+ .containsOnly(
+ Tuple(ObjectStorageClass.STANDARD),
+ Tuple(ObjectStorageClass.STANDARD),
+ )
}
}
@@ -168,6 +177,8 @@ internal class ListObjectsIT : S3TestBase() {
listing.contents().also {
assertThat(it).hasSize(1)
assertThat(it[0].key()).isEqualTo(key)
+ // ListObjectsV2 returns the default storageClass "STANDARD", even though other APIs may not.
+ assertThat(it[0].storageClass()).isEqualTo(ObjectStorageClass.STANDARD)
}
}
}
diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/MultipartIT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/MultipartIT.kt
index 732c03a0c..7ba5cab42 100644
--- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/MultipartIT.kt
+++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/MultipartIT.kt
@@ -187,6 +187,174 @@ internal class MultipartIT : S3TestBase() {
}
}
+ @Test
+ @S3VerifiedSuccess(year = 2025)
+ fun testMultipartUpload_withChecksumType_COMPOSITE(testInfo: TestInfo) {
+ val bucketName = givenBucket(testInfo)
+ val initiateMultipartUploadResult =
+ s3Client
+ .createMultipartUpload {
+ it.bucket(bucketName)
+ it.key(UPLOAD_FILE_NAME)
+ it.checksumAlgorithm(ChecksumAlgorithm.CRC32)
+ it.checksumType(ChecksumType.COMPOSITE)
+ }
+ val uploadId = initiateMultipartUploadResult.uploadId()
+ val uploadPartResult =
+ s3Client.uploadPart(
+ {
+ it.bucket(initiateMultipartUploadResult.bucket())
+ it.key(initiateMultipartUploadResult.key())
+ it.uploadId(uploadId)
+ it.partNumber(1)
+ it.checksumAlgorithm(ChecksumAlgorithm.CRC32)
+ it.contentLength(UPLOAD_FILE_LENGTH)
+ },
+ RequestBody.fromFile(UPLOAD_FILE),
+ )
+
+ val checksum =
+ DigestUtil.checksumMultipart(
+ listOf(UPLOAD_FILE_PATH),
+ DefaultChecksumAlgorithm.CRC32,
+ )
+
+ s3Client
+ .completeMultipartUpload {
+ it.bucket(initiateMultipartUploadResult.bucket())
+ it.key(initiateMultipartUploadResult.key())
+ it.uploadId(initiateMultipartUploadResult.uploadId())
+ it.checksumType(ChecksumType.COMPOSITE)
+ it.multipartUpload {
+ it.parts({
+ it.eTag(uploadPartResult.eTag())
+ it.partNumber(1)
+ it.checksumCRC32(uploadPartResult.checksumCRC32())
+ })
+ }
+ }.also {
+ assertThat(it.checksumCRC32()).isEqualTo(checksum)
+ }
+
+ val etag = "\"${DigestUtil.hexDigestMultipart(listOf(UPLOAD_FILE_PATH))}\""
+ s3Client
+ .getObject {
+ it.bucket(initiateMultipartUploadResult.bucket())
+ it.key(initiateMultipartUploadResult.key())
+ it.checksumMode(ChecksumMode.ENABLED)
+ }.use {
+ assertThat(it.response().eTag()).isEqualTo(etag)
+ assertThat(it.response().checksumCRC32()).isEqualTo(checksum)
+ }
+ }
+
+ @Test
+ @S3VerifiedSuccess(year = 2025)
+ fun testMultipartUpload_withChecksumType_throwsOn_DIFFERENT(testInfo: TestInfo) {
+ val bucketName = givenBucket(testInfo)
+ val initiateMultipartUploadResult =
+ s3Client
+ .createMultipartUpload {
+ it.bucket(bucketName)
+ it.key(UPLOAD_FILE_NAME)
+ it.checksumAlgorithm(ChecksumAlgorithm.CRC32)
+ it.checksumType(ChecksumType.COMPOSITE)
+ }
+ val uploadId = initiateMultipartUploadResult.uploadId()
+ val uploadPartResult =
+ s3Client.uploadPart(
+ {
+ it.bucket(initiateMultipartUploadResult.bucket())
+ it.key(initiateMultipartUploadResult.key())
+ it.uploadId(uploadId)
+ it.partNumber(1)
+ it.checksumAlgorithm(ChecksumAlgorithm.CRC32)
+ it.contentLength(UPLOAD_FILE_LENGTH)
+ },
+ RequestBody.fromFile(UPLOAD_FILE),
+ )
+
+ assertThatThrownBy {
+ s3Client
+ .completeMultipartUpload {
+ it.bucket(initiateMultipartUploadResult.bucket())
+ it.key(initiateMultipartUploadResult.key())
+ it.uploadId(initiateMultipartUploadResult.uploadId())
+ it.checksumType(ChecksumType.FULL_OBJECT) // intentionally different from creteMultipartUpload value
+ it.multipartUpload {
+ it.parts({
+ it.eTag(uploadPartResult.eTag())
+ it.partNumber(1)
+ it.checksumCRC32(uploadPartResult.checksumCRC32())
+ })
+ }
+ }
+ }.isInstanceOf(AwsServiceException::class.java)
+ .hasMessageContaining("Service: S3, Status Code: 400")
+ }
+
+ @Test
+ @S3VerifiedSuccess(year = 2025)
+ fun testMultipartUpload_withChecksumType_FULL_OBJECT(testInfo: TestInfo) {
+ val bucketName = givenBucket(testInfo)
+ val initiateMultipartUploadResult =
+ s3Client
+ .createMultipartUpload {
+ it.bucket(bucketName)
+ it.key(UPLOAD_FILE_NAME)
+ it.checksumAlgorithm(ChecksumAlgorithm.CRC64_NVME)
+ it.checksumType(ChecksumType.FULL_OBJECT)
+ }
+ val uploadId = initiateMultipartUploadResult.uploadId()
+ val uploadPartResult =
+ s3Client.uploadPart(
+ {
+ it.bucket(initiateMultipartUploadResult.bucket())
+ it.key(initiateMultipartUploadResult.key())
+ it.uploadId(uploadId)
+ it.partNumber(1)
+ it.checksumAlgorithm(ChecksumAlgorithm.CRC64_NVME)
+ it.contentLength(UPLOAD_FILE_LENGTH)
+ },
+ RequestBody.fromFile(UPLOAD_FILE),
+ )
+
+ val checksum =
+ DigestUtil.checksumFor(
+ UPLOAD_FILE_PATH,
+ DefaultChecksumAlgorithm.CRC64NVME,
+ )
+
+ s3Client
+ .completeMultipartUpload {
+ it.bucket(initiateMultipartUploadResult.bucket())
+ it.key(initiateMultipartUploadResult.key())
+ it.uploadId(initiateMultipartUploadResult.uploadId())
+ it.checksumType(ChecksumType.FULL_OBJECT)
+ it.multipartUpload {
+ it.parts({
+ it.eTag(uploadPartResult.eTag())
+ it.partNumber(1)
+ it.checksumCRC64NVME(uploadPartResult.checksumCRC64NVME())
+ })
+ }
+ }.also {
+ assertThat(it.checksumCRC64NVME()).isEqualTo(checksum)
+ }
+
+ val etag = "\"${DigestUtil.hexDigestMultipart(listOf(UPLOAD_FILE_PATH))}\""
+
+ s3Client
+ .getObject {
+ it.bucket(initiateMultipartUploadResult.bucket())
+ it.key(initiateMultipartUploadResult.key())
+ it.checksumMode(ChecksumMode.ENABLED)
+ }.use {
+ assertThat(it.response().eTag()).isEqualTo(etag)
+ assertThat(it.response().checksumCRC64NVME()).isEqualTo(checksum)
+ }
+ }
+
/**
* Tests if a multipart upload with the last part being smaller than 5MB works.
*/
@@ -329,6 +497,7 @@ internal class MultipartIT : S3TestBase() {
it.bucket(initiateMultipartUploadResult.bucket())
it.key(initiateMultipartUploadResult.key())
it.uploadId(initiateMultipartUploadResult.uploadId())
+ it.checksumType(ChecksumType.COMPOSITE)
it.multipartUpload {
it.parts(
{
@@ -373,6 +542,7 @@ internal class MultipartIT : S3TestBase() {
it.bucket(initiateMultipartUploadResult.bucket())
it.key(initiateMultipartUploadResult.key())
it.uploadId(initiateMultipartUploadResult.uploadId())
+ it.checksumType(ChecksumType.COMPOSITE)
it.multipartUpload {
it.parts(
{
@@ -395,7 +565,6 @@ internal class MultipartIT : S3TestBase() {
assertThat(completeMultipartUpload.bucket()).isEqualTo(completeMultipartUpload1.bucket())
assertThat(completeMultipartUpload.key()).isEqualTo(completeMultipartUpload1.key())
assertThat(completeMultipartUpload.eTag()).isEqualTo(completeMultipartUpload1.eTag())
- assertThat(completeMultipartUpload.checksumCRC32()).isEqualTo(completeMultipartUpload1.checksumCRC32())
assertThat(completeMultipartUpload.checksumType()).isEqualTo(completeMultipartUpload1.checksumType())
}
@@ -1644,7 +1813,7 @@ internal class MultipartIT : S3TestBase() {
it.sourceKey(sourceKey)
it.sourceBucket(bucketName)
it.partNumber(1)
- it.copySourceRange("bytes=0-$UPLOAD_FILE_LENGTH")
+ it.copySourceRange("bytes=0-${UPLOAD_FILE_LENGTH - 1}")
it.copySourceIfModifiedSince(now)
}
}.isInstanceOf(S3Exception::class.java)
@@ -1913,6 +2082,54 @@ internal class MultipartIT : S3TestBase() {
.hasMessageContaining(INVALID_PART)
}
+ @Test
+ @S3VerifiedSuccess(year = 2025)
+ fun `CompleteMultipart fails with if-none-match=true`(testInfo: TestInfo) {
+ val (bucketName, response) = givenBucketAndObject(testInfo, UPLOAD_FILE_NAME)
+ val initiateMultipartUploadResult =
+ s3Client
+ .createMultipartUpload {
+ it.bucket(bucketName)
+ it.key(UPLOAD_FILE_NAME)
+ }
+ val uploadId = initiateMultipartUploadResult.uploadId()
+
+ assertThat(
+ s3Client
+ .listMultipartUploads {
+ it.bucket(bucketName)
+ }.uploads(),
+ ).isNotEmpty
+
+ val eTag =
+ s3Client
+ .uploadPart(
+ {
+ it.bucket(initiateMultipartUploadResult.bucket())
+ it.key(initiateMultipartUploadResult.key())
+ it.uploadId(uploadId)
+ it.partNumber(1)
+ },
+ RequestBody.fromFile(UPLOAD_FILE),
+ ).eTag()
+
+ assertThatThrownBy {
+ s3Client.completeMultipartUpload {
+ it.bucket(initiateMultipartUploadResult.bucket())
+ it.key(initiateMultipartUploadResult.key())
+ it.uploadId(uploadId)
+ it.ifNoneMatch("*")
+ it.multipartUpload {
+ it.parts({
+ it.eTag(eTag)
+ it.partNumber(1)
+ })
+ }
+ }
+ }.isInstanceOf(S3Exception::class.java)
+ .hasMessageContaining(PRECONDITION_FAILED.message)
+ }
+
private fun uploadPart(
bucketName: String,
key: String,
@@ -1939,7 +2156,7 @@ internal class MultipartIT : S3TestBase() {
private const val NO_SUCH_BUCKET = "The specified bucket does not exist"
private const val INVALID_PART_NUMBER = "Part number must be an integer between 1 and 10000, inclusive"
private const val INVALID_PART =
- "One or more of the specified parts could not be found. " +
- "The part might not have been uploaded, or the specified entity tag may not match the part's entity tag."
+ "One or more of the specified parts could not be found. " +
+ "The part may not have been uploaded, or the specified entity tag may not match the part's entity tag."
}
}
diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/VersionsIT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/VersionsIT.kt
index 3527d8111..bdfc25e0a 100644
--- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/VersionsIT.kt
+++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/VersionsIT.kt
@@ -27,6 +27,7 @@ import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.model.BucketVersioningStatus
import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm
import software.amazon.awssdk.services.s3.model.ObjectAttributes
+import software.amazon.awssdk.services.s3.model.ObjectVersionStorageClass
import software.amazon.awssdk.services.s3.model.S3Exception
import software.amazon.awssdk.services.s3.model.StorageClass
@@ -100,7 +101,7 @@ internal class VersionsIT : S3TestBase() {
)
}.also {
assertThat(it.versionId()).isEqualTo(versionId)
- // default storageClass is STANDARD, which is never returned from APIs
+ // GetObjectAttributes returns the default storageClass "STANDARD", even though other APIs may not.
assertThat(it.storageClass()).isEqualTo(StorageClass.STANDARD)
assertThat(it.objectSize()).isEqualTo(UPLOAD_FILE_LENGTH)
assertThat(it.checksum().checksumSHA1()).isEqualTo(expectedChecksum)
@@ -260,6 +261,8 @@ internal class VersionsIT : S3TestBase() {
assertThat(listObjectVersions.hasVersions()).isTrue
assertThat(listObjectVersions.versions()[0].key()).isEqualTo(UPLOAD_FILE_NAME)
assertThat(listObjectVersions.versions()[0].versionId()).isEqualTo(versionId)
+ // ListObjectVersions returns the default storageClass "STANDARD", even though other APIs may not.
+ assertThat(listObjectVersions.versions()[0].storageClass()).isEqualTo(ObjectVersionStorageClass.STANDARD)
assertThatThrownBy {
s3Client.getObject {
diff --git a/pom.xml b/pom.xml
index 80b6bb541..450e994f7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -95,7 +95,6 @@
s3mock-buildx
0.48.0
adobe/s3mock
- 1.0.0
5.7.2
4.13.2
@@ -131,6 +130,7 @@
0.0.14
3.5.0
26.0.2-1
+ 2.1.0
@@ -158,31 +158,6 @@
s3mock-testsupport-common
${project.version}
-
- junit
- junit
- ${junit.version}
-
-
- org.jetbrains.kotlin
- kotlin-stdlib
- ${kotlin.version}
-
-
- org.jetbrains.kotlin
- kotlin-reflect
- ${kotlin.version}
-
-
- org.jetbrains.kotlin
- kotlin-test
- ${kotlin.version}
-
-
- org.jetbrains.kotlin
- kotlin-test-junit
- ${kotlin.version}
-
org.mockito.kotlin
mockito-kotlin
@@ -226,53 +201,10 @@
software.amazon.awssdk
- aws-query-protocol
- ${aws-v2.version}
-
-
- software.amazon.awssdk
- aws-xml-protocol
- ${aws-v2.version}
-
-
- software.amazon.awssdk
- s3
- ${aws-v2.version}
-
-
- software.amazon.awssdk
- url-connection-client
- ${aws-v2.version}
-
-
- software.amazon.awssdk
- aws-crt-client
- ${aws-v2.version}
-
-
- software.amazon.awssdk
- regions
- ${aws-v2.version}
-
-
- software.amazon.awssdk
- utils
- ${aws-v2.version}
-
-
- software.amazon.awssdk
- auth
- ${aws-v2.version}
-
-
- software.amazon.awssdk
- checksums
- ${aws-v2.version}
-
-
- software.amazon.awssdk
- s3-transfer-manager
+ bom
${aws-v2.version}
+ pom
+ import
aws.sdk.kotlin
@@ -294,11 +226,6 @@
httpmime
${httpmime.version}
-
- org.jspecify
- jspecify
- ${jspecify.version}
-
org.jetbrains
@@ -556,6 +483,25 @@
maven-jar-plugin
${maven-jar-plugin.version}
+
+ org.jetbrains.dokka
+ dokka-maven-plugin
+ ${dokka-maven-plugin.version}
+
+
+ prepare-package
+
+ javadocJar
+
+
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/main/java
+
+
+
maven-javadoc-plugin
${maven-javadoc-plugin.version}
@@ -713,7 +659,8 @@
- maven-javadoc-plugin
+ org.jetbrains.dokka
+ dokka-maven-plugin
maven-source-plugin
diff --git a/server/pom.xml b/server/pom.xml
index 92a0f2cab..f77b4351f 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -78,10 +78,6 @@
software.amazon.awssdk
aws-crt-client
-
- org.jspecify
- jspecify
-
org.jetbrains.kotlin
kotlin-stdlib
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/S3Exception.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/S3Exception.kt
index 8ce571fc4..4a6c051c5 100644
--- a/server/src/main/kotlin/com/adobe/testing/s3mock/S3Exception.kt
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/S3Exception.kt
@@ -20,12 +20,12 @@ import org.springframework.http.HttpStatus
/**
* [RuntimeException] to communicate general S3 errors.
* These are handled by ControllerConfiguration.S3MockExceptionHandler,
- * mapped to [ErrorResponse] and serialized.
+ * mapped to [com.adobe.testing.s3mock.dto.ErrorResponse] and serialized.
* [API Reference](https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html)
*/
class S3Exception
/**
- * Creates a new S3Exception to be mapped as an [ErrorResponse].
+ * Creates a new S3Exception to be mapped as an [com.adobe.testing.s3mock.dto.ErrorResponse].
*
* @param status The Error Status.
* @param code The Error Code.
@@ -43,7 +43,7 @@ class S3Exception
)
val INVALID_PART: S3Exception = S3Exception(
HttpStatus.BAD_REQUEST.value(), "InvalidPart",
- "One or more of the specified parts could not be found. The part might not have been "
+ "One or more of the specified parts could not be found. The part may not have been "
+ "uploaded, or the specified entity tag may not match the part's entity tag."
)
val INVALID_PART_ORDER: S3Exception = S3Exception(
@@ -67,6 +67,14 @@ class S3Exception
)
}
+ fun completeRequestWrongChecksumMode(checksumMode: String): S3Exception {
+ return S3Exception(
+ HttpStatus.BAD_REQUEST.value(), BAD_REQUEST_CODE,
+ ("The upload was created using the $checksumMode checksum mode. " +
+ "The complete request must use the same checksum mode.")
+ )
+ }
+
val NO_SUCH_UPLOAD_MULTIPART: S3Exception = S3Exception(
HttpStatus.NOT_FOUND.value(), "NoSuchUpload",
"The specified multipart upload does not exist. The upload ID might be invalid, or the "
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/controller/ChecksumModeHeaderConverter.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/controller/ChecksumModeHeaderConverter.kt
index 35c8a3d32..81101e8f2 100644
--- a/server/src/main/kotlin/com/adobe/testing/s3mock/controller/ChecksumModeHeaderConverter.kt
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/controller/ChecksumModeHeaderConverter.kt
@@ -19,7 +19,7 @@ import com.adobe.testing.s3mock.dto.ChecksumMode
import org.springframework.core.convert.converter.Converter
/**
- * Converts values of the [AwsHttpHeaders.X_AMZ_CHECKSUM_MODE] which is sent by the Amazon
+ * Converts values of the [com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_CHECKSUM_MODE] which is sent by the Amazon
* client.
* Example: x-amz-checksum-mode: ENABLED
* [API Reference](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html)
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/controller/MultipartController.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/controller/MultipartController.kt
index 64ca0e973..f6e60e2ce 100644
--- a/server/src/main/kotlin/com/adobe/testing/s3mock/controller/MultipartController.kt
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/controller/MultipartController.kt
@@ -23,6 +23,7 @@ import com.adobe.testing.s3mock.dto.CompleteMultipartUploadResult
import com.adobe.testing.s3mock.dto.CopyPartResult
import com.adobe.testing.s3mock.dto.CopySource
import com.adobe.testing.s3mock.dto.InitiateMultipartUploadResult
+import com.adobe.testing.s3mock.dto.Initiator
import com.adobe.testing.s3mock.dto.ListMultipartUploadsResult
import com.adobe.testing.s3mock.dto.ListPartsResult
import com.adobe.testing.s3mock.dto.ObjectKey
@@ -60,6 +61,7 @@ import com.adobe.testing.s3mock.util.HeaderUtil.checksumAlgorithmFromHeader
import com.adobe.testing.s3mock.util.HeaderUtil.checksumAlgorithmFromSdk
import com.adobe.testing.s3mock.util.HeaderUtil.checksumFrom
import com.adobe.testing.s3mock.util.HeaderUtil.checksumHeaderFrom
+import com.adobe.testing.s3mock.util.HeaderUtil.checksumTypeFrom
import com.adobe.testing.s3mock.util.HeaderUtil.encryptionHeadersFrom
import com.adobe.testing.s3mock.util.HeaderUtil.storeHeadersFrom
import com.adobe.testing.s3mock.util.HeaderUtil.userMetadataFrom
@@ -381,7 +383,7 @@ class MultipartController(
contentType,
storeHeadersFrom(httpHeaders),
Owner.DEFAULT_OWNER,
- Owner.DEFAULT_OWNER,
+ Initiator.DEFAULT_INITIATOR,
userMetadataFrom(httpHeaders),
encryptionHeaders,
tags,
@@ -433,7 +435,7 @@ class MultipartController(
multipartService.verifyMultipartParts(bucketName, objectName, uploadId, upload.parts)
}
val s3ObjectMetadata = objectService.getObject(bucketName, key.key, null)
- objectService.verifyObjectMatching(match, noneMatch, null, null, s3ObjectMetadata)
+ objectService.verifyObjectMatching(bucketName, key.key, match, noneMatch)
val locationWithEncodedKey = request
.requestURL
.toString()
@@ -449,6 +451,7 @@ class MultipartController(
encryptionHeadersFrom(httpHeaders),
locationWithEncodedKey,
checksumFrom(httpHeaders),
+ checksumTypeFrom(httpHeaders),
checksumAlgorithmFromHeader(httpHeaders)
)!!
} else {
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/dto/Bucket.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/dto/Bucket.kt
index b4a9c1fc6..c9eb80a4d 100644
--- a/server/src/main/kotlin/com/adobe/testing/s3mock/dto/Bucket.kt
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/dto/Bucket.kt
@@ -31,7 +31,7 @@ data class Bucket(
@param:JsonProperty("CreationDate", namespace = "http://s3.amazonaws.com/doc/2006-03-01/")
val creationDate: String?,
@param:JsonProperty("Name", namespace = "http://s3.amazonaws.com/doc/2006-03-01/")
- val name: String,
+ val name: String?,
@JsonIgnore
val path: Path?
) {
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/dto/ChecksumType.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/dto/ChecksumType.kt
index 5cd6c1136..900c7a100 100644
--- a/server/src/main/kotlin/com/adobe/testing/s3mock/dto/ChecksumType.kt
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/dto/ChecksumType.kt
@@ -33,10 +33,12 @@ enum class ChecksumType @JsonCreator constructor(private val value: String) {
}
companion object {
- fun fromString(value: String): ChecksumType? {
+ fun fromString(value: String?): ChecksumType? {
return when (value) {
"composite" -> COMPOSITE
+ "COMPOSITE" -> COMPOSITE
"full_object" -> FULL_OBJECT
+ "FULL_OBJECT" -> FULL_OBJECT
else -> null
}
}
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/dto/Initiator.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/dto/Initiator.kt
new file mode 100644
index 000000000..15680dfad
--- /dev/null
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/dto/Initiator.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017-2025 Adobe.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.adobe.testing.s3mock.dto
+
+import com.adobe.testing.S3Verified
+import com.fasterxml.jackson.annotation.JsonProperty
+
+/**
+ * [API Reference](https://docs.aws.amazon.com/AmazonS3/latest/API/API_Initiator.html).
+ */
+@S3Verified(year = 2025)
+data class Initiator(
+ @param:JsonProperty("DisplayName", namespace = "http://s3.amazonaws.com/doc/2006-03-01/")
+ val displayName: String?,
+ @param:JsonProperty("ID", namespace = "http://s3.amazonaws.com/doc/2006-03-01/")
+ val id: String?
+) {
+ companion object {
+ val DEFAULT_INITIATOR: Initiator =
+ Initiator("s3-mock-file-store", "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be")
+ }
+}
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/dto/ListPartsResult.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/dto/ListPartsResult.kt
index bf83b5b20..73a45fd42 100644
--- a/server/src/main/kotlin/com/adobe/testing/s3mock/dto/ListPartsResult.kt
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/dto/ListPartsResult.kt
@@ -34,7 +34,7 @@ data class ListPartsResult(
@param:JsonProperty("ChecksumType", namespace = "http://s3.amazonaws.com/doc/2006-03-01/")
val checksumType: ChecksumType?,
@param:JsonProperty("Initiator", namespace = "http://s3.amazonaws.com/doc/2006-03-01/")
- val initiator: Owner?,
+ val initiator: Initiator?,
@param:JsonProperty("IsTruncated", namespace = "http://s3.amazonaws.com/doc/2006-03-01/")
val isTruncated: Boolean,
@param:JsonProperty("Key", namespace = "http://s3.amazonaws.com/doc/2006-03-01/")
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/dto/MultipartUpload.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/dto/MultipartUpload.kt
index 0539ce6e8..657bac141 100644
--- a/server/src/main/kotlin/com/adobe/testing/s3mock/dto/MultipartUpload.kt
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/dto/MultipartUpload.kt
@@ -34,7 +34,7 @@ data class MultipartUpload(
@param:JsonProperty("Initiated", namespace = "http://s3.amazonaws.com/doc/2006-03-01/")
val initiated: Date?,
@param:JsonProperty("Initiator", namespace = "http://s3.amazonaws.com/doc/2006-03-01/")
- val initiator: Owner?,
+ val initiator: Initiator?,
@param:JsonProperty("Key", namespace = "http://s3.amazonaws.com/doc/2006-03-01/")
val key: String,
@param:JsonProperty("Owner", namespace = "http://s3.amazonaws.com/doc/2006-03-01/")
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/dto/Owner.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/dto/Owner.kt
index f561d4479..536fb7356 100644
--- a/server/src/main/kotlin/com/adobe/testing/s3mock/dto/Owner.kt
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/dto/Owner.kt
@@ -24,9 +24,6 @@ import com.fasterxml.jackson.annotation.JsonProperty
*/
@S3Verified(year = 2025)
data class Owner(
- @Deprecated("AWS deprecated this field in 2025-05")
- @param:JsonProperty("DisplayName", namespace = "http://s3.amazonaws.com/doc/2006-03-01/")
- val displayName: String? = null,
@param:JsonProperty("ID", namespace = "http://s3.amazonaws.com/doc/2006-03-01/")
val id: String?
) {
@@ -34,9 +31,7 @@ data class Owner(
/**
* Default owner in S3Mock until support for ownership is implemented.
*/
- val DEFAULT_OWNER: Owner =
- Owner("s3-mock-file-store", "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be")
- val DEFAULT_OWNER_BUCKET: Owner =
- Owner("s3-mock-file-store-bucket", "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2df")
+ val DEFAULT_OWNER: Owner = Owner("79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be")
+ val DEFAULT_OWNER_BUCKET: Owner = Owner("79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2df")
}
}
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/service/BucketService.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/service/BucketService.kt
index 64557d176..25b09aa7c 100644
--- a/server/src/main/kotlin/com/adobe/testing/s3mock/service/BucketService.kt
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/service/BucketService.kt
@@ -43,6 +43,7 @@ import com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_BUCKET_LOCATION_TYPE
import software.amazon.awssdk.utils.http.SdkHttpUtils.urlEncodeIgnoreSlashes
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
+import kotlin.let
open class BucketService(
private val bucketStore: BucketStore,
@@ -92,7 +93,9 @@ open class BucketService(
if (buckets.size > maxBuckets) {
nextContinuationToken = UUID.randomUUID().toString()
buckets = buckets.subList(0, maxBuckets)
- listBucketsPagingStateCache[nextContinuationToken] = buckets[maxBuckets - 1].name
+ buckets[maxBuckets - 1].name?.let {
+ listBucketsPagingStateCache[nextContinuationToken] = it
+ }
}
return ListAllMyBucketsResult(Owner.DEFAULT_OWNER, Buckets(buckets), prefix, nextContinuationToken)
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/service/MultipartService.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/service/MultipartService.kt
index 80bb22f57..8d950d0a7 100644
--- a/server/src/main/kotlin/com/adobe/testing/s3mock/service/MultipartService.kt
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/service/MultipartService.kt
@@ -22,6 +22,7 @@ import com.adobe.testing.s3mock.dto.CompleteMultipartUploadResult
import com.adobe.testing.s3mock.dto.CompletedPart
import com.adobe.testing.s3mock.dto.CopyPartResult
import com.adobe.testing.s3mock.dto.InitiateMultipartUploadResult
+import com.adobe.testing.s3mock.dto.Initiator
import com.adobe.testing.s3mock.dto.ListMultipartUploadsResult
import com.adobe.testing.s3mock.dto.ListPartsResult
import com.adobe.testing.s3mock.dto.MultipartUpload
@@ -161,6 +162,7 @@ open class MultipartService(private val bucketStore: BucketStore, private val mu
encryptionHeaders: Map,
location: String,
checksum: String?,
+ checksumType: ChecksumType?,
checksumAlgorithm: ChecksumAlgorithm?
): CompleteMultipartUploadResult? {
val bucketMetadata = bucketStore.getBucketMetadata(bucketName)
@@ -177,6 +179,7 @@ open class MultipartService(private val bucketStore: BucketStore, private val mu
multipartUploadInfo,
location,
checksum,
+ checksumType,
checksumAlgorithm
)
}
@@ -187,7 +190,7 @@ open class MultipartService(private val bucketStore: BucketStore, private val mu
contentType: String?,
storeHeaders: Map,
owner: Owner,
- initiator: Owner,
+ initiator: Initiator,
userMetadata: Map,
encryptionHeaders: Map,
tags: List?,
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/service/ServiceBase.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/service/ServiceBase.kt
index d2bfd4842..2517b404d 100644
--- a/server/src/main/kotlin/com/adobe/testing/s3mock/service/ServiceBase.kt
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/service/ServiceBase.kt
@@ -93,15 +93,12 @@ abstract class ServiceBase {
fun filterBy(
contents: List,
- selector: (T) -> String,
+ selector: (T) -> String?,
compareTo: String?
- ): List {
- return if (!compareTo.isNullOrEmpty()) {
- contents.filter { selector(it) > compareTo }
- } else {
- contents
- }
- }
+ ): List =
+ compareTo?.let { threshold ->
+ contents.filter { selector(it)?.let { candidate -> candidate > threshold } == true }
+ } ?: contents
fun filterBy(
contents: List,
@@ -117,11 +114,11 @@ abstract class ServiceBase {
fun filterBy(
contents: List,
- selector: (T) -> String,
+ selector: (T) -> String?,
prefixes: List?
): List {
return if (!prefixes.isNullOrEmpty()) {
- contents.filter { content -> prefixes.none { prefix -> selector(content).startsWith(prefix) } }
+ contents.filter { content -> prefixes.none { prefix -> selector(content)?.startsWith(prefix) ?: false } }
} else {
contents
}
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/store/MultipartStore.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/store/MultipartStore.kt
index a359a9599..fba238fdd 100644
--- a/server/src/main/kotlin/com/adobe/testing/s3mock/store/MultipartStore.kt
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/store/MultipartStore.kt
@@ -21,6 +21,7 @@ import com.adobe.testing.s3mock.dto.ChecksumAlgorithm
import com.adobe.testing.s3mock.dto.ChecksumType
import com.adobe.testing.s3mock.dto.CompleteMultipartUploadResult
import com.adobe.testing.s3mock.dto.CompletedPart
+import com.adobe.testing.s3mock.dto.Initiator
import com.adobe.testing.s3mock.dto.MultipartUpload
import com.adobe.testing.s3mock.dto.Owner
import com.adobe.testing.s3mock.dto.Part
@@ -64,7 +65,7 @@ open class MultipartStore(private val objectStore: ObjectStore, private val obje
contentType: String?,
storeHeaders: Map,
owner: Owner,
- initiator: Owner,
+ initiator: Initiator,
userMetadata: Map,
encryptionHeaders: Map,
tags: List?,
@@ -189,6 +190,7 @@ open class MultipartStore(private val objectStore: ObjectStore, private val obje
uploadInfo: MultipartUploadInfo?,
location: String,
checksum: String?,
+ checksumType: ChecksumType?,
checksumAlgorithm: ChecksumAlgorithm?
): CompleteMultipartUploadResult {
requireNotNull(uploadInfo) { "Unknown upload $uploadId" }
@@ -201,42 +203,42 @@ open class MultipartStore(private val objectStore: ObjectStore, private val obje
toInputStream(partsPaths).use { input ->
tempFile.outputStream().use { os ->
input.transferTo(os)
- val checksumFor = validateChecksums(uploadInfo, parts, partsPaths, checksum, checksumAlgorithm)
- val etag = DigestUtil.hexDigestMultipart(partsPaths)
- val s3ObjectMetadata = objectStore.storeS3ObjectMetadata(
- bucket,
- id,
- key,
- uploadInfo.contentType,
- uploadInfo.storeHeaders,
- tempFile,
- uploadInfo.userMetadata,
- encryptionHeaders,
- etag,
- uploadInfo.tags,
- uploadInfo.checksumAlgorithm,
- checksumFor,
- uploadInfo.upload.owner,
- uploadInfo.storageClass,
- ChecksumType.COMPOSITE
- )
- // delete parts and update MultipartInfo
- partsPaths.forEach { runCatching { it.toFile().deleteRecursively() } }
- val completedUploadInfo = uploadInfo.complete()
- writeMetafile(bucket, completedUploadInfo)
- return CompleteMultipartUploadResult.from(
- location,
- completedUploadInfo.bucket,
- key,
- etag,
- completedUploadInfo,
- checksumFor,
- s3ObjectMetadata.checksumType,
- checksumAlgorithm,
- s3ObjectMetadata.versionId
- )
}
}
+ val checksumFor = validateChecksums(uploadInfo, tempFile, parts, partsPaths, checksum, checksumType, checksumAlgorithm)
+ val etag = DigestUtil.hexDigestMultipart(partsPaths)
+ val s3ObjectMetadata = objectStore.storeS3ObjectMetadata(
+ bucket,
+ id,
+ key,
+ uploadInfo.contentType,
+ uploadInfo.storeHeaders,
+ tempFile,
+ uploadInfo.userMetadata,
+ encryptionHeaders,
+ etag,
+ uploadInfo.tags,
+ uploadInfo.checksumAlgorithm,
+ checksumFor,
+ uploadInfo.upload.owner,
+ uploadInfo.storageClass,
+ checksumType
+ )
+ // delete parts and update MultipartInfo
+ partsPaths.forEach { runCatching { it.toFile().deleteRecursively() } }
+ val completedUploadInfo = uploadInfo.complete()
+ writeMetafile(bucket, completedUploadInfo)
+ return CompleteMultipartUploadResult.from(
+ location,
+ completedUploadInfo.bucket,
+ key,
+ etag,
+ completedUploadInfo,
+ checksumFor,
+ s3ObjectMetadata.checksumType,
+ checksumAlgorithm,
+ s3ObjectMetadata.versionId
+ )
} catch (e: IOException) {
throw IllegalStateException("Error finishing multipart upload bucket=$bucket, key=$key, id=$id, uploadId=$uploadId", e)
} finally {
@@ -421,14 +423,24 @@ open class MultipartStore(private val objectStore: ObjectStore, private val obje
private fun validateChecksums(
uploadInfo: MultipartUploadInfo,
+ tempFile: Path,
completedParts: List,
partsPaths: List,
checksum: String?,
+ checksumType: ChecksumType?,
checksumAlgorithm: ChecksumAlgorithm?
): String? {
val checksumToValidate = checksum ?: uploadInfo.checksum
val checksumAlgorithmToValidate = checksumAlgorithm ?: uploadInfo.checksumAlgorithm
- val checksumFor = checksumFor(partsPaths, uploadInfo)
+ if(checksumType != null && uploadInfo.checksumType != null && checksumType != uploadInfo.checksumType) {
+ throw S3Exception.completeRequestWrongChecksumMode(uploadInfo.checksumType.name)
+ }
+ val checksumFor = if (uploadInfo.checksumType == ChecksumType.COMPOSITE) {
+ checksumFor(partsPaths, uploadInfo)
+ } else {
+ checksumFor(tempFile, uploadInfo)
+ }
+
if (checksumAlgorithmToValidate != null) {
completedParts.forEach { part ->
if (part.checksum(checksumAlgorithmToValidate) == null) {
@@ -451,6 +463,11 @@ open class MultipartStore(private val objectStore: ObjectStore, private val obje
DigestUtil.checksumMultipart(paths, algo.toChecksumAlgorithm())
}
+ private fun checksumFor(path: Path, uploadInfo: MultipartUploadInfo): String? =
+ uploadInfo.checksumAlgorithm?.let { algo ->
+ DigestUtil.checksumFor(path, algo.toChecksumAlgorithm())
+ }
+
companion object {
private val LOG: Logger = LoggerFactory.getLogger(MultipartStore::class.java)
private const val PART_SUFFIX = ".part"
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/store/ObjectStore.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/store/ObjectStore.kt
index e9670a57e..f465313a1 100644
--- a/server/src/main/kotlin/com/adobe/testing/s3mock/store/ObjectStore.kt
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/store/ObjectStore.kt
@@ -121,7 +121,7 @@ open class ObjectStore(
private fun privateCannedAcl(owner: Owner): AccessControlPolicy {
val grant = Grant(
- CanonicalUser(owner.displayName, owner.id),
+ CanonicalUser(null, owner.id),
Grant.Permission.FULL_CONTROL
)
return AccessControlPolicy(listOf(grant), owner)
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/store/S3ObjectMetadata.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/store/S3ObjectMetadata.kt
index 7b0c69121..7ee39f74f 100644
--- a/server/src/main/kotlin/com/adobe/testing/s3mock/store/S3ObjectMetadata.kt
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/store/S3ObjectMetadata.kt
@@ -53,7 +53,7 @@ data class S3ObjectMetadata(
val policy: AccessControlPolicy?,
val versionId: String?,
val deleteMarker: Boolean = false,
- val checksumType: ChecksumType? = ChecksumType.FULL_OBJECT
+ val checksumType: ChecksumType?
) {
companion object {
fun deleteMarker(metadata: S3ObjectMetadata, versionId: String?): S3ObjectMetadata =
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/util/CannedAclUtil.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/util/CannedAclUtil.kt
index b1acd7366..49d99d2bc 100644
--- a/server/src/main/kotlin/com/adobe/testing/s3mock/util/CannedAclUtil.kt
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/util/CannedAclUtil.kt
@@ -42,7 +42,7 @@ object CannedAclUtil {
}
private val defaultOwner = Owner.DEFAULT_OWNER
- private val defaultOwnerUser = CanonicalUser(defaultOwner.displayName, defaultOwner.id)
+ private val defaultOwnerUser = CanonicalUser("s3-mock-file-store", defaultOwner.id)
private fun policyWithOwner(vararg additionalGrants: Grant): AccessControlPolicy =
AccessControlPolicy(
@@ -54,7 +54,7 @@ object CannedAclUtil {
policyWithOwner(
Grant(
CanonicalUser(
- Owner.DEFAULT_OWNER_BUCKET.displayName,
+ "s3-mock-file-store",
Owner.DEFAULT_OWNER_BUCKET.id
),
Grant.Permission.READ
@@ -65,7 +65,7 @@ object CannedAclUtil {
policyWithOwner(
Grant(
CanonicalUser(
- Owner.DEFAULT_OWNER_BUCKET.displayName,
+ "s3-mock-file-store",
Owner.DEFAULT_OWNER_BUCKET.id
),
Grant.Permission.READ
diff --git a/server/src/main/kotlin/com/adobe/testing/s3mock/util/HeaderUtil.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/util/HeaderUtil.kt
index 2f206e2d8..c7e0ef72c 100644
--- a/server/src/main/kotlin/com/adobe/testing/s3mock/util/HeaderUtil.kt
+++ b/server/src/main/kotlin/com/adobe/testing/s3mock/util/HeaderUtil.kt
@@ -17,6 +17,7 @@
package com.adobe.testing.s3mock.util
import com.adobe.testing.s3mock.dto.ChecksumAlgorithm
+import com.adobe.testing.s3mock.dto.ChecksumType
import com.adobe.testing.s3mock.dto.CopySource
import com.adobe.testing.s3mock.dto.StorageClass
import com.adobe.testing.s3mock.store.S3ObjectMetadata
@@ -27,6 +28,7 @@ import com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_CHECKSUM_CRC32C
import com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_CHECKSUM_CRC64NVME
import com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_CHECKSUM_SHA1
import com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_CHECKSUM_SHA256
+import com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_CHECKSUM_TYPE
import com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_CONTENT_SHA256
import com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_COPY_SOURCE_VERSION_ID
import com.adobe.testing.s3mock.util.AwsHttpHeaders.X_AMZ_SDK_CHECKSUM_ALGORITHM
@@ -231,6 +233,13 @@ object HeaderUtil {
else null
}
+ @JvmStatic
+ fun checksumTypeFrom(headers: HttpHeaders): ChecksumType? {
+ return if (headers.containsHeader(X_AMZ_CHECKSUM_TYPE))
+ ChecksumType.fromString(headers.getFirst(X_AMZ_CHECKSUM_TYPE))
+ else null
+ }
+
@JvmStatic
fun checksumFrom(headers: HttpHeaders): String? {
return when {
diff --git a/server/src/main/resources/application.properties b/server/src/main/resources/application.properties
index 9a429e10e..3d88ecb37 100644
--- a/server/src/main/resources/application.properties
+++ b/server/src/main/resources/application.properties
@@ -25,7 +25,12 @@ spring.jmx.enabled=false
com.adobe.testing.s3mock.store.region=us-east-1
# allow S3Mock to consume larger payloads
-server.tomcat.max-swallow-size=10MB
+com.adobe.testing.s3mock.max-payload-size=10MB
+server.tomcat.max-part-count=-1
+server.tomcat.max-swallow-size=${com.adobe.testing.s3mock.max-payload-size}
+server.tomcat.max-http-form-post-size=${com.adobe.testing.s3mock.max-payload-size}
+spring.servlet.multipart.max-file-size=${com.adobe.testing.s3mock.max-payload-size}
+spring.servlet.multipart.max-request-size=${com.adobe.testing.s3mock.max-payload-size}
management.endpoints.web.discovery.enabled=false
diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/controller/BaseControllerTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/BaseControllerTest.kt
index d15308df8..65149b15f 100644
--- a/server/src/test/kotlin/com/adobe/testing/s3mock/controller/BaseControllerTest.kt
+++ b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/BaseControllerTest.kt
@@ -140,7 +140,7 @@ internal abstract class BaseControllerTest {
put(AwsHttpHeaders.X_AMZ_SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID, encryptionKey)
}
}
- val TEST_OWNER = Owner("s3-mock-file-store", "123")
+ val TEST_OWNER = Owner("123")
val TEST_BUCKETMETADATA = bucketMetadata()
const val UPLOAD_FILE_NAME = "src/test/resources/sampleFile.txt"
diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/controller/BucketControllerTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/BucketControllerTest.kt
index ca92c23b4..6e7e23da4 100644
--- a/server/src/test/kotlin/com/adobe/testing/s3mock/controller/BucketControllerTest.kt
+++ b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/BucketControllerTest.kt
@@ -80,7 +80,10 @@ import java.nio.file.Path
import java.time.Instant
@MockitoBean(types = [KmsKeyStore::class, ObjectService::class, MultipartService::class, ObjectController::class, MultipartController::class])
-@WebMvcTest(properties = ["com.adobe.testing.s3mock.store.region=us-east-1"])
+@WebMvcTest(
+ controllers = [BucketController::class],
+ properties = ["com.adobe.testing.s3mock.store.region=us-east-1"]
+)
internal class BucketControllerTest : BaseControllerTest() {
@MockitoBean
private lateinit var bucketService: BucketService
diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/controller/ContextPathObjectStoreControllerTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/ContextPathObjectStoreControllerTest.kt
index 64cb537ac..58d101886 100644
--- a/server/src/test/kotlin/com/adobe/testing/s3mock/controller/ContextPathObjectStoreControllerTest.kt
+++ b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/ContextPathObjectStoreControllerTest.kt
@@ -37,7 +37,10 @@ import java.nio.file.Paths
import java.time.Instant
@MockitoBean(types = [KmsKeyStore::class, ObjectService::class, MultipartService::class, MultipartStore::class])
-@WebMvcTest(properties = ["com.adobe.testing.s3mock.controller.contextPath=s3-mock"])
+@WebMvcTest(
+ controllers = [ObjectController::class],
+ properties = ["com.adobe.testing.s3mock.controller.contextPath=s3-mock"]
+)
internal class ContextPathObjectStoreControllerTest : BaseControllerTest() {
@MockitoBean
private lateinit var bucketService: BucketService
diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/controller/FaviconControllerTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/FaviconControllerTest.kt
index b186b7169..5f457dfe7 100644
--- a/server/src/test/kotlin/com/adobe/testing/s3mock/controller/FaviconControllerTest.kt
+++ b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/FaviconControllerTest.kt
@@ -26,7 +26,7 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
@MockitoBean(types = [KmsKeyStore::class, ObjectController::class, BucketController::class, MultipartController::class])
-@WebMvcTest
+@WebMvcTest(controllers = [FaviconController::class])
internal class FaviconControllerTest : BaseControllerTest() {
@Autowired
private lateinit var mockMvc: MockMvc
diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/controller/KmsValidationFilterTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/KmsValidationFilterTest.kt
new file mode 100644
index 000000000..17327681d
--- /dev/null
+++ b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/KmsValidationFilterTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2017-2025 Adobe.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.adobe.testing.s3mock.controller
+
+import com.adobe.testing.s3mock.store.KmsKeyStore
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.mockito.kotlin.whenever
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.TestConfiguration
+import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Import
+import org.springframework.http.MediaType
+import org.springframework.http.ResponseEntity
+import org.springframework.test.context.bean.override.mockito.MockitoBean
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+
+@MockitoBean(types = [KmsKeyStore::class])
+@WebMvcTest(
+ controllers = [KmsValidationFilterTest.DummyPassController::class],
+ properties = ["com.adobe.testing.s3mock.store.region=us-east-1"],
+ useDefaultFilters = false
+)
+@Import(KmsValidationFilterTest.Config::class)
+internal class KmsValidationFilterTest {
+
+ @Autowired
+ private lateinit var mockMvc: MockMvc
+
+ @Autowired
+ private lateinit var kmsKeyStore: KmsKeyStore
+
+
+ @Test
+ fun `denies request with invalid kms key id`() {
+ val badKey = "bad-key-id"
+ whenever(kmsKeyStore.validateKeyId(badKey)).thenReturn(false)
+
+ mockMvc.perform(
+ get("/internal-test/pass")
+ .param("dummy", "true")
+ .header("X-Dummy", "1")
+ .header("x-amz-server-side-encryption", "aws:kms")
+ .header("x-amz-server-side-encryption-aws-kms-key-id", badKey)
+ )
+ .andExpect(status().isBadRequest)
+ .andExpect(content().contentType(MediaType.APPLICATION_XML))
+ .andExpect(content().string(org.hamcrest.Matchers.containsString("Invalid keyId '$badKey'")))
+ }
+
+ @Test
+ fun `allows request with valid kms key id`() {
+ val goodKey = "good-key-id"
+ whenever(kmsKeyStore.validateKeyId(goodKey)).thenReturn(true)
+
+ mockMvc.perform(
+ get("/internal-test/pass")
+ .param("dummy", "true")
+ .header("X-Dummy", "1")
+ .header("x-amz-server-side-encryption", "aws:kms")
+ .header("x-amz-server-side-encryption-aws-kms-key-id", goodKey)
+ )
+ .andExpect { assertThat(it.response.status).isNotEqualTo(400) }
+ }
+
+ @Test
+ fun `allows request when kms headers are missing`() {
+ mockMvc.perform(
+ get("/internal-test/pass")
+ .param("dummy", "true")
+ .header("X-Dummy", "1")
+ ).andExpect { assertThat(it.response.status).isNotEqualTo(400) }
+ }
+
+ @TestConfiguration
+ internal class Config {
+ @Bean
+ fun kmsValidationFilter(
+ kmsKeyStore: KmsKeyStore
+ ) = KmsValidationFilter(kmsKeyStore, ControllerConfiguration().messageConverter())
+ }
+
+ @RestController
+ @RequestMapping("/internal-test", params = ["dummy=true"])
+ internal class DummyPassController {
+ @GetMapping("/pass", headers = ["X-Dummy=1"])
+ fun pass(): ResponseEntity = ResponseEntity.ok("ok")
+ }
+}
diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/controller/MultipartControllerTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/MultipartControllerTest.kt
index 0b41c1395..eb64d646b 100644
--- a/server/src/test/kotlin/com/adobe/testing/s3mock/controller/MultipartControllerTest.kt
+++ b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/MultipartControllerTest.kt
@@ -23,6 +23,7 @@ import com.adobe.testing.s3mock.dto.CompleteMultipartUploadResult
import com.adobe.testing.s3mock.dto.CompletedPart
import com.adobe.testing.s3mock.dto.CopyPartResult
import com.adobe.testing.s3mock.dto.InitiateMultipartUploadResult
+import com.adobe.testing.s3mock.dto.Initiator
import com.adobe.testing.s3mock.dto.ListMultipartUploadsResult
import com.adobe.testing.s3mock.dto.ListPartsResult
import com.adobe.testing.s3mock.dto.MultipartUpload
@@ -63,7 +64,7 @@ import java.util.Date
import java.util.UUID
@MockitoBean(types = [KmsKeyStore::class, ObjectController::class, BucketController::class])
-@WebMvcTest
+@WebMvcTest(controllers = [MultipartController::class])
internal class MultipartControllerTest : BaseControllerTest() {
@MockitoBean
private lateinit var bucketService: BucketService
@@ -244,7 +245,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
whenever(objectService.getObject(TEST_BUCKET_NAME, key, null)).thenReturn(s3meta)
// create result with encryption headers to be echoed
- val mpUpload = MultipartUpload(null, null, Date(), Owner.DEFAULT_OWNER, key, Owner.DEFAULT_OWNER, StorageClass.STANDARD, uploadId.toString())
+ val mpUpload = MultipartUpload(null, null, Date(), Initiator.DEFAULT_INITIATOR, key, Owner.DEFAULT_OWNER, StorageClass.STANDARD, uploadId.toString())
val info = MultipartUploadInfo(
mpUpload,
"application/octet-stream",
@@ -279,6 +280,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
anyOrNull(),
any(),
anyOrNull(),
+ anyOrNull(),
anyOrNull()
)
).thenReturn(result)
@@ -322,7 +324,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
val s3meta = s3ObjectMetadata(key, UUID.randomUUID().toString())
whenever(objectService.getObject(TEST_BUCKET_NAME, key, null)).thenReturn(s3meta)
- val mpUpload = MultipartUpload(null, null, Date(), Owner.DEFAULT_OWNER, key, Owner.DEFAULT_OWNER, StorageClass.STANDARD, uploadId.toString())
+ val mpUpload = MultipartUpload(null, null, Date(), Initiator.DEFAULT_INITIATOR, key, Owner.DEFAULT_OWNER, StorageClass.STANDARD, uploadId.toString())
val info = MultipartUploadInfo(
mpUpload,
"application/octet-stream",
@@ -357,6 +359,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
anyOrNull(),
any(),
anyOrNull(),
+ anyOrNull(),
anyOrNull()
)
).thenReturn(result)
@@ -395,7 +398,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
val s3meta = s3ObjectMetadata(key, UUID.randomUUID().toString())
whenever(objectService.getObject(TEST_BUCKET_NAME, key, null)).thenReturn(s3meta)
- val mpUpload = MultipartUpload(null, null, Date(), Owner.DEFAULT_OWNER, key, Owner.DEFAULT_OWNER, StorageClass.STANDARD, uploadId.toString())
+ val mpUpload = MultipartUpload(null, null, Date(), Initiator.DEFAULT_INITIATOR, key, Owner.DEFAULT_OWNER, StorageClass.STANDARD, uploadId.toString())
val info = MultipartUploadInfo(
mpUpload,
"application/octet-stream",
@@ -430,6 +433,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
anyOrNull(),
any(),
anyOrNull(),
+ anyOrNull(),
anyOrNull()
)
).thenReturn(result)
@@ -472,11 +476,10 @@ internal class MultipartControllerTest : BaseControllerTest() {
doThrow(S3Exception.PRECONDITION_FAILED)
.whenever(objectService)
.verifyObjectMatching(
+ eq(TEST_BUCKET_NAME),
+ eq(key),
anyOrNull(),
anyOrNull(),
- anyOrNull(),
- anyOrNull(),
- eq(s3meta)
)
val uri = UriComponentsBuilder
@@ -571,7 +574,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
null,
null,
Date(),
- Owner.DEFAULT_OWNER,
+ Initiator.DEFAULT_INITIATOR,
"my/key.txt",
Owner.DEFAULT_OWNER,
StorageClass.STANDARD,
@@ -632,7 +635,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
val uploadIdMarker = "u-marker"
val uploads = listOf(
- MultipartUpload(null, null, Date(), Owner.DEFAULT_OWNER, "pre/a.txt", Owner.DEFAULT_OWNER, StorageClass.STANDARD, "u-1")
+ MultipartUpload(null, null, Date(), Initiator.DEFAULT_INITIATOR, "pre/a.txt", Owner.DEFAULT_OWNER, StorageClass.STANDARD, "u-1")
)
val result = ListMultipartUploadsResult(
TEST_BUCKET_NAME,
@@ -687,7 +690,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
whenever(bucketService.verifyBucketExists(TEST_BUCKET_NAME)).thenReturn(bucketMeta)
val uploads = listOf(
- MultipartUpload(null, null, Date(), Owner.DEFAULT_OWNER, "k1", Owner.DEFAULT_OWNER, StorageClass.STANDARD, "u-1")
+ MultipartUpload(null, null, Date(), Initiator.DEFAULT_INITIATOR, "k1", Owner.DEFAULT_OWNER, StorageClass.STANDARD, "u-1")
)
val result = ListMultipartUploadsResult(
@@ -828,7 +831,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
TEST_BUCKET_NAME,
null,
null,
- Owner.DEFAULT_OWNER,
+ Initiator.DEFAULT_INITIATOR,
false,
"my/key.txt",
1000,
@@ -877,7 +880,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
TEST_BUCKET_NAME,
null,
null,
- Owner.DEFAULT_OWNER,
+ Initiator.DEFAULT_INITIATOR,
false,
"my/key.txt",
maxParts,
@@ -930,7 +933,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
TEST_BUCKET_NAME,
null,
null,
- Owner.DEFAULT_OWNER,
+ Initiator.DEFAULT_INITIATOR,
true,
"my/key.txt",
maxParts,
@@ -1460,7 +1463,7 @@ internal class MultipartControllerTest : BaseControllerTest() {
argThat { this.startsWith("application/octet-stream") },
anyOrNull(),
eq(Owner.DEFAULT_OWNER),
- eq(Owner.DEFAULT_OWNER),
+ eq(Initiator.DEFAULT_INITIATOR),
anyOrNull
-
- org.jspecify
- jspecify
-
org.assertj
assertj-core
diff --git a/testsupport/testcontainers/pom.xml b/testsupport/testcontainers/pom.xml
index 3813b26cf..31ca11b9a 100644
--- a/testsupport/testcontainers/pom.xml
+++ b/testsupport/testcontainers/pom.xml
@@ -52,10 +52,6 @@
org.jetbrains.kotlin
kotlin-stdlib
-
- org.jspecify
- jspecify
-
com.adobe.testing
s3mock
diff --git a/testsupport/testng/pom.xml b/testsupport/testng/pom.xml
index 502137c97..98872651c 100644
--- a/testsupport/testng/pom.xml
+++ b/testsupport/testng/pom.xml
@@ -39,10 +39,6 @@
org.testng
testng
-
- org.jspecify
- jspecify
-
org.assertj
assertj-core