diff --git a/CHANGELOG.md b/CHANGELOG.md index 34ad43ff2..c76418e45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,9 @@ * [Planned changes](#planned-changes) * [CURRENT - 3.x - THIS VERSION IS UNDER ACTIVE DEVELOPMENT](#current---3x---this-version-is-under-active-development) * [3.8.0 - PLANNED](#380---planned) - * [3.7.1 - PLANNED](#371---planned) + * [3.7.3 - PLANNED](#373---planned) + * [3.7.2](#372) + * [3.7.1](#371) * [3.7.0](#370) * [3.6.0](#360) * [3.5.2](#352) @@ -132,15 +134,18 @@ Version 3.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav * Version updates * TBD -## 3.7.2 - PLANNED +## 3.7.3 - PLANNED 3.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Java integration. * Features and fixes - * Calculate and validate checksums on upload -* Refactorings - * TBD -* Version updates - * TBD + * Support large, chunked, unsigned, asynchronous uploads (fixes #1818) + +## 3.7.2 +3.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Java integration. + +* Features and fixes + * Calculate and validate checksums on upload (fixes #1827) + * UploadPart API now also returns checksums, if available. ## 3.7.1 3.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Java integration. 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 8c4ca4c32..288150a86 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 @@ -39,28 +39,30 @@ internal class AclIT : S3TestBase() { val sourceKey = UPLOAD_FILE_NAME val (bucketName, _) = givenBucketAndObjectV2(testInfo, sourceKey) - val putAclResponse = s3ClientV2.putObjectAcl( + s3ClientV2.putObjectAcl( PutObjectAclRequest .builder() .bucket(bucketName) .key(sourceKey) .acl(ObjectCannedACL.PRIVATE) .build() - ) - assertThat(putAclResponse.sdkHttpResponse().isSuccessful).isTrue() + ).also { + assertThat(it.sdkHttpResponse().isSuccessful).isTrue() + } - val getAclResponse = s3ClientV2.getObjectAcl( + s3ClientV2.getObjectAcl( GetObjectAclRequest .builder() .bucket(bucketName) .key(sourceKey) .build() - ) - assertThat(getAclResponse.sdkHttpResponse().isSuccessful).isTrue() - assertThat(getAclResponse.owner().id()).isEqualTo(DEFAULT_OWNER.id) - assertThat(getAclResponse.owner().displayName()).isEqualTo(DEFAULT_OWNER.displayName) - assertThat(getAclResponse.grants().size).isEqualTo(1) - assertThat(getAclResponse.grants()[0].permission()).isEqualTo(FULL_CONTROL) + ).also { + assertThat(it.sdkHttpResponse().isSuccessful).isTrue() + assertThat(it.owner().id()).isEqualTo(DEFAULT_OWNER.id) + assertThat(it.owner().displayName()).isEqualTo(DEFAULT_OWNER.displayName) + assertThat(it.grants().size).isEqualTo(1) + assertThat(it.grants()[0].permission()).isEqualTo(FULL_CONTROL) + } } @Test @@ -78,21 +80,22 @@ internal class AclIT : S3TestBase() { .build() ) - val owner = acl.owner() - assertThat(owner.id()).isEqualTo(DEFAULT_OWNER.id) - assertThat(owner.displayName()).isEqualTo(DEFAULT_OWNER.displayName) - - val grants = acl.grants() - assertThat(grants).hasSize(1) + acl.owner().also { owner -> + assertThat(owner.id()).isEqualTo(DEFAULT_OWNER.id) + assertThat(owner.displayName()).isEqualTo(DEFAULT_OWNER.displayName) + } + val grants = acl.grants().also { + assertThat(it).hasSize(1) + } val grant = grants[0] assertThat(grant.permission()).isEqualTo(FULL_CONTROL) - - val grantee = grant.grantee() - assertThat(grantee).isNotNull - assertThat(grantee.id()).isEqualTo(DEFAULT_OWNER.id) - assertThat(grantee.displayName()).isEqualTo(DEFAULT_OWNER.displayName) - assertThat(grantee.type()).isEqualTo(CANONICAL_USER) + grant.grantee().also { + assertThat(it).isNotNull + assertThat(it.id()).isEqualTo(DEFAULT_OWNER.id) + assertThat(it.displayName()).isEqualTo(DEFAULT_OWNER.displayName) + assertThat(it.type()).isEqualTo(CANONICAL_USER) + } } diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/AwsChunkedEndcodingITV2.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/AwsChunkedEndcodingITV2.kt index d47413029..04fda0540 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/AwsChunkedEndcodingITV2.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/AwsChunkedEndcodingITV2.kt @@ -62,22 +62,25 @@ internal class AwsChunkedEndcodingITV2 : S3TestBase() { RequestBody.fromFile(uploadFile) ) - val putChecksum = putObjectResponse.checksumSHA256() - assertThat(putChecksum).isNotBlank - assertThat(putChecksum).isEqualTo(expectedChecksum) + putObjectResponse.checksumSHA256().also { + assertThat(it).isNotBlank + assertThat(it).isEqualTo(expectedChecksum) + } - val getObjectResponse = s3ClientV2.getObject( + s3ClientV2.getObject( GetObjectRequest.builder() .bucket(bucket) .key(UPLOAD_FILE_NAME) .build() - ) - assertThat(getObjectResponse.response().eTag()).isEqualTo(expectedEtag) - assertThat(getObjectResponse.response().contentLength()).isEqualTo(uploadFile.length()) + ).also { getObjectResponse -> + assertThat(getObjectResponse.response().eTag()).isEqualTo(expectedEtag) + assertThat(getObjectResponse.response().contentLength()).isEqualTo(uploadFile.length()) - val getChecksum = getObjectResponse.response().checksumSHA256() - assertThat(getChecksum).isNotBlank - assertThat(getChecksum).isEqualTo(expectedChecksum) + getObjectResponse.response().checksumSHA256().also { + assertThat(it).isNotBlank + assertThat(it).isEqualTo(expectedChecksum) + } + } } /** @@ -104,13 +107,14 @@ internal class AwsChunkedEndcodingITV2 : S3TestBase() { RequestBody.fromFile(uploadFile) ) - val getObjectResponse = s3ClientV2.getObject( + s3ClientV2.getObject( GetObjectRequest.builder() .bucket(bucket) .key(UPLOAD_FILE_NAME) .build() - ) - assertThat(getObjectResponse.response().eTag()).isEqualTo(expectedEtag) - assertThat(getObjectResponse.response().contentLength()).isEqualTo(uploadFile.length()) + ).also { + assertThat(it.response().eTag()).isEqualTo(expectedEtag) + assertThat(it.response().contentLength()).isEqualTo(uploadFile.length()) + } } } diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/BucketV1IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/BucketV1IT.kt index 9a52caf7b..1977a606a 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/BucketV1IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/BucketV1IT.kt @@ -55,9 +55,10 @@ internal class BucketV1IT : S3TestBase() { val createdBucket = buckets[0] assertThat(createdBucket.creationDate).isAfterOrEqualTo(creationDate) - val bucketOwner = createdBucket.owner - assertThat(bucketOwner.displayName).isEqualTo("s3-mock-file-store") - assertThat(bucketOwner.id).isEqualTo("79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be") + createdBucket.owner.also { + assertThat(it.displayName).isEqualTo("s3-mock-file-store") + assertThat(it.id).isEqualTo("79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be") + } } @Test @@ -81,8 +82,9 @@ internal class BucketV1IT : S3TestBase() { s3Client.headBucket(HeadBucketRequest(bucketName)) s3Client.deleteBucket(bucketName) - val doesBucketExist = s3Client.doesBucketExistV2(bucketName) - assertThat(doesBucketExist).isFalse + s3Client.doesBucketExistV2(bucketName).also { + assertThat(it).isFalse + } } @Test @@ -104,8 +106,9 @@ internal class BucketV1IT : S3TestBase() { val bucketName = bucketName(testInfo) s3Client.createBucket(bucketName) - val doesBucketExist = s3Client.doesBucketExistV2(bucketName) - assertThat(doesBucketExist).isTrue + s3Client.doesBucketExistV2(bucketName).also { + assertThat(it).isTrue + } } @Test diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/BucketV2IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/BucketV2IT.kt index d3a6581aa..c0290047f 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/BucketV2IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/BucketV2IT.kt @@ -64,9 +64,10 @@ internal class BucketV2IT : S3TestBase() { s3ClientV2.deleteBucket(DeleteBucketRequest.builder().bucket(bucketName).build()) val bucketDeleted = s3ClientV2.waiter() .waitUntilBucketNotExists(HeadBucketRequest.builder().bucket(bucketName).build()) - val bucketDeletedResponse = bucketDeleted.matched().exception().get() - assertThat(bucketDeletedResponse).isNotNull - assertThat(bucketDeletedResponse).isInstanceOf(NoSuchBucketException::class.java) + bucketDeleted.matched().exception().get().also { + assertThat(it).isNotNull + assertThat(it).isInstanceOf(NoSuchBucketException::class.java) + } } @Test @@ -86,8 +87,9 @@ internal class BucketV2IT : S3TestBase() { val bucketCreated = s3ClientV2.waiter() .waitUntilBucketExists(HeadBucketRequest.builder().bucket(bucketName).build()) - val bucketCreatedResponse = bucketCreated.matched().response().get() - assertThat(bucketCreatedResponse).isNotNull + bucketCreated.matched().response().get().also { + assertThat(it).isNotNull + } assertThatThrownBy { s3ClientV2.createBucket(CreateBucketRequest.builder().bucket(bucketName).build()) @@ -103,9 +105,10 @@ internal class BucketV2IT : S3TestBase() { val bucketDeleted = s3ClientV2.waiter() .waitUntilBucketNotExists(HeadBucketRequest.builder().bucket(bucketName).build()) - val bucketDeletedResponse = bucketDeleted.matched().exception().get() - assertThat(bucketDeletedResponse).isNotNull - assertThat(bucketDeletedResponse).isInstanceOf(NoSuchBucketException::class.java) + bucketDeleted.matched().exception().get().also { + assertThat(it).isNotNull + assertThat(it).isInstanceOf(NoSuchBucketException::class.java) + } } @Test @@ -116,15 +119,17 @@ internal class BucketV2IT : S3TestBase() { val bucketCreated = s3ClientV2.waiter() .waitUntilBucketExists(HeadBucketRequest.builder().bucket(bucketName).build()) - val bucketCreatedResponse = bucketCreated.matched().response().get() - assertThat(bucketCreatedResponse).isNotNull + bucketCreated.matched().response().get().also { + assertThat(it).isNotNull + } s3ClientV2.deleteBucket(DeleteBucketRequest.builder().bucket(bucketName).build()) val bucketDeleted = s3ClientV2.waiter() .waitUntilBucketNotExists(HeadBucketRequest.builder().bucket(bucketName).build()) - val bucketDeletedResponse = bucketDeleted.matched().exception().get() - assertThat(bucketDeletedResponse).isNotNull - assertThat(bucketDeletedResponse).isInstanceOf(NoSuchBucketException::class.java) + bucketDeleted.matched().exception().get().also { + assertThat(it).isNotNull + assertThat(it).isInstanceOf(NoSuchBucketException::class.java) + } assertThatThrownBy { s3ClientV2.deleteBucket(DeleteBucketRequest.builder().bucket(bucketName).build()) @@ -169,8 +174,9 @@ internal class BucketV2IT : S3TestBase() { val bucketCreated = s3ClientV2.waiter() .waitUntilBucketExists(HeadBucketRequest.builder().bucket(bucketName).build()) - val bucketCreatedResponse = bucketCreated.matched().response()!!.get() - assertThat(bucketCreatedResponse).isNotNull + bucketCreated.matched().response()!!.get().also { + assertThat(it).isNotNull + } val configuration = BucketLifecycleConfiguration .builder() @@ -206,20 +212,21 @@ internal class BucketV2IT : S3TestBase() { .build() ) - val configurationResponse = s3ClientV2.getBucketLifecycleConfiguration( + s3ClientV2.getBucketLifecycleConfiguration( GetBucketLifecycleConfigurationRequest .builder() .bucket(bucketName) .build() - ) - - assertThat(configurationResponse.rules()[0]).isEqualTo(configuration.rules()[0]) + ).also { + assertThat(it.rules()[0]).isEqualTo(configuration.rules()[0]) + } - val deleteBucketLifecycle = s3ClientV2.deleteBucketLifecycle( + s3ClientV2.deleteBucketLifecycle( DeleteBucketLifecycleRequest.builder().bucket(bucketName).build() - ) + ).also { + assertThat(it.sdkHttpResponse().statusCode()).isEqualTo(204) + } - assertThat(deleteBucketLifecycle.sdkHttpResponse().statusCode()).isEqualTo(204) // give AWS time to actually delete the lifecycleConfiguration, otherwise the following call // will not fail as expected... diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ConcurrencyIT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ConcurrencyIT.kt index 5ab69525e..693751185 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ConcurrencyIT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ConcurrencyIT.kt @@ -61,31 +61,35 @@ internal class ConcurrencyIT : S3TestBase() { inner class Runner(val bucketName: String, val key: String) : Callable { override fun call(): Boolean { LATCH.countDown() - val putObjectResponse = s3ClientV2.putObject( + s3ClientV2.putObject( PutObjectRequest .builder() .bucket(bucketName) .key(key) .build(), RequestBody.empty() - ) - assertThat(putObjectResponse.eTag()).isNotBlank - val getObjectResponse = s3ClientV2.getObject( + ).also { + assertThat(it.eTag()).isNotBlank + } + + s3ClientV2.getObject( GetObjectRequest .builder() .bucket(bucketName) .key(key) .build() - ) - assertThat(getObjectResponse.response().eTag()).isNotBlank + ).also { + assertThat(it.response().eTag()).isNotBlank + } - val deleteObjectResponse = s3ClientV2.deleteObject( + s3ClientV2.deleteObject( DeleteObjectRequest .builder() .bucket(bucketName) .key(key) .build() - ) - assertThat(deleteObjectResponse.deleteMarker()).isTrue + ).also { + assertThat(it.deleteMarker()).isTrue + } DONE.incrementAndGet() return true } diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/CopyObjectV1IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/CopyObjectV1IT.kt index a1e84e3a7..d778959fc 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/CopyObjectV1IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/CopyObjectV1IT.kt @@ -53,8 +53,10 @@ internal class CopyObjectV1IT : S3TestBase() { val (bucketName, putObjectResult) = givenBucketAndObjectV1(testInfo, sourceKey) val destinationBucketName = givenRandomBucketV1() val destinationKey = "copyOf/$sourceKey" - val copyObjectRequest = CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey) - s3Client.copyObject(copyObjectRequest) + + CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey).also { + s3Client.copyObject(it) + } s3Client.getObject(destinationBucketName, destinationKey).use { val copiedDigest = DigestUtil.hexDigest(it.objectContent) @@ -70,9 +72,11 @@ internal class CopyObjectV1IT : S3TestBase() { val matchingEtag = "\"${putObjectResult.eTag}\"" val destinationBucketName = givenRandomBucketV1() val destinationKey = "copyOf/$sourceKey" - val copyObjectRequest = CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey) - .withMatchingETagConstraint(matchingEtag) - s3Client.copyObject(copyObjectRequest) + + CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey) + .withMatchingETagConstraint(matchingEtag).also { + s3Client.copyObject(it) + } s3Client.getObject(destinationBucketName, destinationKey).use { val copiedDigest = DigestUtil.hexDigest(it.objectContent) @@ -88,9 +92,11 @@ internal class CopyObjectV1IT : S3TestBase() { val nonMatchingEtag = "\"${randomName}\"" val destinationBucketName = givenRandomBucketV1() val destinationKey = "copyOf/$sourceKey" - val copyObjectRequest = CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey) - .withNonmatchingETagConstraint(nonMatchingEtag) - s3Client.copyObject(copyObjectRequest) + + CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey) + .withNonmatchingETagConstraint(nonMatchingEtag).also { + s3Client.copyObject(it) + } s3Client.getObject(destinationBucketName, destinationKey).use { val copiedDigest = DigestUtil.hexDigest(it.objectContent) @@ -106,9 +112,11 @@ internal class CopyObjectV1IT : S3TestBase() { val nonMatchingEtag = "\"${randomName}\"" val destinationBucketName = givenRandomBucketV1() val destinationKey = "copyOf/$sourceKey" - val copyObjectRequest = CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey) - .withMatchingETagConstraint(nonMatchingEtag) - s3Client.copyObject(copyObjectRequest) + + CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey) + .withMatchingETagConstraint(nonMatchingEtag).also { + s3Client.copyObject(it) + } assertThatThrownBy { s3Client.getObject(destinationBucketName, destinationKey) @@ -126,9 +134,11 @@ internal class CopyObjectV1IT : S3TestBase() { val matchingEtag = "\"${putObjectResult.eTag}\"" val destinationBucketName = givenRandomBucketV1() val destinationKey = "copyOf/$sourceKey" - val copyObjectRequest = CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey) - .withNonmatchingETagConstraint(matchingEtag) - s3Client.copyObject(copyObjectRequest) + + CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey) + .withNonmatchingETagConstraint(matchingEtag).also { + s3Client.copyObject(it) + } assertThatThrownBy { s3Client.getObject(destinationBucketName, destinationKey) @@ -151,13 +161,15 @@ internal class CopyObjectV1IT : S3TestBase() { val objectMetadata = ObjectMetadata().apply { this.userMetadata = mapOf("test-key" to "test-value") } - val putObjectRequest = PutObjectRequest(bucketName, sourceKey, uploadFile).withMetadata(objectMetadata) - val putObjectResult = s3Client.putObject(putObjectRequest) + val putObjectResult = PutObjectRequest(bucketName, sourceKey, uploadFile).withMetadata(objectMetadata).let { + s3Client.putObject(it) + } //TODO: this is actually illegal on S3. when copying to the same key like this, S3 will throw: // This copy request is illegal because it is trying to copy an object to itself without // changing the object's metadata, storage class, website redirect location or encryption attributes. - val copyObjectRequest = CopyObjectRequest(bucketName, sourceKey, bucketName, sourceKey) - s3Client.copyObject(copyObjectRequest) + CopyObjectRequest(bucketName, sourceKey, bucketName, sourceKey).also { + s3Client.copyObject(it) + } s3Client.getObject(bucketName, sourceKey).use { val copiedObjectMetadata = it.objectMetadata @@ -182,20 +194,24 @@ internal class CopyObjectV1IT : S3TestBase() { val objectMetadata = ObjectMetadata().apply { this.userMetadata = mapOf("test-key" to "test-value") } - val putObjectRequest = PutObjectRequest(bucketName, sourceKey, uploadFile).withMetadata(objectMetadata) - val putObjectResult = s3Client.putObject(putObjectRequest) + val putObjectResult = PutObjectRequest(bucketName, sourceKey, uploadFile).withMetadata(objectMetadata).let { + s3Client.putObject(it) + } + val replaceObjectMetadata = ObjectMetadata().apply { this.userMetadata = mapOf("test-key2" to "test-value2") } - val copyObjectRequest = CopyObjectRequest() + CopyObjectRequest() .withSourceBucketName(bucketName) .withSourceKey(sourceKey) .withDestinationBucketName(bucketName) .withDestinationKey(sourceKey) .withMetadataDirective(MetadataDirective.REPLACE) .withNewObjectMetadata(replaceObjectMetadata) + .also { + s3Client.copyObject(it) + } - s3Client.copyObject(copyObjectRequest) s3Client.getObject(bucketName, sourceKey).use { val copiedObjectMetadata = it.objectMetadata @@ -224,10 +240,12 @@ internal class CopyObjectV1IT : S3TestBase() { val objectMetadata = ObjectMetadata().apply { this.addUserMetadata("key", "value") } - val copyObjectRequest = CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey).apply { + + CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey).apply { this.newObjectMetadata = objectMetadata + }.also { + s3Client.copyObject(it) } - s3Client.copyObject(copyObjectRequest) s3Client.getObject(destinationBucketName, destinationKey).use { val copiedDigest = DigestUtil.hexDigest(it.objectContent) @@ -253,12 +271,14 @@ internal class CopyObjectV1IT : S3TestBase() { val sourceObjectMetadata = ObjectMetadata().apply { this.addUserMetadata("key", "value") } - val putObjectRequest = PutObjectRequest(bucketName, sourceKey, uploadFile).apply { + val putObjectResult = PutObjectRequest(bucketName, sourceKey, uploadFile).apply { this.metadata = sourceObjectMetadata + }.let { + s3Client.putObject(it) + } + CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey).also { + s3Client.copyObject(it) } - val putObjectResult = s3Client.putObject(putObjectRequest) - val copyObjectRequest = CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey) - s3Client.copyObject(copyObjectRequest) s3Client.getObject(destinationBucketName, destinationKey).use { val copiedDigest = DigestUtil.hexDigest(it.objectContent) assertThat(copiedDigest).isEqualTo(putObjectResult.eTag) @@ -280,8 +300,9 @@ internal class CopyObjectV1IT : S3TestBase() { val destinationBucketName = givenRandomBucketV1() val destinationKey = "copyOf/some escape-worthy characters $@ $sourceKey" val putObjectResult = s3Client.putObject(PutObjectRequest(bucketName, sourceKey, uploadFile)) - val copyObjectRequest = CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey) - s3Client.copyObject(copyObjectRequest) + CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey).also { + s3Client.copyObject(it) + } s3Client.getObject(destinationBucketName, destinationKey).use { val copiedDigest = DigestUtil.hexDigest(it.objectContent) @@ -303,8 +324,9 @@ internal class CopyObjectV1IT : S3TestBase() { val destinationBucketName = givenRandomBucketV1() val destinationKey = "copyOf/$sourceKey" val putObjectResult = s3Client.putObject(PutObjectRequest(bucketName, sourceKey, uploadFile)) - val copyObjectRequest = CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey) - s3Client.copyObject(copyObjectRequest) + CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey).also { + s3Client.copyObject(it) + } s3Client.getObject(destinationBucketName, destinationKey).use { val copiedDigest = DigestUtil.hexDigest(it.objectContent) @@ -326,17 +348,20 @@ internal class CopyObjectV1IT : S3TestBase() { s3Client.putObject(PutObjectRequest(bucketName, sourceKey, uploadFile)) val destinationBucketName = givenRandomBucketV1() val destinationKey = "copyOf/$sourceKey" - val copyObjectRequest = CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey) + val copyObjectResult = CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey) .apply { this.sseAwsKeyManagementParams = SSEAwsKeyManagementParams(TEST_ENC_KEY_ID) + }.let { + s3Client.copyObject(it) } - val copyObjectResult = s3Client.copyObject(copyObjectRequest) - val metadata = s3Client.getObjectMetadata(destinationBucketName, destinationKey) + s3Client.getObjectMetadata(destinationBucketName, destinationKey).also { + assertThat(it.contentLength).isEqualTo(uploadFile.length()) + } - val uploadFileIs: InputStream = FileInputStream(uploadFile) - val uploadDigest = DigestUtil.hexDigest(TEST_ENC_KEY_ID, uploadFileIs) + val uploadDigest = FileInputStream(uploadFile).let { + DigestUtil.hexDigest(TEST_ENC_KEY_ID, it) + } assertThat(copyObjectResult.eTag).isEqualTo(uploadDigest) - assertThat(metadata.contentLength).isEqualTo(uploadFile.length()) } /** @@ -392,17 +417,17 @@ internal class CopyObjectV1IT : S3TestBase() { randomInputStream(contentLen), objectMetadata ) - val uploadResult = upload.waitForUploadResult() - assertThat(uploadResult.key).isEqualTo(assumedSourceKey) + val uploadResult = upload.waitForUploadResult().also { + assertThat(it.key).isEqualTo(assumedSourceKey) + } val assumedDestinationKey = UUID.randomUUID().toString() - val copy = transferManagerV1.copy( + transferManagerV1.copy( sourceBucket, assumedSourceKey, targetBucket, assumedDestinationKey - ) - - val copyResult = copy.waitForCopyResult() - assertThat(copyResult.destinationKey).isEqualTo(assumedDestinationKey) - assertThat(uploadResult.eTag).isEqualTo(copyResult.eTag) + ).waitForCopyResult().also { + assertThat(it.destinationKey).isEqualTo(assumedDestinationKey) + assertThat(uploadResult.eTag).isEqualTo(it.eTag) + } } } diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/CopyObjectV2IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/CopyObjectV2IT.kt index 043e6a1bd..eedf00e96 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/CopyObjectV2IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/CopyObjectV2IT.kt @@ -190,15 +190,13 @@ internal class CopyObjectV2IT : S3TestBase() { .build(), RequestBody.fromFile(uploadFile) ) - val headObject = s3ClientV2.headObject( + val sourceLastModified = s3ClientV2.headObject( HeadObjectRequest .builder() .bucket(bucketName) .key(sourceKey) .build() - ) - - val sourceLastModified = headObject.lastModified() + ).lastModified() await("wait until source object is 5 seconds old").until { sourceLastModified.plusSeconds(5).isBefore(Instant.now()) diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/CorsV2IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/CorsV2IT.kt index 1e402b2eb..7144c03b3 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/CorsV2IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/CorsV2IT.kt @@ -47,10 +47,11 @@ internal class CorsV2IT : S3TestBase() { val optionsRequest = HttpOptions("/${bucketName}/testObjectName").apply { this.addHeader("Origin", "http://localhost/") } - val optionsResponse: HttpResponse = httpclient.execute(HttpHost( + httpclient.execute(HttpHost( host, httpPort - ), optionsRequest) - assertThat(optionsResponse.getFirstHeader("Allow").value).contains("PUT") + ), optionsRequest).also { + assertThat(it.getFirstHeader("Allow").value).contains("PUT") + } val byteArray = UUID.randomUUID().toString().toByteArray() val expectedEtag = "\"${DigestUtil.hexDigest(byteArray)}\"" diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/CrtAsyncV2IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/CrtAsyncV2IT.kt index 49e43e298..edc1f471e 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/CrtAsyncV2IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/CrtAsyncV2IT.kt @@ -66,9 +66,10 @@ internal class CrtAsyncV2IT : S3TestBase() { AsyncRequestBody.fromFile(uploadFile) ).join() - val eTag = putObjectResponse.eTag() - assertThat(eTag).isNotBlank - assertThat(eTag).isEqualTo(expectedEtag) + putObjectResponse.eTag().also { + assertThat(it).isNotBlank + assertThat(it).isEqualTo(expectedEtag) + } } @Test @@ -83,23 +84,23 @@ internal class CrtAsyncV2IT : S3TestBase() { .build() ).join() - val putObjectResponse = autoS3CrtAsyncClientV2.putObject( + val eTag = autoS3CrtAsyncClientV2.putObject( PutObjectRequest.builder() .bucket(bucketName).key(UPLOAD_FILE_NAME).build(), AsyncRequestBody.fromFile(uploadFile) - ).join() - val eTag = putObjectResponse.eTag() + ).join().eTag() - val getObjectResponse = autoS3CrtAsyncClientV2.getObject( + autoS3CrtAsyncClientV2.getObject( GetObjectRequest .builder() .bucket(bucketName) .key(UPLOAD_FILE_NAME) .build(), AsyncResponseTransformer.toBytes() - ).join() - assertThat(getObjectResponse.response().eTag()).isEqualTo(eTag) - assertThat(getObjectResponse.response().contentLength()).isEqualTo(uploadFile.length()) + ).join().also { + assertThat(it.response().eTag()).isEqualTo(eTag) + assertThat(it.response().contentLength()).isEqualTo(uploadFile.length()) + } } @Test @@ -168,13 +169,13 @@ internal class CrtAsyncV2IT : S3TestBase() { ).join() val uploadFileBytes = readStreamIntoByteArray(uploadFile.inputStream()) - val allMd5s = ArrayUtils.addAll( + ArrayUtils.addAll( DigestUtils.md5(randomBytes), *DigestUtils.md5(uploadFileBytes) - ) - - // verify special etag - assertThat(completeMultipartUploadResponse.eTag()).isEqualTo("\"" + DigestUtils.md5Hex(allMd5s) + "-2" + "\"") + ).also { + // verify special etag + assertThat(completeMultipartUploadResponse.eTag()).isEqualTo("\"" + DigestUtils.md5Hex(it) + "-2" + "\"") + } // verify content size assertThat(getObjectResponse.response().contentLength()) @@ -195,7 +196,7 @@ internal class CrtAsyncV2IT : S3TestBase() { partNumber: Int, randomBytes: ByteArray ): String { - val uploadPartResponse = autoS3CrtAsyncClientV2 + return autoS3CrtAsyncClientV2 .uploadPart( UploadPartRequest.builder() .bucket(bucketName) @@ -205,7 +206,6 @@ internal class CrtAsyncV2IT : S3TestBase() { .contentLength(randomBytes.size.toLong()).build(), AsyncRequestBody.fromBytes(randomBytes) ).join() - - return uploadPartResponse.eTag() + .eTag() } } diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/GetPutDeleteObjectV1IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/GetPutDeleteObjectV1IT.kt index f497f2622..fd148f6fd 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/GetPutDeleteObjectV1IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/GetPutDeleteObjectV1IT.kt @@ -80,8 +80,9 @@ internal class GetPutDeleteObjectV1IT : S3TestBase() { .withChunkedEncodingDisabled(uploadChunked) .build() uploadClient.putObject(PutObjectRequest(bucketName, uploadFile.name, uploadFile)) - val s3Object = s3Client.getObject(bucketName, uploadFile.name) - verifyObjectContent(uploadFile, s3Object) + s3Client.getObject(bucketName, uploadFile.name).also { + verifyObjectContent(uploadFile, it) + } } /** @@ -97,8 +98,10 @@ internal class GetPutDeleteObjectV1IT : S3TestBase() { val weirdStuff = "$&_ .,':\u0001" // use only characters that are safe or need special handling val key = weirdStuff + uploadFile.name + weirdStuff s3Client.putObject(PutObjectRequest(bucketName, key, uploadFile)) - val s3Object = s3Client.getObject(bucketName, key) - verifyObjectContent(uploadFile, s3Object) + + s3Client.getObject(bucketName, key).also { + verifyObjectContent(uploadFile, it) + } } /** @@ -112,12 +115,14 @@ internal class GetPutDeleteObjectV1IT : S3TestBase() { val contentEncoding = "gzip" val resource = byteArrayOf(1, 2, 3, 4, 5) val inputStream = ByteArrayInputStream(resource) - val objectMetadata = ObjectMetadata() - objectMetadata.contentLength = resource.size.toLong() - objectMetadata.contentEncoding = contentEncoding + val objectMetadata = ObjectMetadata().apply { + this.contentLength = resource.size.toLong() + this.contentEncoding = contentEncoding + } val putObjectRequest = PutObjectRequest(bucketName, resourceId, inputStream, objectMetadata) - val upload = transferManagerV1.upload(putObjectRequest) - upload.waitForUploadResult() + transferManagerV1.upload(putObjectRequest).also { + it.waitForUploadResult() + } s3Client.getObject(bucketName, resourceId).use { assertThat(it.objectMetadata.contentEncoding).isEqualTo(contentEncoding) val uploadDigest = hexDigest(ByteArrayInputStream(resource)) @@ -136,18 +141,21 @@ internal class GetPutDeleteObjectV1IT : S3TestBase() { val bucketName = givenBucketV1(testInfo) val uploadFile = File(UPLOAD_FILE_NAME) val objectKey = UPLOAD_FILE_NAME - val metadata = ObjectMetadata() - metadata.addUserMetadata("key", "value") - val putObjectRequest = - PutObjectRequest(bucketName, objectKey, uploadFile).withMetadata(metadata) - putObjectRequest.sseAwsKeyManagementParams = - SSEAwsKeyManagementParams(TEST_ENC_KEY_ID) + val metadata = ObjectMetadata().apply { + this.addUserMetadata("key", "value") + } + val putObjectRequest = PutObjectRequest(bucketName, objectKey, uploadFile) + .withMetadata(metadata) + .apply { + this.sseAwsKeyManagementParams = SSEAwsKeyManagementParams(TEST_ENC_KEY_ID) + } s3Client.putObject(putObjectRequest) val getObjectMetadataRequest = GetObjectMetadataRequest(bucketName, objectKey) - val objectMetadata = s3Client.getObjectMetadata(getObjectMetadataRequest) - assertThat(objectMetadata.contentLength).isEqualTo(uploadFile.length()) - assertThat(objectMetadata.userMetadata).isEqualTo(metadata.userMetadata) - assertThat(objectMetadata.sseAwsKmsKeyId).isEqualTo(TEST_ENC_KEY_ID) + s3Client.getObjectMetadata(getObjectMetadataRequest).also { + assertThat(it.contentLength).isEqualTo(uploadFile.length()) + assertThat(it.userMetadata).isEqualTo(metadata.userMetadata) + assertThat(it.sseAwsKmsKeyId).isEqualTo(TEST_ENC_KEY_ID) + } } /** @@ -216,10 +224,11 @@ internal class GetPutDeleteObjectV1IT : S3TestBase() { this.withMetadata(objectMetadata) } ) - val metadataExisting = s3Client.getObjectMetadata(bucketName, UPLOAD_FILE_NAME) - assertThat(metadataExisting.contentEncoding).isEqualTo("gzip") - assertThat(metadataExisting.eTag).isEqualTo(putObjectResult.eTag) - assertThat(metadataExisting.userMetadata).isEqualTo(objectMetadata.userMetadata) + s3Client.getObjectMetadata(bucketName, UPLOAD_FILE_NAME).also { + assertThat(it.contentEncoding).isEqualTo("gzip") + assertThat(it.eTag).isEqualTo(putObjectResult.eTag) + assertThat(it.userMetadata).isEqualTo(objectMetadata.userMetadata) + } assertThatThrownBy { s3Client.getObjectMetadata( bucketName, @@ -313,11 +322,14 @@ internal class GetPutDeleteObjectV1IT : S3TestBase() { fun shouldUploadInParallel(testInfo: TestInfo) { val bucketName = givenBucketV1(testInfo) val uploadFile = File(UPLOAD_FILE_NAME) - val upload = transferManagerV1.upload(PutObjectRequest(bucketName, UPLOAD_FILE_NAME, uploadFile)) - val uploadResult = upload.waitForUploadResult() - assertThat(uploadResult.key).isEqualTo(UPLOAD_FILE_NAME) - val getResult = s3Client.getObject(bucketName, UPLOAD_FILE_NAME) - assertThat(getResult.key).isEqualTo(UPLOAD_FILE_NAME) + transferManagerV1.upload(PutObjectRequest(bucketName, UPLOAD_FILE_NAME, uploadFile)).also { upload -> + upload.waitForUploadResult().also { + assertThat(it.key).isEqualTo(UPLOAD_FILE_NAME) + } + } + s3Client.getObject(bucketName, UPLOAD_FILE_NAME).also { + assertThat(it.key).isEqualTo(UPLOAD_FILE_NAME) + } } /** @@ -334,30 +346,31 @@ internal class GetPutDeleteObjectV1IT : S3TestBase() { val smallRequestStartBytes = 1L val smallRequestEndBytes = 2L val downloadFile1 = File.createTempFile(UUID.randomUUID().toString(), null) - val download1 = transferManagerV1.download( + transferManagerV1.download( GetObjectRequest(bucketName, UPLOAD_FILE_NAME) .withRange(smallRequestStartBytes, smallRequestEndBytes), downloadFile1 - ) - download1.waitForCompletion() - assertThat(downloadFile1.length()).isEqualTo(smallRequestEndBytes) - assertThat(download1.objectMetadata.instanceLength).isEqualTo(uploadFile.length()) - assertThat(download1.objectMetadata.contentLength).isEqualTo(smallRequestEndBytes) + ).also { download -> + download.waitForCompletion() + assertThat(downloadFile1.length()).isEqualTo(smallRequestEndBytes) + assertThat(download.objectMetadata.instanceLength).isEqualTo(uploadFile.length()) + assertThat(download.objectMetadata.contentLength).isEqualTo(smallRequestEndBytes) + } val largeRequestStartBytes = 0L val largeRequestEndBytes = 1000L val downloadFile2 = File.createTempFile(UUID.randomUUID().toString(), null) - val download2 = transferManagerV1 + transferManagerV1 .download( GetObjectRequest(bucketName, UPLOAD_FILE_NAME).withRange(largeRequestStartBytes, largeRequestEndBytes), downloadFile2 - ) - download2.waitForCompletion() - assertThat(downloadFile2.length()).isEqualTo(min(uploadFile.length(), largeRequestEndBytes + 1)) - assertThat(download2.objectMetadata.instanceLength).isEqualTo(uploadFile.length()) - assertThat(download2.objectMetadata.contentLength).isEqualTo(min(uploadFile.length(), largeRequestEndBytes + 1)) - assertThat(download2.objectMetadata.contentRange) - .containsExactlyElementsOf(listOf(largeRequestStartBytes, min(uploadFile.length()-1, largeRequestEndBytes))) - + ).also { download -> + download.waitForCompletion() + assertThat(downloadFile2.length()).isEqualTo(min(uploadFile.length(), largeRequestEndBytes + 1)) + assertThat(download.objectMetadata.instanceLength).isEqualTo(uploadFile.length()) + assertThat(download.objectMetadata.contentLength).isEqualTo(min(uploadFile.length(), largeRequestEndBytes + 1)) + assertThat(download.objectMetadata.contentRange) + .containsExactlyElementsOf(listOf(largeRequestStartBytes, min(uploadFile.length() - 1, largeRequestEndBytes))) + } } @Test @@ -369,10 +382,11 @@ internal class GetPutDeleteObjectV1IT : S3TestBase() { val expectedEtag = hexDigest(uploadFileIs) assertThat(putObjectResult.eTag).isEqualTo(expectedEtag) - val s3ObjectWithEtag = s3Client.getObject(GetObjectRequest(bucketName, UPLOAD_FILE_NAME) - .withMatchingETagConstraint("\"${putObjectResult.eTag}\"")) - //v1 SDK does not return ETag on GetObject. Can only check if response is returned here. - assertThat(s3ObjectWithEtag.objectContent).isNotNull + s3Client.getObject(GetObjectRequest(bucketName, UPLOAD_FILE_NAME) + .withMatchingETagConstraint("\"${putObjectResult.eTag}\"")).also { + //v1 SDK does not return ETag on GetObject. Can only check if response is returned here. + assertThat(it.objectContent).isNotNull + } } @Test @@ -385,10 +399,11 @@ internal class GetPutDeleteObjectV1IT : S3TestBase() { assertThat(putObjectResult.eTag).isEqualTo(expectedEtag) val nonMatchingEtag = "\"$randomName\"" - val s3ObjectWithEtag = s3Client.getObject(GetObjectRequest(bucketName, UPLOAD_FILE_NAME) - .withMatchingETagConstraint(nonMatchingEtag)) - //v1 SDK does not return a 412 error on a non-matching GetObject. Check if response is null. - assertThat(s3ObjectWithEtag).isNull() + s3Client.getObject(GetObjectRequest(bucketName, UPLOAD_FILE_NAME) + .withMatchingETagConstraint(nonMatchingEtag)).also { + //v1 SDK does not return a 412 error on a non-matching GetObject. Check if response is null. + assertThat(it).isNull() + } } @Test @@ -401,10 +416,11 @@ internal class GetPutDeleteObjectV1IT : S3TestBase() { assertThat(putObjectResult.eTag).isEqualTo(expectedEtag) val nonMatchingEtag = "\"$randomName\"" - val s3ObjectWithEtag = s3Client.getObject(GetObjectRequest(bucketName, UPLOAD_FILE_NAME) - .withNonmatchingETagConstraint(nonMatchingEtag)) - //v1 SDK does not return ETag on GetObject. Can only check if response is returned here. - assertThat(s3ObjectWithEtag.objectContent).isNotNull + s3Client.getObject(GetObjectRequest(bucketName, UPLOAD_FILE_NAME) + .withNonmatchingETagConstraint(nonMatchingEtag)).also { + //v1 SDK does not return ETag on GetObject. Can only check if response is returned here. + assertThat(it.objectContent).isNotNull + } } @Test @@ -444,17 +460,18 @@ internal class GetPutDeleteObjectV1IT : S3TestBase() { val resourceUrl = s3Client.generatePresignedUrl(presignedUrlRequest) HttpClients.createDefault().use { val getObject = HttpGet(resourceUrl.toString()) - val getObjectResponse: HttpResponse = it.execute( + it.execute( HttpHost( host, httpPort ), getObject - ) - assertThat(getObjectResponse.getFirstHeader(Headers.CACHE_CONTROL).value).isEqualTo("cacheControl") - assertThat(getObjectResponse.getFirstHeader(Headers.CONTENT_DISPOSITION).value).isEqualTo("contentDisposition") - assertThat(getObjectResponse.getFirstHeader(Headers.CONTENT_ENCODING).value).isEqualTo("contentEncoding") - assertThat(getObjectResponse.getFirstHeader(Headers.CONTENT_LANGUAGE).value).isEqualTo("contentLanguage") - assertThat(getObjectResponse.getFirstHeader(Headers.CONTENT_TYPE).value).isEqualTo("my/contentType") - assertThat(getObjectResponse.getFirstHeader(Headers.EXPIRES).value).isEqualTo("expires") + ).also { response -> + assertThat(response.getFirstHeader(Headers.CACHE_CONTROL).value).isEqualTo("cacheControl") + assertThat(response.getFirstHeader(Headers.CONTENT_DISPOSITION).value).isEqualTo("contentDisposition") + assertThat(response.getFirstHeader(Headers.CONTENT_ENCODING).value).isEqualTo("contentEncoding") + assertThat(response.getFirstHeader(Headers.CONTENT_LANGUAGE).value).isEqualTo("contentLanguage") + assertThat(response.getFirstHeader(Headers.CONTENT_TYPE).value).isEqualTo("my/contentType") + assertThat(response.getFirstHeader(Headers.EXPIRES).value).isEqualTo("expires") + } } } } diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/GetPutDeleteObjectV2IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/GetPutDeleteObjectV2IT.kt index dfe8e3435..acd04ade6 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/GetPutDeleteObjectV2IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/GetPutDeleteObjectV2IT.kt @@ -96,14 +96,14 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { val key = UPLOAD_FILE_NAME - val putObjectResponse = s3ClientV2.putObject( + val eTag = s3ClientV2.putObject( PutObjectRequest.builder() .bucket(bucketName) .key(key) .storageClass(storageClass) .build(), RequestBody.fromFile(uploadFile) - ) + ).eTag() s3ClientV2.headObject( HeadObjectRequest.builder() @@ -111,7 +111,7 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { .key(key) .build() ).also { - assertThat(it.eTag()).isEqualTo(putObjectResponse.eTag()) + assertThat(it.eTag()).isEqualTo(eTag) } s3ClientV2.getObject( @@ -120,7 +120,7 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { .key(key) .build() ).use { - assertThat(it.response().eTag()).isEqualTo(putObjectResponse.eTag()) + assertThat(it.response().eTag()).isEqualTo(eTag) } } @@ -132,9 +132,10 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { val expectedEtag = "\"${DigestUtil.hexDigest(uploadFileIs)}\"" val (_, putObjectResponse) = givenBucketAndObjectV2(testInfo, UPLOAD_FILE_NAME) - val eTag = putObjectResponse.eTag() - assertThat(eTag).isNotBlank - assertThat(eTag).isEqualTo(expectedEtag) + putObjectResponse.eTag().also { + assertThat(it).isNotBlank + assertThat(it).isEqualTo(expectedEtag) + } } @Test @@ -144,17 +145,15 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { val expectedChecksum = DigestUtil.checksumFor(uploadFile.toPath(), Algorithm.SHA1) val bucketName = givenBucketV2(testInfo) - val putObjectResponse = s3ClientV2.putObject( + val eTag = s3ClientV2.putObject( PutObjectRequest.builder() .bucket(bucketName).key(UPLOAD_FILE_NAME) .checksumAlgorithm(ChecksumAlgorithm.SHA1) .build(), RequestBody.fromFile(uploadFile) - ) - - val eTag = putObjectResponse.eTag() + ).eTag() - val objectAttributes = s3ClientV2.getObjectAttributes( + s3ClientV2.getObjectAttributes( GetObjectAttributesRequest.builder() .bucket(bucketName) .key(UPLOAD_FILE_NAME) @@ -164,12 +163,12 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { ObjectAttributes.E_TAG, ObjectAttributes.CHECKSUM) .build() - ) - - assertThat(objectAttributes.eTag()).isEqualTo(eTag) - assertThat(objectAttributes.storageClass()).isEqualTo(StorageClass.STANDARD) - assertThat(objectAttributes.objectSize()).isEqualTo(File(UPLOAD_FILE_NAME).length()) - assertThat(objectAttributes.checksum().checksumSHA1()).isEqualTo(expectedChecksum) + ).also { + assertThat(it.eTag()).isEqualTo(eTag) + assertThat(it.storageClass()).isEqualTo(StorageClass.STANDARD) + assertThat(it.objectSize()).isEqualTo(File(UPLOAD_FILE_NAME).length()) + assertThat(it.checksum().checksumSHA1()).isEqualTo(expectedChecksum) + } } @S3VerifiedTodo @@ -269,6 +268,28 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { } } + @Test + @S3VerifiedTodo + fun testPutObject_wrongChecksum(testInfo: TestInfo) { + val uploadFile = File(UPLOAD_FILE_NAME) + val expectedChecksum = "wrongChecksum" + val checksumAlgorithm = ChecksumAlgorithm.SHA1 + val bucketName = givenBucketV2(testInfo) + + assertThatThrownBy { + s3ClientV2.putObject( + PutObjectRequest + .builder() + .checksum(expectedChecksum, checksumAlgorithm) + .bucket(bucketName).key(UPLOAD_FILE_NAME) + .build(), + RequestBody.fromFile(uploadFile) + ) + } + .isInstanceOf(S3Exception::class.java) + .hasMessageContaining("The Content-MD5 or checksum value that you specified did not match what the server received.") + } + /** * Safe characters: * https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html @@ -279,15 +300,15 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { val uploadFile = File(UPLOAD_FILE_NAME) val bucketName = givenBucketV2(testInfo) - val key = "someKey!-_.*'()" //safe characters as per S3 API + val key = "someKey${charsSafeKey()}" - val putObjectResponse = s3ClientV2.putObject( + val eTag = s3ClientV2.putObject( PutObjectRequest.builder() .bucket(bucketName) .key(key) .build(), RequestBody.fromFile(uploadFile) - ) + ).eTag() s3ClientV2.headObject( HeadObjectRequest.builder() @@ -295,7 +316,7 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { .key(key) .build() ).also { - assertThat(it.eTag()).isEqualTo(putObjectResponse.eTag()) + assertThat(it.eTag()).isEqualTo(eTag) } s3ClientV2.getObject( @@ -304,7 +325,7 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { .key(key) .build() ).use { - assertThat(putObjectResponse.eTag()).isEqualTo(it.response().eTag()) + assertThat(eTag).isEqualTo(it.response().eTag()) } } @@ -318,15 +339,15 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { val uploadFile = File(UPLOAD_FILE_NAME) val bucketName = givenBucketV2(testInfo) - val key = "someKey&$@=;/:+ ,?" //safe characters as per S3 API + val key = "someKey${charsSpecialKey()}" - val putObjectResponse = s3ClientV2.putObject( + val eTag = s3ClientV2.putObject( PutObjectRequest.builder() .bucket(bucketName) .key(key) .build(), RequestBody.fromFile(uploadFile) - ) + ).eTag() s3ClientV2.headObject( HeadObjectRequest.builder() @@ -334,7 +355,7 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { .key(key) .build() ).also { - assertThat(it.eTag()).isEqualTo(putObjectResponse.eTag()) + assertThat(it.eTag()).isEqualTo(eTag) } s3ClientV2.getObject( @@ -343,7 +364,7 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { .key(key) .build() ).use { - assertThat(putObjectResponse.eTag()).isEqualTo(it.response().eTag()) + assertThat(eTag).isEqualTo(it.response().eTag()) } } @@ -395,30 +416,30 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { .build(), RequestBody.fromFile(uploadFile)) - val getObjectResponseResponse = getObjectV2(bucket, UPLOAD_FILE_NAME) + getObjectV2(bucket, UPLOAD_FILE_NAME).also { + assertThat(it.response().contentDisposition()).isEqualTo(contentDisposition) + assertThat(it.response().contentEncoding()).isEqualTo(encoding) + // time in second precision, see + // https://www.rfc-editor.org/rfc/rfc7234#section-5.3 + // https://www.rfc-editor.org/rfc/rfc7231#section-7.1.1.1 + assertThat(it.response().expires()).isEqualTo(expires.truncatedTo(ChronoUnit.SECONDS)) + assertThat(it.response().contentLanguage()).isEqualTo(contentLanguage) + assertThat(it.response().cacheControl()).isEqualTo(cacheControl) + } - assertThat(getObjectResponseResponse.response().contentDisposition()) - .isEqualTo(contentDisposition) - assertThat(getObjectResponseResponse.response().contentEncoding()) - .isEqualTo(encoding) - // time in second precision, see - // https://www.rfc-editor.org/rfc/rfc7234#section-5.3 - // https://www.rfc-editor.org/rfc/rfc7231#section-7.1.1.1 - assertThat(getObjectResponseResponse.response().expires()).isEqualTo(expires.truncatedTo(ChronoUnit.SECONDS)) - assertThat(getObjectResponseResponse.response().contentLanguage()).isEqualTo(contentLanguage) - assertThat(getObjectResponseResponse.response().cacheControl()).isEqualTo(cacheControl) - val headObjectResponse = s3ClientV2.headObject( + s3ClientV2.headObject( HeadObjectRequest.builder() .bucket(bucket) .key(UPLOAD_FILE_NAME) .build() - ) - assertThat(headObjectResponse.contentDisposition()).isEqualTo(contentDisposition) - assertThat(headObjectResponse.contentEncoding()).isEqualTo(encoding) - assertThat(headObjectResponse.expires()).isEqualTo(expires.truncatedTo(ChronoUnit.SECONDS)) - assertThat(headObjectResponse.contentLanguage()).isEqualTo(contentLanguage) - assertThat(headObjectResponse.cacheControl()).isEqualTo(cacheControl) + ).also { + assertThat(it.contentDisposition()).isEqualTo(contentDisposition) + assertThat(it.contentEncoding()).isEqualTo(encoding) + assertThat(it.expires()).isEqualTo(expires.truncatedTo(ChronoUnit.SECONDS)) + assertThat(it.contentLanguage()).isEqualTo(contentLanguage) + assertThat(it.cacheControl()).isEqualTo(cacheControl) + } } @Test @@ -429,8 +450,10 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { val matchingEtag = "\"${DigestUtil.hexDigest(uploadFileIs)}\"" val (bucketName, putObjectResponse) = givenBucketAndObjectV2(testInfo, UPLOAD_FILE_NAME) - val eTag = putObjectResponse.eTag() - assertThat(eTag).isEqualTo(matchingEtag) + val eTag = putObjectResponse.eTag().also { + assertThat(it).isEqualTo(matchingEtag) + } + s3ClientV2.getObject( GetObjectRequest.builder() .bucket(bucketName) @@ -489,17 +512,19 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { val nonMatchingEtag = "\"$randomName\"" val (bucketName, putObjectResponse) = givenBucketAndObjectV2(testInfo, UPLOAD_FILE_NAME) - val eTag = putObjectResponse.eTag() - assertThat(eTag).isEqualTo(expectedEtag) + val eTag = putObjectResponse.eTag().also { + assertThat(it).isEqualTo(expectedEtag) + } - val headObjectResponse = s3ClientV2.headObject( + s3ClientV2.headObject( HeadObjectRequest.builder() .bucket(bucketName) .key(UPLOAD_FILE_NAME) .ifNoneMatch(nonMatchingEtag) .build() - ) - assertThat(headObjectResponse.eTag()).isEqualTo(eTag) + ).also { + assertThat(it.eTag()).isEqualTo(eTag) + } } @Test @@ -512,8 +537,9 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { val nonMatchingEtag = "\"*\"" val (bucketName, putObjectResponse) = givenBucketAndObjectV2(testInfo, UPLOAD_FILE_NAME) - val eTag = putObjectResponse.eTag() - assertThat(eTag).isEqualTo(expectedEtag) + putObjectResponse.eTag().also { + assertThat(it).isEqualTo(expectedEtag) + } assertThatThrownBy { s3ClientV2.headObject( @@ -530,15 +556,15 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { @Test @S3VerifiedSuccess(year = 2022) fun testHeadObject_failureWithMatchEtag(testInfo: TestInfo) { - val uploadFile = File(UPLOAD_FILE_NAME) - val uploadFileIs: InputStream = FileInputStream(uploadFile) - val expectedEtag = "\"${DigestUtil.hexDigest(uploadFileIs)}\"" + val expectedEtag = FileInputStream(File(UPLOAD_FILE_NAME)) + .let {"\"${DigestUtil.hexDigest(it)}\""} val nonMatchingEtag = "\"$randomName\"" val (bucketName, putObjectResponse) = givenBucketAndObjectV2(testInfo, UPLOAD_FILE_NAME) - val eTag = putObjectResponse.eTag() - assertThat(eTag).isEqualTo(expectedEtag) + putObjectResponse.eTag().also { + assertThat(it).isEqualTo(expectedEtag) + } assertThatThrownBy { s3ClientV2.headObject( @@ -561,17 +587,18 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { val smallRequestStartBytes = 1L val smallRequestEndBytes = 2L - val smallObject = s3ClientV2.getObject( + s3ClientV2.getObject( GetObjectRequest.builder() .bucket(bucketName) .key(UPLOAD_FILE_NAME) .ifMatch(eTag) .range("bytes=$smallRequestStartBytes-$smallRequestEndBytes") .build() - ) - assertThat(smallObject.response().contentLength()).isEqualTo(smallRequestEndBytes) - assertThat(smallObject.response().contentRange()) - .isEqualTo("bytes $smallRequestStartBytes-$smallRequestEndBytes/${uploadFile.length()}") + ).also { + assertThat(it.response().contentLength()).isEqualTo(smallRequestEndBytes) + assertThat(it.response().contentRange()) + .isEqualTo("bytes $smallRequestStartBytes-$smallRequestEndBytes/${uploadFile.length()}") + } val largeRequestStartBytes = 0L val largeRequestEndBytes = 1000L @@ -643,7 +670,7 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { val sseCustomerKey = "someCustomerKey" val sseCustomerKeyMD5 = "someCustomerKeyMD5" val ssekmsEncryptionContext = "someEncryptionContext" - val putObject = s3ClientV2.putObject( + s3ClientV2.putObject( PutObjectRequest.builder() .bucket(bucketName) .key(UPLOAD_FILE_NAME) @@ -655,12 +682,13 @@ internal class GetPutDeleteObjectV2IT : S3TestBase() { .serverSideEncryption(ServerSideEncryption.AWS_KMS) .build(), RequestBody.fromFile(uploadFile) - ) + ).also { + assertThat(it.ssekmsKeyId()).isEqualTo(TEST_ENC_KEY_ID) + assertThat(it.sseCustomerAlgorithm()).isEqualTo(sseCustomerAlgorithm) + assertThat(it.sseCustomerKeyMD5()).isEqualTo(sseCustomerKeyMD5) + assertThat(it.serverSideEncryption()).isEqualTo(ServerSideEncryption.AWS_KMS) + } - assertThat(putObject.ssekmsKeyId()).isEqualTo(TEST_ENC_KEY_ID) - assertThat(putObject.sseCustomerAlgorithm()).isEqualTo(sseCustomerAlgorithm) - assertThat(putObject.sseCustomerKeyMD5()).isEqualTo(sseCustomerKeyMD5) - assertThat(putObject.serverSideEncryption()).isEqualTo(ServerSideEncryption.AWS_KMS) s3ClientV2.getObject( GetObjectRequest.builder() diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/LegalHoldV2IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/LegalHoldV2IT.kt index c71fee9c2..eacd98a33 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/LegalHoldV2IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/LegalHoldV2IT.kt @@ -106,13 +106,14 @@ internal class LegalHoldV2IT : S3TestBase() { .build() ) - val objectLegalHold = s3ClientV2.getObjectLegalHold( + s3ClientV2.getObjectLegalHold( GetObjectLegalHoldRequest .builder() .bucket(bucketName) .key(sourceKey) .build() - ) - assertThat(objectLegalHold.legalHold().status()).isEqualTo(ObjectLockLegalHoldStatus.ON) + ).also { + assertThat(it.legalHold().status()).isEqualTo(ObjectLockLegalHoldStatus.ON) + } } } diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectV1IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectV1IT.kt index 6ae36e73c..2a015ebeb 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectV1IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectV1IT.kt @@ -176,10 +176,12 @@ internal class ListObjectV1IT : S3TestBase() { val key = "$prefix$weirdStuff${uploadFile.name}$weirdStuff" s3Client.putObject(PutObjectRequest(bucketName, key, uploadFile)) - val listing = s3Client.listObjects(bucketName, prefix) - val summaries = listing.objectSummaries - assertThat(summaries).hasSize(1) - assertThat(summaries[0].key).isEqualTo(key) + s3Client.listObjects(bucketName, prefix).also { listing -> + listing.objectSummaries.also { + assertThat(it).hasSize(1) + assertThat(it[0].key).isEqualTo(key) + } + } } /** @@ -205,9 +207,10 @@ internal class ListObjectV1IT : S3TestBase() { } val listing = s3Client.listObjectsV2(request) - val summaries = listing.objectSummaries - assertThat(summaries).hasSize(1) - assertThat(summaries[0].key).isEqualTo(key) + listing.objectSummaries.also { + assertThat(it).hasSize(1) + assertThat(it[0].key).isEqualTo(key) + } } @@ -232,10 +235,12 @@ internal class ListObjectV1IT : S3TestBase() { this.encodingType = "url" } - val listing = s3Client.listObjects(lor) - val summaries = listing.objectSummaries - assertThat(summaries).hasSize(1) - assertThat(summaries[0].key).isEqualTo("shouldHonorEncodingType/%01") + s3Client.listObjects(lor).also { listing -> + listing.objectSummaries.also { + assertThat(it).hasSize(1) + assertThat(it[0].key).isEqualTo("shouldHonorEncodingType/%01") + } + } } /** @@ -255,10 +260,12 @@ internal class ListObjectV1IT : S3TestBase() { this.encodingType = "url" } - val listing = s3Client.listObjectsV2(request) - val summaries = listing.objectSummaries - assertThat(summaries).hasSize(1) - assertThat(summaries[0].key).isEqualTo("shouldHonorEncodingType/\u0001") + s3Client.listObjectsV2(request).also { listing -> + listing.objectSummaries.also { + assertThat(it).hasSize(1) + assertThat(it[0].key).isEqualTo("shouldHonorEncodingType/\u0001") + } + } } @Test @@ -268,9 +275,10 @@ internal class ListObjectV1IT : S3TestBase() { val uploadFile = File(UPLOAD_FILE_NAME) s3Client.putObject(PutObjectRequest(bucketName, UPLOAD_FILE_NAME, uploadFile)) - val objectListingResult = s3Client.listObjects(bucketName, UPLOAD_FILE_NAME) - assertThat(objectListingResult.objectSummaries).hasSizeGreaterThan(0) - assertThat(objectListingResult.objectSummaries[0].key).isEqualTo(UPLOAD_FILE_NAME) + s3Client.listObjects(bucketName, UPLOAD_FILE_NAME).also { + assertThat(it.objectSummaries).hasSizeGreaterThan(0) + assertThat(it.objectSummaries[0].key).isEqualTo(UPLOAD_FILE_NAME) + } } /** diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectV1MaxKeysIT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectV1MaxKeysIT.kt index af885df07..46ae69afa 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectV1MaxKeysIT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectV1MaxKeysIT.kt @@ -30,9 +30,10 @@ internal class ListObjectV1MaxKeysIT : S3TestBase() { val bucketName = givenBucketWithTwoObjects(testInfo) val request = ListObjectsRequest().withBucketName(bucketName).withMaxKeys(1) - val objectListing = s3Client.listObjects(request) - assertThat(objectListing.objectSummaries).hasSize(1) - assertThat(objectListing.maxKeys).isEqualTo(1) + s3Client.listObjects(request).also { + assertThat(it.objectSummaries).hasSize(1) + assertThat(it.maxKeys).isEqualTo(1) + } } @Test @@ -41,9 +42,10 @@ internal class ListObjectV1MaxKeysIT : S3TestBase() { val bucketName = givenBucketWithTwoObjects(testInfo) val request = ListObjectsRequest().withBucketName(bucketName) - val objectListing = s3Client.listObjects(request) - assertThat(objectListing.objectSummaries).hasSize(2) - assertThat(objectListing.maxKeys).isEqualTo(1000) + s3Client.listObjects(request).also { + assertThat(it.objectSummaries).hasSize(2) + assertThat(it.maxKeys).isEqualTo(1000) + } } @Test @@ -52,9 +54,10 @@ internal class ListObjectV1MaxKeysIT : S3TestBase() { val bucketName = givenBucketWithTwoObjects(testInfo) val request = ListObjectsRequest().withBucketName(bucketName).withMaxKeys(2) - val objectListing = s3Client.listObjects(request) - assertThat(objectListing.objectSummaries).hasSize(2) - assertThat(objectListing.maxKeys).isEqualTo(2) + s3Client.listObjects(request).also { + assertThat(it.objectSummaries).hasSize(2) + assertThat(it.maxKeys).isEqualTo(2) + } } @Test @@ -63,9 +66,10 @@ internal class ListObjectV1MaxKeysIT : S3TestBase() { val bucketName = givenBucketWithTwoObjects(testInfo) val request = ListObjectsRequest().withBucketName(bucketName).withMaxKeys(3) - val objectListing = s3Client.listObjects(request) - assertThat(objectListing.objectSummaries).hasSize(2) - assertThat(objectListing.maxKeys).isEqualTo(3) + s3Client.listObjects(request).also { + assertThat(it.objectSummaries).hasSize(2) + assertThat(it.maxKeys).isEqualTo(3) + } } @Test @@ -74,9 +78,10 @@ internal class ListObjectV1MaxKeysIT : S3TestBase() { val bucketName = givenBucketWithTwoObjects(testInfo) val request = ListObjectsRequest().withBucketName(bucketName).withMaxKeys(0) - val objectListing = s3Client.listObjects(request) - assertThat(objectListing.objectSummaries).hasSize(0) - assertThat(objectListing.maxKeys).isEqualTo(0) + s3Client.listObjects(request).also { + assertThat(it.objectSummaries).hasSize(0) + assertThat(it.maxKeys).isEqualTo(0) + } } @Test @@ -84,11 +89,11 @@ internal class ListObjectV1MaxKeysIT : S3TestBase() { fun returnsAllObjectsIfMaxKeysIsNegative(testInfo: TestInfo) { val bucketName = givenBucketWithTwoObjects(testInfo) val request = ListObjectsRequest().withBucketName(bucketName).withMaxKeys(-1) - val objectListing = s3Client.listObjects(request) - - // Apparently, the Amazon SDK rejects negative max keys, and by default it's 1000 - assertThat(objectListing.objectSummaries).hasSize(2) - assertThat(objectListing.maxKeys).isEqualTo(1000) + s3Client.listObjects(request).also { + // Apparently, the Amazon SDK rejects negative max keys, and by default it's 1000 + assertThat(it.objectSummaries).hasSize(2) + assertThat(it.maxKeys).isEqualTo(1000) + } } private fun givenBucketWithTwoObjects(testInfo: TestInfo): String { diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectV1PaginationIT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectV1PaginationIT.kt index 7e5d5d19a..c045f95f3 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectV1PaginationIT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectV1PaginationIT.kt @@ -30,16 +30,18 @@ internal class ListObjectV1PaginationIT : S3TestBase() { val bucketName = givenBucketWithTwoObjects(testInfo) val request = ListObjectsRequest().withBucketName(bucketName).withMaxKeys(1) - val objectListing = s3Client.listObjects(request) - assertThat(objectListing.objectSummaries).hasSize(1) - assertThat(objectListing.maxKeys).isEqualTo(1) - assertThat(objectListing.nextMarker).isEqualTo("a") - assertThat(objectListing.isTruncated).isTrue + val objectListing = s3Client.listObjects(request).also { + assertThat(it.objectSummaries).hasSize(1) + assertThat(it.maxKeys).isEqualTo(1) + assertThat(it.nextMarker).isEqualTo("a") + assertThat(it.isTruncated).isTrue + } val continueRequest = ListObjectsRequest().withBucketName(bucketName).withMarker(objectListing.nextMarker) - val continueObjectListing = s3Client.listObjects(continueRequest) - assertThat(continueObjectListing.objectSummaries.size).isEqualTo(1) - assertThat(continueObjectListing.objectSummaries[0].key).isEqualTo("b") + s3Client.listObjects(continueRequest).also { + assertThat(it.objectSummaries.size).isEqualTo(1) + assertThat(it.objectSummaries[0].key).isEqualTo("b") + } } private fun givenBucketWithTwoObjects(testInfo: TestInfo): String { diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectV2IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectV2IT.kt index a5b18a007..431e54e1e 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectV2IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectV2IT.kt @@ -55,19 +55,19 @@ internal class ListObjectV2IT : S3TestBase() { RequestBody.fromFile(uploadFile) ) - val listObjectsV2Response = s3ClientV2.listObjectsV2( + s3ClientV2.listObjectsV2( ListObjectsV2Request.builder() .bucket(bucketName) .build() - ) - - assertThat(listObjectsV2Response.contents()) - .hasSize(2) - .extracting(S3Object::checksumAlgorithm) - .containsOnly( - Tuple(arrayListOf(ChecksumAlgorithm.SHA256)), - Tuple(arrayListOf(ChecksumAlgorithm.SHA256)) - ) + ).also { + assertThat(it.contents()) + .hasSize(2) + .extracting(S3Object::checksumAlgorithm) + .containsOnly( + Tuple(arrayListOf(ChecksumAlgorithm.SHA256)), + Tuple(arrayListOf(ChecksumAlgorithm.SHA256)) + ) + } } @Test @@ -92,19 +92,19 @@ internal class ListObjectV2IT : S3TestBase() { RequestBody.fromFile(uploadFile) ) - val listObjectsResponse = s3ClientV2.listObjects( + s3ClientV2.listObjects( ListObjectsRequest.builder() .bucket(bucketName) .build() - ) - - assertThat(listObjectsResponse.contents()) - .hasSize(2) - .extracting(S3Object::checksumAlgorithm) - .containsOnly( - Tuple(arrayListOf(ChecksumAlgorithm.SHA256)), - Tuple(arrayListOf(ChecksumAlgorithm.SHA256)) - ) + ).also { + assertThat(it.contents()) + .hasSize(2) + .extracting(S3Object::checksumAlgorithm) + .containsOnly( + Tuple(arrayListOf(ChecksumAlgorithm.SHA256)), + Tuple(arrayListOf(ChecksumAlgorithm.SHA256)) + ) + } } /** @@ -128,17 +128,19 @@ internal class ListObjectV2IT : S3TestBase() { RequestBody.fromFile(uploadFile) ) - val listing = s3ClientV2.listObjectsV2( + s3ClientV2.listObjectsV2( ListObjectsV2Request .builder() .bucket(bucketName) .prefix(prefix) .encodingType(EncodingType.URL) .build() - ) - val summaries = listing.contents() - assertThat(summaries).hasSize(1) - assertThat(summaries[0].key()).isEqualTo(key) + ).also { listing -> + listing.contents().also { + assertThat(it).hasSize(1) + assertThat(it[0].key()).isEqualTo(key) + } + } } } diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectVersionsV2IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectVersionsV2IT.kt index 11415fef7..e80da6620 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectVersionsV2IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ListObjectVersionsV2IT.kt @@ -52,18 +52,18 @@ internal class ListObjectVersionsV2IT : S3TestBase() { RequestBody.fromFile(uploadFile) ) - val listObjectVersionsResponse = s3ClientV2.listObjectVersions( + s3ClientV2.listObjectVersions( ListObjectVersionsRequest.builder() .bucket(bucketName) .build() - ) - - assertThat(listObjectVersionsResponse.versions()) - .hasSize(2) - .extracting(ObjectVersion::checksumAlgorithm) - .containsOnly( - Tuple(arrayListOf(ChecksumAlgorithm.SHA256)), - Tuple(arrayListOf(ChecksumAlgorithm.SHA256)) - ) + ).also { + assertThat(it.versions()) + .hasSize(2) + .extracting(ObjectVersion::checksumAlgorithm) + .containsOnly( + Tuple(arrayListOf(ChecksumAlgorithm.SHA256)), + Tuple(arrayListOf(ChecksumAlgorithm.SHA256)) + ) + } } } diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/MultiPartUploadV1IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/MultiPartUploadV1IT.kt index 19533baf8..148f90228 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/MultiPartUploadV1IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/MultiPartUploadV1IT.kt @@ -83,10 +83,11 @@ internal class MultiPartUploadV1IT : S3TestBase() { ) ) - val metadataExisting = s3Client.getObjectMetadata( + s3Client.getObjectMetadata( initiateMultipartUploadResult.bucketName, initiateMultipartUploadResult.key - ) - assertThat(metadataExisting.userMetadata).isEqualTo(objectMetadata.userMetadata) + ).also { + assertThat(it.userMetadata).isEqualTo(objectMetadata.userMetadata) + } } /** @@ -130,13 +131,14 @@ internal class MultiPartUploadV1IT : S3TestBase() { ) // Verify only 1st and 3rd counts val uploadFileBytes = readStreamIntoByteArray(uploadFile.inputStream()) - val allMd5s = ArrayUtils.addAll( + ArrayUtils.addAll( DigestUtils.md5(randomBytes), *DigestUtils.md5(uploadFileBytes) - ) + ).also { + // verify special etag + assertThat(completeMultipartUpload.eTag).isEqualTo(DigestUtils.md5Hex(it) + "-2") + } - // verify special etag - assertThat(completeMultipartUpload.eTag).isEqualTo(DigestUtils.md5Hex(allMd5s) + "-2") s3Client.getObject(bucketName, UPLOAD_FILE_NAME).use { // verify content size @@ -176,18 +178,20 @@ internal class MultiPartUploadV1IT : S3TestBase() { .withLastPart(true) ) - val listPartsRequest = ListPartsRequest( + ListPartsRequest( bucketName, key, uploadId - ) - val partListing = s3Client.listParts(listPartsRequest) - assertThat(partListing.parts).hasSize(1) - - val partSummary = partListing.parts[0] - assertThat(partSummary.eTag).isEqualTo(hash) - assertThat(partSummary.partNumber).isEqualTo(1) - assertThat(partSummary.lastModified).isExactlyInstanceOf(Date::class.java) + ).also { listPartsRequest -> + s3Client.listParts(listPartsRequest).also { partListing -> + assertThat(partListing.parts).hasSize(1) + partListing.parts[0].also { + assertThat(it.eTag).isEqualTo(hash) + assertThat(it.partNumber).isEqualTo(1) + assertThat(it.lastModified).isExactlyInstanceOf(Date::class.java) + } + } + } } /** @@ -205,14 +209,16 @@ internal class MultiPartUploadV1IT : S3TestBase() { .initiateMultipartUpload(InitiateMultipartUploadRequest(bucketName, UPLOAD_FILE_NAME)) val uploadId = initiateMultipartUploadResult.uploadId - val listing = s3Client.listMultipartUploads(ListMultipartUploadsRequest(bucketName)) - assertThat(listing.multipartUploads).isNotEmpty - assertThat(listing.bucketName).isEqualTo(bucketName) - assertThat(listing.multipartUploads).hasSize(1) + s3Client.listMultipartUploads(ListMultipartUploadsRequest(bucketName)).also { listing -> + assertThat(listing.multipartUploads).isNotEmpty + assertThat(listing.bucketName).isEqualTo(bucketName) + assertThat(listing.multipartUploads).hasSize(1) - val upload = listing.multipartUploads[0] - assertThat(upload.uploadId).isEqualTo(uploadId) - assertThat(upload.key).isEqualTo(UPLOAD_FILE_NAME) + listing.multipartUploads[0].also { upload -> + assertThat(upload.uploadId).isEqualTo(uploadId) + assertThat(upload.key).isEqualTo(UPLOAD_FILE_NAME) + } + } } /** @@ -231,11 +237,12 @@ internal class MultiPartUploadV1IT : S3TestBase() { .initiateMultipartUpload(InitiateMultipartUploadRequest(bucketName, UPLOAD_FILE_NAME)) val uploadId = initiateMultipartUploadResult.uploadId - val listing = s3Client.listParts(ListPartsRequest(bucketName, UPLOAD_FILE_NAME, uploadId)) - assertThat(listing.parts).isEmpty() - assertThat(listing.bucketName).isEqualTo(bucketName) - assertThat(listing.uploadId).isEqualTo(uploadId) - assertThat(SdkHttpUtils.urlDecode(listing.key)).isEqualTo(UPLOAD_FILE_NAME) + s3Client.listParts(ListPartsRequest(bucketName, UPLOAD_FILE_NAME, uploadId)).also { listing -> + assertThat(listing.parts).isEmpty() + assertThat(listing.bucketName).isEqualTo(bucketName) + assertThat(listing.uploadId).isEqualTo(uploadId) + assertThat(SdkHttpUtils.urlDecode(listing.key)).isEqualTo(UPLOAD_FILE_NAME) + } } /** @@ -264,12 +271,14 @@ internal class MultiPartUploadV1IT : S3TestBase() { s3Client.initiateMultipartUpload( InitiateMultipartUploadRequest(bucketName, "key2") ) - val listMultipartUploadsRequest = ListMultipartUploadsRequest(bucketName) - listMultipartUploadsRequest.prefix = "key2" + val listMultipartUploadsRequest = ListMultipartUploadsRequest(bucketName).apply { + this.prefix = "key2" + } - val listing = s3Client.listMultipartUploads(listMultipartUploadsRequest) - assertThat(listing.multipartUploads).hasSize(1) - assertThat(listing.multipartUploads[0].key).isEqualTo("key2") + s3Client.listMultipartUploads(listMultipartUploadsRequest).also { listing -> + assertThat(listing.multipartUploads).hasSize(1) + assertThat(listing.multipartUploads[0].key).isEqualTo("key2") + } } /** @@ -291,15 +300,17 @@ internal class MultiPartUploadV1IT : S3TestBase() { // assert multipart upload 1 val listMultipartUploadsRequest1 = ListMultipartUploadsRequest(bucketName1) - val listing1 = s3Client.listMultipartUploads(listMultipartUploadsRequest1) - assertThat(listing1.multipartUploads).hasSize(1) - assertThat(listing1.multipartUploads[0].key).isEqualTo("key1") + s3Client.listMultipartUploads(listMultipartUploadsRequest1).also { listing -> + assertThat(listing.multipartUploads).hasSize(1) + assertThat(listing.multipartUploads[0].key).isEqualTo("key1") + } // assert multipart upload 2 val listMultipartUploadsRequest2 = ListMultipartUploadsRequest(bucketName2) - val listing2 = s3Client.listMultipartUploads(listMultipartUploadsRequest2) - assertThat(listing2.multipartUploads).hasSize(1) - assertThat(listing2.multipartUploads[0].key).isEqualTo("key2") + s3Client.listMultipartUploads(listMultipartUploadsRequest2).also { listing -> + assertThat(listing.multipartUploads).hasSize(1) + assertThat(listing.multipartUploads[0].key).isEqualTo("key2") + } } /** @@ -317,9 +328,12 @@ internal class MultiPartUploadV1IT : S3TestBase() { val partETag = uploadPart(bucketName, UPLOAD_FILE_NAME, uploadId, 1, randomBytes) assertThat(s3Client.listMultipartUploads(ListMultipartUploadsRequest(bucketName)).multipartUploads).isNotEmpty - val partsBeforeComplete = s3Client.listParts(ListPartsRequest(bucketName, UPLOAD_FILE_NAME, uploadId)).parts - assertThat(partsBeforeComplete).hasSize(1) - assertThat(partsBeforeComplete[0].eTag).isEqualTo(partETag.eTag) + s3Client.listParts(ListPartsRequest(bucketName, UPLOAD_FILE_NAME, uploadId)).also { listing -> + listing.parts.also { + assertThat(it).hasSize(1) + assertThat(it[0].eTag).isEqualTo(partETag.eTag) + } + } s3Client.abortMultipartUpload(AbortMultipartUploadRequest(bucketName, UPLOAD_FILE_NAME, uploadId)) assertThat(s3Client.listMultipartUploads(ListMultipartUploadsRequest(bucketName)).multipartUploads).isEmpty() @@ -355,21 +369,23 @@ internal class MultiPartUploadV1IT : S3TestBase() { val partETag3 = uploadPart(bucketName, key, uploadId, 3, randomBytes3) // Adding to parts list only 1st and 3rd part - val parts: MutableList = ArrayList() - parts.add(partETag1) - parts.add(partETag3) + val parts: MutableList = ArrayList().apply { + this.add(partETag1) + this.add(partETag3) + } // Try to complete with these parts val result = s3Client.completeMultipartUpload(CompleteMultipartUploadRequest(bucketName, key, uploadId, parts)) // Verify only 1st and 3rd counts - val allMd5s = ArrayUtils.addAll( + ArrayUtils.addAll( DigestUtils.md5(randomBytes1), *DigestUtils.md5(randomBytes3) - ) + ).also { + // verify special etag + assertThat(result.eTag).isEqualTo(DigestUtils.md5Hex(it) + "-2") + } - // verify special etag - assertThat(result.eTag).isEqualTo(DigestUtils.md5Hex(allMd5s) + "-2") s3Client.getObject(bucketName, key).use { // verify content size @@ -399,9 +415,12 @@ internal class MultiPartUploadV1IT : S3TestBase() { val partETag = uploadPart(bucketName, key, uploadId, 1, randomBytes) // List parts, make sure we find part 1 - val partsBeforeComplete = s3Client.listParts(ListPartsRequest(bucketName, key, uploadId)).parts - assertThat(partsBeforeComplete).hasSize(1) - assertThat(partsBeforeComplete[0].eTag).isEqualTo(partETag.eTag) + s3Client.listParts(ListPartsRequest(bucketName, key, uploadId)).also { listing -> + listing.parts.also { + assertThat(it).hasSize(1) + assertThat(it[0].eTag).isEqualTo(partETag.eTag) + } + } // Complete, ignore result in this test s3Client.completeMultipartUpload(CompleteMultipartUploadRequest(bucketName, key, uploadId, listOf(partETag))) @@ -460,13 +479,14 @@ internal class MultiPartUploadV1IT : S3TestBase() { ) // Verify parts - val allMd5s = ArrayUtils.addAll( + ArrayUtils.addAll( DigestUtils.md5(allRandomBytes[0]), *DigestUtils.md5(allRandomBytes[1]) - ) + ).also { + // verify etag + assertThat(result.eTag).isEqualTo(DigestUtils.md5Hex(it) + "-2") + } - // verify etag - assertThat(result.eTag).isEqualTo(DigestUtils.md5Hex(allMd5s) + "-2") s3Client.getObject(bucketName2, multipartUploadKey).use { // verify content size @@ -513,15 +533,16 @@ internal class MultiPartUploadV1IT : S3TestBase() { } val copyPartResult = s3Client.copyPart(copyPartRequest) - val partListing = s3Client.listParts( + s3Client.listParts( ListPartsRequest( initiateMultipartUploadResult.bucketName, initiateMultipartUploadResult.key, initiateMultipartUploadResult.uploadId ) - ) - assertThat(partListing.parts).hasSize(1) - assertThat(partListing.parts[0].eTag).isEqualTo(copyPartResult.eTag) + ).also { + assertThat(it.parts).hasSize(1) + assertThat(it.parts[0].eTag).isEqualTo(copyPartResult.eTag) + } } /** diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/MultiPartUploadV2IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/MultiPartUploadV2IT.kt index 48b601ea3..783994261 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/MultiPartUploadV2IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/MultiPartUploadV2IT.kt @@ -26,6 +26,8 @@ import org.assertj.core.api.InstanceOfAssertFactories import org.assertj.core.util.Files import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInfo +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource import org.springframework.web.util.UriUtils import software.amazon.awssdk.awscore.exception.AwsErrorDetails import software.amazon.awssdk.awscore.exception.AwsServiceException @@ -72,26 +74,26 @@ internal class MultiPartUploadV2IT : S3TestBase() { fun testMultipartUpload_asyncClient(testInfo: TestInfo) { val bucketName = givenBucketV2(testInfo) val uploadFile = File(UPLOAD_FILE_NAME) - s3CrtAsyncClientV2.putObject( - PutObjectRequest - .builder() - .bucket(bucketName) - .key(uploadFile.name) - .checksumAlgorithm(ChecksumAlgorithm.CRC32) - .build(), - AsyncRequestBody.fromFile(uploadFile) - ).join().also { - assertThat(it.checksumCRC32()).isEqualTo(DigestUtil.checksumFor(uploadFile.toPath(), Algorithm.CRC32)) - } - - s3AsyncClientV2.waiter() - .waitUntilObjectExists( - HeadObjectRequest + s3CrtAsyncClientV2.putObject( + PutObjectRequest .builder() .bucket(bucketName) .key(uploadFile.name) - .build() - ) + .checksumAlgorithm(ChecksumAlgorithm.CRC32) + .build(), + AsyncRequestBody.fromFile(uploadFile) + ).join().also { + assertThat(it.checksumCRC32()).isEqualTo(DigestUtil.checksumFor(uploadFile.toPath(), Algorithm.CRC32)) + } + + s3AsyncClientV2.waiter() + .waitUntilObjectExists( + HeadObjectRequest + .builder() + .bucket(bucketName) + .key(uploadFile.name) + .build() + ) s3ClientV2.getObject( GetObjectRequest @@ -117,18 +119,18 @@ internal class MultiPartUploadV2IT : S3TestBase() { val uploadFile = File(UPLOAD_FILE_NAME) transferManagerV2 .uploadFile( - UploadFileRequest - .builder() - .putObjectRequest( - PutObjectRequest - .builder() - .bucket(bucketName) - .key(UPLOAD_FILE_NAME) - .build() - ) - .source(uploadFile) - .build() - ).completionFuture().join() + UploadFileRequest + .builder() + .putObjectRequest( + PutObjectRequest + .builder() + .bucket(bucketName) + .key(UPLOAD_FILE_NAME) + .build() + ) + .source(uploadFile) + .build() + ).completionFuture().join() s3ClientV2.getObject( GetObjectRequest @@ -141,7 +143,7 @@ internal class MultiPartUploadV2IT : S3TestBase() { } val downloadFile = Files.newTemporaryFile() - val downloadFileResult = transferManagerV2.downloadFile( + transferManagerV2.downloadFile( DownloadFileRequest .builder() .getObjectRequest( @@ -153,10 +155,11 @@ internal class MultiPartUploadV2IT : S3TestBase() { ) .destination(downloadFile) .build() - ) - - val completedFileDownload = downloadFileResult.completionFuture().join().response() - assertThat(completedFileDownload.contentLength()).isEqualTo(uploadFile.length()) + ).also { download -> + download.completionFuture().join().response().also { + assertThat(it.contentLength()).isEqualTo(uploadFile.length()) + } + } assertThat(downloadFile.length()).isEqualTo(uploadFile.length()) assertThat(downloadFile).hasSameBinaryContentAs(uploadFile) } @@ -278,15 +281,16 @@ internal class MultiPartUploadV2IT : S3TestBase() { ) // Verify only 1st and 3rd counts - val uploadFileBytes = readStreamIntoByteArray(uploadFile.inputStream()) - val allMd5s = ArrayUtils.addAll( + + ArrayUtils.addAll( DigestUtils.md5(randomBytes), *DigestUtils.md5(uploadFileBytes) - ) + ).also { + // verify special etag + assertThat(completeMultipartUpload.eTag()).isEqualTo("\"" + DigestUtils.md5Hex(it) + "-2" + "\"") + } - // verify special etag - assertThat(completeMultipartUpload.eTag()).isEqualTo("\"" + DigestUtils.md5Hex(allMd5s) + "-2" + "\"") s3ClientV2.getObject( GetObjectRequest @@ -305,6 +309,113 @@ internal class MultiPartUploadV2IT : S3TestBase() { .isEqualTo("${serviceEndpoint}/$bucketName/${UriUtils.encode(UPLOAD_FILE_NAME, StandardCharsets.UTF_8)}") } + @S3VerifiedTodo + @ParameterizedTest + @MethodSource(value = ["checksumAlgorithms"]) + fun testMultipartUpload_checksumAlgorithm(checksumAlgorithm: ChecksumAlgorithm, testInfo: TestInfo) { + val bucketName = givenBucketV2(testInfo) + val uploadFile = File(UPLOAD_FILE_NAME) + val expectedChecksum = DigestUtil.checksumFor(uploadFile.toPath(), checksumAlgorithm.toAlgorithm()) + val initiateMultipartUploadResult = s3ClientV2 + .createMultipartUpload(CreateMultipartUploadRequest.builder().bucket(bucketName).key(UPLOAD_FILE_NAME).build()) + val uploadId = initiateMultipartUploadResult.uploadId() + + s3ClientV2.uploadPart( + UploadPartRequest + .builder() + .bucket(initiateMultipartUploadResult.bucket()) + .key(initiateMultipartUploadResult.key()) + .uploadId(uploadId) + .checksumAlgorithm(checksumAlgorithm) + .partNumber(1) + .contentLength(uploadFile.length()).build(), + //.lastPart(true) + RequestBody.fromFile(uploadFile), + ).also { + val actualChecksum = it.checksum(checksumAlgorithm) + assertThat(actualChecksum).isNotBlank + assertThat(actualChecksum).isEqualTo(expectedChecksum) + } + s3ClientV2.abortMultipartUpload( + AbortMultipartUploadRequest.builder().bucket(bucketName).key(UPLOAD_FILE_NAME) + .uploadId(uploadId).build() + ) + } + + @S3VerifiedTodo + @ParameterizedTest + @MethodSource(value = ["checksumAlgorithms"]) + fun testMultipartUpload_checksum(checksumAlgorithm: ChecksumAlgorithm, testInfo: TestInfo) { + val bucketName = givenBucketV2(testInfo) + val uploadFile = File(UPLOAD_FILE_NAME) + val expectedChecksum = DigestUtil.checksumFor(uploadFile.toPath(), checksumAlgorithm.toAlgorithm()) + val initiateMultipartUploadResult = s3ClientV2 + .createMultipartUpload(CreateMultipartUploadRequest.builder().bucket(bucketName).key(UPLOAD_FILE_NAME).build()) + val uploadId = initiateMultipartUploadResult.uploadId() + + s3ClientV2.uploadPart( + UploadPartRequest + .builder() + .bucket(initiateMultipartUploadResult.bucket()) + .key(initiateMultipartUploadResult.key()) + .uploadId(uploadId) + .checksum(expectedChecksum, checksumAlgorithm) + .partNumber(1) + .contentLength(uploadFile.length()).build(), + //.lastPart(true) + RequestBody.fromFile(uploadFile), + ).also { + val actualChecksum = it.checksum(checksumAlgorithm) + assertThat(actualChecksum).isNotBlank + assertThat(actualChecksum).isEqualTo(expectedChecksum) + } + s3ClientV2.abortMultipartUpload( + AbortMultipartUploadRequest.builder().bucket(bucketName).key(UPLOAD_FILE_NAME) + .uploadId(uploadId).build() + ) + } + + @Test + @S3VerifiedTodo + fun testMultipartUpload_wrongChecksum(testInfo: TestInfo) { + val bucketName = givenBucketV2(testInfo) + val uploadFile = File(UPLOAD_FILE_NAME) + val expectedChecksum = "wrongChecksum" + val checksumAlgorithm = ChecksumAlgorithm.SHA1 + val initiateMultipartUploadResult = s3ClientV2 + .createMultipartUpload(CreateMultipartUploadRequest.builder().bucket(bucketName).key(UPLOAD_FILE_NAME).build()) + val uploadId = initiateMultipartUploadResult.uploadId() + + assertThatThrownBy { + s3ClientV2.uploadPart( + UploadPartRequest + .builder() + .bucket(initiateMultipartUploadResult.bucket()) + .key(initiateMultipartUploadResult.key()) + .uploadId(uploadId) + .checksum(expectedChecksum, checksumAlgorithm) + .partNumber(1) + .contentLength(uploadFile.length()).build(), + //.lastPart(true) + RequestBody.fromFile(uploadFile), + ) + } + .isInstanceOf(S3Exception::class.java) + .hasMessageContaining("The Content-MD5 or checksum value that you specified did not match what the server received.") + } + + private fun UploadPartRequest.Builder.checksum( + checksum: String, + checksumAlgorithm: ChecksumAlgorithm + ): UploadPartRequest.Builder = + when (checksumAlgorithm) { + ChecksumAlgorithm.SHA1 -> this.checksumSHA1(checksum) + ChecksumAlgorithm.SHA256 -> this.checksumSHA256(checksum) + ChecksumAlgorithm.CRC32 -> this.checksumCRC32(checksum) + ChecksumAlgorithm.CRC32_C -> this.checksumCRC32C(checksum) + else -> error("Unknown checksum algorithm") + } + @Test @S3VerifiedSuccess(year = 2022) fun testInitiateMultipartAndRetrieveParts(testInfo: TestInfo) { @@ -339,12 +450,15 @@ internal class MultiPartUploadV2IT : S3TestBase() { .uploadId(uploadId) .build() val partListing = s3ClientV2.listParts(listPartsRequest) - assertThat(partListing.parts()).hasSize(1) - - val partSummary = partListing.parts()[0] - assertThat(partSummary.eTag()).isEqualTo("\"" + hash + "\"") - assertThat(partSummary.partNumber()).isEqualTo(1) - assertThat(partSummary.lastModified()).isExactlyInstanceOf(Instant::class.java) + .also { + assertThat(it.parts()).hasSize(1) + } + + partListing.parts()[0].also { + assertThat(it.eTag()).isEqualTo("\"" + hash + "\"") + assertThat(it.partNumber()).isEqualTo(1) + assertThat(it.lastModified()).isExactlyInstanceOf(Instant::class.java) + } } /** @@ -366,16 +480,19 @@ internal class MultiPartUploadV2IT : S3TestBase() { ) val uploadId = initiateMultipartUploadResult.uploadId() - val listing = s3ClientV2.listMultipartUploads( + s3ClientV2.listMultipartUploads( ListMultipartUploadsRequest.builder().bucket(bucketName).build() - ) - assertThat(listing.uploads()).isNotEmpty - assertThat(listing.bucket()).isEqualTo(bucketName) - assertThat(listing.uploads()).hasSize(1) - - val upload = listing.uploads()[0] - assertThat(upload.uploadId()).isEqualTo(uploadId) - assertThat(upload.key()).isEqualTo(UPLOAD_FILE_NAME) + ).also { listing -> + assertThat(listing.uploads()).isNotEmpty + assertThat(listing.bucket()).isEqualTo(bucketName) + assertThat(listing.uploads()).hasSize(1) + + listing.uploads()[0] + .also { + assertThat(it.uploadId()).isEqualTo(uploadId) + assertThat(it.key()).isEqualTo(UPLOAD_FILE_NAME) + } + } } /** @@ -396,7 +513,7 @@ internal class MultiPartUploadV2IT : S3TestBase() { ) val uploadId = initiateMultipartUploadResult.uploadId() - val listing = s3ClientV2 + s3ClientV2 .listParts( ListPartsRequest .builder() @@ -404,11 +521,12 @@ internal class MultiPartUploadV2IT : S3TestBase() { .key(UPLOAD_FILE_NAME) .uploadId(uploadId) .build() - ) - assertThat(listing.parts()).isEmpty() - assertThat(listing.bucket()).isEqualTo(bucketName) - assertThat(listing.uploadId()).isEqualTo(uploadId) - assertThat(SdkHttpUtils.urlDecode(listing.key())).isEqualTo(UPLOAD_FILE_NAME) + ).also { + assertThat(it.parts()).isEmpty() + assertThat(it.bucket()).isEqualTo(bucketName) + assertThat(it.uploadId()).isEqualTo(uploadId) + assertThat(SdkHttpUtils.urlDecode(it.key())).isEqualTo(UPLOAD_FILE_NAME) + } } /** @@ -460,28 +578,55 @@ internal class MultiPartUploadV2IT : S3TestBase() { fun testListMultipartUploads_multipleBuckets(testInfo: TestInfo) { // create multipart upload 1 val bucketName1 = givenBucketV2(testInfo) - s3ClientV2 - .createMultipartUpload( - CreateMultipartUploadRequest.builder().bucket(bucketName1).key("key1").build() - ) + .also { + s3ClientV2 + .createMultipartUpload( + CreateMultipartUploadRequest + .builder() + .bucket(it) + .key("key1") + .build() + ) + } + // create multipart upload 2 val bucketName2 = givenRandomBucketV1() - s3ClientV2 - .createMultipartUpload( - CreateMultipartUploadRequest.builder().bucket(bucketName2).key("key2").build() - ) + .also { + s3ClientV2 + .createMultipartUpload( + CreateMultipartUploadRequest + .builder() + .bucket(it) + .key("key2") + .build() + ) + } // assert multipart upload 1 - val listMultipartUploadsRequest1 = ListMultipartUploadsRequest.builder().bucket(bucketName1).build() - val listing = s3ClientV2.listMultipartUploads(listMultipartUploadsRequest1) - assertThat(listing.uploads()).hasSize(1) - assertThat(listing.uploads()[0].key()).isEqualTo("key1") + ListMultipartUploadsRequest + .builder() + .bucket(bucketName1) + .build() + .also { request -> + s3ClientV2.listMultipartUploads(request) + .also { + assertThat(it.uploads()).hasSize(1) + assertThat(it.uploads()[0].key()).isEqualTo("key1") + } + } // assert multipart upload 2 - val listMultipartUploadsRequest2 = ListMultipartUploadsRequest.builder().bucket(bucketName2).build() - val listing2 = s3ClientV2.listMultipartUploads(listMultipartUploadsRequest2) - assertThat(listing2.uploads()).hasSize(1) - assertThat(listing2.uploads()[0].key()).isEqualTo("key2") + ListMultipartUploadsRequest + .builder() + .bucket(bucketName2) + .build() + .also { request -> + s3ClientV2.listMultipartUploads(request) + .also { + assertThat(it.uploads()).hasSize(1) + assertThat(it.uploads()[0].key()).isEqualTo("key2") + } + } } /** @@ -511,13 +656,15 @@ internal class MultiPartUploadV2IT : S3TestBase() { ).hasUploads() ).isTrue - val partsBeforeComplete = - s3ClientV2.listParts( - ListPartsRequest.builder().bucket(bucketName).key(UPLOAD_FILE_NAME).uploadId(uploadId) - .build() - ).parts() - assertThat(partsBeforeComplete).hasSize(1) - assertThat(partsBeforeComplete[0].eTag()).isEqualTo(partETag) + s3ClientV2.listParts( + ListPartsRequest.builder().bucket(bucketName).key(UPLOAD_FILE_NAME).uploadId(uploadId) + .build() + ) + .parts() + .also { + assertThat(it).hasSize(1) + assertThat(it[0].eTag()).isEqualTo(partETag) + } s3ClientV2.abortMultipartUpload( AbortMultipartUploadRequest.builder().bucket(bucketName).key(UPLOAD_FILE_NAME) @@ -575,11 +722,6 @@ internal class MultiPartUploadV2IT : S3TestBase() { val randomBytes3 = randomBytes() val partETag3 = uploadPart(bucketName, key, uploadId, 3, randomBytes3) - // Adding to parts list only 1st and 3rd part - val parts: MutableList = ArrayList() - parts.add(partETag1) - parts.add(partETag3) - // Try to complete with these parts val result = s3ClientV2.completeMultipartUpload( CompleteMultipartUploadRequest.builder() @@ -607,14 +749,14 @@ internal class MultiPartUploadV2IT : S3TestBase() { ) // Verify only 1st and 3rd counts - - val allMd5s = ArrayUtils.addAll( + ArrayUtils.addAll( DigestUtils.md5(randomBytes1), *DigestUtils.md5(randomBytes3) - ) + ).also { + // verify special etag + assertThat(result.eTag()).isEqualTo("\"" + DigestUtils.md5Hex(it) + "-2" + "\"") + } - // verify special etag - assertThat(result.eTag()).isEqualTo("\"" + DigestUtils.md5Hex(allMd5s) + "-2" + "\"") s3ClientV2.getObject( GetObjectRequest @@ -658,16 +800,19 @@ internal class MultiPartUploadV2IT : S3TestBase() { val partETag = uploadPart(bucketName, key, uploadId, 1, randomBytes) // List parts, make sure we find part 1 - val partsBeforeComplete = s3ClientV2.listParts( + s3ClientV2.listParts( ListPartsRequest .builder() .bucket(bucketName) .key(key) .uploadId(uploadId) .build() - ).parts() - assertThat(partsBeforeComplete).hasSize(1) - assertThat(partsBeforeComplete[0].eTag()).isEqualTo(partETag) + ) + .parts() + .also { + assertThat(it).hasSize(1) + assertThat(it[0].eTag()).isEqualTo(partETag) + } // Complete s3ClientV2.completeMultipartUpload( @@ -737,8 +882,9 @@ internal class MultiPartUploadV2IT : S3TestBase() { val key = sourceKeys[i] val partNumber = i + 1 val randomBytes = randomBytes() - val metadata1 = HashMap() - metadata1["contentLength"] = randomBytes.size.toString() + val metadata1 = HashMap().apply { + this["contentLength"] = randomBytes.size.toString() + } s3ClientV2.putObject( PutObjectRequest .builder() @@ -749,7 +895,7 @@ internal class MultiPartUploadV2IT : S3TestBase() { RequestBody.fromInputStream(ByteArrayInputStream(randomBytes), randomBytes.size.toLong()) ) - val result = s3ClientV2.uploadPartCopy( + s3ClientV2.uploadPartCopy( UploadPartCopyRequest.builder() .partNumber(partNumber) .uploadId(uploadId) @@ -757,10 +903,11 @@ internal class MultiPartUploadV2IT : S3TestBase() { .destinationKey(multipartUploadKey) .sourceKey(key) .sourceBucket(bucketName1).build() - ) - val etag = result.copyPartResult().eTag() - parts.add(CompletedPart.builder().eTag(etag).partNumber(partNumber).build()) - allRandomBytes.add(randomBytes) + ).also { + val etag = it.copyPartResult().eTag() + parts.add(CompletedPart.builder().eTag(etag).partNumber(partNumber).build()) + allRandomBytes.add(randomBytes) + } } assertThat(allRandomBytes).hasSize(2) @@ -781,13 +928,13 @@ internal class MultiPartUploadV2IT : S3TestBase() { ) // Verify parts - val allMd5s = ArrayUtils.addAll( + ArrayUtils.addAll( DigestUtils.md5(allRandomBytes[0]), *DigestUtils.md5(allRandomBytes[1]) - ) - - // verify etag - assertThat(result.eTag()).isEqualTo("\"" + DigestUtils.md5Hex(allMd5s) + "-2" + "\"") + ).also { + // verify etag + assertThat(result.eTag()).isEqualTo("\"" + DigestUtils.md5Hex(it) + "-2" + "\"") + } s3ClientV2.getObject( GetObjectRequest @@ -834,21 +981,22 @@ internal class MultiPartUploadV2IT : S3TestBase() { .sourceKey(sourceKey) .sourceBucket(bucketName) .partNumber(1) - .copySourceRange("bytes=0-" + (uploadFile.length() -1)) + .copySourceRange("bytes=0-" + (uploadFile.length() - 1)) .build() ) val etag = result.copyPartResult().eTag() - val partListing = s3ClientV2.listParts( + s3ClientV2.listParts( ListPartsRequest .builder() .bucket(initiateMultipartUploadResult.bucket()) .key(initiateMultipartUploadResult.key()) .uploadId(initiateMultipartUploadResult.uploadId()) .build() - ) - assertThat(partListing.parts()).hasSize(1) - assertThat(partListing.parts()[0].eTag()).isEqualTo(etag) + ).also { + assertThat(it.parts()).hasSize(1) + assertThat(it.parts()[0].eTag()).isEqualTo(etag) + } } /** @@ -914,22 +1062,23 @@ internal class MultiPartUploadV2IT : S3TestBase() { .sourceKey(sourceKey) .sourceBucket(bucketName) .partNumber(1) - .copySourceRange("bytes=0-" + (uploadFile.length() -1)) + .copySourceRange("bytes=0-" + (uploadFile.length() - 1)) .copySourceIfMatch(matchingEtag) .build() ) val etag = result.copyPartResult().eTag() - val partListing = s3ClientV2.listParts( + s3ClientV2.listParts( ListPartsRequest .builder() .bucket(initiateMultipartUploadResult.bucket()) .key(initiateMultipartUploadResult.key()) .uploadId(initiateMultipartUploadResult.uploadId()) .build() - ) - assertThat(partListing.parts()).hasSize(1) - assertThat(partListing.parts()[0].eTag()).isEqualTo(etag) + ).also { + assertThat(it.parts()).hasSize(1) + assertThat(it.parts()[0].eTag()).isEqualTo(etag) + } } @Test @@ -956,22 +1105,23 @@ internal class MultiPartUploadV2IT : S3TestBase() { .sourceKey(sourceKey) .sourceBucket(bucketName) .partNumber(1) - .copySourceRange("bytes=0-" + (uploadFile.length() -1)) + .copySourceRange("bytes=0-" + (uploadFile.length() - 1)) .copySourceIfNoneMatch(noneMatchingEtag) .build() ) val etag = result.copyPartResult().eTag() - val partListing = s3ClientV2.listParts( + s3ClientV2.listParts( ListPartsRequest .builder() .bucket(initiateMultipartUploadResult.bucket()) .key(initiateMultipartUploadResult.key()) .uploadId(initiateMultipartUploadResult.uploadId()) .build() - ) - assertThat(partListing.parts()).hasSize(1) - assertThat(partListing.parts()[0].eTag()).isEqualTo(etag) + ).also { + assertThat(it.parts()).hasSize(1) + assertThat(it.parts()[0].eTag()).isEqualTo(etag) + } } @Test diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ObjectTaggingV1IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ObjectTaggingV1IT.kt index 62c4a0f1a..4b01eb3cc 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ObjectTaggingV1IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/ObjectTaggingV1IT.kt @@ -44,10 +44,11 @@ internal class ObjectTaggingV1IT : S3TestBase() { s3Client.setObjectTagging(setObjectTaggingRequest) val getObjectTaggingRequest = GetObjectTaggingRequest(bucketName, s3Object.key) - val getObjectTaggingResult = s3Client.getObjectTagging(getObjectTaggingRequest) - // There should be 'foo:bar' here - assertThat(getObjectTaggingResult.tagSet).hasSize(1) - assertThat(getObjectTaggingResult.tagSet).contains(tag) + s3Client.getObjectTagging(getObjectTaggingRequest).also { + // There should be 'foo:bar' here + assertThat(it.tagSet).hasSize(1) + assertThat(it.tagSet).contains(tag) + } } @Test @@ -65,10 +66,11 @@ internal class ObjectTaggingV1IT : S3TestBase() { val s3Object = s3Client.getObject(bucketName, uploadFile.name) val getObjectTaggingRequest = GetObjectTaggingRequest(bucketName, s3Object.key) - val getObjectTaggingResult = s3Client.getObjectTagging(getObjectTaggingRequest) - // There should be 'foo:bar' here - assertThat(getObjectTaggingResult.tagSet).hasSize(1) - assertThat(getObjectTaggingResult.tagSet[0].value).isEqualTo("bar") + s3Client.getObjectTagging(getObjectTaggingRequest).also { + // There should be 'foo:bar' here + assertThat(it.tagSet).hasSize(1) + assertThat(it.tagSet[0].value).isEqualTo("bar") + } } /** @@ -91,9 +93,10 @@ internal class ObjectTaggingV1IT : S3TestBase() { val s3Object = s3Client.getObject(bucketName, uploadFile.name) val getObjectTaggingRequest = GetObjectTaggingRequest(bucketName, s3Object.key) - val getObjectTaggingResult = s3Client.getObjectTagging(getObjectTaggingRequest) - assertThat(getObjectTaggingResult.tagSet).hasSize(2) - assertThat(getObjectTaggingResult.tagSet).contains(tag1, tag2) + s3Client.getObjectTagging(getObjectTaggingRequest).also { + assertThat(it.tagSet).hasSize(2) + assertThat(it.tagSet).contains(tag1, tag2) + } } @@ -104,8 +107,9 @@ internal class ObjectTaggingV1IT : S3TestBase() { val s3Object = s3Client.getObject(bucketName, UPLOAD_FILE_NAME) val getObjectTaggingRequest = GetObjectTaggingRequest(bucketName, s3Object.key) - val getObjectTaggingResult = s3Client.getObjectTagging(getObjectTaggingRequest) - // There shouldn't be any tags here - assertThat(getObjectTaggingResult.tagSet).isEmpty() + s3Client.getObjectTagging(getObjectTaggingRequest).also { + // There shouldn't be any tags here + assertThat(it.tagSet).isEmpty() + } } } diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/PlainHttpIT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/PlainHttpIT.kt index 95d30def6..a0d2cae80 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/PlainHttpIT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/PlainHttpIT.kt @@ -131,22 +131,23 @@ internal class PlainHttpIT : S3TestBase() { @Test @S3VerifiedSuccess(year = 2022) fun createBucketWithDisallowedName() { - val putObject = HttpPut("/$INVALID_BUCKET_NAME") - - httpClient.execute( - HttpHost( - host, httpPort - ), putObject - ).use { - assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_BAD_REQUEST) - assertThat( - InputStreamReader(it.entity.content) - .readLines() - .stream() - .collect(Collectors.joining())) - .isEqualTo("InvalidBucketName" + - "The specified bucket is not valid.") + HttpPut("/$INVALID_BUCKET_NAME").also { + httpClient.execute( + HttpHost( + host, httpPort + ), it + ).use { response -> + assertThat(response.statusLine.statusCode).isEqualTo(HttpStatus.SC_BAD_REQUEST) + assertThat( + InputStreamReader(response.entity.content) + .readLines() + .stream() + .collect(Collectors.joining())) + .isEqualTo("InvalidBucketName" + + "The specified bucket is not valid.") + } } + } @Test @@ -154,17 +155,18 @@ internal class PlainHttpIT : S3TestBase() { reason = "No credentials sent in plain HTTP request") fun putObjectEncryptedWithAbsentKeyRef(testInfo: TestInfo) { val targetBucket = givenBucketV2(testInfo) - val putObject = HttpPut("/$targetBucket/testObjectName").apply { + + HttpPut("/$targetBucket/testObjectName").apply { this.addHeader("x-amz-server-side-encryption", "aws:kms") this.entity = ByteArrayEntity(UUID.randomUUID().toString().toByteArray()) - } - - httpClient.execute( - HttpHost( - host, httpPort - ), putObject - ).use { - assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK) + }.also { + httpClient.execute( + HttpHost( + host, httpPort + ), it + ).use { response -> + assertThat(response.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK) + } } } @@ -174,15 +176,17 @@ internal class PlainHttpIT : S3TestBase() { fun listWithPrefixAndMissingSlash(testInfo: TestInfo) { val targetBucket = givenBucketV2(testInfo) s3Client.putObject(targetBucket, "prefix", "Test") - val getObject = HttpGet("/$targetBucket?prefix=prefix%2F&encoding-type=url") - httpClient.execute( - HttpHost( - host, httpPort - ), getObject - ).use { - assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK) + HttpGet("/$targetBucket?prefix=prefix%2F&encoding-type=url").also { + httpClient.execute( + HttpHost( + host, httpPort + ), it + ).use { response -> + assertThat(response.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK) + } } + } @Test @@ -190,8 +194,9 @@ internal class PlainHttpIT : S3TestBase() { fun objectUsesApplicationXmlContentType(testInfo: TestInfo) { val targetBucket = givenBucketV2(testInfo) - val getObject = HttpGet("/$targetBucket") - assertApplicationXmlContentType(getObject) + HttpGet("/$targetBucket").also { + assertApplicationXmlContentType(it) + } } @Test @@ -221,25 +226,27 @@ internal class PlainHttpIT : S3TestBase() { @S3VerifiedSuccess(year = 2022) fun listBucketsUsesApplicationXmlContentType(testInfo: TestInfo) { givenBucketV2(testInfo) - val listBuckets = HttpGet(SLASH) - - assertApplicationXmlContentType(listBuckets) + HttpGet(SLASH).also { + assertApplicationXmlContentType(it) + } } @Test @S3VerifiedSuccess(year = 2022) fun batchDeleteUsesApplicationXmlContentType(testInfo: TestInfo) { val targetBucket = givenBucketV2(testInfo) - val postObject = HttpPost("/$targetBucket?delete").apply { + + HttpPost("/$targetBucket?delete").apply { this.entity = StringEntity( - "" - + "myFile-1" - + "myFile-2" - + "", ContentType.APPLICATION_XML + """ + myFile-1 + myFile-2 + """.trimMargin(), + ContentType.APPLICATION_XML ) + }.also { + assertApplicationXmlContentType(it) } - - assertApplicationXmlContentType(postObject) } @Test @@ -263,19 +270,21 @@ internal class PlainHttpIT : S3TestBase() { .withPartSize(uploadFile.length()) .withLastPart(true) ) - val postObject = HttpPost("/$targetBucket/$UPLOAD_FILE_NAME?uploadId=$uploadId").apply { + + HttpPost("/$targetBucket/$UPLOAD_FILE_NAME?uploadId=$uploadId").apply { this.entity = StringEntity( - "" - + "" - + "" - + "" + uploadPartResult.partETag.eTag + "" - + "1" - + "" - + "", ContentType.APPLICATION_XML + """ + + + ${uploadPartResult.partETag.eTag} + 1 + + """.trimMargin(), + ContentType.APPLICATION_XML ) + }.also { + assertApplicationXmlContentType(it) } - - assertApplicationXmlContentType(postObject) } @Test @@ -285,18 +294,19 @@ internal class PlainHttpIT : S3TestBase() { val fileNameWithSpecialCharacters = ("file=name\$Dollar;Semicolon" + "&Ampersand@At:Colon Space,Comma?Question-mark") val targetBucket = givenBucketV2(testInfo) - val putObject = HttpPut( + + HttpPut( "/$targetBucket/${SdkHttpUtils.urlEncodeIgnoreSlashes(fileNameWithSpecialCharacters)}" ).apply { this.entity = ByteArrayEntity(UUID.randomUUID().toString().toByteArray()) - } - - httpClient.execute( - HttpHost( - host, httpPort - ), putObject - ).use { - assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK) + }.also { + httpClient.execute( + HttpHost( + host, httpPort + ), it + ).use { response -> + assertThat(response.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK) + } } assertThat( @@ -312,15 +322,17 @@ internal class PlainHttpIT : S3TestBase() { reason = "No credentials sent in plain HTTP request") fun deleteNonExistingObjectReturns204(testInfo: TestInfo) { val targetBucket = givenBucketV2(testInfo) - val deleteObject = HttpDelete("/$targetBucket/${UUID.randomUUID()}") - httpClient.execute( - HttpHost( - host, httpPort - ), deleteObject - ).use { - assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_NO_CONTENT) + HttpDelete("/$targetBucket/${UUID.randomUUID()}").also { + httpClient.execute( + HttpHost( + host, httpPort + ), it + ).use { response -> + assertThat(response.statusLine.statusCode).isEqualTo(HttpStatus.SC_NO_CONTENT) + } } + } @Test @@ -328,17 +340,22 @@ internal class PlainHttpIT : S3TestBase() { reason = "No credentials sent in plain HTTP request") fun batchDeleteObjects(testInfo: TestInfo) { val targetBucket = givenBucketV2(testInfo) - val postObject = HttpPost("/$targetBucket?delete").apply { + + HttpPost("/$targetBucket?delete").apply { this.entity = StringEntity( - "myFile-1myFile-2" - + "", ContentType.APPLICATION_XML + """ + + myFile-1 + myFile-2 + """.trimMargin(), + ContentType.APPLICATION_XML ) + }.also { + httpClient.execute(HttpHost(host, httpPort), it).use { response -> + assertThat(response.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK) + } } - httpClient.execute(HttpHost(host, httpPort), postObject).use { - assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK) - } } @Test @@ -357,13 +374,14 @@ internal class PlainHttpIT : S3TestBase() { ByteArrayInputStream(contentAsBytes), md ) - val headObject = HttpHead("/$targetBucket/$blankContentTypeFilename") - httpClient.execute( - HttpHost( - host, httpPort - ), headObject - ).use { - assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK) + HttpHead("/$targetBucket/$blankContentTypeFilename").also { + httpClient.execute( + HttpHost( + host, httpPort + ), it + ).use { response -> + assertThat(response.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK) + } } } diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/PresignedUriV2IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/PresignedUriV2IT.kt index 01c3546c9..1efb94742 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/PresignedUriV2IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/PresignedUriV2IT.kt @@ -115,16 +115,16 @@ internal class PresignedUriV2IT : S3TestBase() { val presignedUrlString = presignedPutObjectRequest.url().toString() assertThat(presignedUrlString).isNotBlank() - val putObject = HttpPut(presignedUrlString).apply { + HttpPut(presignedUrlString).apply { this.entity = FileEntity(File(UPLOAD_FILE_NAME)) - } - - httpClient.execute( - HttpHost( - host, httpPort - ), putObject - ).use { - assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK) + }.also { + httpClient.execute( + HttpHost( + host, httpPort + ), it + ).use { + assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK) + } } s3ClientV2.getObject(GetObjectRequest @@ -161,7 +161,7 @@ internal class PresignedUriV2IT : S3TestBase() { val presignedUrlString = presignCreateMultipartUpload.url().toString() assertThat(presignedUrlString).isNotBlank() - val postObject = HttpPost(presignedUrlString).apply { + HttpPost(presignedUrlString).apply { this.entity = StringEntity( """ @@ -169,25 +169,27 @@ internal class PresignedUriV2IT : S3TestBase() { fileName uploadId - """) - } - httpClient.execute( - HttpHost( - host, httpPort - ), postObject - ).use { - assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK) + """ + ) + }.also { + httpClient.execute( + HttpHost( + host, httpPort + ), it + ).use { + assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK) + } } - val listMultipartUploads = s3ClientV2.listMultipartUploads( + s3ClientV2.listMultipartUploads( ListMultipartUploadsRequest .builder() .bucket(bucketName) .keyMarker(key) .build() - ) - - assertThat(listMultipartUploads.uploads()).hasSize(1) + ).also { + assertThat(it.uploads()).hasSize(1) + } } @Test @@ -234,24 +236,25 @@ internal class PresignedUriV2IT : S3TestBase() { val presignedUrlString = presignAbortMultipartUpload.url().toString() assertThat(presignedUrlString).isNotBlank() - val httpDelete = HttpDelete(presignedUrlString) - httpClient.execute( - HttpHost( - host, httpPort - ), httpDelete - ).use { - assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_NO_CONTENT) + HttpDelete(presignedUrlString).also { + httpClient.execute( + HttpHost( + host, httpPort + ), it + ).use { + assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_NO_CONTENT) + } } - val listMultipartUploads = s3ClientV2.listMultipartUploads( + s3ClientV2.listMultipartUploads( ListMultipartUploadsRequest .builder() .bucket(bucketName) .keyMarker(key) .build() - ) - - assertThat(listMultipartUploads.uploads()).isEmpty() + ).also { + assertThat(it.uploads()).isEmpty() + } } @Test @@ -298,7 +301,7 @@ internal class PresignedUriV2IT : S3TestBase() { val presignedUrlString = presignCompleteMultipartUpload.url().toString() assertThat(presignedUrlString).isNotBlank() - val httpPost = HttpPost(presignedUrlString).apply { + HttpPost(presignedUrlString).apply { this.setHeader(BasicHeader("Content-Type", "application/xml")) this.entity = StringEntity( """ @@ -308,24 +311,25 @@ internal class PresignedUriV2IT : S3TestBase() { """) - } - httpClient.execute( - HttpHost( - host, httpPort - ), httpPost - ).use { - assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK) + }.also { + httpClient.execute( + HttpHost( + host, httpPort + ), it + ).use { + assertThat(it.statusLine.statusCode).isEqualTo(HttpStatus.SC_OK) + } } - val listMultipartUploads = s3ClientV2.listMultipartUploads( + s3ClientV2.listMultipartUploads( ListMultipartUploadsRequest .builder() .bucket(bucketName) .keyMarker(key) .build() - ) - - assertThat(listMultipartUploads.uploads()).isEmpty() + ).also { + assertThat(it.uploads()).isEmpty() + } } @@ -394,15 +398,15 @@ internal class PresignedUriV2IT : S3TestBase() { s3ClientV2.completeMultipartUpload(completeMultipartUploadRequest) } - val listMultipartUploads = s3ClientV2.listMultipartUploads( + s3ClientV2.listMultipartUploads( ListMultipartUploadsRequest .builder() .bucket(bucketName) .keyMarker(key) .build() - ) - - assertThat(listMultipartUploads.uploads()).isEmpty() + ).also { + assertThat(it.uploads()).isEmpty() + } } } diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/RetentionV2IT.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/RetentionV2IT.kt index 72c451820..d36776991 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/RetentionV2IT.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/RetentionV2IT.kt @@ -119,19 +119,20 @@ internal class RetentionV2IT : S3TestBase() { .build() ) - val retention = s3ClientV2.getObjectRetention( + s3ClientV2.getObjectRetention( GetObjectRetentionRequest .builder() .bucket(bucketName) .key(sourceKey) .build() - ) - assertThat(retention.retention().mode()).isEqualTo(ObjectLockRetentionMode.COMPLIANCE) - //the returned date has MILLIS resolution, the local instant is in NANOS. - assertThat(retention.retention().retainUntilDate()) - .isCloseTo( - retainUntilDate, within(1, MILLIS) - ) + ).also { + assertThat(it.retention().mode()).isEqualTo(ObjectLockRetentionMode.COMPLIANCE) + //the returned date has MILLIS resolution, the local instant is in NANOS. + assertThat(it.retention().retainUntilDate()) + .isCloseTo( + retainUntilDate, within(1, MILLIS) + ) + } } @Test diff --git a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/S3TestBase.kt b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/S3TestBase.kt index aafc2ebcf..98ff7ed5c 100644 --- a/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/S3TestBase.kt +++ b/integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/S3TestBase.kt @@ -73,6 +73,7 @@ import software.amazon.awssdk.services.s3.model.S3Exception import software.amazon.awssdk.services.s3.model.S3Object import software.amazon.awssdk.services.s3.model.S3Response import software.amazon.awssdk.services.s3.model.StorageClass +import software.amazon.awssdk.services.s3.model.UploadPartResponse import software.amazon.awssdk.services.s3.multipart.MultipartConfiguration import software.amazon.awssdk.services.s3.presigner.S3Presigner import software.amazon.awssdk.transfer.s3.S3TransferManager @@ -355,8 +356,9 @@ internal abstract class S3TestBase { _s3ClientV2.deleteBucket(DeleteBucketRequest.builder().bucket(bucket.name()).build()) val bucketDeleted = _s3ClientV2.waiter() .waitUntilBucketNotExists(HeadBucketRequest.builder().bucket(bucket.name()).build()) - val bucketDeletedResponse = bucketDeleted.matched().exception().get() - assertThat(bucketDeletedResponse).isNotNull + bucketDeleted.matched().exception().get().also { + assertThat(it).isNotNull + } } private fun deleteObjectsInBucket(bucket: Bucket, objectLockEnabled: Boolean) { @@ -499,14 +501,16 @@ internal abstract class S3TestBase { } fun verifyObjectContent(uploadFile: File, s3Object: com.amazonaws.services.s3.model.S3Object) { - val uploadFileIs: InputStream = FileInputStream(uploadFile) - val uploadDigest = DigestUtil.hexDigest(uploadFileIs) - val downloadedDigest = DigestUtil.hexDigest(s3Object.objectContent) - uploadFileIs.close() - s3Object.close() - assertThat(uploadDigest) - .isEqualTo(downloadedDigest) - .`as`("Up- and downloaded Files should have equal digests") + val uploadDigest = FileInputStream(uploadFile).use { + DigestUtil.hexDigest(it) + } + + s3Object.use { + val downloadedDigest = DigestUtil.hexDigest(s3Object.objectContent) + assertThat(uploadDigest) + .isEqualTo(downloadedDigest) + .`as`("Up- and downloaded Files should have equal digests") + } } @@ -567,6 +571,7 @@ internal abstract class S3TestBase { is GetObjectResponse -> this.checksumSHA1() is PutObjectResponse -> this.checksumSHA1() is HeadObjectResponse -> this.checksumSHA1() + is UploadPartResponse -> this.checksumSHA1() else -> throw RuntimeException("Unexpected response type ${this::class.java}") } } @@ -576,6 +581,7 @@ internal abstract class S3TestBase { is GetObjectResponse -> this.checksumSHA256() is PutObjectResponse -> this.checksumSHA256() is HeadObjectResponse -> this.checksumSHA256() + is UploadPartResponse -> this.checksumSHA256() else -> throw RuntimeException("Unexpected response type ${this::class.java}") } } @@ -585,6 +591,7 @@ internal abstract class S3TestBase { is GetObjectResponse -> this.checksumCRC32() is PutObjectResponse -> this.checksumCRC32() is HeadObjectResponse -> this.checksumCRC32() + is UploadPartResponse -> this.checksumCRC32() else -> throw RuntimeException("Unexpected response type ${this::class.java}") } } @@ -594,6 +601,7 @@ internal abstract class S3TestBase { is GetObjectResponse -> this.checksumCRC32C() is PutObjectResponse -> this.checksumCRC32C() is HeadObjectResponse -> this.checksumCRC32C() + is UploadPartResponse -> this.checksumCRC32C() else -> throw RuntimeException("Unexpected response type ${this::class.java}") } } diff --git a/server/src/main/java/com/adobe/testing/s3mock/MultipartController.java b/server/src/main/java/com/adobe/testing/s3mock/MultipartController.java index 1c39fad22..ef1eb6d22 100644 --- a/server/src/main/java/com/adobe/testing/s3mock/MultipartController.java +++ b/server/src/main/java/com/adobe/testing/s3mock/MultipartController.java @@ -29,13 +29,16 @@ import static com.adobe.testing.s3mock.util.AwsHttpParameters.UPLOADS; import static com.adobe.testing.s3mock.util.AwsHttpParameters.UPLOAD_ID; import static com.adobe.testing.s3mock.util.HeaderUtil.checksumAlgorithmFromHeader; +import static com.adobe.testing.s3mock.util.HeaderUtil.checksumAlgorithmFromSdk; import static com.adobe.testing.s3mock.util.HeaderUtil.checksumFrom; +import static com.adobe.testing.s3mock.util.HeaderUtil.checksumHeaderFrom; import static com.adobe.testing.s3mock.util.HeaderUtil.encryptionHeadersFrom; import static com.adobe.testing.s3mock.util.HeaderUtil.storeHeadersFrom; import static com.adobe.testing.s3mock.util.HeaderUtil.userMetadataFrom; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; import static org.springframework.http.MediaType.APPLICATION_XML_VALUE; +import com.adobe.testing.s3mock.dto.ChecksumAlgorithm; import com.adobe.testing.s3mock.dto.CompleteMultipartUpload; import com.adobe.testing.s3mock.dto.CompleteMultipartUploadResult; import com.adobe.testing.s3mock.dto.CopyPartResult; @@ -51,6 +54,7 @@ import jakarta.servlet.http.HttpServletRequest; import java.io.InputStream; import java.util.List; +import java.util.Map; import java.util.UUID; import org.apache.commons.io.FileUtils; import org.springframework.http.HttpHeaders; @@ -212,22 +216,41 @@ public ResponseEntity uploadPart(@PathVariable String bucketName, multipartService.verifyMultipartUploadExists(uploadId); multipartService.verifyPartNumberLimits(partNumber); - var checksum = checksumFrom(httpHeaders); - var checksumAlgorithm = checksumAlgorithmFromHeader(httpHeaders); + String checksum = null; + ChecksumAlgorithm checksumAlgorithm = null; + ChecksumAlgorithm algorithmFromSdk = checksumAlgorithmFromSdk(httpHeaders); + if (algorithmFromSdk != null) { + checksum = tempFileAndChecksum.getRight(); + checksumAlgorithm = algorithmFromSdk; + } + ChecksumAlgorithm algorithmFromHeader = checksumAlgorithmFromHeader(httpHeaders); + if (algorithmFromHeader != null) { + checksum = checksumFrom(httpHeaders); + checksumAlgorithm = algorithmFromHeader; + } + + var tempFile = tempFileAndChecksum.getLeft(); + if (checksum != null) { + objectService.verifyChecksum(tempFile, checksum, checksumAlgorithm); + } //persist checksum per part var etag = multipartService.putPart(bucketName, key.key(), uploadId, partNumber, - tempFileAndChecksum.getLeft(), + tempFile, encryptionHeadersFrom(httpHeaders)); - FileUtils.deleteQuietly(tempFileAndChecksum.getLeft().toFile()); + FileUtils.deleteQuietly(tempFile.toFile()); - //return checksum headers - //return encryption headers - return ResponseEntity.ok().eTag("\"" + etag + "\"").build(); + Map checksumHeader = checksumHeaderFrom(checksum, checksumAlgorithm); + return ResponseEntity + .ok() + .headers(h -> h.setAll(checksumHeader)) + .headers(h -> h.setAll(encryptionHeadersFrom(httpHeaders))) + .eTag("\"" + etag + "\"") + .build(); } /** @@ -263,7 +286,7 @@ public ResponseEntity uploadPartCopy( @RequestParam String uploadId, @RequestParam String partNumber, @RequestHeader HttpHeaders httpHeaders) { - //TODO: needs modified-since handling, see API + //needs modified-since handling, see API bucketService.verifyBucketExists(bucketName); multipartService.verifyPartNumberLimits(partNumber); var s3ObjectMetadata = objectService.verifyObjectExists(copySource.bucket(), copySource.key()); diff --git a/server/src/main/java/com/adobe/testing/s3mock/ObjectController.java b/server/src/main/java/com/adobe/testing/s3mock/ObjectController.java index 860085034..9111790ee 100644 --- a/server/src/main/java/com/adobe/testing/s3mock/ObjectController.java +++ b/server/src/main/java/com/adobe/testing/s3mock/ObjectController.java @@ -87,6 +87,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.Map; @@ -615,7 +616,11 @@ public ResponseEntity putObject(@PathVariable String bucketName, checksumAlgorithm = algorithmFromHeader; } bucketService.verifyBucketExists(bucketName); - objectService.verifyMd5(tempFileAndChecksum.getLeft(), contentMd5); + Path tempFile = tempFileAndChecksum.getLeft(); + objectService.verifyMd5(tempFile, contentMd5); + if (checksum != null) { + objectService.verifyChecksum(tempFile, checksum, checksumAlgorithm); + } //TODO: need to extract owner from headers var owner = Owner.DEFAULT_OWNER; @@ -624,7 +629,7 @@ public ResponseEntity putObject(@PathVariable String bucketName, key.key(), mediaTypeFrom(contentType).toString(), storeHeadersFrom(httpHeaders), - tempFileAndChecksum.getLeft(), + tempFile, userMetadataFrom(httpHeaders), encryptionHeadersFrom(httpHeaders), tags, @@ -633,7 +638,7 @@ public ResponseEntity putObject(@PathVariable String bucketName, owner, storageClass); - FileUtils.deleteQuietly(tempFileAndChecksum.getLeft().toFile()); + FileUtils.deleteQuietly(tempFile.toFile()); //return version id return ResponseEntity diff --git a/server/src/main/java/com/adobe/testing/s3mock/service/ObjectService.java b/server/src/main/java/com/adobe/testing/s3mock/service/ObjectService.java index cd36bb44d..1974cf191 100644 --- a/server/src/main/java/com/adobe/testing/s3mock/service/ObjectService.java +++ b/server/src/main/java/com/adobe/testing/s3mock/service/ObjectService.java @@ -282,7 +282,7 @@ public Pair toTempFile(InputStream inputStream, HttpHeaders httpHe } } - void validateChecksum(Path path, String checksum, ChecksumAlgorithm checksumAlgorithm) { + public void verifyChecksum(Path path, String checksum, ChecksumAlgorithm checksumAlgorithm) { String checksumFor = DigestUtil.checksumFor(path, checksumAlgorithm.toAlgorithm()); if (!checksum.equals(checksumFor)) { throw BAD_DIGEST; diff --git a/server/src/main/java/com/adobe/testing/s3mock/util/HeaderUtil.java b/server/src/main/java/com/adobe/testing/s3mock/util/HeaderUtil.java index 45d2ac9f2..7a900d969 100644 --- a/server/src/main/java/com/adobe/testing/s3mock/util/HeaderUtil.java +++ b/server/src/main/java/com/adobe/testing/s3mock/util/HeaderUtil.java @@ -175,10 +175,16 @@ public static Map overrideHeadersFrom(Map queryPa public static Map checksumHeaderFrom(S3ObjectMetadata s3ObjectMetadata) { - Map headers = new HashMap<>(); ChecksumAlgorithm checksumAlgorithm = s3ObjectMetadata.checksumAlgorithm(); - if (checksumAlgorithm != null) { - headers.put(mapChecksumToHeader(checksumAlgorithm), s3ObjectMetadata.checksum()); + String checksum = s3ObjectMetadata.checksum(); + return checksumHeaderFrom(checksum, checksumAlgorithm); + } + + public static Map checksumHeaderFrom(String checksum, + ChecksumAlgorithm checksumAlgorithm) { + Map headers = new HashMap<>(); + if (checksumAlgorithm != null && checksum != null) { + headers.put(mapChecksumToHeader(checksumAlgorithm), checksum); } return headers; }