updated openapi

This commit is contained in:
2026-03-03 14:49:12 +00:00
parent 4328ada595
commit 5e3726de39
6 changed files with 937 additions and 740 deletions

View File

@@ -4,6 +4,7 @@ import { join } from "node:path";
import { tmpdir } from "node:os";
import { reseedDatabase } from "../src/database";
import { createServer } from "../src/index";
import { openApiDocument } from "../src/openapi";
let tempDir: string | null = null;
@@ -101,6 +102,53 @@ describe("mock task API", () => {
expect((body.task as Record<string, unknown>).assigneeUserId).toBeNull();
});
it("returns 400 for invalid create payloads", async () => {
const server = createTestServer();
const response = await server.fetch(
new Request("http://mock.local/tasks", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
orgId: "org_acme",
projectId: "proj_acme_ops",
}),
}),
);
const body = await readJson(response);
expect(response.status).toBe(400);
expect(body.error).toBe("title is required.");
});
it("returns 404 when updating a missing task", async () => {
const server = createTestServer();
const response = await server.fetch(
new Request("http://mock.local/tasks/task_missing", {
method: "PATCH",
headers: { "content-type": "application/json" },
body: JSON.stringify({
status: "done",
}),
}),
);
const body = await readJson(response);
expect(response.status).toBe(404);
expect(body.error).toBe("Task not found.");
});
it("returns 404 for unknown routes", async () => {
const server = createTestServer();
const response = await server.fetch(new Request("http://mock.local/unknown"));
const body = await readJson(response);
expect(response.status).toBe(404);
expect(body.error).toBe("Route not found.");
});
it("returns a valid openapi document at the import path", async () => {
const server = createTestServer();
@@ -109,11 +157,34 @@ describe("mock task API", () => {
const paths = body.paths as Record<string, unknown>;
expect(response.status).toBe(200);
expect(body.openapi).toBe("3.1.0");
expect(body.openapi).toBe("3.0.3");
expect(paths["/tasks"]).toBeDefined();
expect(paths["/orgs/{orgId}/tasks"]).toBeDefined();
});
it("advertises every implemented route in the openapi document", () => {
const operations = Object.entries(openApiDocument.paths).flatMap(([path, pathItem]) =>
Object.keys(pathItem).map((method) => `${method.toUpperCase()} ${path}`),
);
expect(operations).toEqual([
"GET /health",
"GET /orgs",
"GET /orgs/{orgId}",
"GET /orgs/{orgId}/projects",
"GET /orgs/{orgId}/tasks",
"GET /users",
"GET /projects",
"GET /tasks",
"POST /tasks",
"GET /tasks/{taskId}",
"PATCH /tasks/{taskId}",
"GET /openapi.json",
"GET /swagger.json",
"GET /api-docs",
]);
});
it("supports the swagger alias path used by import tools", async () => {
const server = createTestServer();
@@ -121,4 +192,20 @@ describe("mock task API", () => {
expect(response.status).toBe(200);
});
it("serves the same generated document on all openapi aliases", async () => {
const server = createTestServer();
const openApiResponse = await server.fetch(new Request("http://mock.local/openapi.json"));
const swaggerResponse = await server.fetch(new Request("http://mock.local/swagger.json"));
const apiDocsResponse = await server.fetch(new Request("http://mock.local/api-docs"));
const openApiBody = await readJson(openApiResponse);
const swaggerBody = await readJson(swaggerResponse);
const apiDocsBody = await readJson(apiDocsResponse);
expect(openApiBody).toEqual(swaggerBody);
expect(swaggerBody).toEqual(apiDocsBody);
expect(openApiBody).toEqual(openApiDocument);
});
});

41
tests/routes.test.ts Normal file
View File

@@ -0,0 +1,41 @@
import { describe, expect, it } from "bun:test";
import { openApiDocument } from "../src/openapi";
import { matchRoute, routes } from "../src/routes";
describe("route registry", () => {
it("matches concrete paths and extracts route params", () => {
const matched = matchRoute("GET", "/orgs/org_acme/tasks");
expect(matched).not.toBeNull();
expect(matched?.route.operationId).toBe("listOrganizationTasks");
expect(matched?.params).toEqual({ orgId: "org_acme" });
});
it("returns null for unsupported methods and unknown paths", () => {
expect(matchRoute("DELETE", "/tasks/task_1001")).toBeNull();
expect(matchRoute("GET", "/does-not-exist")).toBeNull();
});
it("builds an openapi path entry for every registered route", () => {
const registeredOperations = routes.map(
(route) => `${route.method} ${route.path}:${route.operationId}`,
);
const documentedOperations = Object.entries(openApiDocument.paths).flatMap(
([path, pathItem]) =>
Object.entries(pathItem).map(
([method, operation]) =>
`${method.toUpperCase()} ${path}:${(operation as { operationId?: string }).operationId ?? ""}`,
),
);
expect(documentedOperations).toEqual(registeredOperations);
});
it("keeps spec-serving routes explicit in the registry", () => {
const openApiAliases = routes
.filter((route) => route.servesOpenApiDocument)
.map((route) => route.path);
expect(openApiAliases).toEqual(["/openapi.json", "/swagger.json", "/api-docs"]);
});
});