PM-28053: Ensure any exception thrown during re-auth is an IO exception (#6175)

This commit is contained in:
David Perez 2025-11-17 14:24:48 -06:00 committed by GitHub
parent 4623a4f079
commit 169b21cfdb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 41 additions and 3 deletions

View File

@ -81,6 +81,7 @@ internal class AuthTokenManager(
}
}
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val token = getAccessToken()
?: throw IOException(IllegalStateException(MISSING_TOKEN_MESSAGE))
@ -96,9 +97,12 @@ internal class AuthTokenManager(
}
@Synchronized
@Throws(IOException::class)
private fun getAccessToken(): String? = authTokenProvider
.getAuthTokenDataOrNull()
?.let { getAccessToken(authTokenData = it).getOrThrow() }
?.let { authTokenData ->
getAccessToken(authTokenData = authTokenData).getOrElse { throw it.toIoException() }
}
@Synchronized
private fun getAccessToken(authTokenData: AuthTokenData): Result<String> {
@ -117,3 +121,12 @@ internal class AuthTokenManager(
private fun Response.shouldSkipAuthentication(): Boolean = this.priorResponse != null
}
/**
* Helper method to ensure the exception is an [IOException].
*/
private fun Throwable.toIoException(): IOException =
when (this) {
is IOException -> this
else -> IOException(this)
}

View File

@ -21,6 +21,7 @@ import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import retrofit2.HttpException
import java.io.IOException
import java.time.Clock
import java.time.Instant
@ -197,7 +198,7 @@ class AuthTokenManagerTest {
@Suppress("MaxLineLength")
@Test
fun `intercept should throw an exception when auth token is expired and refreshAccessTokenSynchronously returns an error`() {
fun `intercept should throw an io exception when auth token is expired and refreshAccessTokenSynchronously returns an error`() {
val errorMessage = "Fail!"
authTokenManager.refreshTokenProvider = object : RefreshTokenProvider {
override fun refreshAccessTokenSynchronously(
@ -216,7 +217,31 @@ class AuthTokenManagerTest {
chain = FakeInterceptorChain(request = request),
)
}
assertEquals(errorMessage, throwable?.message)
assertEquals(errorMessage, throwable.message)
}
@Suppress("MaxLineLength")
@Test
fun `intercept should throw a http exception when auth token is expired and refreshAccessTokenSynchronously returns an error`() {
val error = mockk<HttpException>()
authTokenManager.refreshTokenProvider = object : RefreshTokenProvider {
override fun refreshAccessTokenSynchronously(
userId: String,
): Result<String> = error.asFailure()
}
val authTokenData = AuthTokenData(
userId = USER_ID,
accessToken = ACCESS_TOKEN,
expiresAtSec = FIXED_CLOCK.instant().epochSecond - 3600L,
)
every { mockAuthTokenProvider.getAuthTokenDataOrNull() } returns authTokenData
val throwable = assertThrows(IOException::class.java) {
authTokenManager.intercept(
chain = FakeInterceptorChain(request = request),
)
}
assertEquals(throwable.cause, error)
}
@Suppress("MaxLineLength")