74 lines
3.4 KiB
TypeScript
74 lines
3.4 KiB
TypeScript
import { afterEach, describe, expect, it } from "vitest";
|
|
import { createTestApp, makeSessionBusy } from "./test-app.js";
|
|
|
|
const apps: Array<ReturnType<typeof createTestApp>["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");
|
|
});
|
|
});
|