Skip to content

Commit 30e23e8

Browse files
committed
feat: Add Handlers.aliveHandler and Handlers.specHandler
1 parent 2e80522 commit 30e23e8

2 files changed

Lines changed: 118 additions & 0 deletions

File tree

src/main/java/com/retailsvc/http/Handlers.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
77
import static java.nio.charset.StandardCharsets.UTF_8;
88

9+
import com.retailsvc.http.internal.ClasspathResourceHandler;
10+
import com.retailsvc.http.internal.MethodLimitedHandler;
911
import com.retailsvc.http.internal.ProblemDetailRenderer;
1012
import com.sun.net.httpserver.HttpHandler;
1113
import java.io.IOException;
@@ -53,4 +55,24 @@ public static HttpHandler notFoundHandler() {
5355
}
5456
};
5557
}
58+
59+
/** Returns 204 No Content on GET/HEAD; 405 with {@code Allow: GET, HEAD} otherwise. */
60+
public static HttpHandler aliveHandler() {
61+
return new MethodLimitedHandler(
62+
exchange -> {
63+
try (exchange) {
64+
exchange.sendResponseHeaders(204, -1);
65+
}
66+
});
67+
}
68+
69+
/**
70+
* Serves a classpath resource. Content-Type is inferred from the file extension. The resource is
71+
* loaded eagerly; a missing resource fails immediately with {@link IllegalArgumentException}.
72+
*
73+
* @param classpathResource absolute classpath path, e.g. {@code /schemas/v1/openapi.yaml}
74+
*/
75+
public static HttpHandler specHandler(String classpathResource) {
76+
return new MethodLimitedHandler(new ClasspathResourceHandler(classpathResource));
77+
}
5678
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.retailsvc.http;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
5+
import static org.mockito.Mockito.mock;
6+
import static org.mockito.Mockito.verify;
7+
import static org.mockito.Mockito.when;
8+
9+
import com.sun.net.httpserver.Headers;
10+
import com.sun.net.httpserver.HttpExchange;
11+
import java.io.ByteArrayOutputStream;
12+
import java.io.IOException;
13+
import org.junit.jupiter.api.Test;
14+
15+
class HandlersTest {
16+
17+
@Test
18+
void aliveHandlerReturns204OnGet() throws IOException {
19+
HttpExchange ex = newExchange("GET");
20+
Handlers.aliveHandler().handle(ex);
21+
verify(ex).sendResponseHeaders(204, -1);
22+
}
23+
24+
@Test
25+
void aliveHandlerReturns204OnHead() throws IOException {
26+
HttpExchange ex = newExchange("HEAD");
27+
Handlers.aliveHandler().handle(ex);
28+
verify(ex).sendResponseHeaders(204, -1);
29+
}
30+
31+
@Test
32+
void aliveHandlerReturns405OnPost() throws IOException {
33+
HttpExchange ex = newExchange("POST");
34+
Headers headers = new Headers();
35+
when(ex.getResponseHeaders()).thenReturn(headers);
36+
Handlers.aliveHandler().handle(ex);
37+
verify(ex).sendResponseHeaders(405, -1);
38+
assertThat(headers.getFirst("Allow")).isEqualTo("GET, HEAD");
39+
}
40+
41+
@Test
42+
void specHandlerServesYamlWithInferredContentType() throws IOException {
43+
HttpExchange ex = newExchange("GET");
44+
Headers responseHeaders = new Headers();
45+
when(ex.getResponseHeaders()).thenReturn(responseHeaders);
46+
ByteArrayOutputStream body = new ByteArrayOutputStream();
47+
when(ex.getResponseBody()).thenReturn(body);
48+
49+
Handlers.specHandler("/openapi.yaml").handle(ex);
50+
51+
assertThat(responseHeaders.getFirst("Content-Type")).isEqualTo("application/yaml");
52+
verify(ex)
53+
.sendResponseHeaders(
54+
org.mockito.ArgumentMatchers.eq(200),
55+
org.mockito.ArgumentMatchers.longThat(n -> n > 0));
56+
assertThat(body.toByteArray()).isNotEmpty();
57+
}
58+
59+
@Test
60+
void specHandlerInfersJsonContentType() throws IOException {
61+
HttpExchange ex = newExchange("GET");
62+
Headers responseHeaders = new Headers();
63+
when(ex.getResponseHeaders()).thenReturn(responseHeaders);
64+
when(ex.getResponseBody()).thenReturn(new ByteArrayOutputStream());
65+
66+
Handlers.specHandler("/openapi.json").handle(ex);
67+
68+
assertThat(responseHeaders.getFirst("Content-Type")).isEqualTo("application/json");
69+
}
70+
71+
@Test
72+
void specHandlerThrowsAtConstructionForMissingResource() {
73+
assertThatThrownBy(() -> Handlers.specHandler("/does-not-exist.yaml"))
74+
.isInstanceOf(IllegalArgumentException.class)
75+
.hasMessageContaining("/does-not-exist.yaml");
76+
}
77+
78+
@Test
79+
void specHandlerReturns405OnPost() throws IOException {
80+
HttpExchange ex = newExchange("POST");
81+
Headers headers = new Headers();
82+
when(ex.getResponseHeaders()).thenReturn(headers);
83+
84+
Handlers.specHandler("/openapi.yaml").handle(ex);
85+
86+
verify(ex).sendResponseHeaders(405, -1);
87+
assertThat(headers.getFirst("Allow")).isEqualTo("GET, HEAD");
88+
}
89+
90+
private static HttpExchange newExchange(String method) {
91+
HttpExchange ex = mock(HttpExchange.class);
92+
when(ex.getRequestMethod()).thenReturn(method);
93+
when(ex.getResponseHeaders()).thenReturn(new Headers());
94+
return ex;
95+
}
96+
}

0 commit comments

Comments
 (0)