package dev.relism.routing; import dev.relism.fpr.core.ByteView; import dev.relism.http.HttpMethod; 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; import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.*; class GlobalRouterTest { // --- mock --- static class MockSubRouter extends AbstractRouter { RequestHandler matchedHandler; MockSubRouter(RequestHandler handler) { this.matchedHandler = handler; } @Override public RequestHandler route(Request request) { return matchedHandler; } @Override protected AbstractRouter addRoute(HttpMethod method, String path, RequestHandler handler) { return this; } } 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), new HeaderMap() ); return new Request(line, new byte[0]); } // --- mount & route --- @Test void route_delegatesToSubRouterBasedOnLongestPrefix() { GlobalRouter global = new GlobalRouter(); 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); // Path matches /api but not /api/v1 RequestHandler resolved2 = global.route(mockRequest("/api/v2/users")); assertEquals(hApi, resolved2); } @Test void route_fallsBackToInternalRouter() throws Exception { GlobalRouter global = new GlobalRouter(); RequestHandler internalHandler = new SimpleHandler((req, res) -> "internal"); global.get("/hello", (req, res) -> "internal"); // We know it routes to internal. Let's send a request. 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); } // --- resolveExceptionHandler --- @Test void resolveExceptionHandler_returnsScopedHandler() { GlobalRouter global = new GlobalRouter(); MockSubRouter sub = new MockSubRouter(null); AbstractRouter.ExceptionHandler customSubHandler = (ex, req, res) -> "sub error"; sub.onException(customSubHandler); 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"))); } }