diff --git a/.changes/9636f12b-5612-41ec-a9ae-9434724e97a5.json b/.changes/9636f12b-5612-41ec-a9ae-9434724e97a5.json new file mode 100644 index 00000000000..4eadb0f7122 --- /dev/null +++ b/.changes/9636f12b-5612-41ec-a9ae-9434724e97a5.json @@ -0,0 +1,5 @@ +{ + "id": "9636f12b-5612-41ec-a9ae-9434724e97a5", + "type": "feature", + "description": "Add SigV4a support to the default AWS signer" +} \ No newline at end of file diff --git a/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/UnsupportedSigningAlgorithmInterceptor.kt b/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/UnsupportedSigningAlgorithmInterceptor.kt index e154ae53d80..d1e70eb706f 100644 --- a/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/UnsupportedSigningAlgorithmInterceptor.kt +++ b/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/UnsupportedSigningAlgorithmInterceptor.kt @@ -18,6 +18,7 @@ import aws.smithy.kotlin.runtime.http.response.HttpResponse * If so it sends users to a section in the AWS SDK for Kotlin documentation on how to fix it. */ @InternalSdkApi +@Deprecated("This interceptor is no longer used. It will be removed in the next minor version, v1.5.x.") public class UnsupportedSigningAlgorithmInterceptor : HttpInterceptor { override suspend fun modifyBeforeCompletion(context: ResponseInterceptorContext): Result { context.response.exceptionOrNull()?.let { diff --git a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt index ce35e258e41..d776c25a2af 100644 --- a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt +++ b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt @@ -59,7 +59,6 @@ object AwsRuntimeTypes { object Http : RuntimeTypePackage(AwsKotlinDependency.AWS_HTTP) { object Interceptors : RuntimeTypePackage(AwsKotlinDependency.AWS_HTTP, "interceptors") { val AddUserAgentMetadataInterceptor = symbol("AddUserAgentMetadataInterceptor") - val UnsupportedSigningAlgorithmInterceptor = symbol("UnsupportedSigningAlgorithmInterceptor") val IgnoreCompositeFlexibleChecksumResponseInterceptor = symbol("IgnoreCompositeFlexibleChecksumResponseInterceptor") object BusinessMetrics : RuntimeTypePackage(AwsKotlinDependency.AWS_HTTP, "interceptors.businessmetrics") { diff --git a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/UnsupportedSigningAlgorithmIntegration.kt b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/UnsupportedSigningAlgorithmIntegration.kt deleted file mode 100644 index c862059f2d7..00000000000 --- a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/UnsupportedSigningAlgorithmIntegration.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.sdk.kotlin.codegen.customization.s3 - -import aws.sdk.kotlin.codegen.AwsRuntimeTypes -import software.amazon.smithy.aws.traits.auth.SigV4ATrait -import software.amazon.smithy.kotlin.codegen.KotlinSettings -import software.amazon.smithy.kotlin.codegen.core.KotlinWriter -import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration -import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator -import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware -import software.amazon.smithy.model.Model -import software.amazon.smithy.model.knowledge.ServiceIndex -import software.amazon.smithy.model.shapes.OperationShape - -// FIXME: Remove this once sigV4a is supported by default AWS signer -/** - * Registers an interceptor for sigV4a services to deal with the default signer not supporting sigV4a - * See: [aws.sdk.kotlin.runtime.http.interceptors.UnsupportedSigningAlgorithmInterceptor] - */ -class UnsupportedSigningAlgorithmIntegration : KotlinIntegration { - override fun enabledForService(model: Model, settings: KotlinSettings): Boolean = - ServiceIndex - .of(model) - .getAuthSchemes(settings.service) - .values - .any { it.javaClass == SigV4ATrait::class.java } - - override fun customizeMiddleware( - ctx: ProtocolGenerator.GenerationContext, - resolved: List, - ): List = resolved + UnsupportedSigningAlgorithmMiddleware -} - -private val UnsupportedSigningAlgorithmMiddleware = object : ProtocolMiddleware { - override val name: String = "UnsupportedSigningAlgorithmMiddleware" - - override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { - writer.write( - "op.interceptors.add(#T())", - AwsRuntimeTypes.Http.Interceptors.UnsupportedSigningAlgorithmInterceptor, - ) - } -} diff --git a/codegen/aws-sdk-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration b/codegen/aws-sdk-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration index 7786616c0a6..e5dfdcff080 100644 --- a/codegen/aws-sdk-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration +++ b/codegen/aws-sdk-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration @@ -35,7 +35,6 @@ aws.sdk.kotlin.codegen.customization.machinelearning.MachineLearningEndpointCust aws.sdk.kotlin.codegen.customization.route53.TrimResourcePrefix aws.sdk.kotlin.codegen.customization.ec2.EC2MakePrimitivesOptional aws.sdk.kotlin.codegen.customization.RemoveDefaults -aws.sdk.kotlin.codegen.customization.s3.UnsupportedSigningAlgorithmIntegration aws.sdk.kotlin.codegen.customization.SigV4AsymmetricTraitCustomization aws.sdk.kotlin.codegen.customization.cloudfrontkeyvaluestore.BackfillSigV4ACustomization aws.sdk.kotlin.codegen.customization.s3.express.SigV4S3ExpressAuthSchemeIntegration diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3feadf9b887..6a4be30d30c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,8 +12,8 @@ atomicfu-version = "0.25.0" binary-compatibility-validator-version = "0.16.3" # smithy-kotlin codegen and runtime are versioned separately -smithy-kotlin-runtime-version = "1.4.3" -smithy-kotlin-codegen-version = "0.34.3" +smithy-kotlin-runtime-version = "1.4.5" +smithy-kotlin-codegen-version = "0.34.5" # codegen smithy-version = "1.53.0" diff --git a/services/s3/e2eTest/src/MultiRegionAccessPointTest.kt b/services/s3/e2eTest/src/MultiRegionAccessPointTest.kt new file mode 100644 index 00000000000..1ccdcb6a5e8 --- /dev/null +++ b/services/s3/e2eTest/src/MultiRegionAccessPointTest.kt @@ -0,0 +1,195 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.e2etest + +import aws.sdk.kotlin.e2etest.S3TestUtils.deleteBucketAndAllContents +import aws.sdk.kotlin.e2etest.S3TestUtils.getAccountId +import aws.sdk.kotlin.e2etest.S3TestUtils.getBucketWithPrefix +import aws.sdk.kotlin.services.s3.S3Client +import aws.sdk.kotlin.services.s3.deleteObject +import aws.sdk.kotlin.services.s3.putObject +import aws.sdk.kotlin.services.s3.withConfig +import aws.sdk.kotlin.services.s3control.S3ControlClient +import aws.sdk.kotlin.services.s3control.createMultiRegionAccessPoint +import aws.sdk.kotlin.services.s3control.deleteMultiRegionAccessPoint +import aws.sdk.kotlin.services.s3control.describeMultiRegionAccessPointOperation +import aws.sdk.kotlin.services.s3control.getMultiRegionAccessPoint +import aws.sdk.kotlin.services.s3control.model.Region +import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigner +import aws.smithy.kotlin.runtime.auth.awssigning.DefaultAwsSigner +import aws.smithy.kotlin.runtime.auth.awssigning.crt.CrtAwsSigner +import aws.smithy.kotlin.runtime.http.auth.SigV4AsymmetricAuthScheme +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds + +private const val MRAP_BUCKET_PREFIX = "s3-mrap-test-bucket-" +private const val MULTI_REGION_ACCESS_POINT_NAME = "aws-sdk-for-kotlin-test-multi-region-access-point" +private const val TEST_OBJECT_KEY = "test.txt" + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class MutliRegionAccessPointTest { + private lateinit var s3West: S3Client + private lateinit var s3East: S3Client + private lateinit var s3Control: S3ControlClient + + private lateinit var accountId: String + private lateinit var multiRegionAccessPointArn: String + private lateinit var usWestBucket: String + private lateinit var usEastBucket: String + + @BeforeAll + fun setup(): Unit = runBlocking { + s3West = S3Client { region = "us-west-2" } + s3East = S3Client { region = "us-east-2" } + s3Control = S3ControlClient { region = "us-west-2" } + + accountId = getAccountId() + usWestBucket = getBucketWithPrefix(s3West, MRAP_BUCKET_PREFIX, "us-west-2", accountId) + usEastBucket = getBucketWithPrefix(s3East, MRAP_BUCKET_PREFIX, "us-east-2", accountId) + + multiRegionAccessPointArn = s3Control.createMultiRegionAccessPoint( + MULTI_REGION_ACCESS_POINT_NAME, + accountId, + listOf(usWestBucket, usEastBucket), + ) + } + + @AfterAll + fun cleanup(): Unit = runBlocking { + s3Control.deleteMultiRegionAccessPoint(MULTI_REGION_ACCESS_POINT_NAME, accountId) + + deleteBucketAndAllContents(s3West, usWestBucket) + deleteBucketAndAllContents(s3East, usEastBucket) + + s3West.close() + s3East.close() + s3Control.close() + } + + @ParameterizedTest + @MethodSource("signerProvider") + fun testMultiRegionAccessPointOperation(signer: AwsSigner): Unit = runBlocking { + println("Testing multi-region access point operations with $signer") + + val s3SigV4a = s3West.withConfig { + authSchemes = listOf(SigV4AsymmetricAuthScheme(signer)) + } + + s3SigV4a.putObject { + bucket = multiRegionAccessPointArn + key = TEST_OBJECT_KEY + } + + s3SigV4a.deleteObject { + bucket = multiRegionAccessPointArn + key = TEST_OBJECT_KEY + } + } + + fun signerProvider(): Stream = Stream.of( + Arguments.of(DefaultAwsSigner), + Arguments.of(CrtAwsSigner), + ) +} + +/** + * Create a multi-region access point named [name] in account [accountId] with [buckets] buckets. + * @return the ARN of the multi-region access point that was created + */ +private suspend fun S3ControlClient.createMultiRegionAccessPoint( + name: String, + accountId: String, + buckets: List, +): String { + println("Creating multi-region access point: $name") + + val requestTokenArn = checkNotNull( + createMultiRegionAccessPoint { + this.accountId = accountId + details { + this.name = name + this.regions = buckets.map { Region { bucket = it } } + } + }.requestTokenArn, + ) { "createMultiRegionAccessPoint requestTokenArn was unexpectedly null" } + + waitUntilOperationCompletes("createMultiRegionAccessPoint", accountId, requestTokenArn, 10.minutes) + + return getMultiRegionAccessPointArn(name, accountId) +} + +private suspend fun S3ControlClient.getMultiRegionAccessPointArn( + name: String, + accountId: String, +): String = getMultiRegionAccessPoint { + this.name = name + this.accountId = accountId +}.accessPoint?.alias?.let { + "arn:aws:s3::$accountId:accesspoint/$it" +} ?: throw IllegalStateException("Failed to get ARN for multi-region access point $name") + +private suspend fun S3ControlClient.deleteMultiRegionAccessPoint( + name: String, + accountId: String, +) { + println("Deleting multi-region access point $name") + + val requestTokenArn = checkNotNull( + deleteMultiRegionAccessPoint { + this.accountId = accountId + details { + this.name = name + } + }.requestTokenArn, + ) { "deleteMultiRegionAccessPoint requestTokenArn was unexpectedly null" } + + waitUntilOperationCompletes("deleteMultiRegionAccessPoint", accountId, requestTokenArn, 5.minutes) +} + +/** + * Continuously poll the status of [requestTokenArn] until its status is "SUCCEEDED" or [timeout] duration has passed. + */ +private suspend fun S3ControlClient.waitUntilOperationCompletes( + operation: String, + accountId: String, + requestTokenArn: String, + timeout: Duration, +) = withTimeout(timeout) { + var status: String? = null + + while (true) { + val latestStatus = describeMultiRegionAccessPointOperation { + this.accountId = accountId + this.requestTokenArn = requestTokenArn + }.asyncOperation?.requestStatus + + when (latestStatus) { + "SUCCEEDED" -> { + println("$operation operation succeeded.") + return@withTimeout + } + "FAILED" -> throw IllegalStateException("$operation operation failed") + else -> { + if (status == null || latestStatus != status) { + println("Waiting for $operation to complete. Status: $latestStatus ") + status = latestStatus + } + } + } + + delay(10.seconds) // Avoid constant status checks + } +} diff --git a/services/s3/e2eTest/src/MutliRegionAccessPointTest.kt b/services/s3/e2eTest/src/MutliRegionAccessPointTest.kt deleted file mode 100644 index 49630763536..00000000000 --- a/services/s3/e2eTest/src/MutliRegionAccessPointTest.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.sdk.kotlin.e2etest - -import aws.sdk.kotlin.e2etest.S3TestUtils.createMultiRegionAccessPoint -import aws.sdk.kotlin.e2etest.S3TestUtils.deleteBucketAndAllContents -import aws.sdk.kotlin.e2etest.S3TestUtils.deleteMultiRegionAccessPoint -import aws.sdk.kotlin.e2etest.S3TestUtils.getAccountId -import aws.sdk.kotlin.e2etest.S3TestUtils.getBucketWithPrefix -import aws.sdk.kotlin.e2etest.S3TestUtils.getMultiRegionAccessPointArn -import aws.sdk.kotlin.e2etest.S3TestUtils.multiRegionAccessPointWasCreated -import aws.sdk.kotlin.services.s3.S3Client -import aws.sdk.kotlin.services.s3.deleteObject -import aws.sdk.kotlin.services.s3.putObject -import aws.sdk.kotlin.services.s3.withConfig -import aws.sdk.kotlin.services.s3control.S3ControlClient -import aws.smithy.kotlin.runtime.auth.awssigning.UnsupportedSigningAlgorithmException -import aws.smithy.kotlin.runtime.auth.awssigning.crt.CrtAwsSigner -import aws.smithy.kotlin.runtime.http.auth.SigV4AsymmetricAuthScheme -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.TestInstance -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -private const val MRAP_BUCKET_PREFIX = "s3-mrap-test-bucket-" - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class MutliRegionAccessPointTest { - private val s3West = S3Client { region = "us-west-2" } - private val s3East = s3West.withConfig { region = "us-east-2" } - private val s3SigV4a = s3West.withConfig { authSchemes = listOf(SigV4AsymmetricAuthScheme(CrtAwsSigner)) } - private val s3Control = S3ControlClient { region = "us-west-2" } - - private val multiRegionAccessPoint = "aws-sdk-for-kotlin-test-multi-region-access-point" - private val objectKey = "test.txt" - - private lateinit var accountId: String - private lateinit var multiRegionAccessPointArn: String - private lateinit var usWestBucket: String - private lateinit var usEastBucket: String - - @BeforeAll - private fun setUp(): Unit = runBlocking { - accountId = getAccountId() - usWestBucket = getBucketWithPrefix(s3West, MRAP_BUCKET_PREFIX, "us-west-2", accountId) - usEastBucket = getBucketWithPrefix(s3East, MRAP_BUCKET_PREFIX, "us-east-2", accountId) - - createMultiRegionAccessPoint( - s3Control, - multiRegionAccessPoint, - usWestBucket, - usEastBucket, - accountId, - ) - - multiRegionAccessPointArn = - getMultiRegionAccessPointArn( - s3Control, - multiRegionAccessPoint, - accountId, - ) - } - - @AfterAll - private fun cleanUp(): Unit = runBlocking { - if (multiRegionAccessPointWasCreated(s3Control, multiRegionAccessPoint, accountId)) { - deleteMultiRegionAccessPoint(s3Control, multiRegionAccessPoint, accountId) - } - - deleteBucketAndAllContents(s3West, usWestBucket) - deleteBucketAndAllContents(s3East, usEastBucket) - - s3West.close() - s3East.close() - s3SigV4a.close() - s3Control.close() - } - - @Test - fun testMultiRegionAccessPointOperation(): Unit = runBlocking { - s3SigV4a.putObject { - bucket = multiRegionAccessPointArn - key = objectKey - } - - s3SigV4a.deleteObject { - bucket = multiRegionAccessPointArn - key = objectKey - } - } - - @Test - fun testUnsupportedSigningAlgorithm(): Unit = runBlocking { - val ex = assertFailsWith { - s3West.putObject { - bucket = multiRegionAccessPointArn - key = objectKey - } - } - - assertEquals( - ex.message, - "SIGV4A support is not yet implemented for the default signer. For more information on how to enable it with the CRT signer, please refer to: https://a.co/3sf8533", - ) - } -} diff --git a/services/s3/e2eTest/src/S3TestUtils.kt b/services/s3/e2eTest/src/S3TestUtils.kt index d3e46bcb5c5..c6162a76294 100644 --- a/services/s3/e2eTest/src/S3TestUtils.kt +++ b/services/s3/e2eTest/src/S3TestUtils.kt @@ -25,8 +25,6 @@ import java.io.OutputStreamWriter import java.net.URL import java.util.* import javax.net.ssl.HttpsURLConnection -import kotlin.time.Duration -import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds object S3TestUtils { @@ -209,120 +207,6 @@ object S3TestUtils { return checkNotNull(accountId) { "Unable to get AWS account ID" } } - internal suspend fun createMultiRegionAccessPoint( - s3ControlClient: S3ControlClient, - multiRegionAccessPointName: String, - regionOneBucket: String, - regionTwoBucket: String, - testAccountId: String, - ) { - println("Creating multi region access point: $multiRegionAccessPointName") - - val createRequestToken = s3ControlClient.createMultiRegionAccessPoint { - accountId = testAccountId - details { - name = multiRegionAccessPointName - regions = listOf( - Region { bucket = regionOneBucket }, - Region { bucket = regionTwoBucket }, - ) - } - } - - waitUntilMultiRegionAccessPointOperationCompletes( - s3ControlClient, - checkNotNull(createRequestToken.requestTokenArn) { "Unable to get request token ARN" }, - 10.minutes, - testAccountId, - "createMultiRegionAccessPoint", - ) - } - - internal suspend fun getMultiRegionAccessPointArn( - s3ControlClient: S3ControlClient, - multiRegionAccessPointName: String, - testAccountId: String, - ): String { - println("Getting multi region access point arn for: $multiRegionAccessPointName") - - s3ControlClient.getMultiRegionAccessPoint { - accountId = testAccountId - name = multiRegionAccessPointName - }.accessPoint?.alias?.let { alias -> - return "arn:aws:s3::$testAccountId:accesspoint/$alias" - } - throw Exception("Unable to get multi region access point arn") - } - - internal suspend fun deleteMultiRegionAccessPoint( - s3ControlClient: S3ControlClient, - multiRegionAccessPointName: String, - testAccountId: String, - ) { - println("Deleting multi region access point: $multiRegionAccessPointName") - - val deleteRequestToken = s3ControlClient.deleteMultiRegionAccessPoint { - accountId = testAccountId - details { - name = multiRegionAccessPointName - } - } - - waitUntilMultiRegionAccessPointOperationCompletes( - s3ControlClient, - checkNotNull(deleteRequestToken.requestTokenArn) { "Unable to get request token ARN" }, - 5.minutes, - testAccountId, - "deleteMultiRegionAccessPoint", - ) - } - - private suspend fun waitUntilMultiRegionAccessPointOperationCompletes( - s3ControlClient: S3ControlClient, - request: String, - timeoutAfter: Duration, - testAccountId: String, - operation: String, - ) { - withTimeout(timeoutAfter) { - var status: String? = null - while (true) { - val latestStatus = s3ControlClient.describeMultiRegionAccessPointOperation { - accountId = testAccountId - requestTokenArn = request - }.asyncOperation?.requestStatus - - when (latestStatus) { - "SUCCEEDED" -> { - println("$operation operation succeeded.") - return@withTimeout - } - "FAILED" -> throw IllegalStateException("$operation operation failed") - else -> { - if (status == null || latestStatus != status) { - println("Waiting on $operation operation. Status: $latestStatus ") - status = latestStatus - } - } - } - - delay(10.seconds) // Avoid constant status checks - } - } - } - - internal suspend fun multiRegionAccessPointWasCreated( - s3Control: S3ControlClient, - multiRegionAccessPointName: String, - testAccountId: String, - ): Boolean { - println("Checking if multi region access point was created: $multiRegionAccessPointName") - - return s3Control.listMultiRegionAccessPoints { - accountId = testAccountId - }.accessPoints?.any { it.name == multiRegionAccessPointName } ?: false - } - internal suspend fun deleteMultiPartUploads(client: S3Client, bucketName: String) { client.listMultipartUploads { bucket = bucketName diff --git a/services/sesv2/e2eTest/src/Sigv4aTest.kt b/services/sesv2/e2eTest/src/Sigv4aTest.kt index 601bd132578..04aeab6336d 100644 --- a/services/sesv2/e2eTest/src/Sigv4aTest.kt +++ b/services/sesv2/e2eTest/src/Sigv4aTest.kt @@ -12,7 +12,6 @@ import aws.smithy.kotlin.runtime.http.auth.SigV4AsymmetricAuthScheme import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor import aws.smithy.kotlin.runtime.http.request.HttpRequest import kotlinx.coroutines.runBlocking -import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertEquals @@ -21,7 +20,6 @@ import kotlin.test.assertNotNull class Sigv4aTest { @Test - @Ignore // TODO enable once SESv2 model adds endpointId and Sigv4a fun testSigv4a() = runBlocking { val interceptor = RequestCapturingInterceptor() @@ -36,7 +34,7 @@ class Sigv4aTest { }.use { ses -> assertFailsWith { ses.sendEmail { - // endpointId = "bdm3x3zl.n5x" // TODO uncomment + endpointId = "bdm3x3zl.n5x" } } }