Skip to content

Commit fd83072

Browse files
committed
test: Prove multi-spec loading from directory and JAR classpath
1 parent 10251a8 commit fd83072

4 files changed

Lines changed: 175 additions & 0 deletions

File tree

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package com.retailsvc.http;
2+
3+
import static java.net.HttpURLConnection.HTTP_OK;
4+
import static org.assertj.core.api.Assertions.assertThat;
5+
6+
import com.retailsvc.http.spec.Spec;
7+
import com.retailsvc.http.support.SpecAnchor;
8+
import java.io.IOException;
9+
import java.io.InputStream;
10+
import java.net.URI;
11+
import java.net.URL;
12+
import java.net.URLClassLoader;
13+
import java.net.http.HttpClient;
14+
import java.net.http.HttpRequest;
15+
import java.net.http.HttpResponse;
16+
import java.nio.file.Files;
17+
import java.nio.file.Path;
18+
import java.util.LinkedHashMap;
19+
import java.util.Map;
20+
import java.util.jar.JarEntry;
21+
import java.util.jar.JarOutputStream;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.io.TempDir;
24+
25+
class MultiSpecClasspathLoadingTest {
26+
27+
@Test
28+
void loadsTwoSpecsFromDirectoryClasspath() throws Exception {
29+
Spec v1 = Spec.fromClasspath(SpecAnchor.class, "/schemas/v1/openapi.json");
30+
Spec v2 = Spec.fromClasspath(SpecAnchor.class, "/schemas/v2/openapi.json");
31+
32+
assertThat(v1.basePath()).isEqualTo("/api/v1");
33+
assertThat(v2.basePath()).isEqualTo("/api/v2");
34+
35+
bootAndAssertBothPingsReturnOk(v1, v2);
36+
}
37+
38+
@Test
39+
void loadsTwoSpecsFromJarClasspath(@TempDir Path tmp) throws Exception {
40+
Path jarFile = buildSchemasJar(tmp);
41+
42+
try (URLClassLoader cl =
43+
new URLClassLoader(new URL[] {jarFile.toUri().toURL()}, getClass().getClassLoader())) {
44+
Class<?> anchor = Class.forName("com.retailsvc.http.support.SpecAnchor", true, cl);
45+
46+
Spec v1 = Spec.fromClasspath(anchor, "/schemas/v1/openapi.json");
47+
Spec v2 = Spec.fromClasspath(anchor, "/schemas/v2/openapi.json");
48+
49+
assertThat(v1.basePath()).isEqualTo("/api/v1");
50+
assertThat(v2.basePath()).isEqualTo("/api/v2");
51+
52+
bootAndAssertBothPingsReturnOk(v1, v2);
53+
}
54+
}
55+
56+
private static void bootAndAssertBothPingsReturnOk(Spec v1, Spec v2) throws Exception {
57+
Map<String, RequestHandler> v1Handlers = handlersFor(v1, req -> Response.ok(Map.of("v", 1)));
58+
Map<String, RequestHandler> v2Handlers = handlersFor(v2, req -> Response.ok(Map.of("v", 2)));
59+
60+
try (OpenApiServer server =
61+
OpenApiServer.builder()
62+
.port(0)
63+
.addSpec(v1, v1Handlers)
64+
.addSpec(v2, v2Handlers)
65+
.useExternalAuthentication()
66+
.build()) {
67+
68+
int port = server.listenPort();
69+
HttpResponse<String> r1 = get("http://localhost:" + port + "/api/v1/ping");
70+
HttpResponse<String> r2 = get("http://localhost:" + port + "/api/v2/ping");
71+
assertThat(r1.statusCode()).isEqualTo(HTTP_OK);
72+
assertThat(r1.body()).contains("\"v\":1");
73+
assertThat(r2.statusCode()).isEqualTo(HTTP_OK);
74+
assertThat(r2.body()).contains("\"v\":2");
75+
}
76+
}
77+
78+
private static Path buildSchemasJar(Path tmp) throws IOException {
79+
Path jar = tmp.resolve("specs.jar");
80+
try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(jar))) {
81+
copyToJar(out, "schemas/v1/openapi.json");
82+
copyToJar(out, "schemas/v2/openapi.json");
83+
copyToJar(out, "com/retailsvc/http/support/SpecAnchor.class");
84+
}
85+
return jar;
86+
}
87+
88+
private static void copyToJar(JarOutputStream out, String resourcePath) throws IOException {
89+
try (InputStream in =
90+
MultiSpecClasspathLoadingTest.class.getClassLoader().getResourceAsStream(resourcePath)) {
91+
if (in == null) {
92+
throw new IllegalStateException("missing test resource: " + resourcePath);
93+
}
94+
out.putNextEntry(new JarEntry(resourcePath));
95+
in.transferTo(out);
96+
out.closeEntry();
97+
}
98+
}
99+
100+
private static Map<String, RequestHandler> handlersFor(Spec spec, RequestHandler shared) {
101+
Map<String, RequestHandler> out = new LinkedHashMap<>();
102+
spec.operations().forEach(op -> out.put(op.operationId(), shared));
103+
return out;
104+
}
105+
106+
private static HttpResponse<String> get(String url) throws Exception {
107+
return HttpClient.newHttpClient()
108+
.send(
109+
HttpRequest.newBuilder(URI.create(url)).GET().build(),
110+
HttpResponse.BodyHandlers.ofString());
111+
}
112+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.retailsvc.http.support;
2+
3+
/**
4+
* Marker class used by classpath-loading tests as the {@code Class} argument to {@link
5+
* com.retailsvc.http.spec.Spec#fromClasspath(Class, String)}.
6+
*/
7+
public final class SpecAnchor {
8+
private SpecAnchor() {}
9+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"openapi": "3.1.0",
3+
"info": {
4+
"title": "Multi-spec test API v1",
5+
"version": "1.0.0"
6+
},
7+
"servers": [
8+
{"url": "/api/v1"}
9+
],
10+
"paths": {
11+
"/ping": {
12+
"get": {
13+
"operationId": "v1-ping",
14+
"responses": {
15+
"200": {
16+
"description": "OK",
17+
"content": {
18+
"application/json": {
19+
"schema": {"type": "object"}
20+
}
21+
}
22+
}
23+
}
24+
}
25+
}
26+
}
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"openapi": "3.1.0",
3+
"info": {
4+
"title": "Multi-spec test API v2",
5+
"version": "1.0.0"
6+
},
7+
"servers": [
8+
{"url": "/api/v2"}
9+
],
10+
"paths": {
11+
"/ping": {
12+
"get": {
13+
"operationId": "v2-ping",
14+
"responses": {
15+
"200": {
16+
"description": "OK",
17+
"content": {
18+
"application/json": {
19+
"schema": {"type": "object"}
20+
}
21+
}
22+
}
23+
}
24+
}
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)