Skip to content

Commit 4ca6d26

Browse files
committed
chore(android): read and log http response body
1 parent 8c9830d commit 4ca6d26

File tree

6 files changed

+111
-24
lines changed

6 files changed

+111
-24
lines changed

android/measure/src/main/java/sh/measure/android/exporter/HttpResponse.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ package sh.measure.android.exporter
44
* Represents the response of an HTTP request. It can be either a success or an error.
55
*/
66
internal sealed class HttpResponse {
7-
data object Success : HttpResponse()
7+
data class Success(val body: String? = null) : HttpResponse()
88
sealed class Error : HttpResponse() {
9-
data class ClientError(val code: Int) : Error()
10-
data class ServerError(val code: Int) : Error()
11-
data object RateLimitError : Error()
9+
data class ClientError(val code: Int, val body: String? = null) : Error()
10+
data class ServerError(val code: Int, val body: String? = null) : Error()
11+
data class RateLimitError(val body: String? = null) : Error()
1212
data class UnknownError(val exception: Exception? = null) : Error()
1313
}
1414
}

android/measure/src/main/java/sh/measure/android/exporter/HttpUrlConnectionClient.kt

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import okio.Source
55
import okio.buffer
66
import okio.sink
77
import okio.source
8+
import sh.measure.android.logger.LogLevel
89
import sh.measure.android.logger.Logger
910
import java.io.IOException
1011
import java.io.InputStream
@@ -75,6 +76,7 @@ internal class HttpUrlConnectionClient(private val logger: Logger) : HttpClient
7576
}
7677
return processResponse(connection)
7778
} catch (e: IOException) {
79+
logger.log(LogLevel.Error, "Failed to send request", e)
7880
return HttpResponse.Error.UnknownError(e)
7981
} finally {
8082
connection?.disconnect()
@@ -193,15 +195,35 @@ internal class HttpUrlConnectionClient(private val logger: Logger) : HttpClient
193195
connection.outputStream.sink().buffer()
194196
}
195197
}
196-
}
197198

198-
private fun processResponse(connection: HttpURLConnection): HttpResponse {
199-
return when (val responseCode = connection.responseCode) {
200-
in 200..299 -> HttpResponse.Success
201-
429 -> HttpResponse.Error.RateLimitError
202-
in 400..499 -> HttpResponse.Error.ClientError(responseCode)
203-
in 500..599 -> HttpResponse.Error.ServerError(responseCode)
204-
else -> HttpResponse.Error.UnknownError()
199+
private fun getResponseBody(connection: HttpURLConnection): String? {
200+
return try {
201+
when (connection.responseCode) {
202+
in 200..299 -> {
203+
connection.inputStream.source().buffer().readString(Charsets.UTF_8)
204+
}
205+
206+
else -> {
207+
connection.errorStream?.source()?.buffer()?.readString(Charsets.UTF_8)
208+
}
209+
}
210+
} catch (e: IOException) {
211+
logger.log(LogLevel.Error, "Error reading response: ${e.message}")
212+
null
213+
}
214+
}
215+
216+
private fun processResponse(connection: HttpURLConnection): HttpResponse {
217+
val body = getResponseBody(connection)?.also {
218+
logger.log(LogLevel.Info, "Response: $it")
219+
}
220+
return when (val responseCode = connection.responseCode) {
221+
in 200..299 -> HttpResponse.Success(body = body)
222+
429 -> HttpResponse.Error.RateLimitError(body = body)
223+
in 400..499 -> HttpResponse.Error.ClientError(responseCode, body)
224+
in 500..599 -> HttpResponse.Error.ServerError(responseCode, body)
225+
else -> HttpResponse.Error.UnknownError()
226+
}
205227
}
206228
}
207229

android/measure/src/test/java/sh/measure/android/exporter/EventExporterTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ internal class EventExporterTest {
137137

138138
@Test
139139
fun `deletes the batch, events and attachments on successful export`() {
140-
`when`(networkClient.execute(any(), any(), any())).thenReturn(HttpResponse.Success)
140+
`when`(networkClient.execute(any(), any(), any())).thenReturn(HttpResponse.Success())
141141
val attachment1 = AttachmentEntity("attachment1", "type", "name", "path")
142142
val attachmentPath = getPathForAttachment(attachment1)
143143
insertEventInDb("event1", attachmentEntities = listOf(attachment1), attachmentSize = 100)

android/measure/src/test/java/sh/measure/android/exporter/HttpUrlConnectionClientTest.kt

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,43 @@ class HttpUrlConnectionClientTest {
2727

2828
@Test
2929
fun `test successful multipart request`() {
30+
// given
3031
mockWebServer.enqueue(MockResponse().setResponseCode(200))
3132

33+
// when
3234
val result = client.sendMultipartRequest(
3335
mockWebServer.url("/").toString(),
3436
"POST",
3537
mapOf("Authorization" to "Bearer token"),
3638
listOf(MultipartData.FormField("key", "value")),
3739
)
3840

41+
// then
3942
val request = mockWebServer.takeRequest()
4043
assertEquals("POST", request.method)
4144
assertTrue(request.headers["Content-Type"]?.startsWith("multipart/form-data; boundary=") == true)
4245
assertEquals("Bearer token", request.headers["Authorization"])
4346
val body = request.body.readUtf8()
4447
assertTrue(body.contains("Content-Disposition: form-data; name=\"key\""))
4548
assertTrue(body.contains("value"))
46-
assertEquals(HttpResponse.Success, result)
49+
assertTrue(result is HttpResponse.Success)
50+
}
51+
52+
@Test
53+
fun `test successful response with body`() {
54+
// given
55+
mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody("expected-body"))
56+
57+
// when
58+
val result = client.sendMultipartRequest(
59+
mockWebServer.url("/").toString(),
60+
"POST",
61+
emptyMap(),
62+
emptyList(),
63+
)
64+
65+
// then
66+
assertEquals(HttpResponse.Success(body = "expected-body"), result)
4767
}
4868

4969
@Test
@@ -57,7 +77,21 @@ class HttpUrlConnectionClientTest {
5777
emptyList(),
5878
)
5979

60-
assertEquals(HttpResponse.Error.RateLimitError, result)
80+
assertEquals(HttpResponse.Error.RateLimitError(), result)
81+
}
82+
83+
@Test
84+
fun `test rate limit error with body`() {
85+
mockWebServer.enqueue(MockResponse().setResponseCode(429).setBody("error-body"))
86+
87+
val result = client.sendMultipartRequest(
88+
mockWebServer.url("/").toString(),
89+
"POST",
90+
emptyMap(),
91+
emptyList(),
92+
)
93+
94+
assertEquals(HttpResponse.Error.RateLimitError("error-body"), result)
6195
}
6296

6397
@Test
@@ -74,6 +108,20 @@ class HttpUrlConnectionClientTest {
74108
assertEquals(HttpResponse.Error.ClientError(400), result)
75109
}
76110

111+
@Test
112+
fun `test client error with body`() {
113+
mockWebServer.enqueue(MockResponse().setResponseCode(400).setBody("error-body"))
114+
115+
val result = client.sendMultipartRequest(
116+
mockWebServer.url("/").toString(),
117+
"POST",
118+
emptyMap(),
119+
emptyList(),
120+
)
121+
122+
assertEquals(HttpResponse.Error.ClientError(400, "error-body"), result)
123+
}
124+
77125
@Test
78126
fun `test server error`() {
79127
mockWebServer.enqueue(MockResponse().setResponseCode(500))
@@ -88,6 +136,20 @@ class HttpUrlConnectionClientTest {
88136
assertEquals(HttpResponse.Error.ServerError(500), result)
89137
}
90138

139+
@Test
140+
fun `test server error with body`() {
141+
mockWebServer.enqueue(MockResponse().setResponseCode(500).setBody("error-body"))
142+
143+
val result = client.sendMultipartRequest(
144+
mockWebServer.url("/").toString(),
145+
"POST",
146+
emptyMap(),
147+
emptyList(),
148+
)
149+
150+
assertEquals(HttpResponse.Error.ServerError(500, "error-body"), result)
151+
}
152+
91153
@Test
92154
fun `test unknown error`() {
93155
mockWebServer.enqueue(MockResponse().setResponseCode(600))
@@ -104,21 +166,24 @@ class HttpUrlConnectionClientTest {
104166

105167
@Test
106168
fun `test file upload`() {
169+
// given
107170
mockWebServer.enqueue(MockResponse().setResponseCode(200))
108-
109171
val inputStream: InputStream = ByteArrayInputStream("file content".toByteArray())
172+
173+
// when
110174
val result = client.sendMultipartRequest(
111175
mockWebServer.url("/").toString(),
112176
"POST",
113177
emptyMap(),
114178
listOf(MultipartData.FileData("file", "test.txt", inputStream)),
115179
)
116180

181+
// then
117182
val request = mockWebServer.takeRequest()
118183
val body = request.body.readUtf8()
119184
assertTrue(body.contains("Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\""))
120185
assertTrue(body.contains("file content"))
121-
assertEquals(HttpResponse.Success, result)
186+
assertTrue(result is HttpResponse.Success)
122187
}
123188

124189
@Test
@@ -145,7 +210,7 @@ class HttpUrlConnectionClientTest {
145210
assertTrue(body.contains("value2"))
146211
assertTrue(body.contains("Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\""))
147212
assertTrue(body.contains("file content"))
148-
assertEquals(HttpResponse.Success, result)
213+
assertTrue(result is HttpResponse.Success)
149214
}
150215

151216
@Test
@@ -170,7 +235,7 @@ class HttpUrlConnectionClientTest {
170235
)
171236

172237
// Then
173-
assertEquals(HttpResponse.Success, result)
238+
assertTrue(result is HttpResponse.Success)
174239
assertEquals(2, mockWebServer.requestCount)
175240

176241
val originalRequest = mockWebServer.takeRequest()

android/measure/src/test/java/sh/measure/android/exporter/NetworkClientTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class NetworkClientTest {
3434
`when`(multipartDataFactory.createFromEventPacket(any())).thenReturn(null)
3535
`when`(multipartDataFactory.createFromAttachmentPacket(any())).thenReturn(null)
3636
`when`(httpClient.sendMultipartRequest(anyString(), anyString(), any(), any())).thenReturn(
37-
HttpResponse.Success,
37+
HttpResponse.Success(),
3838
)
3939

4040
networkClient.execute("batch123", eventPackets, attachmentPackets)
@@ -63,7 +63,7 @@ class NetworkClientTest {
6363
attachmentMultipartData,
6464
)
6565
`when`(httpClient.sendMultipartRequest(anyString(), anyString(), any(), any())).thenReturn(
66-
HttpResponse.Success,
66+
HttpResponse.Success(),
6767
)
6868

6969
networkClient.execute("batch123", listOf(eventPacket), listOf(attachmentPacket))
@@ -78,7 +78,7 @@ class NetworkClientTest {
7878

7979
@Test
8080
fun `execute handles successful response`() {
81-
val successResponse = HttpResponse.Success
81+
val successResponse = HttpResponse.Success()
8282
`when`(httpClient.sendMultipartRequest(anyString(), anyString(), any(), any())).thenReturn(
8383
successResponse,
8484
)
@@ -90,7 +90,7 @@ class NetworkClientTest {
9090

9191
@Test
9292
fun `execute handles rate limit error`() {
93-
val rateLimitResponse = HttpResponse.Error.RateLimitError
93+
val rateLimitResponse = HttpResponse.Error.RateLimitError()
9494
`when`(httpClient.sendMultipartRequest(anyString(), anyString(), any(), any())).thenReturn(
9595
rateLimitResponse,
9696
)

android/measure/src/test/java/sh/measure/android/exporter/PeriodicEventExporterTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ class PeriodicEventExporterTest {
101101
batches[batch1.first] = batch1.second
102102
batches[batch2.first] = batch2.second
103103
`when`(eventExporter.getExistingBatches()).thenReturn(batches)
104-
`when`(eventExporter.export(batch1.first, batch1.second)).thenReturn(HttpResponse.Error.RateLimitError)
104+
`when`(eventExporter.export(batch1.first, batch1.second)).thenReturn(HttpResponse.Error.RateLimitError())
105105

106106
periodicEventExporter.onAppBackground()
107107

0 commit comments

Comments
 (0)