Skip to content

Commit cc59afb

Browse files
authored
Merge pull request #70 from halcyonmobile/issue#69-search-for-root-http-exception
Issue#69 search for root http exception
2 parents 6a700c6 + 9f6ec92 commit cc59afb

6 files changed

Lines changed: 187 additions & 13 deletions

File tree

build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ allprojects {
4242
repositories {
4343
google()
4444
mavenCentral()
45+
maven {
46+
url "https://maven.pkg.github.com/halcyonmobile/halcyon-custom-gradle-publish-plugin"
47+
credentials {
48+
username = project.findProperty("GITHUB_USERNAME") ?: System.getenv("GITHUB_USERNAME")
49+
password = project.findProperty("GITHUB_TOKEN") ?: System.getenv("GITHUB_TOKEN")
50+
}
51+
}
4552
}
4653
}
4754

core/build.gradle

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,20 @@ dependencies {
2121
implementation(project(':oauth'))
2222
kapt project(':oauthadaptergenerator')
2323

24-
testImplementation ("com.squareup.okhttp3:mockwebserver:$okHttpVersion") {
24+
implementation ("com.squareup.okhttp3:logging-interceptor:$okHttpVersion") {
2525
exclude module: 'okhttp'
2626
}
27-
implementation "junit:junit:$junitVersion"
28-
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0"
29-
implementation ("com.squareup.okhttp3:logging-interceptor:$okHttpVersion") {
27+
28+
testImplementation ("com.squareup.okhttp3:mockwebserver:$okHttpVersion") {
3029
exclude module: 'okhttp'
3130
}
31+
testImplementation "junit:junit:$junitVersion"
32+
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0"
33+
34+
// compatibility
35+
testImplementation "com.halcyonmobile.retrofit-error-parsing:retrofit-error-parsing:2.0.1"
36+
// incompatible with error-parsing 2.0.0,
37+
// testImplementation "com.halcyonmobile.error-handler:rest:1.0.0"
3238
}
3339

3440
sourceSets {
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package com.halcyonmobile.core.compatibility.error.wrapping
2+
3+
import com.halcyonmobile.errorparsing2.ErrorWrappingAndParserCallAdapterFactory
4+
import com.halcyonmobile.oauth.dependencies.AuthenticationLocalStorage
5+
import com.halcyonmobile.oauth.dependencies.SessionExpiredEventHandler
6+
import com.halcyonmobile.oauthgson.OauthRetrofitWithGsonContainerBuilder
7+
import com.halcyonmobile.oauthmoshi.OauthRetrofitWithMoshiContainerBuilder
8+
import com.nhaarman.mockitokotlin2.doReturn
9+
import com.nhaarman.mockitokotlin2.mock
10+
import com.nhaarman.mockitokotlin2.times
11+
import com.nhaarman.mockitokotlin2.verify
12+
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
13+
import com.nhaarman.mockitokotlin2.whenever
14+
import okhttp3.mockwebserver.MockResponse
15+
import okhttp3.mockwebserver.MockWebServer
16+
import org.junit.After
17+
import org.junit.Before
18+
import org.junit.Test
19+
import org.junit.runner.RunWith
20+
import org.junit.runners.Parameterized
21+
import retrofit2.Call
22+
import retrofit2.Retrofit
23+
import retrofit2.create
24+
import retrofit2.http.GET
25+
26+
@Suppress("TestFunctionName")
27+
@RunWith(Parameterized::class)
28+
class ExceptionWrappingCompatibilityTest(
29+
private val additionalRetrofitConfiguration: (Retrofit.Builder) -> Retrofit.Builder
30+
) {
31+
32+
private lateinit var basePath: String
33+
private lateinit var mockAuthenticationLocalStorage: AuthenticationLocalStorage
34+
private lateinit var mockSessionExpiredEventHandler: SessionExpiredEventHandler
35+
private lateinit var mockWebServer: MockWebServer
36+
37+
@Before
38+
fun setup() {
39+
basePath = "/google.com/test/"
40+
mockWebServer = MockWebServer()
41+
mockAuthenticationLocalStorage = mock()
42+
mockSessionExpiredEventHandler = mock()
43+
}
44+
45+
46+
@After
47+
fun tearDown() {
48+
mockWebServer.shutdown()
49+
}
50+
51+
@Test//(timeout = 5000L)
52+
fun GIVEN_moshi_and_standard_sessionExpiration_exception_WHEN_a_refresh_request_is_run_THEN_the_session_expiration_is_Called() {
53+
whenever(mockAuthenticationLocalStorage.refreshToken).doReturn("alma")
54+
mockWebServer.enqueue(MockResponse().apply {
55+
setResponseCode(401)
56+
setBody("")
57+
})
58+
mockWebServer.enqueue(MockResponse().apply {
59+
setResponseCode(400)
60+
setBody("{\"Invalid refresh token: unrecognized\"}")
61+
})
62+
63+
val service = OauthRetrofitWithMoshiContainerBuilder(
64+
clientId = "clientId",
65+
authenticationLocalStorage = mockAuthenticationLocalStorage,
66+
sessionExpiredEventHandler = mockSessionExpiredEventHandler
67+
)
68+
.configureRetrofit {
69+
additionalRetrofitConfiguration(baseUrl(mockWebServer.url(basePath)))
70+
}
71+
.build()
72+
.oauthRetrofitContainer
73+
.sessionRetrofit
74+
.create<Service>()
75+
76+
try {
77+
service.request().execute()
78+
} catch (throwable: Throwable) {
79+
throwable.printStackTrace()
80+
} finally {
81+
verify(mockSessionExpiredEventHandler, times(1)).onSessionExpired()
82+
verifyNoMoreInteractions(mockSessionExpiredEventHandler)
83+
}
84+
}
85+
86+
@Test(timeout = 5000L)
87+
fun GIVEN_gson_and_standard_sessionExpiration_exception_WHEN_a_refresh_request_is_run_THEN_the_session_expiration_is_Called() {
88+
whenever(mockAuthenticationLocalStorage.refreshToken).doReturn("alma")
89+
mockWebServer.enqueue(MockResponse().apply {
90+
setResponseCode(401)
91+
setBody("")
92+
})
93+
mockWebServer.enqueue(MockResponse().apply {
94+
setResponseCode(400)
95+
setBody("{\"Invalid refresh token: unrecognized\"}")
96+
})
97+
98+
val service = OauthRetrofitWithGsonContainerBuilder(
99+
clientId = "clientId",
100+
authenticationLocalStorage = mockAuthenticationLocalStorage,
101+
sessionExpiredEventHandler = mockSessionExpiredEventHandler
102+
)
103+
.configureRetrofit {
104+
additionalRetrofitConfiguration(baseUrl(mockWebServer.url(basePath)))
105+
}
106+
.build()
107+
.oauthRetrofitContainer
108+
.sessionRetrofit
109+
.create<Service>()
110+
111+
try {
112+
service.request().execute()
113+
} catch (throwable: Throwable) {
114+
throwable.printStackTrace()
115+
} finally {
116+
verify(mockSessionExpiredEventHandler, times(1)).onSessionExpired()
117+
verifyNoMoreInteractions(mockSessionExpiredEventHandler)
118+
}
119+
}
120+
121+
interface Service {
122+
@GET("service")
123+
fun request(): Call<Any?>
124+
}
125+
126+
companion object {
127+
128+
private fun standard(builder: Retrofit.Builder): Retrofit.Builder = builder
129+
130+
private fun errorWrapping(builder: Retrofit.Builder): Retrofit.Builder =
131+
builder.addCallAdapterFactory(ErrorWrappingAndParserCallAdapterFactory(workWithoutAnnotation = true))
132+
133+
// library is incompatible with 2.0.0 of error wrapper
134+
// private fun errorHandler(builder: Retrofit.Builder): Retrofit.Builder =
135+
// builder.addCallAdapterFactory(RestHandlerCallAdapter.Builder().build())
136+
137+
@Parameterized.Parameters
138+
@JvmStatic
139+
fun setupParameters(): Collection<Array<Any>> {
140+
return listOf(
141+
arrayOf(::standard),
142+
arrayOf(::errorWrapping),
143+
// arrayOf(::errorHandler)
144+
)
145+
}
146+
}
147+
}

oauth/src/main/java/com/halcyonmobile/oauth/DeprecatedIsSessionExpiredExceptionAdapter.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ package com.halcyonmobile.oauth
22

33
import com.halcyonmobile.oauth.IsSessionExpiredException as DeprecatedIsSessionExpiredException
44
import com.halcyonmobile.oauth.dependencies.IsSessionExpiredException
5-
import retrofit2.HttpException
65

76
@Deprecated("Only needed as long as com.halcyonmobile.oauth.IsSessionExpiredException] is kept.", level = DeprecationLevel.WARNING)
87
internal class DeprecatedIsSessionExpiredExceptionAdapter(
98
private val delegate: DeprecatedIsSessionExpiredException
109
) : IsSessionExpiredException {
11-
override fun invoke(throwable: Throwable): Boolean =
12-
throwable is HttpException && delegate.invoke(throwable)
10+
override fun invoke(throwable: Throwable): Boolean {
11+
val httpException = throwable.causeHttpException ?: return false
12+
return delegate.invoke(httpException)
13+
}
1314
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.halcyonmobile.oauth
2+
3+
import retrofit2.HttpException
4+
5+
val Throwable.causeHttpException: HttpException?
6+
get() {
7+
var current: Throwable? = this
8+
while (current?.cause !== current && current != null && current !is HttpException) {
9+
current = current.cause
10+
}
11+
12+
return current as? HttpException
13+
}

oauth/src/main/java/com/halcyonmobile/oauth/internal/DefaultIsSessionExpiredException.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
*/
1717
package com.halcyonmobile.oauth.internal
1818

19+
import com.halcyonmobile.oauth.causeHttpException
1920
import com.halcyonmobile.oauth.dependencies.IsSessionExpiredException
20-
import java.net.HttpURLConnection
2121
import retrofit2.HttpException
22+
import java.net.HttpURLConnection
2223

2324
/**
2425
* Default implementation of [IsSessionExpiredException].
@@ -27,11 +28,10 @@ import retrofit2.HttpException
2728
* responses.
2829
*/
2930
class DefaultIsSessionExpiredException : IsSessionExpiredException {
30-
override fun invoke(throwable: Throwable): Boolean =
31-
when (throwable) {
32-
is HttpException -> throwable.isInvalidTokenException() || throwable.isExpiredTokenException()
33-
else -> false
34-
}
31+
override fun invoke(throwable: Throwable): Boolean {
32+
val httpException = throwable.causeHttpException ?: return false
33+
return httpException.isInvalidTokenException() || httpException.isExpiredTokenException()
34+
}
3535

3636
companion object {
3737
private fun HttpException.isInvalidTokenException() =

0 commit comments

Comments
 (0)