import { describe, expect, test } from "bun:test"; import { OpenAPIRegistry } from "@asteasolutions/zod-to-openapi"; import { createRouteRegistrar, generateAgentOpenApi, } from "../../src/contracts/openapi.js"; const KEBAB_SEGMENT = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; describe("Agent REST OpenAPI", () => { test("rejects duplicate method and path registrations", () => { const register = createRouteRegistrar(new OpenAPIRegistry()); const config = { summary: "Test operation", responses: { 200: { description: "Successful response" } }, }; register("/api/v1/agent/test", "get", config); expect(() => register("/api/v1/agent/test", "get", config)).toThrow( "Duplicate Agent OpenAPI operation: GET /api/v1/agent/test", ); }); test("publishes only unique kebab-case public operations", () => { const document = generateAgentOpenApi(); const operationIds = new Set(); let operationCount = 0; for (const [path, pathItem] of Object.entries(document.paths)) { expect(path.endsWith("/")).toBe(false); for (const segment of path.split("/").filter(Boolean)) { if (!segment.startsWith("{")) { expect(segment).toMatch(KEBAB_SEGMENT); } } for (const operation of Object.values(pathItem ?? {})) { if (!operation || typeof operation !== "object" || !("responses" in operation)) { continue; } operationCount += 1; expect(operation.operationId).toBeTruthy(); expect(operationIds.has(operation.operationId!)).toBe(false); operationIds.add(operation.operationId!); expect(operation.security).toEqual([{ bearerAuth: [] }]); } } expect(operationCount).toBe(13); }); test("models runs as session subresources", () => { const document = generateAgentOpenApi(); expect(document.paths["/api/v1/agent/sessions/{session_id}/runs"]?.post).toBeTruthy(); expect( document.paths[ "/api/v1/agent/sessions/{session_id}/runs/current/events" ]?.get, ).toBeTruthy(); expect(document.paths["/api/v1/agent/chat/stream"]).toBeUndefined(); }); test("matches the public session runtime response shapes", () => { const document = generateAgentOpenApi(); const schemas = document.components?.schemas ?? {}; const createResponses = document.paths["/api/v1/agent/sessions"]?.post?.responses ?? {}; const patchOperation = document.paths["/api/v1/agent/sessions/{session_id}"]?.patch; const patchRequestBody = patchOperation?.requestBody; const forkResponses = document.paths["/api/v1/agent/sessions/{session_id}/forks"]?.post ?.responses ?? {}; expect(Object.keys(createResponses)).toContain("200"); expect(Object.keys(createResponses)).toContain("201"); expect(schemas.AgentSessionSummary).toMatchObject({ required: expect.arrayContaining(["id", "created_at", "updated_at", "status"]), }); expect(schemas.AgentSessionSummary).not.toMatchObject({ required: expect.arrayContaining(["session_id"]), }); expect( patchRequestBody && !("$ref" in patchRequestBody) ? patchRequestBody.content["application/json"]?.schema : undefined, ).toMatchObject({ properties: { is_title_manually_edited: { type: "boolean" }, }, }); expect(patchOperation?.responses["200"]).toMatchObject({ content: { "application/json": { schema: { $ref: "#/components/schemas/AgentSessionUpdate" }, }, }, }); expect(forkResponses["200"]).toMatchObject({ content: { "application/json": { schema: { $ref: "#/components/schemas/AgentSessionFork" }, }, }, }); expect(forkResponses["201"]).toBeUndefined(); }); test("documents runtime query parameters and error statuses", () => { const document = generateAgentOpenApi(); const renderOperation = document.paths["/api/v1/agent/render-references/{render_ref}"]?.get; expect(renderOperation?.parameters).toEqual( expect.arrayContaining([ expect.objectContaining({ in: "query", name: "session_id", required: false, }), ]), ); expect(Object.keys(renderOperation?.responses ?? {})).toEqual( expect.arrayContaining(["400", "500", "502"]), ); }); });