Files
agent/apps/server/tests/api.test.ts
T

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");
});
});