212 lines
6.5 KiB
TypeScript
212 lines
6.5 KiB
TypeScript
import { afterEach, describe, expect, it } from "bun:test";
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
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;
|
|
|
|
function createTestServer() {
|
|
tempDir = mkdtempSync(join(tmpdir(), "mock-task-api-http-"));
|
|
const db = reseedDatabase(join(tempDir, "test.sqlite"));
|
|
return createServer(db);
|
|
}
|
|
|
|
async function readJson(response: Response) {
|
|
return (await response.json()) as Record<string, unknown>;
|
|
}
|
|
|
|
afterEach(() => {
|
|
if (tempDir) {
|
|
rmSync(tempDir, { recursive: true, force: true });
|
|
tempDir = null;
|
|
}
|
|
});
|
|
|
|
describe("mock task API", () => {
|
|
it("serves a health endpoint", async () => {
|
|
const server = createTestServer();
|
|
|
|
const response = await server.fetch(new Request("http://mock.local/health"));
|
|
const body = await readJson(response);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(body.status).toBe("ok");
|
|
});
|
|
|
|
it("lists organizations", async () => {
|
|
const server = createTestServer();
|
|
|
|
const response = await server.fetch(new Request("http://mock.local/orgs"));
|
|
const body = await readJson(response);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(Array.isArray(body.organizations)).toBe(true);
|
|
expect((body.organizations as unknown[]).length).toBe(3);
|
|
});
|
|
|
|
it("filters tasks by org and status", async () => {
|
|
const server = createTestServer();
|
|
|
|
const response = await server.fetch(
|
|
new Request("http://mock.local/tasks?orgId=org_acme&status=blocked"),
|
|
);
|
|
const body = await readJson(response);
|
|
const tasks = body.tasks as Array<Record<string, unknown>>;
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(tasks.length).toBe(1);
|
|
expect(tasks[0]?.id).toBe("task_1003");
|
|
});
|
|
|
|
it("creates a task through the HTTP contract", 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",
|
|
title: "Run API smoke test",
|
|
priority: "low",
|
|
}),
|
|
}),
|
|
);
|
|
const body = await readJson(response);
|
|
|
|
expect(response.status).toBe(201);
|
|
expect((body.task as Record<string, unknown>).title).toBe("Run API smoke test");
|
|
});
|
|
|
|
it("updates a task through the HTTP contract", async () => {
|
|
const server = createTestServer();
|
|
|
|
const response = await server.fetch(
|
|
new Request("http://mock.local/tasks/task_1001", {
|
|
method: "PATCH",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
status: "done",
|
|
assigneeUserId: null,
|
|
}),
|
|
}),
|
|
);
|
|
const body = await readJson(response);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect((body.task as Record<string, unknown>).status).toBe("done");
|
|
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();
|
|
|
|
const response = await server.fetch(new Request("http://mock.local/openapi.json"));
|
|
const body = await readJson(response);
|
|
const paths = body.paths as Record<string, unknown>;
|
|
|
|
expect(response.status).toBe(200);
|
|
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();
|
|
|
|
const response = await server.fetch(new Request("http://mock.local/swagger.json"));
|
|
|
|
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);
|
|
});
|
|
});
|