preparing for a conceptual refactoring...
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
package dev.relism;
|
||||
|
||||
import dev.relism.extension.FlashApp;
|
||||
import dev.relism.extension.FlashConfiguration;
|
||||
import dev.relism.http.ContentType;
|
||||
import dev.relism.models.Response;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -23,7 +25,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class HttpServerConcurrencyTest {
|
||||
|
||||
private HttpServer server;
|
||||
private FlashApp app;
|
||||
private int port;
|
||||
private HttpClient httpClient;
|
||||
|
||||
@@ -32,19 +34,19 @@ class HttpServerConcurrencyTest {
|
||||
try (ServerSocket s = new ServerSocket(0)) {
|
||||
port = s.getLocalPort();
|
||||
}
|
||||
HttpServerConfiguration config = HttpServerConfiguration.builder()
|
||||
|
||||
app = FlashApp.create(FlashConfiguration.builder()
|
||||
.port(port)
|
||||
.host("127.0.0.1")
|
||||
.build();
|
||||
.build());
|
||||
|
||||
server = new HttpServer(config);
|
||||
server.get("/ping", (req, res) -> "pong").with();
|
||||
server.post("/echo", (req, res) -> {
|
||||
app.get("/ping", (req, res) -> "pong");
|
||||
app.post("/echo", (req, res) -> {
|
||||
byte[] body = req.body().bytes();
|
||||
return new Response(200, body, ContentType.TEXT_PLAIN);
|
||||
}).with();
|
||||
});
|
||||
|
||||
server.start().get(5, TimeUnit.SECONDS);
|
||||
app.start().get(5, TimeUnit.SECONDS);
|
||||
|
||||
httpClient = HttpClient.newBuilder()
|
||||
.version(HttpClient.Version.HTTP_1_1)
|
||||
@@ -53,8 +55,7 @@ class HttpServerConcurrencyTest {
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (server != null)
|
||||
server.stop();
|
||||
if (app != null) app.stop();
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
@@ -91,7 +92,6 @@ class HttpServerConcurrencyTest {
|
||||
CountDownLatch start = new CountDownLatch(1);
|
||||
AtomicInteger successes = new AtomicInteger();
|
||||
List<Throwable> errors = new CopyOnWriteArrayList<>();
|
||||
|
||||
List<String> responses = new CopyOnWriteArrayList<>();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
@@ -100,11 +100,9 @@ class HttpServerConcurrencyTest {
|
||||
try {
|
||||
start.await();
|
||||
HttpResponse<String> res = get("/ping");
|
||||
String summary = res.statusCode() + "|" + res.body();
|
||||
responses.add(summary);
|
||||
if (res.statusCode() == 200 && "pong".equals(res.body())) {
|
||||
responses.add(res.statusCode() + "|" + res.body());
|
||||
if (res.statusCode() == 200 && "pong".equals(res.body()))
|
||||
successes.incrementAndGet();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
errors.add(e);
|
||||
}
|
||||
@@ -169,7 +167,7 @@ class HttpServerConcurrencyTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Races the router's lazy-compile step: a fresh server with 10 registered routes
|
||||
* Races the router's lazy-compile step: a fresh app with 10 registered routes
|
||||
* is hit by threads simultaneously before any request has been processed,
|
||||
* causing multiple threads to compete on the first compilation.
|
||||
*/
|
||||
@@ -180,17 +178,17 @@ class HttpServerConcurrencyTest {
|
||||
freshPort = s.getLocalPort();
|
||||
}
|
||||
|
||||
HttpServer freshServer = new HttpServer(HttpServerConfiguration.builder()
|
||||
FlashApp freshApp = FlashApp.create(FlashConfiguration.builder()
|
||||
.port(freshPort)
|
||||
.host("127.0.0.1")
|
||||
.build());
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
final int idx = i;
|
||||
freshServer.get("/route" + idx, (req, res) -> "handler" + idx).with();
|
||||
freshApp.get("/route" + idx, (req, res) -> "handler" + idx);
|
||||
}
|
||||
|
||||
freshServer.start().get(5, TimeUnit.SECONDS);
|
||||
freshApp.start().get(5, TimeUnit.SECONDS);
|
||||
|
||||
int count = 20;
|
||||
ExecutorService pool = Executors.newFixedThreadPool(count);
|
||||
@@ -206,9 +204,8 @@ class HttpServerConcurrencyTest {
|
||||
try {
|
||||
start.await();
|
||||
HttpResponse<String> res = get(freshPort, "/route" + routeIdx);
|
||||
if (res.statusCode() == 200 && ("handler" + routeIdx).equals(res.body())) {
|
||||
if (res.statusCode() == 200 && ("handler" + routeIdx).equals(res.body()))
|
||||
successes.incrementAndGet();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
errors.add(e);
|
||||
}
|
||||
@@ -224,7 +221,7 @@ class HttpServerConcurrencyTest {
|
||||
assertTrue(errors.isEmpty(), () -> "Unexpected errors: " + errors);
|
||||
assertEquals(count, successes.get());
|
||||
} finally {
|
||||
freshServer.stop();
|
||||
freshApp.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package dev.relism;
|
||||
|
||||
import dev.relism.extension.FlashApp;
|
||||
import dev.relism.extension.FlashConfiguration;
|
||||
import dev.relism.http.ContentType;
|
||||
import dev.relism.models.Response;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -19,58 +21,52 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class HttpServerTest {
|
||||
|
||||
private HttpServer server;
|
||||
private FlashApp app;
|
||||
private int port;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
// Find a free ephemeral port
|
||||
try (ServerSocket s = new ServerSocket(0)) {
|
||||
port = s.getLocalPort();
|
||||
}
|
||||
|
||||
HttpServerConfiguration config = HttpServerConfiguration.builder()
|
||||
app = FlashApp.create(FlashConfiguration.builder()
|
||||
.port(port)
|
||||
.host("127.0.0.1")
|
||||
.build();
|
||||
.build());
|
||||
|
||||
server = new HttpServer(config);
|
||||
|
||||
// Setup some routes
|
||||
server.get("/api/ping", (req, res) -> "pong").with();
|
||||
app.get("/api/ping", (req, res) -> "pong");
|
||||
|
||||
server.post("/api/echo", (req, res) -> {
|
||||
app.post("/api/echo", (req, res) -> {
|
||||
byte[] body = req.body().bytes();
|
||||
return res.status(201).body(body); // echo body & change status
|
||||
}).with();
|
||||
return res.status(201).body(body);
|
||||
});
|
||||
|
||||
server.get("/api/crash", (req, res) -> {
|
||||
app.get("/api/crash", (req, res) -> {
|
||||
throw new RuntimeException("Simulated Crash");
|
||||
}).with();
|
||||
});
|
||||
|
||||
byte[] streamData = "streaming response body".getBytes(StandardCharsets.UTF_8);
|
||||
server.get("/api/stream", (req, res) ->
|
||||
res.stream(new ByteArrayInputStream(streamData), streamData.length)).with();
|
||||
app.get("/api/stream", (req, res) ->
|
||||
res.stream(new ByteArrayInputStream(streamData), streamData.length));
|
||||
|
||||
server.get("/api/chunked-out", (req, res) ->
|
||||
res.chunked(new ByteArrayInputStream(streamData))).with();
|
||||
app.get("/api/chunked-out", (req, res) ->
|
||||
res.chunked(new ByteArrayInputStream(streamData)));
|
||||
|
||||
server.get("/api/custom-header", (req, res) ->
|
||||
res.body("ok").header("X-Flash", "works")).with();
|
||||
app.get("/api/custom-header", (req, res) ->
|
||||
res.body("ok").header("X-Flash", "works"));
|
||||
|
||||
server.post("/api/chunked-in", (req, res) -> {
|
||||
app.post("/api/chunked-in", (req, res) -> {
|
||||
byte[] body = req.body().bytes();
|
||||
return res.body(body);
|
||||
}).with();
|
||||
});
|
||||
|
||||
server.start().get(5, TimeUnit.SECONDS);
|
||||
app.start().get(5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (server != null) {
|
||||
server.stop();
|
||||
}
|
||||
if (app != null) app.stop();
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
@@ -125,7 +121,7 @@ class HttpServerTest {
|
||||
int size = Integer.parseInt(sizeLine.toString().trim(), 16);
|
||||
if (size == 0) break;
|
||||
result.write(in.readNBytes(size));
|
||||
in.read(); in.read(); // \r\n after chunk data
|
||||
in.read(); in.read();
|
||||
}
|
||||
return result.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
@@ -134,28 +130,18 @@ class HttpServerTest {
|
||||
|
||||
@Test
|
||||
void testGet_pingRoute_returns200AndStringBody() throws Exception {
|
||||
String req = "GET /api/ping HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"\r\n";
|
||||
|
||||
String req = "GET /api/ping HTTP/1.1\r\nHost: localhost\r\n\r\n";
|
||||
String res = sendRawRequest(req);
|
||||
|
||||
assertTrue(res.startsWith("HTTP/1.1 200 OK"));
|
||||
assertTrue(res.contains("Content-Length: 4")); // "pong"
|
||||
assertTrue(res.contains("Content-Length: 4"));
|
||||
assertTrue(res.endsWith("pong"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPost_echoRoute_returns201AndEchoesBody() throws Exception {
|
||||
String body = "Hello, Flash!";
|
||||
String req = "POST /api/echo HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Content-Length: " + body.length() + "\r\n" +
|
||||
"\r\n" +
|
||||
body;
|
||||
|
||||
String req = "POST /api/echo HTTP/1.1\r\nHost: localhost\r\nContent-Length: " + body.length() + "\r\n\r\n" + body;
|
||||
String res = sendRawRequest(req);
|
||||
|
||||
assertTrue(res.startsWith("HTTP/1.1 201 Created"));
|
||||
assertTrue(res.contains("Content-Length: " + body.length()));
|
||||
assertTrue(res.endsWith(body));
|
||||
@@ -163,12 +149,8 @@ class HttpServerTest {
|
||||
|
||||
@Test
|
||||
void testNotFound_returns404Html() throws Exception {
|
||||
String req = "GET /api/unknown HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"\r\n";
|
||||
|
||||
String req = "GET /api/unknown HTTP/1.1\r\nHost: localhost\r\n\r\n";
|
||||
String res = sendRawRequest(req);
|
||||
|
||||
assertTrue(res.startsWith("HTTP/1.1 404 Not Found"));
|
||||
assertTrue(res.contains("404"));
|
||||
assertTrue(res.contains("No route matched this request"));
|
||||
@@ -176,12 +158,8 @@ class HttpServerTest {
|
||||
|
||||
@Test
|
||||
void testException_returns500Html() throws Exception {
|
||||
String req = "GET /api/crash HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"\r\n";
|
||||
|
||||
String req = "GET /api/crash HTTP/1.1\r\nHost: localhost\r\n\r\n";
|
||||
String res = sendRawRequest(req);
|
||||
|
||||
assertTrue(res.startsWith("HTTP/1.1 500 Internal Server Error"));
|
||||
assertTrue(res.contains("500"));
|
||||
assertTrue(res.contains("Simulated Crash"));
|
||||
@@ -189,25 +167,18 @@ class HttpServerTest {
|
||||
|
||||
@Test
|
||||
void testRoot_returns404_noExceptionTossed() throws Exception {
|
||||
String req = "GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"\r\n";
|
||||
|
||||
String req = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
|
||||
String res = sendRawRequest(req);
|
||||
|
||||
assertTrue(res.startsWith("HTTP/1.1 404 Not Found"));
|
||||
assertTrue(res.contains("No route matched this request."));
|
||||
}
|
||||
|
||||
// --- streaming response ---
|
||||
|
||||
@Test
|
||||
void testStreamingResponse_writesContentLengthAndBody() throws Exception {
|
||||
String req = "GET /api/stream HTTP/1.1\r\nHost: localhost\r\n\r\n";
|
||||
String res = sendRawRequest(req);
|
||||
|
||||
assertTrue(res.startsWith("HTTP/1.1 200 OK"));
|
||||
assertTrue(res.contains("Content-Length: 23")); // "streaming response body"
|
||||
assertTrue(res.contains("Content-Length: 23"));
|
||||
assertTrue(res.endsWith("streaming response body"));
|
||||
}
|
||||
|
||||
@@ -215,41 +186,28 @@ class HttpServerTest {
|
||||
void testChunkedResponse_writesTransferEncodingChunked() throws Exception {
|
||||
String req = "GET /api/chunked-out HTTP/1.1\r\nHost: localhost\r\n\r\n";
|
||||
String res = sendRawRequest(req);
|
||||
|
||||
assertTrue(res.startsWith("HTTP/1.1 200 OK"));
|
||||
assertTrue(res.contains("Transfer-Encoding: chunked"));
|
||||
assertTrue(res.endsWith("streaming response body"));
|
||||
}
|
||||
|
||||
// --- custom response headers ---
|
||||
|
||||
@Test
|
||||
void testCustomHeader_appearsInResponse() throws Exception {
|
||||
String req = "GET /api/custom-header HTTP/1.1\r\nHost: localhost\r\n\r\n";
|
||||
String res = sendRawRequest(req);
|
||||
|
||||
assertTrue(res.startsWith("HTTP/1.1 200 OK"));
|
||||
assertTrue(res.contains("X-Flash: works\r\n"));
|
||||
}
|
||||
|
||||
// --- chunked request body ---
|
||||
|
||||
@Test
|
||||
void testChunkedRequestBody_decodedAndEchoed() throws Exception {
|
||||
String req = "POST /api/chunked-in HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Transfer-Encoding: chunked\r\n" +
|
||||
"\r\n" +
|
||||
"5\r\nhello\r\n6\r\n world\r\n0\r\n\r\n";
|
||||
|
||||
String req = "POST /api/chunked-in HTTP/1.1\r\nHost: localhost\r\nTransfer-Encoding: chunked\r\n\r\n"
|
||||
+ "5\r\nhello\r\n6\r\n world\r\n0\r\n\r\n";
|
||||
String res = sendRawRequest(req);
|
||||
|
||||
assertTrue(res.startsWith("HTTP/1.1 200 OK"));
|
||||
assertTrue(res.endsWith("hello world"));
|
||||
}
|
||||
|
||||
// --- keep-alive ---
|
||||
|
||||
@Test
|
||||
void testKeepAlive_twoRequestsOnSameConnection() throws Exception {
|
||||
String req = "GET /api/ping HTTP/1.1\r\nHost: localhost\r\n\r\n";
|
||||
@@ -257,7 +215,6 @@ class HttpServerTest {
|
||||
try (Socket socket = new Socket("127.0.0.1", port);
|
||||
OutputStream out = socket.getOutputStream();
|
||||
InputStream in = socket.getInputStream()) {
|
||||
|
||||
socket.setSoTimeout(SOCKET_TIMEOUT_MS);
|
||||
out.write(req.getBytes(StandardCharsets.UTF_8));
|
||||
out.write(req.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
@@ -14,84 +14,72 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class AbstractRouterTest {
|
||||
|
||||
// A dummy router for testing base functionality
|
||||
// A minimal concrete router for testing base-class functionality
|
||||
static class DummyRouter extends AbstractRouter {
|
||||
RequestHandler lastAddedHandler;
|
||||
HttpMethod lastAddedMethod;
|
||||
String lastAddedPath;
|
||||
HttpMethod lastAddedMethod;
|
||||
String lastAddedPath;
|
||||
|
||||
@Override
|
||||
public RequestHandler route(Request request) {
|
||||
return null; // Not testing routing logic here
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractRouter addRoute(HttpMethod method, String path, RequestHandler handler) {
|
||||
this.lastAddedMethod = method;
|
||||
this.lastAddedPath = path;
|
||||
this.lastAddedMethod = method;
|
||||
this.lastAddedPath = path;
|
||||
this.lastAddedHandler = handler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Route(method = dev.relism.http.HttpMethod.POST, path = "/profile")
|
||||
static class ProfileHandler extends RequestHandler {
|
||||
@Override
|
||||
public Object handle(Request request, Response response) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static class UnannotatedHandler extends RequestHandler {
|
||||
@Override
|
||||
public Object handle(Request request, Response response) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// --- namespace ---
|
||||
|
||||
@Test
|
||||
void setNamespace_updatesStringAndBytes() {
|
||||
DummyRouter router = new DummyRouter();
|
||||
assertEquals("/", router.getNamespace());
|
||||
|
||||
|
||||
router.setNamespace("/api");
|
||||
assertEquals("/api", router.getNamespace());
|
||||
assertArrayEquals("/api".getBytes(StandardCharsets.UTF_8), router.getNamespaceBytes());
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
// --- doRegister (infrastructure method used by FlashApp/FlashScope) ---
|
||||
|
||||
@Test
|
||||
void getPostPutDelete_delegatesToAddRouteWithSanitizedPath() {
|
||||
void doRegister_lambda_sanitizesPathAndWrapsHandler() {
|
||||
DummyRouter router = new DummyRouter();
|
||||
SimpleHandler.FunctionalHandler func = (req, res) -> "OK";
|
||||
|
||||
router.get("users/", func).with();
|
||||
router.doRegister(HttpMethod.GET, "users/", func, new Middleware[0]);
|
||||
assertEquals(HttpMethod.GET, router.lastAddedMethod);
|
||||
assertEquals("/users", router.lastAddedPath);
|
||||
assertTrue(router.lastAddedHandler instanceof SimpleHandler);
|
||||
assertNotNull(router.lastAddedHandler);
|
||||
|
||||
router.post("/items", func).with();
|
||||
assertEquals(HttpMethod.POST, router.lastAddedMethod);
|
||||
|
||||
router.put("update", func).with();
|
||||
assertEquals(HttpMethod.PUT, router.lastAddedMethod);
|
||||
|
||||
router.delete("//delete//", func).with();
|
||||
router.doRegister(HttpMethod.DELETE, "//delete//", func, new Middleware[0]);
|
||||
assertEquals(HttpMethod.DELETE, router.lastAddedMethod);
|
||||
assertEquals("/delete", router.lastAddedPath);
|
||||
}
|
||||
|
||||
// --- register ---
|
||||
@Route(method = HttpMethod.POST, path = "/profile")
|
||||
static class ProfileHandler extends RequestHandler {
|
||||
@Override
|
||||
public Object handle(Request request, Response response) { return null; }
|
||||
}
|
||||
|
||||
static class UnannotatedHandler extends RequestHandler {
|
||||
@Override
|
||||
public Object handle(Request request, Response response) { return null; }
|
||||
}
|
||||
|
||||
@Test
|
||||
void register_annotatedHandler_addsRoute() {
|
||||
void doRegister_annotatedHandler_addsRoute() {
|
||||
DummyRouter router = new DummyRouter();
|
||||
ProfileHandler handler = new ProfileHandler();
|
||||
|
||||
router.register(handler).with();
|
||||
router.doRegister(handler, new Middleware[0]);
|
||||
|
||||
assertEquals(HttpMethod.POST, router.lastAddedMethod);
|
||||
assertEquals("/profile", router.lastAddedPath);
|
||||
@@ -99,24 +87,22 @@ class AbstractRouterTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void register_unannotatedHandler_doesNothing() {
|
||||
void doRegister_unannotatedHandler_doesNothing() {
|
||||
DummyRouter router = new DummyRouter();
|
||||
router.register(new UnannotatedHandler()).with();
|
||||
assertNull(router.lastAddedMethod); // Nothing added
|
||||
router.doRegister(new UnannotatedHandler(), new Middleware[0]);
|
||||
assertNull(router.lastAddedMethod);
|
||||
}
|
||||
|
||||
// --- default handlers ---
|
||||
// --- error handlers ---
|
||||
|
||||
@Test
|
||||
void defaultNotFoundHandler_returns404Html() throws Exception {
|
||||
DummyRouter router = new DummyRouter();
|
||||
Response res = new Response(200, new byte[0], ContentType.TEXT_PLAIN);
|
||||
|
||||
|
||||
assertNotNull(router.getNotFoundHandler());
|
||||
|
||||
SimpleHandler.FunctionalHandler custom = (req, resp) -> "Custom 404";
|
||||
router.onNotFound(custom);
|
||||
|
||||
|
||||
router.onNotFound((req, resp) -> "Custom 404");
|
||||
assertEquals("Custom 404", router.getNotFoundHandler().handle(null, res));
|
||||
}
|
||||
|
||||
@@ -125,9 +111,7 @@ class AbstractRouterTest {
|
||||
DummyRouter router = new DummyRouter();
|
||||
assertNotNull(router.getExceptionHandler());
|
||||
|
||||
AbstractRouter.ExceptionHandler custom = (ex, req, res) -> "Caught";
|
||||
router.onException(custom);
|
||||
|
||||
router.onException((ex, req, res) -> "Caught");
|
||||
assertEquals("Caught", router.getExceptionHandler().handle(new RuntimeException(), null, null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import dev.relism.models.HeaderMap;
|
||||
import dev.relism.models.Request;
|
||||
import dev.relism.models.RequestHandler;
|
||||
import dev.relism.models.RequestLine;
|
||||
import dev.relism.models.Response;
|
||||
import dev.relism.models.SimpleHandler;
|
||||
import dev.relism.routing.routers.fastpathrouter.FastPathViews;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -40,7 +39,6 @@ class GlobalRouterTest {
|
||||
private Request mockRequest(String path) {
|
||||
byte[] bytes = path.getBytes(StandardCharsets.UTF_8);
|
||||
FastPathViews.RequestByteView pathView = new FastPathViews.RequestByteView(bytes, 0, bytes.length);
|
||||
|
||||
RequestLine line = new RequestLine(
|
||||
HttpMethod.GET, pathView, null,
|
||||
new FastPathViews.RequestByteView("HTTP/1.1".getBytes(StandardCharsets.UTF_8), 0, 8),
|
||||
@@ -54,39 +52,30 @@ class GlobalRouterTest {
|
||||
@Test
|
||||
void route_delegatesToSubRouterBasedOnLongestPrefix() {
|
||||
GlobalRouter global = new GlobalRouter();
|
||||
|
||||
RequestHandler hApi = new SimpleHandler((req, res) -> "api");
|
||||
|
||||
RequestHandler hApi = new SimpleHandler((req, res) -> "api");
|
||||
RequestHandler hApiV1 = new SimpleHandler((req, res) -> "apiv1");
|
||||
|
||||
global.mount("/api", new MockSubRouter(hApi));
|
||||
global.mount("/api/v1", new MockSubRouter(hApiV1)); // longer prefix
|
||||
|
||||
// Path matches /api/v1 -> Should pick hApiV1 because it's longer and sorted first
|
||||
RequestHandler resolved = global.route(mockRequest("/api/v1/users"));
|
||||
assertEquals(hApiV1, resolved);
|
||||
global.mount("/api", new MockSubRouter(hApi));
|
||||
global.mount("/api/v1", new MockSubRouter(hApiV1));
|
||||
|
||||
// Path matches /api but not /api/v1
|
||||
RequestHandler resolved2 = global.route(mockRequest("/api/v2/users"));
|
||||
assertEquals(hApi, resolved2);
|
||||
assertEquals(hApiV1, global.route(mockRequest("/api/v1/users")));
|
||||
assertEquals(hApi, global.route(mockRequest("/api/v2/users")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void route_fallsBackToInternalRouter() throws Exception {
|
||||
GlobalRouter global = new GlobalRouter();
|
||||
RequestHandler internalHandler = new SimpleHandler((req, res) -> "internal");
|
||||
|
||||
global.get("/hello", (req, res) -> "internal").with();
|
||||
// We know it routes to internal. Let's send a request.
|
||||
global.doRegister(HttpMethod.GET, "/hello", (req, res) -> "internal", new Middleware[0]);
|
||||
|
||||
RequestHandler resolved = global.route(mockRequest("/hello"));
|
||||
assertNotNull(resolved);
|
||||
// It's the compiled FastPathRouter handler, let's verify it works
|
||||
assertEquals("internal", resolved.handle(null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void route_noMatch_returnsNotFoundHandler() {
|
||||
GlobalRouter global = new GlobalRouter();
|
||||
// Nothing registered. Should return the global notFoundHandler.
|
||||
RequestHandler resolved = global.route(mockRequest("/unknown"));
|
||||
assertEquals(global.getNotFoundHandler(), resolved);
|
||||
}
|
||||
@@ -102,10 +91,7 @@ class GlobalRouterTest {
|
||||
|
||||
global.mount("/api", sub);
|
||||
|
||||
// Under sub-namespace
|
||||
assertEquals(customSubHandler, global.resolveExceptionHandler(mockRequest("/api/fail")));
|
||||
|
||||
// Outside sub-namespace (global)
|
||||
assertEquals(global.getExceptionHandler(), global.resolveExceptionHandler(mockRequest("/other")));
|
||||
}
|
||||
}
|
||||
|
||||
+10
-12
@@ -5,7 +5,7 @@ import dev.relism.models.HeaderMap;
|
||||
import dev.relism.models.Request;
|
||||
import dev.relism.models.RequestHandler;
|
||||
import dev.relism.models.RequestLine;
|
||||
import dev.relism.models.SimpleHandler;
|
||||
import dev.relism.routing.Middleware;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -14,12 +14,13 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class FastPathRouterImplTest {
|
||||
|
||||
private static final Middleware[] NO_MIDDLEWARE = new Middleware[0];
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
private Request mockRequest(HttpMethod method, String path) {
|
||||
byte[] bytes = path.getBytes(StandardCharsets.UTF_8);
|
||||
FastPathViews.RequestByteView pathView = new FastPathViews.RequestByteView(bytes, 0, bytes.length);
|
||||
|
||||
RequestLine line = new RequestLine(
|
||||
method, pathView, null,
|
||||
new FastPathViews.RequestByteView("HTTP/1.1".getBytes(StandardCharsets.UTF_8), 0, 8),
|
||||
@@ -33,9 +34,9 @@ class FastPathRouterImplTest {
|
||||
@Test
|
||||
void route_lazyCompilationAndMatch() throws Exception {
|
||||
FastPathRouterImpl router = new FastPathRouterImpl();
|
||||
|
||||
router.get("/a", (req, res) -> "A").with();
|
||||
router.post("/b", (req, res) -> "B").with();
|
||||
|
||||
router.doRegister(HttpMethod.GET, "/a", (req, res) -> "A", NO_MIDDLEWARE);
|
||||
router.doRegister(HttpMethod.POST, "/b", (req, res) -> "B", NO_MIDDLEWARE);
|
||||
|
||||
RequestHandler res1 = router.route(mockRequest(HttpMethod.GET, "/a"));
|
||||
assertNotNull(res1);
|
||||
@@ -49,26 +50,23 @@ class FastPathRouterImplTest {
|
||||
@Test
|
||||
void route_noMatch_returnsNull() {
|
||||
FastPathRouterImpl router = new FastPathRouterImpl();
|
||||
router.get("/a", (req, res) -> "A").with();
|
||||
router.doRegister(HttpMethod.GET, "/a", (req, res) -> "A", NO_MIDDLEWARE);
|
||||
|
||||
assertNull(router.route(mockRequest(HttpMethod.GET, "/b")));
|
||||
// Wrong method
|
||||
assertNull(router.route(mockRequest(HttpMethod.POST, "/a")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void route_extractsPathParams() throws Exception {
|
||||
FastPathRouterImpl router = new FastPathRouterImpl();
|
||||
|
||||
router.get("/users/{id}/items/{itemId}", (req, res) -> "Extract").with();
|
||||
router.doRegister(HttpMethod.GET, "/users/{id}/items/{itemId}",
|
||||
(req, res) -> "Extract", NO_MIDDLEWARE);
|
||||
|
||||
Request request = mockRequest(HttpMethod.GET, "/users/123/items/456");
|
||||
RequestHandler handler = router.route(request);
|
||||
|
||||
|
||||
assertNotNull(handler);
|
||||
assertEquals("Extract", handler.handle(request, null));
|
||||
|
||||
// Verify path params were injected
|
||||
assertNotNull(request.getPathParams());
|
||||
assertEquals("123", request.param("id"));
|
||||
assertEquals("456", request.param("itemId"));
|
||||
|
||||
Reference in New Issue
Block a user