Skip to content

Commit 93cd2a7

Browse files
committed
Adding tests for backoff and retry for export result archive.
1 parent 74ec75d commit 93cd2a7

File tree

1 file changed

+156
-0
lines changed

1 file changed

+156
-0
lines changed

plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/clients/AmazonQStreamingClientTest.kt

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ package software.aws.toolkits.jetbrains.services.amazonq.clients
55

66
import com.intellij.testFramework.RuleChain
77
import com.intellij.testFramework.replaceService
8+
import kotlinx.coroutines.runBlocking
89
import kotlinx.coroutines.test.runTest
10+
import org.assertj.core.api.Assertions.assertThat
911
import org.junit.Before
1012
import org.junit.Rule
1113
import org.junit.Test
1214
import org.mockito.kotlin.any
1315
import org.mockito.kotlin.argumentCaptor
16+
import org.mockito.kotlin.doAnswer
1417
import org.mockito.kotlin.doReturn
1518
import org.mockito.kotlin.mock
1619
import org.mockito.kotlin.stub
@@ -20,6 +23,7 @@ import software.amazon.awssdk.services.codewhispererstreaming.CodeWhispererStrea
2023
import software.amazon.awssdk.services.codewhispererstreaming.model.ExportIntent
2124
import software.amazon.awssdk.services.codewhispererstreaming.model.ExportResultArchiveRequest
2225
import software.amazon.awssdk.services.codewhispererstreaming.model.ExportResultArchiveResponseHandler
26+
import software.amazon.awssdk.services.codewhispererstreaming.model.ValidationException
2327
import software.amazon.awssdk.services.ssooidc.SsoOidcClient
2428
import software.aws.toolkits.core.TokenConnectionSettings
2529
import software.aws.toolkits.core.utils.test.aString
@@ -81,4 +85,156 @@ class AmazonQStreamingClientTest : AmazonQTestBase() {
8185
verify(streamingBearerClient).exportResultArchive(requestCaptor.capture(), handlerCaptor.capture())
8286
}
8387
}
88+
89+
@Test
90+
fun `verify retry on ValidationException`(): Unit = runBlocking {
91+
var attemptCount = 0
92+
streamingBearerClient = mockClientManagerRule.create<CodeWhispererStreamingAsyncClient>().stub {
93+
on {
94+
exportResultArchive(any<ExportResultArchiveRequest>(), any<ExportResultArchiveResponseHandler>())
95+
} doAnswer {
96+
attemptCount++
97+
if (attemptCount <= 2) {
98+
CompletableFuture<Void>().apply {
99+
completeExceptionally(VALIDATION_EXCEPTION)
100+
}
101+
} else {
102+
CompletableFuture.completedFuture(mock())
103+
}
104+
}
105+
}
106+
107+
amazonQStreamingClient.exportResultArchive("test-id", ExportIntent.TRANSFORMATION, null, {}, {})
108+
109+
assertThat(attemptCount).isEqualTo(3)
110+
}
111+
112+
@Test
113+
fun `verify retry gives up after max attempts`(): Unit = runBlocking {
114+
var attemptCount = 0
115+
streamingBearerClient = mockClientManagerRule.create<CodeWhispererStreamingAsyncClient>().stub {
116+
on {
117+
exportResultArchive(any<ExportResultArchiveRequest>(), any<ExportResultArchiveResponseHandler>())
118+
} doAnswer {
119+
attemptCount++
120+
CompletableFuture<Void>().apply {
121+
completeExceptionally(VALIDATION_EXCEPTION)
122+
}
123+
}
124+
}
125+
126+
val thrown = catchCoroutineException {
127+
amazonQStreamingClient.exportResultArchive("test-id", ExportIntent.TRANSFORMATION, null, {}, {})
128+
}
129+
130+
assertThat(attemptCount).isEqualTo(3)
131+
assertThat(thrown)
132+
.isInstanceOf(ValidationException::class.java)
133+
.hasMessage("Resource validation failed")
134+
}
135+
136+
@Test
137+
fun `verify no retry on non-retryable exception`(): Unit = runBlocking {
138+
var attemptCount = 0
139+
140+
streamingBearerClient = mockClientManagerRule.create<CodeWhispererStreamingAsyncClient>().stub {
141+
on {
142+
exportResultArchive(any<ExportResultArchiveRequest>(), any<ExportResultArchiveResponseHandler>())
143+
} doAnswer {
144+
attemptCount++
145+
CompletableFuture<Void>().apply {
146+
completeExceptionally(IllegalArgumentException("Non-retryable error"))
147+
}
148+
}
149+
}
150+
151+
val thrown = catchCoroutineException {
152+
amazonQStreamingClient.exportResultArchive("test-id", ExportIntent.TRANSFORMATION, null, {}, {})
153+
}
154+
155+
assertThat(attemptCount).isEqualTo(1)
156+
assertThat(thrown)
157+
.isInstanceOf(IllegalArgumentException::class.java)
158+
.hasMessage("Non-retryable error")
159+
}
160+
161+
@Test
162+
fun `verify backoff timing between retries`(): Unit = runBlocking {
163+
var lastAttemptTime = 0L
164+
var minBackoffObserved = Long.MAX_VALUE
165+
var maxBackoffObserved = 0L
166+
167+
streamingBearerClient = mockClientManagerRule.create<CodeWhispererStreamingAsyncClient>().stub {
168+
on {
169+
exportResultArchive(any<ExportResultArchiveRequest>(), any<ExportResultArchiveResponseHandler>())
170+
} doAnswer {
171+
val currentTime = System.currentTimeMillis()
172+
if (lastAttemptTime > 0) {
173+
val backoffTime = currentTime - lastAttemptTime
174+
minBackoffObserved = minOf(minBackoffObserved, backoffTime)
175+
maxBackoffObserved = maxOf(maxBackoffObserved, backoffTime)
176+
}
177+
lastAttemptTime = currentTime
178+
179+
CompletableFuture<Void>().apply {
180+
completeExceptionally(VALIDATION_EXCEPTION)
181+
}
182+
}
183+
}
184+
185+
val thrown = catchCoroutineException {
186+
amazonQStreamingClient.exportResultArchive("test-id", ExportIntent.TRANSFORMATION, null, {}, {})
187+
}
188+
189+
assertThat(thrown)
190+
.isInstanceOf(ValidationException::class.java)
191+
.hasMessage("Resource validation failed")
192+
assertThat(minBackoffObserved).isGreaterThanOrEqualTo(100)
193+
assertThat(maxBackoffObserved).isLessThanOrEqualTo(10000)
194+
}
195+
196+
@Test
197+
fun `verify onError callback is called with final exception`(): Unit = runBlocking {
198+
var errorCaught: Exception? = null
199+
200+
streamingBearerClient = mockClientManagerRule.create<CodeWhispererStreamingAsyncClient>().stub {
201+
on {
202+
exportResultArchive(any<ExportResultArchiveRequest>(), any<ExportResultArchiveResponseHandler>())
203+
} doAnswer {
204+
CompletableFuture<Void>().apply {
205+
completeExceptionally(VALIDATION_EXCEPTION)
206+
}
207+
}
208+
}
209+
210+
val thrown = catchCoroutineException {
211+
amazonQStreamingClient.exportResultArchive(
212+
"test-id",
213+
ExportIntent.TRANSFORMATION,
214+
null,
215+
{ errorCaught = it },
216+
{}
217+
)
218+
}
219+
220+
assertThat(thrown)
221+
.isInstanceOf(ValidationException::class.java)
222+
.hasMessage("Resource validation failed")
223+
assertThat(errorCaught).isEqualTo(VALIDATION_EXCEPTION)
224+
}
225+
226+
private suspend fun catchCoroutineException(block: suspend () -> Unit): Throwable {
227+
try {
228+
block()
229+
error("Expected exception was not thrown")
230+
} catch (e: Throwable) {
231+
return e
232+
}
233+
}
234+
235+
companion object {
236+
private val VALIDATION_EXCEPTION = ValidationException.builder()
237+
.message("Resource validation failed")
238+
.build()
239+
}
84240
}

0 commit comments

Comments
 (0)