Skip to content

Commit a3988e9

Browse files
thcedclaude
andcommitted
refactor(test): Migrate test launcher, handlers, and integration tests to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c7ea34a commit a3988e9

7 files changed

Lines changed: 127 additions & 135 deletions

File tree

src/test/java/com/retailsvc/http/OpenApiServerIT.java

Lines changed: 71 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,13 @@ void getData_shouldReturnBadRequestOnInvalidXNameHeader() {
6363

6464
var response = client.send(request, BodyHandlers.ofString());
6565
var statusCode = response.statusCode();
66+
var contentType = response.headers().firstValue("Content-Type").orElse("");
6667
var responseBody = response.body();
6768

6869
assertThat(statusCode).isEqualTo(400);
69-
assertThat(responseBody).isEmpty();
70+
assertThat(contentType).contains("application/problem+json");
71+
assertThat(responseBody).contains("keyword");
72+
assertThat(responseBody).contains("pointer");
7073

7174
} catch (IOException e) {
7275
fail(e);
@@ -83,28 +86,29 @@ void postData_shouldReturnJsonBody() {
8386

8487
var body =
8588
"""
86-
{
87-
"id": "some-id",
88-
"age": 42,
89-
"random": "d5af5004-8b5a-4db6-838e-38be773eac34",
90-
"status": "ERROR",
91-
"feelingGood": true,
92-
"aList": [ "string", "string" ],
93-
"anObject": {
94-
"id": "some-id",
95-
"age": 42,
96-
"longNumber": 900,
97-
"nested": {
98-
"nestedValue": 43
99-
}
100-
},
101-
"aListOfObjects": [
102-
{ "value": 42 },
103-
{ "value": 43 }
104-
],
105-
"aDate": "2025-03-02",
106-
"aDateTime": "2025-03-02T12:34:56Z"
107-
}""";
89+
{
90+
"id": "some-id",
91+
"age": 42,
92+
"random": "d5af5004-8b5a-4db6-838e-38be773eac34",
93+
"status": "ERROR",
94+
"feelingGood": true,
95+
"aList": [ "string", "string" ],
96+
"anObject": {
97+
"id": "some-id",
98+
"age": 42,
99+
"longNumber": 900,
100+
"nested": {
101+
"nestedValue": 43
102+
}
103+
},
104+
"aListOfObjects": [
105+
{ "value": 42 },
106+
{ "value": 43 }
107+
],
108+
"aDate": "2025-03-02",
109+
"aDateTime": "2025-03-02T12:34:56Z"
110+
}\
111+
""";
108112
var headers = Map.of("correlation-id", UUID.randomUUID().toString());
109113
var request = newRequest(server, path, "POST", ofString(body), headers);
110114

@@ -132,35 +136,39 @@ void postData_shouldReturnBadRequestOnMissingRequiredProperties() {
132136

133137
var body =
134138
"""
135-
{
136-
"id": "some-id",
137-
"age": 42,
138-
"random": "d5af5004-8b5a-4db6-838e-38be773eac34",
139-
"status": "ERROR",
140-
"anObject": {
141-
"id": "some-id",
142-
"age": 42,
143-
"longNumber": 900,
144-
"nested": {
145-
"nestedValue": 43
146-
}
147-
},
148-
"aListOfObjects": [
149-
{ "value": 42 },
150-
{ "value": 43 }
151-
],
152-
"aDate": "2025-03-02",
153-
"aDateTime": "2025-03-02T12:34:56Z"
154-
}""";
139+
{
140+
"id": "some-id",
141+
"age": 42,
142+
"random": "d5af5004-8b5a-4db6-838e-38be773eac34",
143+
"status": "ERROR",
144+
"anObject": {
145+
"id": "some-id",
146+
"age": 42,
147+
"longNumber": 900,
148+
"nested": {
149+
"nestedValue": 43
150+
}
151+
},
152+
"aListOfObjects": [
153+
{ "value": 42 },
154+
{ "value": 43 }
155+
],
156+
"aDate": "2025-03-02",
157+
"aDateTime": "2025-03-02T12:34:56Z"
158+
}\
159+
""";
155160
var headers = Map.of("correlation-id", UUID.randomUUID().toString());
156161
var request = newRequest(server, path, "POST", ofString(body), headers);
157162

158163
var response = client.send(request, BodyHandlers.ofString());
159164
var statusCode = response.statusCode();
165+
var contentType = response.headers().firstValue("Content-Type").orElse("");
160166
var responseBody = response.body();
161167

162168
assertThat(statusCode).isEqualTo(400);
163-
assertThat(responseBody).isEmpty();
169+
assertThat(contentType).contains("application/problem+json");
170+
assertThat(responseBody).contains("keyword");
171+
assertThat(responseBody).contains("pointer");
164172

165173
} catch (IOException e) {
166174
fail(e);
@@ -183,11 +191,11 @@ void listObjects_shouldReturnJsonBody() {
183191

184192
var body =
185193
"""
186-
[
187-
{ "value": 42 },
188-
{ "value": 43 }
189-
]
190-
""";
194+
[
195+
{ "value": 42 },
196+
{ "value": 43 }
197+
]
198+
""";
191199
var headers = Map.of("correlation-id", UUID.randomUUID().toString());
192200
var request = newRequest(server, path, "POST", ofString(body), headers);
193201

@@ -217,10 +225,13 @@ void listObjects_shouldReturnBadRequestOnPassingObjectInsteadOfArray() {
217225

218226
var response = client.send(request, BodyHandlers.ofString());
219227
var statusCode = response.statusCode();
228+
var contentType = response.headers().firstValue("Content-Type").orElse("");
220229
var responseBody = response.body();
221230

222231
assertThat(statusCode).isEqualTo(400);
223-
assertThat(responseBody).isEmpty();
232+
assertThat(contentType).contains("application/problem+json");
233+
assertThat(responseBody).contains("keyword");
234+
assertThat(responseBody).contains("pointer");
224235

225236
} catch (IOException e) {
226237
fail(e);
@@ -271,10 +282,13 @@ void paramsQuery_shouldReturnBadRequestOnMissingRequiredQueryParams() {
271282

272283
var response = client.send(request, BodyHandlers.ofString());
273284
var statusCode = response.statusCode();
285+
var contentType = response.headers().firstValue("Content-Type").orElse("");
274286
var responseBody = response.body();
275287

276288
assertThat(statusCode).isEqualTo(400);
277-
assertThat(responseBody).isEmpty();
289+
assertThat(contentType).contains("application/problem+json");
290+
assertThat(responseBody).contains("keyword");
291+
assertThat(responseBody).contains("pointer");
278292

279293
} catch (IOException e) {
280294
fail(e);
@@ -341,16 +355,19 @@ void getPathParams_shouldReturnBadRequestOnBadFormatPathParam() {
341355
try (var server = newServer(Map.of("path-params-multi", new EchoHandler()));
342356
var client = httpClient()) {
343357

344-
// '123' is not in [A-Za-z]
358+
// '123' does not match pattern [A-Za-z]+ for Name parameter
345359
var pathWithParams = path + "/1234567890/123/Case";
346360
var request = newRequest(server, pathWithParams, "GET", noBody());
347361

348362
var response = client.send(request, BodyHandlers.ofString());
349363
var statusCode = response.statusCode();
364+
var contentType = response.headers().firstValue("Content-Type").orElse("");
350365
var responseBody = response.body();
351366

352-
assertThat(statusCode).isEqualTo(404);
353-
assertThat(responseBody).isEmpty();
367+
assertThat(statusCode).isEqualTo(400);
368+
assertThat(contentType).contains("application/problem+json");
369+
assertThat(responseBody).contains("keyword");
370+
assertThat(responseBody).contains("pointer");
354371

355372
} catch (IOException e) {
356373
fail(e);

src/test/java/com/retailsvc/http/OpenApiServerTest.java

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,9 @@
44
import static org.assertj.core.api.Assertions.assertThatThrownBy;
55
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
66

7-
import com.retailsvc.http.openapi.model.Components;
8-
import com.retailsvc.http.openapi.model.Info;
9-
import com.retailsvc.http.openapi.model.JsonMapper;
10-
import com.retailsvc.http.openapi.model.OpenApi;
11-
import com.retailsvc.http.openapi.model.Server;
7+
import com.retailsvc.http.spec.Spec;
128
import com.sun.net.httpserver.HttpHandler;
13-
import java.util.Collections;
14-
import java.util.HashMap;
9+
import java.util.List;
1510
import java.util.Map;
1611
import org.junit.jupiter.api.Test;
1712
import org.junit.jupiter.api.extension.ExtendWith;
@@ -20,19 +15,12 @@
2015
@ExtendWith(MockitoExtension.class)
2116
class OpenApiServerTest {
2217

23-
Server server = new Server("http://localhost:8080/api");
2418
ExceptionHandler onError = Handlers.defaultExceptionHandler();
25-
JsonMapper jsonMapper =
26-
new JsonMapper() {
27-
@Override
28-
public <T> T mapFrom(byte[] body) {
29-
return (T) new HashMap<String, Object>();
30-
}
31-
};
19+
JsonMapper jsonMapper = body -> new java.util.HashMap<String, Object>();
3220

3321
@Test
3422
void shouldStartHttpServerWithValidConfiguration() {
35-
OpenApi validSpec = testSpecification();
23+
Spec validSpec = testSpec();
3624
Map<String, HttpHandler> handlers = emptyMap();
3725

3826
assertDoesNotThrow(
@@ -44,47 +32,48 @@ void shouldStartHttpServerWithValidConfiguration() {
4432
}
4533

4634
@Test
47-
void shouldThrowExceptionWhenOpenApiSpecificationIsNull() {
35+
void shouldThrowExceptionWhenSpecIsNull() {
4836
Map<String, HttpHandler> handlers = emptyMap();
4937

5038
assertThatThrownBy(() -> new OpenApiServer(null, jsonMapper, handlers, onError))
5139
.isInstanceOf(NullPointerException.class)
52-
.hasMessageContaining("OpenAPI specification must not be null");
40+
.hasMessageContaining("Spec must not be null");
5341
}
5442

5543
@Test
56-
void shouldThrowExceptionWhenRequestBodyMapperIsNull() {
57-
OpenApi validSpec = testSpecification();
44+
void shouldThrowExceptionWhenJsonMapperIsNull() {
45+
Spec validSpec = testSpec();
5846
Map<String, HttpHandler> handlers = emptyMap();
5947

6048
assertThatThrownBy(() -> new OpenApiServer(validSpec, null, handlers, onError))
6149
.isInstanceOf(NullPointerException.class)
62-
.hasMessageContaining("Request body mapper must not be null");
50+
.hasMessageContaining("JsonMapper must not be null");
6351
}
6452

6553
@Test
66-
void shouldThrowExceptionWhenRequestHandlersMapIsNull() {
67-
OpenApi validSpec = testSpecification();
54+
void shouldThrowExceptionWhenHandlersMapIsNull() {
55+
Spec validSpec = testSpec();
6856

6957
assertThatThrownBy(() -> new OpenApiServer(validSpec, jsonMapper, null, onError))
7058
.isInstanceOf(NullPointerException.class)
71-
.hasMessageContaining("Request handlers must not be null");
59+
.hasMessageContaining("handlers must not be null");
7260
}
7361

7462
@Test
7563
void testExceptionIsThrownOnInvalidHttpPort() {
76-
OpenApi validSpec = testSpecification();
64+
Spec validSpec = testSpec();
7765
Map<String, HttpHandler> handlers = emptyMap();
7866
assertThatThrownBy(() -> new OpenApiServer(validSpec, jsonMapper, handlers, onError, -1))
7967
.isInstanceOf(IllegalArgumentException.class);
8068
}
8169

82-
private OpenApi testSpecification() {
83-
return new OpenApi(
84-
"3.1.0",
85-
new Info("API", "1.0"),
86-
Collections.singletonList(server),
87-
emptyMap(),
88-
new Components(emptyMap(), emptyMap()));
70+
private Spec testSpec() {
71+
Map<String, Object> raw =
72+
Map.of(
73+
"openapi", "3.1.0",
74+
"info", Map.of("title", "Test API", "version", "1.0"),
75+
"servers", List.of(Map.of("url", "http://localhost:8080/api")),
76+
"paths", emptyMap());
77+
return Spec.from(raw);
8978
}
9079
}

src/test/java/com/retailsvc/http/ServerBaseTest.java

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
package com.retailsvc.http;
22

33
import static com.retailsvc.http.Handlers.defaultExceptionHandler;
4-
import static com.retailsvc.http.openapi.SpecificationLoader.parseSpecification;
54
import static java.net.http.HttpClient.Version.HTTP_1_1;
65
import static java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor;
76
import static org.assertj.core.api.Assertions.fail;
87

98
import com.google.gson.Gson;
10-
import com.retailsvc.http.openapi.model.JsonMapper;
11-
import com.retailsvc.http.openapi.model.OpenApi;
9+
import com.retailsvc.http.spec.Spec;
1210
import com.sun.net.httpserver.HttpHandler;
11+
import java.io.InputStream;
1312
import java.net.URI;
1413
import java.net.http.HttpClient;
1514
import java.net.http.HttpRequest;
1615
import java.net.http.HttpRequest.BodyPublisher;
17-
import java.util.List;
16+
import java.nio.charset.StandardCharsets;
1817
import java.util.Map;
1918
import java.util.Objects;
2019
import java.util.Optional;
@@ -26,12 +25,19 @@ public abstract class ServerBaseTest {
2625

2726
protected Gson gson = new Gson();
2827

29-
protected OpenApi specification;
28+
protected Spec spec;
3029
protected OpenApiServer server;
3130

3231
@BeforeEach
3332
void setUp() {
34-
specification = parseSpecification("openapi.json", s -> gson.fromJson(s, OpenApi.class), null);
33+
try (InputStream in = ServerBaseTest.class.getResourceAsStream("/openapi.json")) {
34+
String text = new String(in.readAllBytes(), StandardCharsets.UTF_8);
35+
@SuppressWarnings("unchecked")
36+
Map<String, Object> raw = (Map<String, Object>) gson.fromJson(text, Map.class);
37+
spec = Spec.from(raw);
38+
} catch (Exception e) {
39+
fail(e);
40+
}
3541
}
3642

3743
@AfterEach
@@ -40,21 +46,12 @@ void tearDown() {
4046
}
4147

4248
protected JsonMapper jsonMapper() {
43-
return new JsonMapper() {
44-
@Override
45-
public <T> T mapFrom(byte[] body) {
46-
if (body.length > 0 && body[0] == '[') {
47-
return (T) gson.fromJson(new String(body), List.class);
48-
}
49-
return (T) gson.fromJson(new String(body), Map.class);
50-
}
51-
};
49+
return body -> gson.fromJson(new String(body), Object.class);
5250
}
5351

5452
protected OpenApiServer newServer(Map<String, HttpHandler> handlers) {
5553
try {
56-
server =
57-
new OpenApiServer(specification, jsonMapper(), handlers, defaultExceptionHandler(), 0);
54+
server = new OpenApiServer(spec, jsonMapper(), handlers, defaultExceptionHandler(), 0);
5855
return server;
5956
} catch (Exception e) {
6057
fail(e);

src/test/java/com/retailsvc/http/start/EchoHandler.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.retailsvc.http.start;
22

3-
import com.retailsvc.http.openapi.model.GetRequestBody;
3+
import com.retailsvc.http.Request;
44
import com.sun.net.httpserver.HttpExchange;
55
import com.sun.net.httpserver.HttpHandler;
66
import java.io.IOException;
@@ -9,13 +9,13 @@
99
import org.slf4j.LoggerFactory;
1010

1111
/** Echoes back the request body as a response body */
12-
public class EchoHandler implements HttpHandler, GetRequestBody {
12+
public class EchoHandler implements HttpHandler {
1313

1414
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
1515

1616
@Override
1717
public void handle(HttpExchange exchange) throws IOException {
18-
byte[] bytes = getRequestBody(exchange);
18+
byte[] bytes = Request.bytes(exchange);
1919

2020
if (bytes.length == 0) {
2121
LOG.debug("No bytes available to read from the request body");

0 commit comments

Comments
 (0)