Skip to content

Commit 8f19da1

Browse files
committed
More tests added
1 parent f14c30f commit 8f19da1

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package com.auth0.android.request
2+
3+
import com.auth0.android.dpop.DPoPKeyStore
4+
import com.auth0.android.dpop.DPoPProvider
5+
import com.auth0.android.dpop.FakeECPrivateKey
6+
import com.auth0.android.dpop.FakeECPublicKey
7+
import com.nhaarman.mockitokotlin2.any
8+
import com.nhaarman.mockitokotlin2.argumentCaptor
9+
import com.nhaarman.mockitokotlin2.mock
10+
import com.nhaarman.mockitokotlin2.times
11+
import com.nhaarman.mockitokotlin2.verify
12+
import com.nhaarman.mockitokotlin2.whenever
13+
import okhttp3.Interceptor
14+
import okhttp3.Protocol
15+
import okhttp3.Request
16+
import okhttp3.RequestBody.Companion.toRequestBody
17+
import okhttp3.Response
18+
import okhttp3.ResponseBody.Companion.toResponseBody
19+
import org.hamcrest.CoreMatchers.`is`
20+
import org.hamcrest.CoreMatchers.not
21+
import org.hamcrest.CoreMatchers.nullValue
22+
import org.hamcrest.MatcherAssert.assertThat
23+
import org.junit.Before
24+
import org.junit.Test
25+
import org.junit.runner.RunWith
26+
import org.robolectric.RobolectricTestRunner
27+
28+
@RunWith(RobolectricTestRunner::class)
29+
public class RetryInterceptorTest {
30+
31+
private lateinit var mockChain: Interceptor.Chain
32+
private lateinit var mockKeyStore: DPoPKeyStore
33+
34+
private lateinit var retryInterceptor: RetryInterceptor
35+
36+
@Before
37+
public fun setUp() {
38+
mockChain = mock()
39+
mockKeyStore = mock()
40+
41+
DPoPProvider.keyStore = mockKeyStore
42+
retryInterceptor = RetryInterceptor()
43+
}
44+
45+
@Test
46+
public fun `should proceed without retry if response is not a DPoP nonce error`() {
47+
val request = createRequest()
48+
val okResponse = createOkResponse(request)
49+
whenever(mockChain.request()).thenReturn(request)
50+
whenever(mockChain.proceed(request)).thenReturn(okResponse)
51+
52+
val result = retryInterceptor.intercept(mockChain)
53+
54+
assertThat(result, `is`(okResponse))
55+
verify(mockChain).proceed(request)
56+
}
57+
58+
@Test
59+
public fun `should retry request when DPoP nonce error occurs and key pair is available`() {
60+
val initialRequest = createRequest(accessToken = "test-access-token")
61+
val errorResponse = createDpopNonceErrorResponse(initialRequest)
62+
val successResponse = createOkResponse(initialRequest)
63+
val newRequestCaptor = argumentCaptor<Request>()
64+
65+
whenever(mockChain.request()).thenReturn(initialRequest)
66+
67+
whenever(mockChain.proceed(any()))
68+
.thenReturn(errorResponse)
69+
.thenReturn(successResponse)
70+
71+
val mockKeyPair = Pair(FakeECPrivateKey(), FakeECPublicKey())
72+
whenever(mockKeyStore.hasKeyPair()).thenReturn(true)
73+
whenever(mockKeyStore.getKeyPair()).thenReturn(mockKeyPair)
74+
75+
val result = retryInterceptor.intercept(mockChain)
76+
77+
assertThat(result, `is`(successResponse))
78+
verify(mockChain, times(2)).proceed(newRequestCaptor.capture())
79+
80+
val retriedRequest = newRequestCaptor.secondValue
81+
assertThat(retriedRequest.header("DPoP"), not(nullValue()))
82+
assertThat(retriedRequest.header("X-Internal-Retry-Count"), `is`("1"))
83+
assertThat(DPoPProvider.auth0Nonce, `is`("new-nonce-from-header"))
84+
}
85+
86+
@Test
87+
public fun `should not retry request when DPoP nonce error occurs and retry count reaches max`() {
88+
val request = createRequest(retryCount = 1)
89+
val errorResponse = createDpopNonceErrorResponse(request)
90+
whenever(mockChain.request()).thenReturn(request)
91+
whenever(mockChain.proceed(request)).thenReturn(errorResponse)
92+
93+
val result = retryInterceptor.intercept(mockChain)
94+
95+
assertThat(result, `is`(errorResponse))
96+
verify(mockChain).proceed(request)
97+
}
98+
99+
@Test
100+
public fun `should not retry request when DPoP nonce error occurs but proof generation fails`() {
101+
val request = createRequest()
102+
val errorResponse = createDpopNonceErrorResponse(request)
103+
whenever(mockChain.request()).thenReturn(request)
104+
whenever(mockChain.proceed(request)).thenReturn(errorResponse)
105+
106+
whenever(mockKeyStore.hasKeyPair()).thenReturn(false)
107+
108+
val result = retryInterceptor.intercept(mockChain)
109+
110+
assertThat(result, `is`(errorResponse))
111+
verify(mockChain).proceed(request)
112+
}
113+
114+
@Test
115+
public fun `should handle initial request with no retry header`() {
116+
val initialRequest = createRequest(accessToken = "test-access-token", retryCount = null)
117+
val errorResponse = createDpopNonceErrorResponse(initialRequest)
118+
val successResponse = createOkResponse(initialRequest)
119+
val newRequestCaptor = argumentCaptor<Request>()
120+
121+
whenever(mockChain.request()).thenReturn(initialRequest)
122+
whenever(mockChain.proceed(any()))
123+
.thenReturn(errorResponse)
124+
.thenReturn(successResponse)
125+
126+
val mockKeyPair = Pair(FakeECPrivateKey(), FakeECPublicKey())
127+
whenever(mockKeyStore.hasKeyPair()).thenReturn(true)
128+
whenever(mockKeyStore.getKeyPair()).thenReturn(mockKeyPair)
129+
130+
val result = retryInterceptor.intercept(mockChain)
131+
132+
assertThat(result, `is`(successResponse))
133+
verify(mockChain, times(2)).proceed(newRequestCaptor.capture())
134+
val retriedRequest = newRequestCaptor.secondValue
135+
assertThat(retriedRequest.header("X-Internal-Retry-Count"), `is`("1"))
136+
}
137+
138+
private fun createRequest(accessToken: String? = null, retryCount: Int? = 0): Request {
139+
val builder = Request.Builder()
140+
.url("https://test.com/api")
141+
.method("POST", "{}".toRequestBody())
142+
143+
if (accessToken != null) {
144+
builder.header("Authorization", "DPoP $accessToken")
145+
}
146+
if (retryCount != null) {
147+
builder.header("X-Internal-Retry-Count", retryCount.toString())
148+
}
149+
return builder.build()
150+
}
151+
152+
private fun createOkResponse(request: Request): Response {
153+
return Response.Builder()
154+
.request(request)
155+
.protocol(Protocol.HTTP_2)
156+
.code(200)
157+
.message("OK")
158+
.body("{}".toResponseBody())
159+
.build()
160+
}
161+
162+
private fun createDpopNonceErrorResponse(request: Request): Response {
163+
return Response.Builder()
164+
.request(request)
165+
.protocol(Protocol.HTTP_2)
166+
.code(401)
167+
.message("Unauthorized")
168+
.header("WWW-Authenticate", "DPoP error=\"use_dpop_nonce\"")
169+
.header("dpop-nonce", "new-nonce-from-header")
170+
.body("".toResponseBody())
171+
.build()
172+
}
173+
}

0 commit comments

Comments
 (0)