feat(api): expose REST-only agent routes
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
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<string>();
|
||||
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"]),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user