Skip to content

Commit d96606c

Browse files
committed
test: Add more tests and doc comments
1 parent 1924023 commit d96606c

File tree

4 files changed

+220
-3
lines changed

4 files changed

+220
-3
lines changed

src/main/java/com/spotify/github/http/BaseHttpResponse.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.List;
2424
import java.util.Map;
2525

26+
/** BaseHttpResponse is the base implementation of HttpResponse. */
2627
public abstract class BaseHttpResponse implements HttpResponse {
2728
private static final int HTTP_OK = 200;
2829
private static final int HTTP_BAD_REQUEST = 400;
@@ -43,30 +44,66 @@ public BaseHttpResponse(
4344
this.headers = headers;
4445
}
4546

47+
/**
48+
* Returns the request that generated this response.
49+
*
50+
* @return HttpRequest the request that generated this response
51+
*/
4652
@Override
4753
public HttpRequest request() {
4854
return request;
4955
}
5056

57+
/**
58+
* Returns the HTTP status code of the response.
59+
*
60+
* @return the status code of the response
61+
*/
62+
@Override
5163
public int statusCode() {
5264
return statusCode;
5365
}
5466

67+
/**
68+
* Returns the HTTP status message of the response.
69+
*
70+
* @return the status message of the response
71+
*/
5572
@Override
5673
public String statusMessage() {
5774
return statusMessage;
5875
}
5976

77+
/**
78+
* Returns the headers of the response.
79+
*
80+
* @return the headers of the response as a Map of strings
81+
*/
6082
@Override
6183
public Map<String, List<String>> headers() {
6284
return this.headers;
6385
}
6486

87+
/**
88+
* Returns the values of the header with the given name. If the header is not present, this method
89+
* returns null.
90+
*
91+
* @param headerName the name of the header
92+
* @return the values of the header with the given name as a List of strings, or null if the
93+
* header is not present
94+
*/
6595
@Override
6696
public List<String> headers(final String headerName) {
6797
return this.headers.get(headerName);
6898
}
6999

100+
/**
101+
* Returns the first value of the header with the given name. If the header is not present, this
102+
* method returns null.
103+
*
104+
* @param headerName the name of the header
105+
* @return the first value of the header with the given name, or null if the header is not present
106+
*/
70107
@Override
71108
public String header(final String headerName) {
72109
List<String> headerValues = this.headers(headerName);
@@ -80,6 +117,11 @@ public String header(final String headerName) {
80117
}
81118
}
82119

120+
/**
121+
* Was the request successful?
122+
*
123+
* @return true if the status code is in the range [200, 400)
124+
*/
83125
@Override
84126
public boolean isSuccessful() {
85127
return this.statusCode() >= HTTP_OK && this.statusCode() < HTTP_BAD_REQUEST;

src/main/java/com/spotify/github/http/okhttp/OkHttpHttpClient.java

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
import okhttp3.*;
4040
import org.jetbrains.annotations.NotNull;
4141

42+
/**
43+
* OkHttpHttpClient is the implementation of HttpClient using OkHttp. This also serves as an example
44+
* of how to create a custom HttpClient. This HttpClient is also capable of tracing the requests
45+
* using OpenCensus or OpenTelemetry.
46+
*/
4247
public class OkHttpHttpClient implements HttpClient {
4348
private final OkHttpClient client;
4449
private Tracer tracer;
@@ -56,9 +61,15 @@ public OkHttpHttpClient(final OkHttpClient client, final Tracer tracer) {
5661
this.callFactory = createTracedClient();
5762
}
5863

64+
/**
65+
* Send a request and return a future with the response.
66+
*
67+
* @param httpRequest the request to send
68+
* @return a future with the response
69+
*/
5970
@Override
6071
public CompletableFuture<HttpResponse> send(final HttpRequest httpRequest) {
61-
Request request = buildRequest(httpRequest);
72+
Request request = buildOkHttpRequest(httpRequest);
6273
CompletableFuture<HttpResponse> future = new CompletableFuture<>();
6374
try (Span span = tracer.span(httpRequest)) {
6475
if (this.callFactory == null) {
@@ -91,7 +102,13 @@ public void setTracer(final Tracer tracer) {
91102
this.callFactory = createTracedClient();
92103
}
93104

94-
private Request buildRequest(final HttpRequest request) {
105+
/**
106+
* Build an OkHttp Request from an HttpRequest.
107+
*
108+
* @param request the HttpRequest
109+
* @return the OkHttp Request
110+
*/
111+
private Request buildOkHttpRequest(final HttpRequest request) {
95112
Request.Builder requestBuilder = new Request.Builder().url(request.url());
96113
request
97114
.headers()
@@ -109,6 +126,12 @@ private Request buildRequest(final HttpRequest request) {
109126
return requestBuilder.build();
110127
}
111128

129+
/**
130+
* Build an HttpRequest from an OkHttp Request.
131+
*
132+
* @param request the OkHttp Request
133+
* @return the HttpRequest
134+
*/
112135
private HttpRequest buildHttpRequest(final Request request) {
113136
return ImmutableHttpRequest.builder()
114137
.url(request.url().toString())
@@ -118,6 +141,11 @@ private HttpRequest buildHttpRequest(final Request request) {
118141
.build();
119142
}
120143

144+
/**
145+
* Create a traced client based on the tracer.
146+
*
147+
* @return the traced client
148+
*/
121149
private Call.Factory createTracedClient() {
122150
if (this.tracer == null || this.tracer instanceof NoopTracer) {
123151
return createTracedClientNoopTracer();
@@ -131,6 +159,11 @@ private Call.Factory createTracedClient() {
131159
return createTracedClientNoopTracer();
132160
}
133161

162+
/**
163+
* Create a traced client with a NoopTracer.
164+
*
165+
* @return the traced client
166+
*/
134167
private Call.Factory createTracedClientNoopTracer() {
135168
return new Call.Factory() {
136169
@NotNull
@@ -141,12 +174,23 @@ public Call newCall(@NotNull final Request request) {
141174
};
142175
}
143176

177+
/**
178+
* Create a traced client with OpenTelemetry.
179+
*
180+
* @return the traced client
181+
*/
144182
private Call.Factory createTracedClientOpenTelemetry() {
183+
// OkHttpTelemetry is a helper class that provides a Call.Factory that can be used to trace
145184
return OkHttpTelemetry.builder(((OpenTelemetryTracer) this.tracer).getOpenTelemetry())
146185
.build()
147186
.newCallFactory(client);
148187
}
149188

189+
/**
190+
* Create a traced client with OpenCensus.
191+
*
192+
* @return the traced client
193+
*/
150194
private Call.Factory createTracedClientOpenCensus() {
151195
return new Call.Factory() {
152196
@NotNull
@@ -159,6 +203,7 @@ public Call newCall(@NotNull final Request request) {
159203
.span(buildHttpRequest(request))
160204
.addTag(TraceHelper.TraceTags.HTTP_URL, request.url().toString());
161205
OkHttpClient.Builder okBuilder = client.newBuilder();
206+
// Add a network interceptor to trace the request
162207
okBuilder
163208
.networkInterceptors()
164209
.add(

src/main/java/com/spotify/github/http/okhttp/OkHttpHttpResponse.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@
3232
import org.slf4j.Logger;
3333
import org.slf4j.LoggerFactory;
3434

35+
/** OkHttpHttpResponse is the implementation of HttpResponse using OkHttp. */
3536
public class OkHttpHttpResponse extends BaseHttpResponse {
3637
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
37-
38+
3839
private final Response response;
3940
private InputStream body;
4041
private String bodyString;
@@ -52,6 +53,7 @@ public InputStream body() {
5253
return body;
5354
}
5455

56+
@Override
5557
public String bodyString() {
5658
if (bodyString == null) {
5759
if (response != null) {
@@ -75,6 +77,12 @@ public void close() {
7577
}
7678
}
7779

80+
/**
81+
* Get the response body as a string.
82+
*
83+
* @param response the response
84+
* @return the response body as a string
85+
*/
7886
private static String responseBodyUnchecked(final Response response) {
7987
if (response.body() == null) {
8088
return null;
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package com.spotify.github.http.okhttp;
2+
3+
import com.spotify.github.http.HttpRequest;
4+
import com.spotify.github.http.HttpResponse;
5+
import com.spotify.github.http.ImmutableHttpRequest;
6+
import com.spotify.github.tracing.Tracer;
7+
import okhttp3.*;
8+
import org.junit.jupiter.api.AfterEach;
9+
import org.junit.jupiter.api.BeforeAll;
10+
import org.junit.jupiter.api.Test;
11+
import org.mockito.ArgumentCaptor;
12+
13+
import java.io.IOException;
14+
import java.util.concurrent.CompletableFuture;
15+
import java.util.concurrent.CompletionException;
16+
17+
import static org.junit.jupiter.api.Assertions.*;
18+
import static org.mockito.ArgumentMatchers.any;
19+
import static org.mockito.Mockito.*;
20+
21+
class OkHttpHttpClientTest {
22+
private static OkHttpClient okHttpClient;
23+
private static OkHttpHttpClient httpClient;
24+
private static Tracer tracer;
25+
26+
@BeforeAll
27+
static void setUp() {
28+
okHttpClient = mock(OkHttpClient.class);
29+
tracer = mock(Tracer.class);
30+
httpClient = new OkHttpHttpClient(okHttpClient, tracer);
31+
}
32+
33+
@AfterEach
34+
void tearDown() {
35+
reset(okHttpClient, tracer);
36+
}
37+
38+
@Test
39+
void sendSuccessfully() throws IOException {
40+
// Given
41+
final Call call = mock(Call.class);
42+
final ArgumentCaptor<Callback> capture = ArgumentCaptor.forClass(Callback.class);
43+
doNothing().when(call).enqueue(capture.capture());
44+
final Response response =
45+
new okhttp3.Response.Builder()
46+
.code(200)
47+
.body(ResponseBody.create(MediaType.get("application/json"), "{\"foo\":\"bar\"}"))
48+
.message("foo")
49+
.protocol(Protocol.HTTP_1_1)
50+
.request(new Request.Builder().url("https://example.com").build())
51+
.build();
52+
53+
HttpRequest httpRequest = ImmutableHttpRequest.builder().url("https://example.com").build();
54+
when(okHttpClient.newCall(any())).thenReturn(call);
55+
56+
// When
57+
CompletableFuture<HttpResponse> futureResponse = httpClient.send(httpRequest);
58+
capture.getValue().onResponse(call, response);
59+
HttpResponse httpResponse = futureResponse.join();
60+
61+
// Then
62+
assertNotNull(httpResponse);
63+
assertEquals("{\"foo\":\"bar\"}", httpResponse.bodyString());
64+
assertEquals(200, httpResponse.statusCode());
65+
assertEquals("foo", httpResponse.statusMessage());
66+
assertTrue(httpResponse.isSuccessful());
67+
verify(tracer, times(1)).span(any(HttpRequest.class));
68+
}
69+
70+
@Test
71+
void sendWithException() {
72+
// Given
73+
final Call call = mock(Call.class);
74+
final ArgumentCaptor<Callback> capture = ArgumentCaptor.forClass(Callback.class);
75+
doNothing().when(call).enqueue(capture.capture());
76+
final IOException exception = new IOException("Network error");
77+
78+
HttpRequest httpRequest = ImmutableHttpRequest.builder().url("https://example.com").build();
79+
when(okHttpClient.newCall(any())).thenReturn(call);
80+
81+
// When
82+
CompletableFuture<HttpResponse> futureResponse = httpClient.send(httpRequest);
83+
capture.getValue().onFailure(call, exception);
84+
85+
// Then
86+
assertThrows(CompletionException.class, futureResponse::join);
87+
verify(tracer, times(1)).span(any(HttpRequest.class));
88+
}
89+
90+
@Test
91+
void sendWithClientError() throws IOException {
92+
// Given
93+
final Call call = mock(Call.class);
94+
final ArgumentCaptor<Callback> capture = ArgumentCaptor.forClass(Callback.class);
95+
doNothing().when(call).enqueue(capture.capture());
96+
final Response response =
97+
new okhttp3.Response.Builder()
98+
.code(404)
99+
.body(
100+
ResponseBody.create(MediaType.get("application/json"), "{\"error\":\"Not Found\"}"))
101+
.message("Not Found")
102+
.protocol(Protocol.HTTP_1_1)
103+
.request(new Request.Builder().url("https://example.com").build())
104+
.build();
105+
106+
HttpRequest httpRequest = ImmutableHttpRequest.builder().url("https://example.com").build();
107+
when(okHttpClient.newCall(any())).thenReturn(call);
108+
109+
// When
110+
CompletableFuture<HttpResponse> futureResponse = httpClient.send(httpRequest);
111+
capture.getValue().onResponse(call, response);
112+
HttpResponse httpResponse = futureResponse.join();
113+
114+
// Then
115+
assertNotNull(httpResponse);
116+
assertEquals("{\"error\":\"Not Found\"}", httpResponse.bodyString());
117+
assertEquals(404, httpResponse.statusCode());
118+
assertEquals("Not Found", httpResponse.statusMessage());
119+
assertFalse(httpResponse.isSuccessful());
120+
verify(tracer, times(1)).span(any(HttpRequest.class));
121+
}
122+
}

0 commit comments

Comments
 (0)