import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import express, { Router } from "express"; import type { Server } from "node:http"; import { generateAgentOpenApi } from "../../src/contracts/openapi.js"; import { buildChatRouter } from "../../src/routes/chat.js"; import { buildAgentPublicRouter } from "../../src/routes/publicApi.js"; describe("Agent public REST router", () => { let baseUrl = ""; let server: Server; beforeAll(async () => { const chatRouter = Router(); chatRouter.use(express.json()); chatRouter.post("/sessions/:session_id/runs", (req, res) => { res.json({ method: req.method, path: req.path, body: req.body }); }); chatRouter.delete("/sessions/:session_id/runs/current", (req, res) => { res.json({ method: req.method, path: req.path, body: req.body }); }); chatRouter.get("/auth-failure", (_req, res) => { res.status(503).json({ message: "authentication service unavailable" }); }); const app = express(); app.use(express.json()); app.use("/api/v1/agent", buildAgentPublicRouter(chatRouter)); server = app.listen(0); await new Promise((resolve) => server.once("listening", resolve)); const address = server.address(); if (!address || typeof address === "string") { throw new Error("test server did not expose a TCP port"); } baseUrl = `http://127.0.0.1:${address.port}`; }); afterAll(() => { server.close(); }); test("matches the published OpenAPI method and path set", () => { type RouterLayer = { route?: { methods: Record; path: string; }; }; const router = buildChatRouter( undefined as never, undefined as never, undefined as never, undefined as never, undefined as never, undefined as never, undefined as never, undefined as never, ); const layers = (router as unknown as { stack: RouterLayer[] }).stack; const runtimeOperations = layers .flatMap((layer) => { if (!layer.route) { return []; } const path = `/api/v1/agent${layer.route.path.replace( /:([^/]+)/g, "{$1}", )}`; return Object.entries(layer.route.methods) .filter(([, enabled]) => enabled) .map(([method]) => `${method.toUpperCase()} ${path}`); }) .sort(); const document = generateAgentOpenApi(); const contractOperations = Object.entries(document.paths) .flatMap(([path, pathItem]) => Object.entries(pathItem ?? {}) .filter(([, operation]) => { return ( operation && typeof operation === "object" && "responses" in operation ); }) .map(([method]) => `${method.toUpperCase()} ${path}`), ) .sort(); expect(runtimeOperations).toEqual(contractOperations); }); test("serves session runs without rewriting the REST request", async () => { const response = await fetch( `${baseUrl}/api/v1/agent/sessions/session-1/runs`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: "hello" }), }, ); expect(await response.json()).toEqual({ method: "POST", path: "/sessions/session-1/runs", body: { message: "hello" }, }); }); test("serves DELETE current run without rewriting the HTTP method", async () => { const response = await fetch( `${baseUrl}/api/v1/agent/sessions/session-2/runs/current`, { method: "DELETE" }, ); expect(await response.json()).toEqual({ method: "DELETE", path: "/sessions/session-2/runs/current", body: {}, }); }); test("wraps authentication middleware failures as Problem Details", async () => { const response = await fetch(`${baseUrl}/api/v1/agent/auth-failure`); const body = await response.json(); expect(response.status).toBe(503); expect(response.headers.get("content-type")).toContain( "application/problem+json", ); expect(body).toMatchObject({ status: 503, code: "dependency_unavailable", detail: "authentication service unavailable", }); }); });