@@ -36,6 +36,8 @@ import okhttp3.Request
3636import okhttp3.mockwebserver.MockResponse
3737import okhttp3.mockwebserver.MockWebServer
3838import org.junit.After
39+ import org.junit.Assert.assertFalse
40+ import org.junit.Assert.assertNull
3941import org.junit.Before
4042import org.junit.Test
4143
@@ -187,109 +189,109 @@ class StreamAuthInterceptorTest {
187189 }
188190
189191 @Test
190- fun `non- token error throws StreamEndpointException without retry` () {
191- val token = streamToken(" t1 " )
192+ fun `token error with alreadyRetried header passes through without retry` () {
193+ val token = streamToken(" stale " )
192194 coEvery { tokenManager.loadIfAbsent() } returns Result .success(token)
193195
194- val nonTokenError = tokenErrorData(422 )
196+ // Proper token error code handled by this interceptor
197+ val tokenError = tokenErrorData(40 )
195198 every { json.fromJson(any(), StreamEndpointErrorData ::class .java) } returns
196- Result .success(nonTokenError )
199+ Result .success(tokenError )
197200
198201 val interceptor = StreamAuthInterceptor (tokenManager, json, authType = " jwt" )
199202 val client = client(interceptor)
200203
201- server.enqueue(MockResponse ().setResponseCode(422 ).setBody(""" {"error":"unprocessable "}""" ))
204+ server.enqueue(MockResponse ().setResponseCode(401 ).setBody(""" {"error":"token invalid "}""" ))
202205
203- val url = server.url(" /v1/fail" )
204- val ex =
205- assertFailsWith<StreamEndpointException > {
206- client
207- .newCall(Request .Builder ().url(url).build())
208- .execute()
209- .use { /* force execution */ }
210- }
211- assertTrue(ex.message!! .contains(" Failed request" ))
206+ val url = server.url(" /v1/protected" )
212207
213- // Consume the single (failed) request
214- val first = server.takeRequest(2 , TimeUnit .SECONDS )
215- kotlin.test.assertNotNull(first, " Expected exactly one request to be sent" )
208+ client.newCall(
209+ Request .Builder ()
210+ .url(url)
211+ .header(" x-stream-retried-on-auth" , " present" ) // simulate already retried
212+ .build()
213+ ).execute().use { resp ->
214+ assertFalse(resp.isSuccessful) // pass-through, no exception here
215+ assertEquals(401 , resp.code)
216+ }
216217
217- // Assert no second request (i.e., no retry )
218- val second = server.takeRequest( 300 , TimeUnit . MILLISECONDS )
219- kotlin.test.assertNull(second, " Interceptor should not retry on non-token errors " )
218+ val first = server.takeRequest( 2 , TimeUnit . SECONDS )
219+ kotlin.test.assertNotNull(first )
220+ kotlin.test.assertNull(server.takeRequest( 300 , TimeUnit . MILLISECONDS )) // no second try
220221
222+ // No refresh/invalidate when header indicates we already retried
221223 coVerify(exactly = 1 ) { tokenManager.loadIfAbsent() }
222- io.mockk. verify(exactly = 0 ) { tokenManager.invalidate() }
224+ verify(exactly = 0 ) { tokenManager.invalidate() }
223225 coVerify(exactly = 0 ) { tokenManager.refresh() }
224226 }
225227
228+ /* *
229+ * Non-token error codes are NOT handled here; pass response through without retry.
230+ */
226231 @Test
227- fun `unparseable error throws StreamEndpointException ` () {
232+ fun `non-token error passes through without retry ` () {
228233 val token = streamToken(" t1" )
229234 coEvery { tokenManager.loadIfAbsent() } returns Result .success(token)
230235
236+ // e.g., business error code that is not 40/41/42
237+ val nonTokenError = tokenErrorData(13 )
231238 every { json.fromJson(any(), StreamEndpointErrorData ::class .java) } returns
232- Result .failure( IllegalStateException ( " parse error " ) )
239+ Result .success(nonTokenError )
233240
234241 val interceptor = StreamAuthInterceptor (tokenManager, json, authType = " jwt" )
235242 val client = client(interceptor)
236243
237- server.enqueue(MockResponse ().setResponseCode(500 ).setBody(" not-json " ))
244+ server.enqueue(MockResponse ().setResponseCode(422 ).setBody(""" {"error":"validation"} "" " ))
238245
239- val url = server.url(" /v1/error" )
240- val ex =
241- assertFailsWith<StreamEndpointException > {
242- client.newCall(Request .Builder ().url(url).build()).execute().use { /* consume */ }
243- }
244- assertTrue(ex.message!! .contains(" Failed to serialize response error body" ))
246+ val url = server.url(" /v1/endpoint" )
247+ client.newCall(Request .Builder ().url(url).build()).execute().use { resp ->
248+ assertFalse(resp.isSuccessful) // still an error; just passed along
249+ assertEquals(422 , resp.code)
250+ }
251+
252+ // No retry, no token refresh
253+ val req = server.takeRequest(2 , TimeUnit .SECONDS )!!
254+ assertEquals(" t1" , req.getHeader(" Authorization" ))
255+ kotlin.test.assertNull(server.takeRequest(300 , TimeUnit .MILLISECONDS ))
245256
246- coVerify(exactly = 1 ) { tokenManager.loadIfAbsent() }
247257 verify(exactly = 0 ) { tokenManager.invalidate() }
248258 coVerify(exactly = 0 ) { tokenManager.refresh() }
249259 }
250260
261+ /* *
262+ * If the error body cannot be parsed into StreamEndpointErrorData, pass through.
263+ */
251264 @Test
252- fun `token error with alreadyRetried header does not retry again ` () {
253- val token = streamToken(" stale " )
265+ fun `unparsable error body passes through without retry` () {
266+ val token = streamToken(" t1 " )
254267 coEvery { tokenManager.loadIfAbsent() } returns Result .success(token)
255- every { tokenManager.invalidate() } returns Result .success(Unit )
256268
257- val tokenError = tokenErrorData(401 )
258269 every { json.fromJson(any(), StreamEndpointErrorData ::class .java) } returns
259- Result .success(tokenError )
270+ Result .failure( IllegalStateException ( " bad json " ) )
260271
261272 val interceptor = StreamAuthInterceptor (tokenManager, json, authType = " jwt" )
262273 val client = client(interceptor)
263274
264- server.enqueue(MockResponse ().setResponseCode(401 ).setBody(""" {"error":"token invalid"} """ ))
275+ server.enqueue(MockResponse ().setResponseCode(500 ).setBody(""" <html>oops</html> """ ))
265276
266- val url = server.url(" /v1/protected" )
267- val ex =
268- assertFailsWith<StreamEndpointException > {
269- client
270- .newCall(
271- Request .Builder ()
272- .url(url)
273- .header(
274- " x-stream-retried-on-auth" ,
275- " present" ,
276- ) // simulate already retried
277- .build()
278- )
279- .execute()
280- .use { /* consume */ }
281- }
282- assertTrue(ex.message!! .contains(" Failed request" ))
277+ val url = server.url(" /v1/boom" )
278+ client.newCall(Request .Builder ().url(url).build()).execute().use { resp ->
279+ assertFalse(resp.isSuccessful)
280+ assertEquals(500 , resp.code)
281+ }
283282
284- val first = server.takeRequest(2 , TimeUnit .SECONDS )
285- kotlin.test.assertNotNull(first)
283+ // Consume the single request we expect
284+ val first = server.takeRequest(2 , TimeUnit .SECONDS )!!
285+ assertEquals(" t1" , first.getHeader(" Authorization" ))
286+
287+ // Now verify there's no retry
286288 kotlin.test.assertNull(server.takeRequest(300 , TimeUnit .MILLISECONDS ))
287289
288- coVerify(exactly = 1 ) { tokenManager.loadIfAbsent() }
289290 verify(exactly = 0 ) { tokenManager.invalidate() }
290291 coVerify(exactly = 0 ) { tokenManager.refresh() }
291292 }
292293
294+
293295 // ----------------- Helpers -----------------
294296
295297 private fun client (interceptor : Interceptor ): OkHttpClient =
0 commit comments