@@ -12,6 +12,10 @@ import io.mockk.every
12
12
import io.mockk.mockkConstructor
13
13
import io.mockk.mockkStatic
14
14
import kotlinx.coroutines.ExperimentalCoroutinesApi
15
+ import kotlinx.coroutines.test.StandardTestDispatcher
16
+ import kotlinx.coroutines.test.TestCoroutineScheduler
17
+ import kotlinx.coroutines.test.advanceUntilIdle
18
+ import kotlinx.coroutines.test.runTest
15
19
import okhttp3.mockwebserver.MockResponse
16
20
import okhttp3.mockwebserver.MockWebServer
17
21
import okhttp3.mockwebserver.RecordedRequest
@@ -26,12 +30,14 @@ import org.junit.runner.RunWith
26
30
import org.robolectric.RobolectricTestRunner
27
31
import java.util.concurrent.TimeUnit
28
32
33
+ private const val FLUSH_INTERVAL_IN_MS = 150
34
+
35
+ @ExperimentalCoroutinesApi
29
36
@RunWith(RobolectricTestRunner ::class )
30
37
class ResponseHandlerTest {
31
38
private lateinit var server: MockWebServer
32
39
private lateinit var amplitude: Amplitude
33
40
34
- @ExperimentalCoroutinesApi
35
41
@Before
36
42
fun setup () {
37
43
server = MockWebServer ()
@@ -46,7 +52,9 @@ class ResponseHandlerTest {
46
52
context = context,
47
53
serverUrl = server.url(" /" ).toString(),
48
54
autocapture = setOf (),
49
- flushIntervalMillis = 150 ,
55
+ // required for [PayloadTooLargeResponse] test
56
+ flushQueueSize = 2 ,
57
+ flushIntervalMillis = FLUSH_INTERVAL_IN_MS ,
50
58
identifyBatchIntervalMillis = 1000 ,
51
59
flushMaxRetries = 3 ,
52
60
identityStorageProvider = IMIdentityStorageProvider (),
@@ -84,7 +92,8 @@ class ResponseHandlerTest {
84
92
}
85
93
86
94
@Test
87
- fun `test handle on rate limit` () {
95
+ fun `test handle on rate limit` () = runTest {
96
+ setAmplitudeDispatchers(amplitude, testScheduler)
88
97
val rateLimitBody = """
89
98
{
90
99
"code": 429,
@@ -95,18 +104,24 @@ class ResponseHandlerTest {
95
104
for (i in 1 .. 6 ) {
96
105
server.enqueue(MockResponse ().setBody(rateLimitBody).setResponseCode(429 ))
97
106
}
107
+
108
+ amplitude.isBuilt.await()
98
109
for (k in 1 .. 4 ) {
99
110
amplitude.track(" test event $k " )
100
- runRequest ()
111
+ advanceUntilIdle ()
101
112
}
102
113
Thread .sleep(100 )
114
+
103
115
// verify the total request count when reaching max retries
104
116
assertEquals(6 , server.requestCount)
105
117
}
106
118
107
119
@Test
108
120
fun `test handle payload too large with only one event` () {
109
- server.enqueue(MockResponse ().setBody(" {\" code\" : \" 413\" , \" error\" : \" payload too large\" }" ).setResponseCode(413 ))
121
+ server.enqueue(
122
+ MockResponse ().setBody(" {\" code\" : \" 413\" , \" error\" : \" payload too large\" }" )
123
+ .setResponseCode(413 )
124
+ )
110
125
val options = EventOptions ()
111
126
var statusCode = 0
112
127
var callFinished = false
@@ -128,40 +143,67 @@ class ResponseHandlerTest {
128
143
}
129
144
130
145
@Test
131
- fun `test handle payload too large` () {
132
- server.enqueue(MockResponse ().setBody(" {\" code\" : \" 413\" , \" error\" : \" payload too large\" }" ).setResponseCode(413 ))
133
- server.enqueue(MockResponse ().setBody(" {\" code\" : \" 200\" }" ).setResponseCode(200 ))
134
- server.enqueue(MockResponse ().setBody(" {\" code\" : \" 200\" }" ).setResponseCode(200 ))
135
- server.enqueue(MockResponse ().setBody(" {\" code\" : \" 200\" }" ).setResponseCode(200 ))
146
+ fun `test handle payload too large` () = runTest {
147
+ setAmplitudeDispatchers(amplitude, testScheduler)
148
+ val expectedSuccesEvents = 5
149
+ server.enqueue(
150
+ MockResponse ().setBody(" {\" code\" : \" 413\" , \" error\" : \" payload too large\" }" )
151
+ .setResponseCode(413 )
152
+ )
153
+ repeat(expectedSuccesEvents) {
154
+ server.enqueue(MockResponse ().setBody(" {\" code\" : \" 200\" }" ).setResponseCode(200 ))
155
+ }
136
156
var eventCompleteCount = 0
137
157
val statusMap = mutableMapOf<Int , Int >()
138
158
val options = EventOptions ()
139
159
options.callback = { _: BaseEvent , status: Int , _: String ->
140
160
eventCompleteCount++
141
- statusMap.put( status, statusMap.getOrDefault(status, 0 ) + 1 )
161
+ statusMap[ status] = statusMap.getOrDefault(status, 0 ) + 1
142
162
}
163
+
164
+ // send 2 events so the event size will be greater than 1 and call split event file
165
+ amplitude.isBuilt.await()
143
166
amplitude.track(" test event 1" , options = options)
144
167
amplitude.track(" test event 2" , options = options)
168
+ advanceUntilIdle()
169
+
170
+ // verify first request that hit 413
171
+ val request = runRequest()
172
+ requireNotNull(request)
173
+ val failedEvents = getEventsFromRequest(request)
174
+ assertEquals(2 , failedEvents.size)
175
+
176
+ // send succeeding events
145
177
amplitude.track(" test event 3" , options = options)
146
178
amplitude.track(" test event 4" , options = options)
147
- val request = runRequest()
148
- // verify the first request hit 413
149
- assertNotNull(request)
150
- val events = getEventsFromRequest(request!! )
151
- assertEquals(4 , events.size)
152
179
amplitude.track(" test event 5" , options = options)
153
- runRequest()
154
- runRequest()
155
- runRequest()
156
- Thread .sleep(150 )
180
+ advanceUntilIdle()
181
+
182
+ // verify next requests after split event file
183
+ val splitRequest1 = runRequest()
184
+ requireNotNull(splitRequest1)
185
+ val splitEvents1 = getEventsFromRequest(splitRequest1)
186
+ assertEquals(1 , splitEvents1.size)
187
+ val splitRequest2 = runRequest()
188
+ requireNotNull(splitRequest2)
189
+ val splitEvents2 = getEventsFromRequest(splitRequest2)
190
+ assertEquals(1 , splitEvents2.size)
191
+
192
+ // verify we completed processing for the events after file split
193
+ val afterSplitRequest = runRequest()
194
+ requireNotNull(afterSplitRequest)
195
+ val afterSplitEvents = getEventsFromRequest(afterSplitRequest)
196
+ assertEquals(3 , afterSplitEvents.size)
197
+
157
198
// verify we completed processing for the events after file split
158
199
assertEquals(4 , server.requestCount)
159
- assertEquals(5 , statusMap.get( 200 ) )
160
- assertEquals(5 , eventCompleteCount)
200
+ assertEquals(expectedSuccesEvents , statusMap[ 200 ] )
201
+ assertEquals(expectedSuccesEvents , eventCompleteCount)
161
202
}
162
203
163
204
@Test
164
- fun `test handle bad request response` () {
205
+ fun `test handle bad request response` () = runTest {
206
+ setAmplitudeDispatchers(amplitude, testScheduler)
165
207
val badRequestResponseBody = """
166
208
{
167
209
"code": 400,
@@ -185,27 +227,37 @@ class ResponseHandlerTest {
185
227
val options = EventOptions ()
186
228
options.callback = { _: BaseEvent , status: Int , _: String ->
187
229
eventCompleteCount++
188
- statusMap.put( status, statusMap.getOrDefault(status, 0 ) + 1 )
230
+ statusMap[ status] = statusMap.getOrDefault(status, 0 ) + 1
189
231
}
232
+
233
+ amplitude.isBuilt.await()
190
234
amplitude.track(" test event 1" , options = options)
235
+ advanceUntilIdle()
236
+
237
+ // verify first request that hit 400
238
+ val request = runRequest()
239
+ requireNotNull(request)
240
+ val failedEvents = getEventsFromRequest(request)
241
+ assertEquals(1 , failedEvents.size)
242
+
243
+ // send succeeding events
191
244
amplitude.track(" test event 2" , options = options)
192
245
amplitude.track(" test event 3" , options = options)
193
246
amplitude.track(" test event 4" , options = options)
194
- // verify first request take 4 events hit 400
195
- val request = runRequest()
196
- assertNotNull(request)
197
- val events = getEventsFromRequest(request!! )
198
- assertEquals(4 , events.size)
199
- // verify second request take 2 events after removing 2 bad events
200
- val request2 = runRequest()
201
- assertNotNull(request2)
202
- val events2 = getEventsFromRequest(request2!! )
203
- assertEquals(2 , events2.size)
247
+ advanceUntilIdle()
248
+
249
+ // verify next request that the 3 events with 200
250
+ val successRequest = runRequest()
251
+ requireNotNull(successRequest)
252
+ val successfulEvents = getEventsFromRequest(successRequest)
253
+ assertEquals(3 , successfulEvents.size)
254
+
204
255
assertEquals(2 , server.requestCount)
205
256
Thread .sleep(10 )
257
+
206
258
// verify the processed status
207
- assertEquals(2 , statusMap.get( 400 ) )
208
- assertEquals(2 , statusMap.get( 200 ) )
259
+ assertEquals(1 , statusMap[ 400 ] )
260
+ assertEquals(3 , statusMap[ 200 ] )
209
261
assertEquals(4 , eventCompleteCount)
210
262
}
211
263
@@ -287,4 +339,25 @@ class ResponseHandlerTest {
287
339
every { anyConstructed<AndroidContextProvider >().mostRecentLocation } returns null
288
340
every { anyConstructed<AndroidContextProvider >().appSetId } returns " "
289
341
}
342
+
343
+ companion object {
344
+ private fun setAmplitudeDispatchers (
345
+ amplitude : com.amplitude.core.Amplitude ,
346
+ testCoroutineScheduler : TestCoroutineScheduler ,
347
+ ) {
348
+ // inject these dispatcher fields with reflection, as the field is val (read-only)
349
+ listOf (
350
+ " amplitudeDispatcher" ,
351
+ " networkIODispatcher" ,
352
+ " storageIODispatcher" ,
353
+ " retryDispatcher"
354
+ ).forEach { dispatcherField ->
355
+ com.amplitude.core.Amplitude ::class .java.getDeclaredField(dispatcherField)
356
+ .apply {
357
+ isAccessible = true
358
+ set(amplitude, StandardTestDispatcher (testCoroutineScheduler))
359
+ }
360
+ }
361
+ }
362
+ }
290
363
}
0 commit comments