import { afterEach, describe, expect, it } from "vitest"; import { createTestApp, makeSessionBusy } from "./test-app.js"; const apps: Array["app"]> = []; afterEach(async () => Promise.all(apps.splice(0).map((app) => app.close()))); describe("HTTP API", () => { it("serves health, skills, and creates a session with request IDs", async () => { const { app } = createTestApp(); apps.push(app); const health = await app.inject({ method: "GET", url: "/health" }); const skills = await app.inject({ method: "GET", url: "/api/skills" }); const created = await app.inject({ method: "POST", url: "/api/sessions" }); const healthBody = health.json<{ status: string; requestId: string }>(); const skillsBody = skills.json<{ skills: Array<{ name: string }>; requestId: string }>(); const createdBody = created.json<{ sessionId: string; requestId: string }>(); expect(health.statusCode).toBe(200); expect(healthBody.status).toBe("ok"); expect(healthBody.requestId).toMatch(/^req_/); expect(skillsBody.skills[0]?.name).toBe("ads-analysis"); expect(skillsBody.requestId).toMatch(/^req_/); expect(created.statusCode).toBe(201); expect(createdBody.sessionId).toBe("ses_1"); expect(createdBody.requestId).toMatch(/^req_/); }); it("accepts a run without waiting for completion", async () => { const { app, session } = createTestApp(); apps.push(app); const response = await app.inject({ method: "POST", url: "/api/sessions/ses_1/messages", payload: { text: "hello", attachmentIds: [] } }); expect(response.statusCode).toBe(202); const body = response.json<{ runId: string; requestId: string }>(); expect(body.runId).toBe("run_1"); expect(body.requestId).toMatch(/^req_/); expect(session.startRun).toHaveBeenCalledWith({ text: "hello", attachmentIds: [] }); }); it("returns safe validation, not-found, and busy errors", async () => { const { app, session } = createTestApp(); apps.push(app); const invalid = await app.inject({ method: "POST", url: "/api/sessions/ses_1/messages", payload: { text: "", attachmentIds: [] } }); const missing = await app.inject({ method: "POST", url: "/api/sessions/ses_missing/cancel" }); makeSessionBusy(session); const busy = await app.inject({ method: "POST", url: "/api/sessions/ses_1/messages", payload: { text: "again" } }); expect(invalid.statusCode).toBe(400); expect(invalid.json<{ error: { code: string; requestId: string } }>().error).toMatchObject({ code: "VALIDATION_ERROR" }); expect(invalid.json<{ error: { requestId: string } }>().error.requestId).toMatch(/^req_/); expect(missing.statusCode).toBe(404); expect(missing.json()).toMatchObject({ error: { code: "SESSION_NOT_FOUND" } }); expect(busy.statusCode).toBe(409); expect(busy.body).not.toContain("stack"); }); it("keeps Fastify body parse errors as safe client errors", async () => { const { app } = createTestApp(); apps.push(app); const response = await app.inject({ method: "POST", url: "/api/sessions", headers: { "content-type": "application/json" }, payload: "", }); expect(response.statusCode).toBe(400); expect(response.json()).toMatchObject({ error: { code: "INVALID_REQUEST_BODY", message: "Request body could not be processed" }, }); expect(response.body).not.toContain("stack"); }); });