From f2e362d56c7d58ea286954652ee974bc32d2efc2 Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Mon, 8 Dec 2025 12:40:00 +0100 Subject: [PATCH 01/17] fix(docker): use absolute path to run s3mock.jar Also copy to "/opt/" instead of the root directory. Fixes #2827 --- CHANGELOG.md | 3 ++- docker/Dockerfile | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c8b44cb0..5877d4a4b 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 @@ -150,6 +150,7 @@ Version 5.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav * Features and fixes * 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) * Refactorings * Use Jackson 3 annotations and mappers. * AWS has deprecated SDK for Java v1 and will remove support EOY 2025. 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" ] From d28d5edf017d4d580e8a1f8eeccb83eff00cec22 Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Mon, 8 Dec 2025 13:34:34 +0100 Subject: [PATCH 02/17] fix: generate JavaDoc for Kotlin classes --- pom.xml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 80b6bb541..0283cca2a 100644 --- a/pom.xml +++ b/pom.xml @@ -131,6 +131,7 @@ 0.0.14 3.5.0 26.0.2-1 + 2.1.0 @@ -556,6 +557,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 +733,8 @@ - maven-javadoc-plugin + org.jetbrains.dokka + dokka-maven-plugin maven-source-plugin From d4f8ad62b81ebd66cb625671fada258a2df2db8d Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Thu, 11 Dec 2025 14:34:46 +0100 Subject: [PATCH 03/17] feat: allow simple configuration of max payload size. Fixes #2200 --- server/src/main/resources/application.properties | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 From 28a064379599a313e4ef7560d18cdf03f6a7646e Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Thu, 11 Dec 2025 14:44:43 +0100 Subject: [PATCH 04/17] chore: changelog for 5.0.0 --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5877d4a4b..d7c62b9c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -160,14 +160,18 @@ 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 +* Version updates (deliverable dependencies) * Bump Spring Boot version to 4.0.0 * Bump Spring Framework version to 7.0.1 * Bump java version from 17 to 25 * Compile with Java 25, target Java 17 * Docker container runs Java 25 * Bump TestContainers to 2.0.2 +* Version updates (build dependencies) * Bump Maven to 4.0.0 + * Bump github/codeql-action from 4.31.6 to 4.31.7 + * 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. From a99a2e51d108a244044afc7c3f7e27089adb8aa8 Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Fri, 12 Dec 2025 12:50:58 +0100 Subject: [PATCH 05/17] fix: Bucket name is not required --- .../com/adobe/testing/s3mock/dto/Bucket.kt | 2 +- .../testing/s3mock/service/BucketService.kt | 5 ++++- .../adobe/testing/s3mock/service/ServiceBase.kt | 17 +++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) 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/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/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 } From 9fe3b55bd65ae1e07a0b6f1b16a63dde407961f8 Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Tue, 16 Dec 2025 15:57:49 +0100 Subject: [PATCH 06/17] fix: Let S3Mock support ChecksumType.FULL_OBJECT for Multipart uploads Fixes #2843 --- CHANGELOG.md | 1 + .../adobe/testing/s3mock/its/MultipartIT.kt | 168 ++++++++++++++++++ .../com/adobe/testing/s3mock/S3Exception.kt | 8 + .../s3mock/controller/MultipartController.kt | 2 + .../adobe/testing/s3mock/dto/ChecksumType.kt | 4 +- .../s3mock/service/MultipartService.kt | 2 + .../testing/s3mock/store/MultipartStore.kt | 86 +++++---- .../testing/s3mock/store/S3ObjectMetadata.kt | 2 +- .../adobe/testing/s3mock/util/HeaderUtil.kt | 9 + .../controller/MultipartControllerTest.kt | 3 + .../s3mock/store/MultipartStoreTest.kt | 11 ++ 11 files changed, 259 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7c62b9c1..819a4c44c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -151,6 +151,7 @@ Version 5.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav * Features and fixes * 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) * Refactorings * Use Jackson 3 annotations and mappers. * AWS has deprecated SDK for Java v1 and will remove support EOY 2025. 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..ee8dfa4f9 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. */ 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..96ecf9213 100644 --- a/server/src/main/kotlin/com/adobe/testing/s3mock/S3Exception.kt +++ b/server/src/main/kotlin/com/adobe/testing/s3mock/S3Exception.kt @@ -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/MultipartController.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/controller/MultipartController.kt index 64ca0e973..785bf1ee9 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 @@ -60,6 +60,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 @@ -449,6 +450,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/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/service/MultipartService.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/service/MultipartService.kt index 80bb22f57..08f10bec8 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 @@ -161,6 +161,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 +178,7 @@ open class MultipartService(private val bucketStore: BucketStore, private val mu multipartUploadInfo, location, checksum, + checksumType, checksumAlgorithm ) } 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..1244c7443 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 @@ -189,6 +189,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 +202,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 +422,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 +462,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/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/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/test/kotlin/com/adobe/testing/s3mock/controller/MultipartControllerTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/MultipartControllerTest.kt index 0b41c1395..d7be5ffbe 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 @@ -279,6 +279,7 @@ internal class MultipartControllerTest : BaseControllerTest() { anyOrNull(), any(), anyOrNull(), + anyOrNull(), anyOrNull() ) ).thenReturn(result) @@ -357,6 +358,7 @@ internal class MultipartControllerTest : BaseControllerTest() { anyOrNull(), any(), anyOrNull(), + anyOrNull(), anyOrNull() ) ).thenReturn(result) @@ -430,6 +432,7 @@ internal class MultipartControllerTest : BaseControllerTest() { anyOrNull(), any(), anyOrNull(), + anyOrNull(), anyOrNull() ) ).thenReturn(result) diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/store/MultipartStoreTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/store/MultipartStoreTest.kt index 8c4fa99fc..b2c49574b 100644 --- a/server/src/test/kotlin/com/adobe/testing/s3mock/store/MultipartStoreTest.kt +++ b/server/src/test/kotlin/com/adobe/testing/s3mock/store/MultipartStoreTest.kt @@ -211,6 +211,7 @@ internal class MultipartStoreTest : StoreTestBase() { multipartUploadInfo, "location", NO_CHECKSUM, + NO_CHECKSUMTYPE, NO_CHECKSUM_ALGORITHM, ) val md5 = MessageDigest.getInstance("MD5") @@ -288,6 +289,7 @@ internal class MultipartStoreTest : StoreTestBase() { multipartUploadInfo, "location", NO_CHECKSUM, + NO_CHECKSUMTYPE, NO_CHECKSUM_ALGORITHM, ) @@ -355,6 +357,7 @@ internal class MultipartStoreTest : StoreTestBase() { multipartUploadInfo, "location", NO_CHECKSUM, + NO_CHECKSUMTYPE, NO_CHECKSUM_ALGORITHM, ) @@ -445,6 +448,7 @@ internal class MultipartStoreTest : StoreTestBase() { multipartUploadInfo, "location", checksum, + ChecksumType.COMPOSITE, ChecksumAlgorithm.CRC32, ) } @@ -504,6 +508,7 @@ internal class MultipartStoreTest : StoreTestBase() { multipartUploadInfo, "location", wrongOverallChecksum, + ChecksumType.COMPOSITE, ChecksumAlgorithm.CRC32, ) }.isInstanceOf(S3Exception::class.java) @@ -570,6 +575,7 @@ internal class MultipartStoreTest : StoreTestBase() { multipartUploadInfo, "location", checksum, + ChecksumType.COMPOSITE, ChecksumAlgorithm.CRC32, ) }.isInstanceOf(S3Exception::class.java) @@ -704,6 +710,7 @@ internal class MultipartStoreTest : StoreTestBase() { multipartUploadInfo, "location", NO_CHECKSUM, + NO_CHECKSUMTYPE, NO_CHECKSUM_ALGORITHM, ) @@ -757,6 +764,7 @@ internal class MultipartStoreTest : StoreTestBase() { multipartUploadInfo, "location", NO_CHECKSUM, + NO_CHECKSUMTYPE, NO_CHECKSUM_ALGORITHM, ) @@ -842,6 +850,7 @@ internal class MultipartStoreTest : StoreTestBase() { multipartUploadInfo1, "location", NO_CHECKSUM, + NO_CHECKSUMTYPE, NO_CHECKSUM_ALGORITHM, ) multipartStore.completeMultipartUpload( @@ -854,6 +863,7 @@ internal class MultipartStoreTest : StoreTestBase() { multipartUploadInfo2, "location", NO_CHECKSUM, + NO_CHECKSUMTYPE, NO_CHECKSUM_ALGORITHM, ) @@ -1125,6 +1135,7 @@ internal class MultipartStoreTest : StoreTestBase() { multipartUploadInfo, "location", NO_CHECKSUM, + NO_CHECKSUMTYPE, NO_CHECKSUM_ALGORITHM, ) val s = objectStore.getS3ObjectMetadata(bucket, id, null)!!.dataPath From 9bdb8db8cfa1caea94f6e75e979088b31770a517 Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Tue, 16 Dec 2025 18:35:53 +0100 Subject: [PATCH 07/17] fix: Return 412 on if-none-match=true in CompleteMultipartRequest Also fixes links in Kotlin doc and wording in S3 Exception. Fixes #2790 --- CHANGELOG.md | 6 +- .../adobe/testing/s3mock/its/MultipartIT.kt | 57 +++++++++++++++++-- .../com/adobe/testing/s3mock/S3Exception.kt | 6 +- .../controller/ChecksumModeHeaderConverter.kt | 2 +- .../s3mock/controller/MultipartController.kt | 2 +- .../controller/MultipartControllerTest.kt | 5 +- 6 files changed, 65 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 819a4c44c..de1c0c9a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -152,6 +152,7 @@ Version 5.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav * 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. @@ -170,7 +171,10 @@ Version 5.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav * Bump TestContainers to 2.0.2 * Version updates (build dependencies) * Bump Maven to 4.0.0 - * Bump github/codeql-action from 4.31.6 to 4.31.7 + * 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.8 * 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 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 ee8dfa4f9..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 @@ -497,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( { @@ -541,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( { @@ -563,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()) } @@ -1812,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) @@ -2081,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, @@ -2107,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/server/src/main/kotlin/com/adobe/testing/s3mock/S3Exception.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/S3Exception.kt index 96ecf9213..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( 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 785bf1ee9..3e2c1500b 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 @@ -434,7 +434,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() 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 d7be5ffbe..70c1653ff 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 @@ -475,11 +475,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 From 76b1f9022b140cfa95eab2355b83e913d38df752 Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Tue, 16 Dec 2025 19:26:18 +0100 Subject: [PATCH 08/17] chore(deps): use BOM instead of library import for AWS and Kotlin --- pom.xml | 74 +++------------------------------------------------------ 1 file changed, 3 insertions(+), 71 deletions(-) diff --git a/pom.xml b/pom.xml index 0283cca2a..43c20dd2e 100644 --- a/pom.xml +++ b/pom.xml @@ -159,31 +159,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 @@ -227,53 +202,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 From 67899ecb349d2342060c7845b16bc2451fadc598 Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Wed, 17 Dec 2025 10:51:54 +0100 Subject: [PATCH 09/17] chore(build): bump Java versions in actions, allow legacy branches --- .github/workflows/codeql.yml | 10 +++++----- .github/workflows/maven-ci-and-prb.yml | 4 ++-- .github/workflows/maven-release.yml | 5 +++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8c8f71677..555cebe71 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -13,10 +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] + branches: [s3mock-v2, s3mock-v3, s3mock-v4, main] schedule: - cron: '43 21 * * 6' @@ -64,12 +64,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 From 2d3709efb9a896ecb0b6ab5edc3307a63898502a Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Wed, 17 Dec 2025 10:52:28 +0100 Subject: [PATCH 10/17] chore: changelog for 5.0.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de1c0c9a4..5a9dbd049 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -174,7 +174,7 @@ Version 5.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav * 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.8 + * 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 From 5f7cd2881b563d17392753a667c569d99d4fef58 Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Wed, 17 Dec 2025 11:49:50 +0100 Subject: [PATCH 11/17] fix(breaking): remove "DisplayName" from Owner This is a breaking change, S3Mock will not be able to read from existing file systems created with older versions. Pretty confusing - "DisplayName" is used all over the place in AWS, but for some reason, it's only removed from the "Owner" data structure. There is no documentation, blog post, etc to be found, just emails sent to account owners. The API for "Owner" still lists "DisplayName", with no deprecation notice. The SDKs also do not contain a deprecation notice. Fixes #2738 --- CHANGELOG.md | 3 ++ .../com/adobe/testing/s3mock/its/AclIT.kt | 5 --- .../com/adobe/testing/s3mock/its/BucketIT.kt | 4 --- .../s3mock/controller/MultipartController.kt | 3 +- .../com/adobe/testing/s3mock/dto/Initiator.kt | 35 +++++++++++++++++++ .../testing/s3mock/dto/ListPartsResult.kt | 2 +- .../testing/s3mock/dto/MultipartUpload.kt | 2 +- .../com/adobe/testing/s3mock/dto/Owner.kt | 9 ++--- .../s3mock/service/MultipartService.kt | 3 +- .../testing/s3mock/store/MultipartStore.kt | 3 +- .../adobe/testing/s3mock/store/ObjectStore.kt | 2 +- .../testing/s3mock/util/CannedAclUtil.kt | 6 ++-- .../s3mock/controller/BaseControllerTest.kt | 2 +- .../controller/MultipartControllerTest.kt | 27 +++++++------- .../s3mock/controller/ObjectControllerTest.kt | 14 +++----- .../s3mock/dto/AccessControlPolicyTest.kt | 12 ++----- .../dto/CompleteMultipartUploadResultTest.kt | 4 +-- .../s3mock/dto/ListAllMyBucketsResultTest.kt | 2 +- .../s3mock/dto/ListBucketResultTest.kt | 2 +- .../s3mock/dto/ListBucketResultV2Test.kt | 2 +- .../dto/ListMultipartUploadsResultTest.kt | 4 +-- .../testing/s3mock/dto/ListPartsResultTest.kt | 4 +-- .../testing/s3mock/service/ServiceTestBase.kt | 4 +-- .../s3mock/store/MultipartStoreTest.kt | 34 +++++++++--------- .../testing/s3mock/store/ObjectStoreTest.kt | 7 ++-- .../s3mock/store/S3ObjectMetadataTest.kt | 1 - .../testing/s3mock/store/StoreTestBase.kt | 4 ++- .../testing/s3mock/util/HeaderUtilTest.kt | 2 +- ...sControlPolicyTest_testDeserialization.xml | 1 - ...essControlPolicyTest_testSerialization.xml | 1 - ...lMyBucketsResultTest_testSerialization.xml | 1 - ...ListBucketResultTest_testSerialization.xml | 2 -- ...stBucketResultV2Test_testSerialization.xml | 2 -- ...artUploadsResultTest_testSerialization.xml | 2 -- .../ListPartsResultTest_testSerialization.xml | 1 - ...bjectMetadataTest_testDeserialization.json | 2 -- ...3ObjectMetadataTest_testSerialization.json | 2 -- 37 files changed, 108 insertions(+), 108 deletions(-) create mode 100644 server/src/main/kotlin/com/adobe/testing/s3mock/dto/Initiator.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a9dbd049..a6b94b263 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -149,6 +149,9 @@ 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) 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/server/src/main/kotlin/com/adobe/testing/s3mock/controller/MultipartController.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/controller/MultipartController.kt index 3e2c1500b..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 @@ -382,7 +383,7 @@ class MultipartController( contentType, storeHeadersFrom(httpHeaders), Owner.DEFAULT_OWNER, - Owner.DEFAULT_OWNER, + Initiator.DEFAULT_INITIATOR, userMetadataFrom(httpHeaders), encryptionHeaders, tags, 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/MultipartService.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/service/MultipartService.kt index 08f10bec8..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 @@ -189,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/store/MultipartStore.kt b/server/src/main/kotlin/com/adobe/testing/s3mock/store/MultipartStore.kt index 1244c7443..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?, 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/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/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/MultipartControllerTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/MultipartControllerTest.kt index 70c1653ff..1ff33ab4a 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 @@ -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", @@ -323,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", @@ -397,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", @@ -573,7 +574,7 @@ internal class MultipartControllerTest : BaseControllerTest() { null, null, Date(), - Owner.DEFAULT_OWNER, + Initiator.DEFAULT_INITIATOR, "my/key.txt", Owner.DEFAULT_OWNER, StorageClass.STANDARD, @@ -634,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, @@ -689,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( @@ -830,7 +831,7 @@ internal class MultipartControllerTest : BaseControllerTest() { TEST_BUCKET_NAME, null, null, - Owner.DEFAULT_OWNER, + Initiator.DEFAULT_INITIATOR, false, "my/key.txt", 1000, @@ -879,7 +880,7 @@ internal class MultipartControllerTest : BaseControllerTest() { TEST_BUCKET_NAME, null, null, - Owner.DEFAULT_OWNER, + Initiator.DEFAULT_INITIATOR, false, "my/key.txt", maxParts, @@ -932,7 +933,7 @@ internal class MultipartControllerTest : BaseControllerTest() { TEST_BUCKET_NAME, null, null, - Owner.DEFAULT_OWNER, + Initiator.DEFAULT_INITIATOR, true, "my/key.txt", maxParts, @@ -1462,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>(), anyOrNull>(), anyOrNull>(), @@ -1530,7 +1531,7 @@ internal class MultipartControllerTest : BaseControllerTest() { anyOrNull(), anyOrNull(), eq(Owner.DEFAULT_OWNER), - eq(Owner.DEFAULT_OWNER), + eq(Initiator.DEFAULT_INITIATOR), anyOrNull>(), eq(mapOf("x-amz-server-side-encryption" to "AES256")), anyOrNull>(), @@ -1573,7 +1574,7 @@ internal class MultipartControllerTest : BaseControllerTest() { anyOrNull(), anyOrNull(), eq(Owner.DEFAULT_OWNER), - eq(Owner.DEFAULT_OWNER), + eq(Initiator.DEFAULT_INITIATOR), anyOrNull>(), anyOrNull>(), anyOrNull>(), @@ -1615,7 +1616,7 @@ internal class MultipartControllerTest : BaseControllerTest() { eq(null), anyOrNull(), eq(Owner.DEFAULT_OWNER), - eq(Owner.DEFAULT_OWNER), + eq(Initiator.DEFAULT_INITIATOR), anyOrNull>(), anyOrNull>(), anyOrNull>(), diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/controller/ObjectControllerTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/ObjectControllerTest.kt index 0d771b2d3..e5642c28f 100644 --- a/server/src/test/kotlin/com/adobe/testing/s3mock/controller/ObjectControllerTest.kt +++ b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/ObjectControllerTest.kt @@ -409,11 +409,8 @@ internal class ObjectControllerTest : BaseControllerTest() { givenBucket() val key = "name" - val owner = Owner( - "mtd@amazon.com", - "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a" - ) - val grantee = CanonicalUser(owner.displayName, owner.id) + val owner = Owner("75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a") + val grantee = CanonicalUser(null, owner.id) val policy = AccessControlPolicy( listOf(Grant(grantee, Grant.Permission.FULL_CONTROL)), owner @@ -443,11 +440,8 @@ internal class ObjectControllerTest : BaseControllerTest() { givenBucket() val key = "name" - val owner = Owner( - "mtd@amazon.com", - "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a" - ) - val grantee = CanonicalUser(owner.displayName, owner.id) + val owner = Owner("75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a") + val grantee = CanonicalUser(null, owner.id) val policy = AccessControlPolicy( listOf(Grant(grantee, Grant.Permission.FULL_CONTROL)), owner diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/dto/AccessControlPolicyTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/dto/AccessControlPolicyTest.kt index 0211d81cb..fd67fc472 100644 --- a/server/src/test/kotlin/com/adobe/testing/s3mock/dto/AccessControlPolicyTest.kt +++ b/server/src/test/kotlin/com/adobe/testing/s3mock/dto/AccessControlPolicyTest.kt @@ -30,10 +30,7 @@ internal class AccessControlPolicyTest { val owner = iut.owner assertThat(owner).isNotNull() - assertThat(owner?.id).isEqualTo( - "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a" - ) - assertThat(owner?.displayName).isEqualTo("mtd@amazon.com") + assertThat(owner!!.id).isEqualTo("75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a") assertThat(iut.accessControlList).hasSize(3) iut.accessControlList?.get(0).also { @@ -65,11 +62,8 @@ internal class AccessControlPolicyTest { @Test fun testSerialization(testInfo: TestInfo) { - val owner = Owner( - "mtd@amazon.com", - "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a" - ) - val grantee = CanonicalUser(owner.displayName, owner.id) + val owner = Owner("75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a") + val grantee = CanonicalUser("mtd@amazon.com", owner.id) val group = Group(URI.create("http://acs.amazonaws.com/groups/s3/LogDelivery")) val customer = AmazonCustomerByEmail("xyz@amazon.com") diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/dto/CompleteMultipartUploadResultTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/dto/CompleteMultipartUploadResultTest.kt index 893de6b1c..7db778ad9 100644 --- a/server/src/test/kotlin/com/adobe/testing/s3mock/dto/CompleteMultipartUploadResultTest.kt +++ b/server/src/test/kotlin/com/adobe/testing/s3mock/dto/CompleteMultipartUploadResultTest.kt @@ -35,9 +35,9 @@ internal class CompleteMultipartUploadResultTest { ChecksumAlgorithm.SHA256, ChecksumType.COMPOSITE, Date(1514477008120L), - Owner("displayName100", (100L).toString()), + Initiator("displayName100", (100L).toString()), "key", - Owner("displayName10", (10L).toString()), + Owner((10L).toString()), StorageClass.STANDARD, "uploadId", ), diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListAllMyBucketsResultTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListAllMyBucketsResultTest.kt index ed40468df..fd31afb33 100644 --- a/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListAllMyBucketsResultTest.kt +++ b/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListAllMyBucketsResultTest.kt @@ -26,7 +26,7 @@ internal class ListAllMyBucketsResultTest { fun testSerialization(testInfo: TestInfo) { val iut = ListAllMyBucketsResult( - Owner("displayName", 10L.toString()), + Owner(10L.toString()), createBuckets(), "some-prefix", "50d8e003-0451-48fd-9c49-8208b151649c" diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListBucketResultTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListBucketResultTest.kt index 05dfca094..fe89fda96 100644 --- a/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListBucketResultTest.kt +++ b/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListBucketResultTest.kt @@ -48,7 +48,7 @@ internal class ListBucketResultTest { "\"fba9dede5f27731c9771645a39863328\"", "key$it", "2009-10-12T17:50:30.000Z", - Owner("displayName", (10L + it).toString()), + Owner((10L + it).toString()), null, "434234", StorageClass.STANDARD diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListBucketResultV2Test.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListBucketResultV2Test.kt index 104e7fbc4..53384ab97 100644 --- a/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListBucketResultV2Test.kt +++ b/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListBucketResultV2Test.kt @@ -53,7 +53,7 @@ internal class ListBucketResultV2Test { "\"fba9dede5f27731c9771645a39863328\"", "key$it", "2009-10-12T17:50:30.000Z", - Owner("displayName", (10L + it).toString()), + Owner((10L + it).toString()), null, "434234", StorageClass.STANDARD diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListMultipartUploadsResultTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListMultipartUploadsResultTest.kt index b8b19c48b..e12d5b2fb 100644 --- a/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListMultipartUploadsResultTest.kt +++ b/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListMultipartUploadsResultTest.kt @@ -49,9 +49,9 @@ internal class ListMultipartUploadsResultTest { ChecksumAlgorithm.SHA256, ChecksumType.COMPOSITE, Date(1514477008120L), - Owner("displayName100$it", (100L + it).toString()), + Initiator("displayName100$it", (100L + it).toString()), "key$it", - Owner("displayName10$it", (10L + it).toString()), + Owner((10L + it).toString()), StorageClass.STANDARD, "uploadId$it", ) diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListPartsResultTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListPartsResultTest.kt index e95a73e74..ab83e9912 100644 --- a/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListPartsResultTest.kt +++ b/server/src/test/kotlin/com/adobe/testing/s3mock/dto/ListPartsResultTest.kt @@ -29,12 +29,12 @@ internal class ListPartsResultTest { "bucketName", ChecksumAlgorithm.CRC32, ChecksumType.COMPOSITE, - Owner("id", "displayName"), + Initiator("id", "displayName"), false, "fileName", 1000, 100, - Owner("id", "displayName"), + Owner("displayName"), createParts(), 5, StorageClass.STANDARD, diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/service/ServiceTestBase.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/service/ServiceTestBase.kt index 3130d3b05..d4a09c28f 100644 --- a/server/src/test/kotlin/com/adobe/testing/s3mock/service/ServiceTestBase.kt +++ b/server/src/test/kotlin/com/adobe/testing/s3mock/service/ServiceTestBase.kt @@ -148,7 +148,7 @@ internal abstract class ServiceTestBase { val lastModified = "lastModified" val etag = "etag" val size = "size" - val owner = Owner("name", 0L.toString()) + val owner = Owner(0L.toString()) return S3Object( ChecksumAlgorithm.SHA256, ChecksumType.FULL_OBJECT, @@ -166,7 +166,7 @@ internal abstract class ServiceTestBase { val lastModified = "lastModified" val etag = "etag" val size = "size" - val owner = Owner("name", 0L.toString()) + val owner = Owner(0L.toString()) return S3ObjectMetadata( id, key, diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/store/MultipartStoreTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/store/MultipartStoreTest.kt index b2c49574b..adb967699 100644 --- a/server/src/test/kotlin/com/adobe/testing/s3mock/store/MultipartStoreTest.kt +++ b/server/src/test/kotlin/com/adobe/testing/s3mock/store/MultipartStoreTest.kt @@ -83,7 +83,7 @@ internal class MultipartStoreTest : StoreTestBase() { DEFAULT_CONTENT_TYPE, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, NO_USER_METADATA, NO_ENCRYPTION_HEADERS, NO_TAGS, @@ -123,7 +123,7 @@ internal class MultipartStoreTest : StoreTestBase() { DEFAULT_CONTENT_TYPE, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, NO_USER_METADATA, NO_ENCRYPTION_HEADERS, NO_TAGS, @@ -172,7 +172,7 @@ internal class MultipartStoreTest : StoreTestBase() { DEFAULT_CONTENT_TYPE, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, NO_USER_METADATA, NO_ENCRYPTION_HEADERS, NO_TAGS, @@ -252,7 +252,7 @@ internal class MultipartStoreTest : StoreTestBase() { DEFAULT_CONTENT_TYPE, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, NO_USER_METADATA, NO_ENCRYPTION_HEADERS, NO_TAGS, @@ -320,7 +320,7 @@ internal class MultipartStoreTest : StoreTestBase() { DEFAULT_CONTENT_TYPE, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, userMetadata, NO_ENCRYPTION_HEADERS, NO_TAGS, @@ -392,7 +392,7 @@ internal class MultipartStoreTest : StoreTestBase() { DEFAULT_CONTENT_TYPE, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, userMetadata, NO_ENCRYPTION_HEADERS, NO_TAGS, @@ -477,7 +477,7 @@ internal class MultipartStoreTest : StoreTestBase() { DEFAULT_CONTENT_TYPE, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, NO_USER_METADATA, NO_ENCRYPTION_HEADERS, NO_TAGS, @@ -537,7 +537,7 @@ internal class MultipartStoreTest : StoreTestBase() { DEFAULT_CONTENT_TYPE, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, userMetadata, NO_ENCRYPTION_HEADERS, NO_TAGS, @@ -615,7 +615,7 @@ internal class MultipartStoreTest : StoreTestBase() { DEFAULT_CONTENT_TYPE, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, NO_USER_METADATA, NO_ENCRYPTION_HEADERS, NO_TAGS, @@ -679,7 +679,7 @@ internal class MultipartStoreTest : StoreTestBase() { DEFAULT_CONTENT_TYPE, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, NO_USER_METADATA, NO_ENCRYPTION_HEADERS, NO_TAGS, @@ -735,7 +735,7 @@ internal class MultipartStoreTest : StoreTestBase() { DEFAULT_CONTENT_TYPE, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, NO_USER_METADATA, NO_ENCRYPTION_HEADERS, NO_TAGS, @@ -790,7 +790,7 @@ internal class MultipartStoreTest : StoreTestBase() { DEFAULT_CONTENT_TYPE, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, NO_USER_METADATA, NO_ENCRYPTION_HEADERS, NO_TAGS, @@ -810,7 +810,7 @@ internal class MultipartStoreTest : StoreTestBase() { DEFAULT_CONTENT_TYPE, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, NO_USER_METADATA, NO_ENCRYPTION_HEADERS, NO_TAGS, @@ -886,7 +886,7 @@ internal class MultipartStoreTest : StoreTestBase() { DEFAULT_CONTENT_TYPE, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, NO_USER_METADATA, NO_ENCRYPTION_HEADERS, NO_TAGS, @@ -963,7 +963,7 @@ internal class MultipartStoreTest : StoreTestBase() { DEFAULT_CONTENT_TYPE, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, NO_USER_METADATA, NO_ENCRYPTION_HEADERS, NO_TAGS, @@ -1035,7 +1035,7 @@ internal class MultipartStoreTest : StoreTestBase() { DEFAULT_CONTENT_TYPE, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, NO_USER_METADATA, NO_ENCRYPTION_HEADERS, NO_TAGS, @@ -1106,7 +1106,7 @@ internal class MultipartStoreTest : StoreTestBase() { TEXT_PLAIN, storeHeaders(), TEST_OWNER, - TEST_OWNER, + TEST_INITIATOR, NO_USER_METADATA, NO_ENCRYPTION_HEADERS, NO_TAGS, diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/store/ObjectStoreTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/store/ObjectStoreTest.kt index 46a9dec25..f4871e76f 100644 --- a/server/src/test/kotlin/com/adobe/testing/s3mock/store/ObjectStoreTest.kt +++ b/server/src/test/kotlin/com/adobe/testing/s3mock/store/ObjectStoreTest.kt @@ -333,11 +333,8 @@ internal class ObjectStoreTest : StoreTestBase() { @Test fun testStoreAndRetrieveAcl() { - val owner = Owner( - "mtd@amazon.com", - "75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a" - ) - val grantee = CanonicalUser(owner.displayName, owner.id) + val owner = Owner("75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a") + val grantee = CanonicalUser(null, owner.id) val policy = AccessControlPolicy( listOf(Grant(grantee, Grant.Permission.FULL_CONTROL)), owner diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/store/S3ObjectMetadataTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/store/S3ObjectMetadataTest.kt index 286fa7f1f..0aaceb969 100644 --- a/server/src/test/kotlin/com/adobe/testing/s3mock/store/S3ObjectMetadataTest.kt +++ b/server/src/test/kotlin/com/adobe/testing/s3mock/store/S3ObjectMetadataTest.kt @@ -57,7 +57,6 @@ class S3ObjectMetadataTest { assertThat(iut.legalHold).isEqualTo(LegalHold(LegalHold.Status.ON)) assertThat(iut.retention).isEqualTo(Retention(Mode.GOVERNANCE, Instant.parse("2025-09-12T22:50:49.038Z"))) assertThat(iut.owner).isNotNull() - assertThat(iut.owner.displayName).isEqualTo("s3-mock-file-store") assertThat(iut.owner.id).isEqualTo("79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be") assertThat(iut.storeHeaders).isEqualTo( mapOf( diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/store/StoreTestBase.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/store/StoreTestBase.kt index d44f9b7fb..c10d6ed30 100644 --- a/server/src/test/kotlin/com/adobe/testing/s3mock/store/StoreTestBase.kt +++ b/server/src/test/kotlin/com/adobe/testing/s3mock/store/StoreTestBase.kt @@ -18,6 +18,7 @@ package com.adobe.testing.s3mock.store import com.adobe.testing.s3mock.dto.ChecksumAlgorithm import com.adobe.testing.s3mock.dto.ChecksumType +import com.adobe.testing.s3mock.dto.Initiator import com.adobe.testing.s3mock.dto.ObjectOwnership import com.adobe.testing.s3mock.dto.Owner import com.adobe.testing.s3mock.dto.Tag @@ -75,7 +76,8 @@ internal abstract class StoreTestBase { const val ENCODING_GZIP = "gzip" val NO_PREFIX: String? = null const val DEFAULT_CONTENT_TYPE = MediaType.APPLICATION_OCTET_STREAM_VALUE - val TEST_OWNER: Owner = Owner("s3-mock-file-store", "123") + val TEST_OWNER: Owner = Owner("123") + val TEST_INITIATOR: Initiator = Initiator("s3-mock-file-store", "123") val BUCKET_NAMES = mutableSetOf() } diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/util/HeaderUtilTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/util/HeaderUtilTest.kt index d760cb25a..82d5ab615 100644 --- a/server/src/test/kotlin/com/adobe/testing/s3mock/util/HeaderUtilTest.kt +++ b/server/src/test/kotlin/com/adobe/testing/s3mock/util/HeaderUtilTest.kt @@ -80,7 +80,7 @@ internal class HeaderUtilTest { null, null, null, - Owner("name", 0L.toString()), + Owner(0L.toString()), null, null, ChecksumAlgorithm.SHA256, diff --git a/server/src/test/resources/com/adobe/testing/s3mock/dto/AccessControlPolicyTest_testDeserialization.xml b/server/src/test/resources/com/adobe/testing/s3mock/dto/AccessControlPolicyTest_testDeserialization.xml index 011198d96..f6ec147d5 100644 --- a/server/src/test/resources/com/adobe/testing/s3mock/dto/AccessControlPolicyTest_testDeserialization.xml +++ b/server/src/test/resources/com/adobe/testing/s3mock/dto/AccessControlPolicyTest_testDeserialization.xml @@ -19,7 +19,6 @@ 75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a - mtd@amazon.com diff --git a/server/src/test/resources/com/adobe/testing/s3mock/dto/AccessControlPolicyTest_testSerialization.xml b/server/src/test/resources/com/adobe/testing/s3mock/dto/AccessControlPolicyTest_testSerialization.xml index 434f78617..a2458a788 100644 --- a/server/src/test/resources/com/adobe/testing/s3mock/dto/AccessControlPolicyTest_testSerialization.xml +++ b/server/src/test/resources/com/adobe/testing/s3mock/dto/AccessControlPolicyTest_testSerialization.xml @@ -19,7 +19,6 @@ 75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a - mtd@amazon.com diff --git a/server/src/test/resources/com/adobe/testing/s3mock/dto/ListAllMyBucketsResultTest_testSerialization.xml b/server/src/test/resources/com/adobe/testing/s3mock/dto/ListAllMyBucketsResultTest_testSerialization.xml index c57633fb0..f24986036 100644 --- a/server/src/test/resources/com/adobe/testing/s3mock/dto/ListAllMyBucketsResultTest_testSerialization.xml +++ b/server/src/test/resources/com/adobe/testing/s3mock/dto/ListAllMyBucketsResultTest_testSerialization.xml @@ -19,7 +19,6 @@ 10 - displayName diff --git a/server/src/test/resources/com/adobe/testing/s3mock/dto/ListBucketResultTest_testSerialization.xml b/server/src/test/resources/com/adobe/testing/s3mock/dto/ListBucketResultTest_testSerialization.xml index caac62f2c..11102e9cb 100644 --- a/server/src/test/resources/com/adobe/testing/s3mock/dto/ListBucketResultTest_testSerialization.xml +++ b/server/src/test/resources/com/adobe/testing/s3mock/dto/ListBucketResultTest_testSerialization.xml @@ -32,7 +32,6 @@ STANDARD 10 - displayName SHA256 FULL_OBJECT @@ -45,7 +44,6 @@ STANDARD 11 - displayName SHA256 FULL_OBJECT diff --git a/server/src/test/resources/com/adobe/testing/s3mock/dto/ListBucketResultV2Test_testSerialization.xml b/server/src/test/resources/com/adobe/testing/s3mock/dto/ListBucketResultV2Test_testSerialization.xml index 19b6aaa36..24b7be4f5 100644 --- a/server/src/test/resources/com/adobe/testing/s3mock/dto/ListBucketResultV2Test_testSerialization.xml +++ b/server/src/test/resources/com/adobe/testing/s3mock/dto/ListBucketResultV2Test_testSerialization.xml @@ -29,7 +29,6 @@ STANDARD 10 - displayName SHA256 FULL_OBJECT @@ -42,7 +41,6 @@ STANDARD 11 - displayName SHA256 FULL_OBJECT diff --git a/server/src/test/resources/com/adobe/testing/s3mock/dto/ListMultipartUploadsResultTest_testSerialization.xml b/server/src/test/resources/com/adobe/testing/s3mock/dto/ListMultipartUploadsResultTest_testSerialization.xml index 4d69636d9..fbc85468b 100644 --- a/server/src/test/resources/com/adobe/testing/s3mock/dto/ListMultipartUploadsResultTest_testSerialization.xml +++ b/server/src/test/resources/com/adobe/testing/s3mock/dto/ListMultipartUploadsResultTest_testSerialization.xml @@ -34,7 +34,6 @@ uploadId0 10 - displayName100 100 @@ -50,7 +49,6 @@ uploadId1 11 - displayName101 101 diff --git a/server/src/test/resources/com/adobe/testing/s3mock/dto/ListPartsResultTest_testSerialization.xml b/server/src/test/resources/com/adobe/testing/s3mock/dto/ListPartsResultTest_testSerialization.xml index 536bd6d9a..e0762097f 100644 --- a/server/src/test/resources/com/adobe/testing/s3mock/dto/ListPartsResultTest_testSerialization.xml +++ b/server/src/test/resources/com/adobe/testing/s3mock/dto/ListPartsResultTest_testSerialization.xml @@ -29,7 +29,6 @@ 1000 100 - id displayName diff --git a/server/src/test/resources/com/adobe/testing/s3mock/store/S3ObjectMetadataTest_testDeserialization.json b/server/src/test/resources/com/adobe/testing/s3mock/store/S3ObjectMetadataTest_testDeserialization.json index 775a865a7..69aae9181 100644 --- a/server/src/test/resources/com/adobe/testing/s3mock/store/S3ObjectMetadataTest_testDeserialization.json +++ b/server/src/test/resources/com/adobe/testing/s3mock/store/S3ObjectMetadataTest_testDeserialization.json @@ -25,7 +25,6 @@ "RetainUntilDate": "2025-09-12T22:50:49.038Z" }, "owner": { - "DisplayName": "s3-mock-file-store", "ID": "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be" }, "storeHeaders": { @@ -54,7 +53,6 @@ } ], "Owner": { - "DisplayName": "s3-mock-file-store", "ID": "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be" } }, diff --git a/server/src/test/resources/com/adobe/testing/s3mock/store/S3ObjectMetadataTest_testSerialization.json b/server/src/test/resources/com/adobe/testing/s3mock/store/S3ObjectMetadataTest_testSerialization.json index ad61d21a0..1e07e4344 100644 --- a/server/src/test/resources/com/adobe/testing/s3mock/store/S3ObjectMetadataTest_testSerialization.json +++ b/server/src/test/resources/com/adobe/testing/s3mock/store/S3ObjectMetadataTest_testSerialization.json @@ -25,7 +25,6 @@ "RetainUntilDate": "2025-09-12T22:50:49.038Z" }, "owner": { - "DisplayName": "s3-mock-file-store", "ID": "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be" }, "storeHeaders": { @@ -54,7 +53,6 @@ } ], "Owner": { - "DisplayName": "s3-mock-file-store", "ID": "79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be" } }, From ea1e0df7c1f5c9c0718a1caea4f105606b55aeff Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Sat, 20 Dec 2025 10:48:08 +0100 Subject: [PATCH 12/17] chore(deps): remove jspecify, not needed with Kotlin --- pom.xml | 6 ------ server/pom.xml | 4 ---- testsupport/common/pom.xml | 4 ---- testsupport/testcontainers/pom.xml | 4 ---- testsupport/testng/pom.xml | 4 ---- 5 files changed, 22 deletions(-) diff --git a/pom.xml b/pom.xml index 43c20dd2e..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 @@ -227,11 +226,6 @@ httpmime ${httpmime.version} - - org.jspecify - jspecify - ${jspecify.version} - org.jetbrains 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/testsupport/common/pom.xml b/testsupport/common/pom.xml index 587df03fb..3f35068b7 100644 --- a/testsupport/common/pom.xml +++ b/testsupport/common/pom.xml @@ -43,10 +43,6 @@ software.amazon.awssdk url-connection-client - - 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 From 1b810c562bf166e2c37041e4d59ab74f05c481dd Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Tue, 9 Dec 2025 22:35:57 +0100 Subject: [PATCH 13/17] chore(tests): limit WebMvcTests to specific controllers --- .../adobe/testing/s3mock/controller/BucketControllerTest.kt | 5 ++++- .../controller/ContextPathObjectStoreControllerTest.kt | 5 ++++- .../adobe/testing/s3mock/controller/FaviconControllerTest.kt | 2 +- .../testing/s3mock/controller/MultipartControllerTest.kt | 2 +- .../adobe/testing/s3mock/controller/ObjectControllerTest.kt | 5 ++++- 5 files changed, 14 insertions(+), 5 deletions(-) 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/MultipartControllerTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/MultipartControllerTest.kt index 1ff33ab4a..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 @@ -64,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 diff --git a/server/src/test/kotlin/com/adobe/testing/s3mock/controller/ObjectControllerTest.kt b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/ObjectControllerTest.kt index e5642c28f..6a860f5d1 100644 --- a/server/src/test/kotlin/com/adobe/testing/s3mock/controller/ObjectControllerTest.kt +++ b/server/src/test/kotlin/com/adobe/testing/s3mock/controller/ObjectControllerTest.kt @@ -79,7 +79,10 @@ import java.time.Instant import java.util.UUID @MockitoBean(types = [KmsKeyStore::class, MultipartService::class, BucketController::class, MultipartController::class]) -@WebMvcTest(properties = ["com.adobe.testing.s3mock.store.region=us-east-1"]) +@WebMvcTest( + controllers = [ObjectController::class], + properties = ["com.adobe.testing.s3mock.store.region=us-east-1"] +) internal class ObjectControllerTest : BaseControllerTest() { @MockitoBean private lateinit var objectService: ObjectService From 716f4d1d5e4eeb2382d8b1b984189a7979d15e06 Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Mon, 22 Dec 2025 00:18:06 +0100 Subject: [PATCH 14/17] chore(tests): add test for KmsValidationFilter --- .../controller/KmsValidationFilterTest.kt | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 server/src/test/kotlin/com/adobe/testing/s3mock/controller/KmsValidationFilterTest.kt 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") + } +} From cf746284962caf8a356eab9629f43d520173d0fc Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Mon, 22 Dec 2025 15:06:37 +0100 Subject: [PATCH 15/17] chore(build): run CodeQL on PR and push only --- .github/workflows/codeql.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 555cebe71..c9b2fffc0 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -17,8 +17,6 @@ on: pull_request: # The branches below must be a subset of the branches above branches: [s3mock-v2, s3mock-v3, s3mock-v4, main] - schedule: - - cron: '43 21 * * 6' # Declare default permissions as read only. permissions: read-all From e2a39bd02cb722e8a3a81fc6d863c70e57a9a7e6 Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Mon, 22 Dec 2025 15:54:24 +0100 Subject: [PATCH 16/17] chore(tests): assert StorageClass#STANDARD in some responses Some S3 APIs return STANDARD, while others do not. Just ran these tests against S3 APIs and they pass. See also #2736 --- .../adobe/testing/s3mock/its/GetPutDeleteObjectIT.kt | 2 +- .../com/adobe/testing/s3mock/its/ListObjectsIT.kt | 11 +++++++++++ .../kotlin/com/adobe/testing/s3mock/its/VersionsIT.kt | 5 ++++- 3 files changed, 16 insertions(+), 2 deletions(-) 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/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 { From 3582d7ba3d2e69b61228737610d5f7fe391271ce Mon Sep 17 00:00:00 2001 From: Arne Franken Date: Mon, 22 Dec 2025 16:10:28 +0100 Subject: [PATCH 17/17] chore: changelog for 5.0.0 --- CHANGELOG.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6b94b263..41cadc184 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -166,14 +166,16 @@ Version 5.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav * 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 (deliverable dependencies) - * Bump Spring Boot version to 4.0.0 + * 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 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 + * 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