Skip to content

Commit

Permalink
Merge pull request #70 from halcyonmobile/issue#69-search-for-root-ht…
Browse files Browse the repository at this point in the history
…tp-exception

Issue#69 search for root http exception
  • Loading branch information
fknives committed Apr 28, 2022
2 parents 6a700c6 + 9f6ec92 commit cc59afb
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 13 deletions.
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ allprojects {
repositories {
google()
mavenCentral()
maven {
url "https://maven.pkg.github.com/halcyonmobile/halcyon-custom-gradle-publish-plugin"
credentials {
username = project.findProperty("GITHUB_USERNAME") ?: System.getenv("GITHUB_USERNAME")
password = project.findProperty("GITHUB_TOKEN") ?: System.getenv("GITHUB_TOKEN")
}
}
}
}

Expand Down
14 changes: 10 additions & 4 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,20 @@ dependencies {
implementation(project(':oauth'))
kapt project(':oauthadaptergenerator')

testImplementation ("com.squareup.okhttp3:mockwebserver:$okHttpVersion") {
implementation ("com.squareup.okhttp3:logging-interceptor:$okHttpVersion") {
exclude module: 'okhttp'
}
implementation "junit:junit:$junitVersion"
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0"
implementation ("com.squareup.okhttp3:logging-interceptor:$okHttpVersion") {

testImplementation ("com.squareup.okhttp3:mockwebserver:$okHttpVersion") {
exclude module: 'okhttp'
}
testImplementation "junit:junit:$junitVersion"
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0"

// compatibility
testImplementation "com.halcyonmobile.retrofit-error-parsing:retrofit-error-parsing:2.0.1"
// incompatible with error-parsing 2.0.0,
// testImplementation "com.halcyonmobile.error-handler:rest:1.0.0"
}

sourceSets {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package com.halcyonmobile.core.compatibility.error.wrapping

import com.halcyonmobile.errorparsing2.ErrorWrappingAndParserCallAdapterFactory
import com.halcyonmobile.oauth.dependencies.AuthenticationLocalStorage
import com.halcyonmobile.oauth.dependencies.SessionExpiredEventHandler
import com.halcyonmobile.oauthgson.OauthRetrofitWithGsonContainerBuilder
import com.halcyonmobile.oauthmoshi.OauthRetrofitWithMoshiContainerBuilder
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import com.nhaarman.mockitokotlin2.whenever
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.create
import retrofit2.http.GET

@Suppress("TestFunctionName")
@RunWith(Parameterized::class)
class ExceptionWrappingCompatibilityTest(
private val additionalRetrofitConfiguration: (Retrofit.Builder) -> Retrofit.Builder
) {

private lateinit var basePath: String
private lateinit var mockAuthenticationLocalStorage: AuthenticationLocalStorage
private lateinit var mockSessionExpiredEventHandler: SessionExpiredEventHandler
private lateinit var mockWebServer: MockWebServer

@Before
fun setup() {
basePath = "/google.com/test/"
mockWebServer = MockWebServer()
mockAuthenticationLocalStorage = mock()
mockSessionExpiredEventHandler = mock()
}


@After
fun tearDown() {
mockWebServer.shutdown()
}

@Test//(timeout = 5000L)
fun GIVEN_moshi_and_standard_sessionExpiration_exception_WHEN_a_refresh_request_is_run_THEN_the_session_expiration_is_Called() {
whenever(mockAuthenticationLocalStorage.refreshToken).doReturn("alma")
mockWebServer.enqueue(MockResponse().apply {
setResponseCode(401)
setBody("")
})
mockWebServer.enqueue(MockResponse().apply {
setResponseCode(400)
setBody("{\"Invalid refresh token: unrecognized\"}")
})

val service = OauthRetrofitWithMoshiContainerBuilder(
clientId = "clientId",
authenticationLocalStorage = mockAuthenticationLocalStorage,
sessionExpiredEventHandler = mockSessionExpiredEventHandler
)
.configureRetrofit {
additionalRetrofitConfiguration(baseUrl(mockWebServer.url(basePath)))
}
.build()
.oauthRetrofitContainer
.sessionRetrofit
.create<Service>()

try {
service.request().execute()
} catch (throwable: Throwable) {
throwable.printStackTrace()
} finally {
verify(mockSessionExpiredEventHandler, times(1)).onSessionExpired()
verifyNoMoreInteractions(mockSessionExpiredEventHandler)
}
}

@Test(timeout = 5000L)
fun GIVEN_gson_and_standard_sessionExpiration_exception_WHEN_a_refresh_request_is_run_THEN_the_session_expiration_is_Called() {
whenever(mockAuthenticationLocalStorage.refreshToken).doReturn("alma")
mockWebServer.enqueue(MockResponse().apply {
setResponseCode(401)
setBody("")
})
mockWebServer.enqueue(MockResponse().apply {
setResponseCode(400)
setBody("{\"Invalid refresh token: unrecognized\"}")
})

val service = OauthRetrofitWithGsonContainerBuilder(
clientId = "clientId",
authenticationLocalStorage = mockAuthenticationLocalStorage,
sessionExpiredEventHandler = mockSessionExpiredEventHandler
)
.configureRetrofit {
additionalRetrofitConfiguration(baseUrl(mockWebServer.url(basePath)))
}
.build()
.oauthRetrofitContainer
.sessionRetrofit
.create<Service>()

try {
service.request().execute()
} catch (throwable: Throwable) {
throwable.printStackTrace()
} finally {
verify(mockSessionExpiredEventHandler, times(1)).onSessionExpired()
verifyNoMoreInteractions(mockSessionExpiredEventHandler)
}
}

interface Service {
@GET("service")
fun request(): Call<Any?>
}

companion object {

private fun standard(builder: Retrofit.Builder): Retrofit.Builder = builder

private fun errorWrapping(builder: Retrofit.Builder): Retrofit.Builder =
builder.addCallAdapterFactory(ErrorWrappingAndParserCallAdapterFactory(workWithoutAnnotation = true))

// library is incompatible with 2.0.0 of error wrapper
// private fun errorHandler(builder: Retrofit.Builder): Retrofit.Builder =
// builder.addCallAdapterFactory(RestHandlerCallAdapter.Builder().build())

@Parameterized.Parameters
@JvmStatic
fun setupParameters(): Collection<Array<Any>> {
return listOf(
arrayOf(::standard),
arrayOf(::errorWrapping),
// arrayOf(::errorHandler)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package com.halcyonmobile.oauth

import com.halcyonmobile.oauth.IsSessionExpiredException as DeprecatedIsSessionExpiredException
import com.halcyonmobile.oauth.dependencies.IsSessionExpiredException
import retrofit2.HttpException

@Deprecated("Only needed as long as com.halcyonmobile.oauth.IsSessionExpiredException] is kept.", level = DeprecationLevel.WARNING)
internal class DeprecatedIsSessionExpiredExceptionAdapter(
private val delegate: DeprecatedIsSessionExpiredException
) : IsSessionExpiredException {
override fun invoke(throwable: Throwable): Boolean =
throwable is HttpException && delegate.invoke(throwable)
override fun invoke(throwable: Throwable): Boolean {
val httpException = throwable.causeHttpException ?: return false
return delegate.invoke(httpException)
}
}
13 changes: 13 additions & 0 deletions oauth/src/main/java/com/halcyonmobile/oauth/ThrowableException.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.halcyonmobile.oauth

import retrofit2.HttpException

val Throwable.causeHttpException: HttpException?
get() {
var current: Throwable? = this
while (current?.cause !== current && current != null && current !is HttpException) {
current = current.cause
}

return current as? HttpException
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
*/
package com.halcyonmobile.oauth.internal

import com.halcyonmobile.oauth.causeHttpException
import com.halcyonmobile.oauth.dependencies.IsSessionExpiredException
import java.net.HttpURLConnection
import retrofit2.HttpException
import java.net.HttpURLConnection

/**
* Default implementation of [IsSessionExpiredException].
Expand All @@ -27,11 +28,10 @@ import retrofit2.HttpException
* responses.
*/
class DefaultIsSessionExpiredException : IsSessionExpiredException {
override fun invoke(throwable: Throwable): Boolean =
when (throwable) {
is HttpException -> throwable.isInvalidTokenException() || throwable.isExpiredTokenException()
else -> false
}
override fun invoke(throwable: Throwable): Boolean {
val httpException = throwable.causeHttpException ?: return false
return httpException.isInvalidTokenException() || httpException.isExpiredTokenException()
}

companion object {
private fun HttpException.isInvalidTokenException() =
Expand Down

0 comments on commit cc59afb

Please sign in to comment.