From 0aecd354c78edbdee36c9e33f2396c4e4e9846b7 Mon Sep 17 00:00:00 2001 From: Emmaline Date: Thu, 3 Jul 2025 01:43:49 -0600 Subject: [PATCH] tests passing --- router.new.test.ts | 56 ++++++++++++---------- router.new.ts | 115 ++++++++++++++++++++++++++++++++++----------- util/join.ts | 3 +- 3 files changed, 121 insertions(+), 53 deletions(-) diff --git a/router.new.test.ts b/router.new.test.ts index 73f03dd..76bc7ff 100644 --- a/router.new.test.ts +++ b/router.new.test.ts @@ -80,10 +80,10 @@ describe("Router", () => { it("should execute global middleware", async () => { let middlewareExecuted = false; - // router.use(async (_ctx, next) => { - // middlewareExecuted = true; - // return await next(); - // }); + router.use(async (_, _ctx, next) => { + middlewareExecuted = true; + return await next(); + }); router.route("/test") .get(async () => new Response("test")); @@ -101,8 +101,7 @@ describe("Router", () => { router.use("/test", async (_, _ctx, next) => { middlewareExecuted = true; - console.log("middlware happened"); - return await next() ?? new Response("unresolved stack"); + return await next(); }); router.route("/test") @@ -121,7 +120,7 @@ describe("Router", () => { router.use("/:version/.*", async (_, ctx, next) => { capturedParams.version = ctx.params.version; - return await next() ?? new Response("unresolved stack"); + return await next(); }); router.route("/:version/test") @@ -136,16 +135,16 @@ describe("Router", () => { }); it("should execute middleware in correct order", async () => { - const order: number[] = [1]; + const order: number[] = []; - // router.use(async (_, _ctx, next) => { - // order.push(1); - // return await next()?? new Response("unresolved stack"); - // }); + router.use(async (_, _ctx, next) => { + order.push(1); + return await next(); + }); router.use("/test", async (_, _ctx, next) => { order.push(2); - return await next() ?? new Response("unresolved stack"); + return await next(); }); router.route("/test") @@ -198,10 +197,10 @@ describe("Router", () => { const apiRouter = new Router(); let middlewareExecuted = false; - // apiRouter.use(async (_,_ctx, next) => { - // middlewareExecuted = true; - // return await next(); - // }); + apiRouter.use(async (_, _ctx, next) => { + middlewareExecuted = true; + return await next(); + }); apiRouter.route("/test") .get(async () => new Response("test")); @@ -219,10 +218,10 @@ describe("Router", () => { describe("Context State", () => { it("should maintain state across middleware chain", async () => { - // router.use(async (_,ctx, next) => { - // ctx.state.test = "value"; - // return await next(); - // }); + router.use(async (_, ctx, next) => { + ctx.state.test = "value"; + return await next(); + }); router.route("/test") .get(async (_, ctx) => { @@ -255,9 +254,9 @@ describe("Router", () => { }); it("should handle errors in middleware", async () => { - // router.use(async () => { - // throw new Error("Middleware error"); - // }); + router.use(async () => { + throw new Error("Middleware error"); + }); router.route("/test") .get(async () => new Response("test")); @@ -269,6 +268,15 @@ describe("Router", () => { const res = await router.handle(req); assertEquals(res.status, 500); }); + + it("should handle no handlers returning a response", async () => { + router.get("/test", async () => undefined as unknown as Response); + const req = new Request("http://localhost/test", { + method: "GET", + }); + const res = await router.handle(req); + assertEquals(res.status, 501); + }); }); describe("HTTP Methods", () => { diff --git a/router.new.ts b/router.new.ts index b0b637a..d8ef974 100644 --- a/router.new.ts +++ b/router.new.ts @@ -1,11 +1,12 @@ import type { RouterContext as oldContext } from "@bearmetal/router/types"; import { joinPaths } from "./util/join.ts"; +import { InternalError, NotFound } from "@bearmetal/router/util/response"; type RouterContext = Omit; type Handler = ( req: Request, ctx: RouterContext, - next: () => Promise | null, + next: () => Promise, ) => Promise; type RouteConfigurator = { get(handler: Handler): RouteConfigurator; @@ -95,44 +96,93 @@ export class Router { return configurator; } - get(path: string, handler: Handler) { - path = fixPath(path); + get(handler: Handler): void; + get(path: string, handler: Handler): void; + get(path: string | Handler, handler?: Handler): void { + if (typeof path !== "string") { + handler = path; + path = "/.*"; + } else { + path = fixPath(path); + } const config = this.getOrCreateConfig(path); - this.getOrCreateConfigHandlers(GET, config).push(handler); + this.getOrCreateConfigHandlers(GET, config).push(handler!); } - post(path: string, handler: Handler) { - path = fixPath(path); + post(handler: Handler): void; + post(path: string, handler: Handler): void; + post(path: string | Handler, handler?: Handler): void { + if (typeof path !== "string") { + handler = path; + path = "/.*"; + } else { + path = fixPath(path); + } const config = this.getOrCreateConfig(path); - this.getOrCreateConfigHandlers(POST, config).push(handler); + this.getOrCreateConfigHandlers(POST, config).push(handler!); } - put(path: string, handler: Handler) { - path = fixPath(path); + put(handler: Handler): void; + put(path: string, handler: Handler): void; + put(path: string | Handler, handler?: Handler): void { + if (typeof path !== "string") { + handler = path; + path = "/.*"; + } else { + path = fixPath(path); + } const config = this.getOrCreateConfig(path); - this.getOrCreateConfigHandlers(PUT, config).push(handler); + this.getOrCreateConfigHandlers(PUT, config).push(handler!); } - patch(path: string, handler: Handler) { - path = fixPath(path); + patch(handler: Handler): void; + patch(path: string, handler: Handler): void; + patch(path: string | Handler, handler?: Handler): void { + if (typeof path !== "string") { + handler = path; + path = "/.*"; + } else { + path = fixPath(path); + } const config = this.getOrCreateConfig(path); - this.getOrCreateConfigHandlers(PATCH, config).push(handler); + this.getOrCreateConfigHandlers(PATCH, config).push(handler!); } - delete(path: string, handler: Handler) { - path = fixPath(path); + delete(handler: Handler): void; + delete(path: string, handler: Handler): void; + delete(path: string | Handler, handler?: Handler): void { + if (typeof path !== "string") { + handler = path; + path = "/.*"; + } else { + path = fixPath(path); + } const config = this.getOrCreateConfig(path); - this.getOrCreateConfigHandlers(DELETE, config).push(handler); + this.getOrCreateConfigHandlers(DELETE, config).push(handler!); } - options(path: string, handler: Handler) { - path = fixPath(path); + options(handler: Handler): void; + options(path: string, handler: Handler): void; + options(path: string | Handler, handler?: Handler): void { + if (typeof path !== "string") { + handler = path; + path = "/.*"; + } else { + path = fixPath(path); + } const config = this.getOrCreateConfig(path); - this.getOrCreateConfigHandlers(OPTIONS, config).push(handler); + this.getOrCreateConfigHandlers(OPTIONS, config).push(handler!); } - use(path: string, handler: Handler | Router) { - path = fixPath(path); + use(handler: Handler | Router): void; + use(path: string, handler: Handler | Router): void; + use(path: string | Handler | Router, handler?: Handler | Router): void { + if (typeof path !== "string") { + handler = path; + path = "/.*"; + } else { + path = fixPath(path); + } if (handler instanceof Router) { return this.resolveRouterHandlerStack(path, handler); } const config = this.getOrCreateConfig(path); - this.getOrCreateConfigHandlers(_use, config).push(handler); + this.getOrCreateConfigHandlers(_use, config).push(handler!); } private getOrCreateConfig(path: string): RouteConfig { @@ -142,6 +192,7 @@ export class Router { handlers: {}, pattern: new URLPattern({ pathname: path }), }; + this.routes.set(path, config); } return config; } @@ -172,12 +223,16 @@ export class Router { const url = new URL(req.url); const method = req.method; - const matchingRoutes = this.findMatchingRoutes(url).filter((r) => - Object.hasOwn(r.config.handlers, method) || - Object.hasOwn(r.config.handlers, _use) + const matchingRoutes = this.findMatchingRoutes(url); + if (!matchingRoutes.length) return NotFound(); + const matchingMethods = matchingRoutes.some((r) => + Object.hasOwn(r.config.handlers, method) ); + if (!matchingMethods) { + return new Response("Method Not Allowed", { status: 405 }); + } const middlewareStack = matchingRoutes.flatMap((r) => - r.config.handlers[method] + (r.config.handlers[_use] ?? []).concat(r.config.handlers[method] ?? []) ); const ctx: RouterContext = { url, @@ -189,7 +244,11 @@ export class Router { let index = 0; const executeMiddleware = async (): Promise => { if (index < middlewareStack.length) { - const res = await middlewareStack[index++](req, ctx, executeMiddleware); + const res = await middlewareStack[index++]?.( + req, + ctx, + executeMiddleware, + ); if (res instanceof Response) return res; } return new Response("End of stack", { status: 501 }); @@ -197,7 +256,7 @@ export class Router { try { return await executeMiddleware(); } catch { - return new Response("Internal Server Error", { status: 500 }); + return InternalError(); } } diff --git a/util/join.ts b/util/join.ts index bf56a78..e5be5fb 100644 --- a/util/join.ts +++ b/util/join.ts @@ -1,2 +1,3 @@ export const joinPaths = (...args: string[]) => - args.map((a) => a.replace(/\/?\*?$/, "")).join(); + args.map((a, i, l) => i === l.length - 1 ? a : a.replace(/\/?\.?\*?$/, "")) + .join("");