From 3805f06f3522466793116541fa5cf836c3812114 Mon Sep 17 00:00:00 2001 From: David Hasani Date: Thu, 22 May 2025 00:20:58 -0700 Subject: [PATCH 01/12] fix(amazonq): add retries for /transform APIs --- .../codemodernizer/client/GumbyClient.kt | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/client/GumbyClient.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/client/GumbyClient.kt index f94e25d166f..37b6c3b2069 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/client/GumbyClient.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/client/GumbyClient.kt @@ -7,6 +7,7 @@ import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.util.io.HttpRequests +import software.amazon.awssdk.core.exception.SdkException import software.amazon.awssdk.services.codewhispererruntime.CodeWhispererRuntimeClient import software.amazon.awssdk.services.codewhispererruntime.model.CodeWhispererRuntimeResponse import software.amazon.awssdk.services.codewhispererruntime.model.ContentChecksumType @@ -32,8 +33,10 @@ import software.amazon.awssdk.services.codewhispererruntime.model.UploadContext import software.amazon.awssdk.services.codewhispererruntime.model.UploadIntent import software.amazon.awssdk.services.codewhispererstreaming.model.ExportContext import software.amazon.awssdk.services.codewhispererstreaming.model.ExportIntent +import software.amazon.awssdk.services.codewhispererstreaming.model.ThrottlingException import software.amazon.awssdk.services.codewhispererstreaming.model.TransformationDownloadArtifactType import software.amazon.awssdk.services.codewhispererstreaming.model.TransformationExportContext +import software.amazon.awssdk.services.codewhispererstreaming.model.ValidationException import software.aws.toolkits.core.utils.error import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.info @@ -41,6 +44,7 @@ import software.aws.toolkits.jetbrains.core.AwsClientManager import software.aws.toolkits.jetbrains.services.amazonq.APPLICATION_ZIP import software.aws.toolkits.jetbrains.services.amazonq.AWS_KMS import software.aws.toolkits.jetbrains.services.amazonq.CONTENT_SHA256 +import software.aws.toolkits.jetbrains.services.amazonq.RetryableOperation import software.aws.toolkits.jetbrains.services.amazonq.SERVER_SIDE_ENCRYPTION import software.aws.toolkits.jetbrains.services.amazonq.SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID import software.aws.toolkits.jetbrains.services.amazonq.clients.AmazonQStreamingClient @@ -52,7 +56,9 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.utils.calculateTo import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getTelemetryOptOutPreference import java.io.File import java.net.HttpURLConnection +import java.net.SocketTimeoutException import java.time.Instant +import java.util.concurrent.TimeoutException @Service(Service.Level.PROJECT) class GumbyClient(private val project: Project) { @@ -152,15 +158,34 @@ class GumbyClient(private val project: Project) { apiCall: () -> T, apiName: String, ): T { - var result: CodeWhispererRuntimeResponse? = null + var result: T? = null try { - result = apiCall() - LOG.info { "$apiName request ID: ${result.responseMetadata()?.requestId()}" } - return result + RetryableOperation().execute( + operation = { + result = apiCall() + }, + isRetryable = { e -> + when (e) { + is ValidationException, + is ThrottlingException, + is SdkException, + is TimeoutException, + is SocketTimeoutException, + -> true + else -> false + } + }, + errorHandler = { e, attempts -> + LOG.error(e) { "After $attempts attempts, $apiName failed: ${e.message}" } + throw e + } + ) } catch (e: Exception) { - LOG.error(e) { "$apiName failed: ${e.message}" } - throw e // pass along error to callee + LOG.error(e) { "$apiName failed: ${e.message}; may have been retried up to 3 times" } + throw e } + LOG.info { "$apiName request ID: ${result?.responseMetadata()?.requestId()}" } + return result ?: error("$apiName failed") } suspend fun downloadExportResultArchive( From 058d228d2cd36e6cf0f7f2299b37ba23f4429af5 Mon Sep 17 00:00:00 2001 From: David Hasani Date: Thu, 22 May 2025 00:26:30 -0700 Subject: [PATCH 02/12] set attempts to 4 --- .../jetbrains/services/amazonq/RetryableOperation.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/RetryableOperation.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/RetryableOperation.kt index 049ca957d08..36da6aac003 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/RetryableOperation.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/RetryableOperation.kt @@ -30,12 +30,12 @@ class RetryableOperation { isRetryable: (Exception) -> Boolean = { it is RetryableException }, errorHandler: (suspend (Exception, Int) -> Nothing), ): T { - while (attempts < MAX_RETRY_ATTEMPTS) { + while (attempts < MAX_ATTEMPTS) { try { return operation() } catch (e: Exception) { attempts++ - if (attempts >= MAX_RETRY_ATTEMPTS || !isRetryable(e)) { + if (attempts >= MAX_ATTEMPTS || !isRetryable(e)) { errorHandler.invoke(e, attempts) } delay(getJitteredDelay()) @@ -48,6 +48,6 @@ class RetryableOperation { companion object { private const val INITIAL_DELAY = 100L private const val MAX_BACKOFF = 10000L - private const val MAX_RETRY_ATTEMPTS = 3 + private const val MAX_ATTEMPTS = 4 } } From 3679de38d2d18eee327b615ec4a0a94a712023fb Mon Sep 17 00:00:00 2001 From: David Hasani Date: Thu, 22 May 2025 00:48:30 -0700 Subject: [PATCH 03/12] handle re-auth --- .../codemodernizer/utils/CodeTransformApiUtils.kt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt index 730ab8ea267..759d512467f 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt @@ -144,16 +144,10 @@ suspend fun JobId.pollTransformationStatusAndPlan( refreshToken(project) return@waitUntil state } catch (e: InvalidGrantException) { - CodeTransformMessageListener.instance.onCheckAuth() + CodeTransformMessageListener.instance.onReauthStarted() notifyStickyWarn( message("codemodernizer.notification.warn.expired_credentials.title"), message("codemodernizer.notification.warn.expired_credentials.content"), - project, - listOf( - NotificationAction.createSimpleExpiring(message("codemodernizer.notification.warn.action.reauthenticate")) { - CodeTransformMessageListener.instance.onReauthStarted() - } - ) ) return@waitUntil state } finally { From 21d39668f68168b07bc7183625d4309c603100ba Mon Sep 17 00:00:00 2001 From: David Hasani Date: Thu, 22 May 2025 00:55:08 -0700 Subject: [PATCH 04/12] text change --- .../software/aws/toolkits/resources/MessagesBundle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties index 0aaf82bbd84..eee209db88c 100644 --- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties +++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -626,7 +626,7 @@ codemodernizer.chat.form.user_selection.item.choose_sql_metadata_file=Okay, I ca codemodernizer.chat.form.user_selection.item.choose_target_version=Choose the target code version codemodernizer.chat.form.user_selection.title=Q - Code transformation codemodernizer.chat.message.absolute_path_detected=I detected {0} potential absolute file path(s) in your {1} file: **{2}**. Absolute file paths might cause issues when I build your code. Any errors will show up in the build log. -codemodernizer.chat.message.auth_prompt=Follow instructions to re-authenticate ... +codemodernizer.chat.message.auth_prompt=Follow instructions to re-authenticate. If you experience difficulty, sign out of the Q plugin and sign in again. codemodernizer.chat.message.button.cancel=Cancel codemodernizer.chat.message.button.confirm=Confirm codemodernizer.chat.message.button.hil_cancel=Cancel From bd6cbd7c5265202d3607d95eebf20a50fb3b7811 Mon Sep 17 00:00:00 2001 From: David Hasani Date: Thu, 22 May 2025 01:06:51 -0700 Subject: [PATCH 05/12] minor text change --- .../software/aws/toolkits/resources/MessagesBundle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties index eee209db88c..81529761bff 100644 --- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties +++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -780,7 +780,7 @@ codemodernizer.notification.warn.download_failed_invalid_artifact=Amazon Q was u codemodernizer.notification.warn.download_failed_other.content=Amazon Q ran into an issue while trying to download your {0}. Please try again. {1} codemodernizer.notification.warn.download_failed_ssl.content=Please make sure all your certificates for your proxy client have been set up correctly for your IDE. codemodernizer.notification.warn.download_failed_wildcard.content=Check your IDE proxy settings and remove any wildcard (*) references, and then try viewing the diff again. -codemodernizer.notification.warn.expired_credentials.content=Unable to check transformation status as your credentials expired. Try signing out of Amazon Q and signing back in again if 'Reauthenticate' below does not work. +codemodernizer.notification.warn.expired_credentials.content=Unable to check transformation status as your credentials expired. Try signing out of the Amazon Q plugin and signing in again. codemodernizer.notification.warn.expired_credentials.title=Your connection to Q has expired codemodernizer.notification.warn.invalid_project.description.reason.missing_content_roots=None of your open modules are supported for code transformation with Amazon Q. Amazon Q can upgrade Java 8, Java 11, Java 17, and Java 21 projects built on Maven, with content roots configured. codemodernizer.notification.warn.invalid_project.description.reason.not_logged_in=Amazon Q cannot start the transformation as you are not logged in with Identity Center or Builder ID. Also ensure that you are not using IntelliJ version 232.8660.185 and that you are not developing on a remote host (uncommon). From db33b04219da363907bda703539aa8892a31a543 Mon Sep 17 00:00:00 2001 From: David Hasani Date: Thu, 22 May 2025 01:07:36 -0700 Subject: [PATCH 06/12] minor text change --- .../software/aws/toolkits/resources/MessagesBundle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties index 81529761bff..9b189e2b222 100644 --- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties +++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -626,7 +626,7 @@ codemodernizer.chat.form.user_selection.item.choose_sql_metadata_file=Okay, I ca codemodernizer.chat.form.user_selection.item.choose_target_version=Choose the target code version codemodernizer.chat.form.user_selection.title=Q - Code transformation codemodernizer.chat.message.absolute_path_detected=I detected {0} potential absolute file path(s) in your {1} file: **{2}**. Absolute file paths might cause issues when I build your code. Any errors will show up in the build log. -codemodernizer.chat.message.auth_prompt=Follow instructions to re-authenticate. If you experience difficulty, sign out of the Q plugin and sign in again. +codemodernizer.chat.message.auth_prompt=Follow instructions to re-authenticate. If you experience difficulty, sign out of the Amazon Q plugin and sign in again. codemodernizer.chat.message.button.cancel=Cancel codemodernizer.chat.message.button.confirm=Confirm codemodernizer.chat.message.button.hil_cancel=Cancel From 5abc53b89b9fdfd87604a12da6bd9920236e3803 Mon Sep 17 00:00:00 2001 From: David Hasani Date: Thu, 22 May 2025 01:10:12 -0700 Subject: [PATCH 07/12] remove unused import --- .../services/codemodernizer/utils/CodeTransformApiUtils.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt index 759d512467f..774d99e961b 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt @@ -5,7 +5,6 @@ package software.aws.toolkits.jetbrains.services.codemodernizer.utils import com.fasterxml.jackson.module.kotlin.readValue import com.intellij.grazie.utils.orFalse -import com.intellij.notification.NotificationAction import com.intellij.openapi.application.runInEdt import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.diff.impl.patch.PatchReader From bcfeb21d34d2aae58c7d72d03044aeebf15e4513 Mon Sep 17 00:00:00 2001 From: David Hasani Date: Thu, 22 May 2025 01:18:47 -0700 Subject: [PATCH 08/12] add changelog --- .../bugfix-ddfb0a9a-529f-4597-8b7a-519d4e838fa4.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .changes/next-release/bugfix-ddfb0a9a-529f-4597-8b7a-519d4e838fa4.json diff --git a/.changes/next-release/bugfix-ddfb0a9a-529f-4597-8b7a-519d4e838fa4.json b/.changes/next-release/bugfix-ddfb0a9a-529f-4597-8b7a-519d4e838fa4.json new file mode 100644 index 00000000000..f1a3fe8dc39 --- /dev/null +++ b/.changes/next-release/bugfix-ddfb0a9a-529f-4597-8b7a-519d4e838fa4.json @@ -0,0 +1,4 @@ +{ + "type" : "bugfix", + "description" : "/transform: handle InvalidGrantException properly when polling job status" +} \ No newline at end of file From d4f9edf504c16ad133dcfeffb7c5c1f6ca7fcb1c Mon Sep 17 00:00:00 2001 From: David Hasani Date: Thu, 22 May 2025 03:38:07 -0700 Subject: [PATCH 09/12] fix test --- .../services/amazonq/clients/AmazonQStreamingClientTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/clients/AmazonQStreamingClientTest.kt b/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/clients/AmazonQStreamingClientTest.kt index 3727a46aa7f..20e21f46ba5 100644 --- a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/clients/AmazonQStreamingClientTest.kt +++ b/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/clients/AmazonQStreamingClientTest.kt @@ -127,7 +127,7 @@ class AmazonQStreamingClientTest : AmazonQTestBase() { amazonQStreamingClient.exportResultArchive("test-id", ExportIntent.TRANSFORMATION, null, {}, {}) } - assertThat(attemptCount).isEqualTo(3) + assertThat(attemptCount).isEqualTo(4) assertThat(thrown) .isInstanceOf(ValidationException::class.java) .hasMessage("Resource validation failed") From 766a1c300f64260242dfbdbea05d5e9f295b631e Mon Sep 17 00:00:00 2001 From: David Hasani Date: Fri, 23 May 2025 10:51:08 -0700 Subject: [PATCH 10/12] log error --- .../jetbrains/services/codemodernizer/CodeModernizerSession.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt index 88b055a41b8..d15047c4fe8 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt @@ -547,7 +547,7 @@ class CodeModernizerSession( } catch (e: Exception) { return when (e) { is AlreadyDisposedException, is CancellationException -> { - LOG.warn { "The session was disposed while polling for job details." } + LOG.error(e) { "The session was disposed while polling for job details." } CodeModernizerJobCompletedResult.ManagerDisposed } From 907959037a1cf67600742f57c15c6a9cc61d6c9f Mon Sep 17 00:00:00 2001 From: David Hasani Date: Fri, 23 May 2025 12:06:33 -0700 Subject: [PATCH 11/12] add log --- .../services/codemodernizer/utils/CodeTransformApiUtils.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt index 774d99e961b..f3b424d8e3c 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt @@ -154,6 +154,7 @@ suspend fun JobId.pollTransformationStatusAndPlan( } } } catch (e: Exception) { + getLogger().error(e) { "Error when polling for job status & plan" } // Still call onStateChange to update the UI onStateChange(state, TransformationStatus.FAILED, transformationPlan) when (e) { From a6092d5aecf3324cfc28af7d2dfa67b478e6b259 Mon Sep 17 00:00:00 2001 From: David Hasani Date: Fri, 23 May 2025 13:03:47 -0700 Subject: [PATCH 12/12] add tests for retry logic --- ...eWhispererCodeModernizerGumbyClientTest.kt | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt index ab3a4985b34..6e8da48cce8 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerGumbyClientTest.kt @@ -13,10 +13,12 @@ import org.junit.Rule import org.junit.Test import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.stub +import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.verifyNoMoreInteractions @@ -32,6 +34,7 @@ import software.amazon.awssdk.services.codewhispererruntime.model.StartTransform import software.amazon.awssdk.services.codewhispererruntime.model.StartTransformationResponse import software.amazon.awssdk.services.codewhispererruntime.model.StopTransformationRequest import software.amazon.awssdk.services.codewhispererruntime.model.StopTransformationResponse +import software.amazon.awssdk.services.codewhispererruntime.model.ThrottlingException import software.amazon.awssdk.services.codewhispererruntime.model.TransformationLanguage import software.amazon.awssdk.services.codewhispererstreaming.CodeWhispererStreamingAsyncClient import software.amazon.awssdk.services.codewhispererstreaming.model.ExportIntent @@ -135,6 +138,80 @@ class CodeWhispererCodeModernizerGumbyClientTest : CodeWhispererCodeModernizerTe } } + @Test + fun `getCodeModernizationJob should retry on retryable exceptions`() { + var callCount = 0 + bearerClient = mockClientManagerRule.create().stub { + on { getTransformation(any()) } doAnswer { + callCount++ + when (callCount) { + 1 -> throw ThrottlingException.builder().message("Throttled 1").build() + 2 -> throw ThrottlingException.builder().message("Throttled 2").build() + else -> exampleGetCodeMigrationResponse + } + } + } + val actual = gumbyClient.getCodeModernizationJob("jobId") + argumentCaptor().apply { + // succeeds on 3rd attempt + verify(bearerClient, times(3)).getTransformation(capture()) + verifyNoMoreInteractions(bearerClient) + verifyNoInteractions(streamingBearerClient) + assertThat(allValues).hasSize(3) + allValues.forEach { request -> + assertThat(request.transformationJobId()).isEqualTo("jobId") + } + assertThat(actual).isInstanceOf(GetTransformationResponse::class.java) + assertThat(actual).usingRecursiveComparison() + .comparingOnlyFields("jobId", "status", "transformationType", "source") + .isEqualTo(exampleGetCodeMigrationResponse) + } + } + + @Test + fun `getCodeModernizationJob should fail immediately on non-retryable exception`() { + val exception = IllegalArgumentException("Non-retryable error") + bearerClient = mockClientManagerRule.create().stub { + on { getTransformation(any()) } doAnswer { + throw exception + } + } + val thrown = runCatching { + gumbyClient.getCodeModernizationJob("jobId") + }.exceptionOrNull() + assertThat(thrown) + .isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("Non-retryable error") + // called just once since it fails immediately + verify(bearerClient, times(1)).getTransformation(any()) + verifyNoMoreInteractions(bearerClient) + verifyNoInteractions(streamingBearerClient) + } + + @Test + fun `getCodeModernizationJob should fail after max attempts`() { + bearerClient = mockClientManagerRule.create().stub { + on { getTransformation(any()) } doAnswer { + throw ThrottlingException.builder().message("Always throttled").build() + } + } + val thrown = runCatching { + gumbyClient.getCodeModernizationJob("jobId") + }.exceptionOrNull() + assertThat(thrown) + .isInstanceOf(ThrottlingException::class.java) + .hasMessage("Always throttled") + argumentCaptor().apply { + // called 4 times since it always fails + verify(bearerClient, times(4)).getTransformation(capture()) + allValues.forEach { request -> + assertThat(request.transformationJobId()).isEqualTo("jobId") + } + } + verifyNoMoreInteractions(bearerClient) + verifyNoInteractions(streamingBearerClient) + } + @Test fun `check startCodeModernization on JAVA_17 target`() { val actual = gumbyClient.startCodeModernization("jobId", TransformationLanguage.JAVA_8, TransformationLanguage.JAVA_17)