import type { Server } from "node:http"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { createCommandBoardServer } from "./index.js"; let server: Server; let baseUrl: string; beforeAll(async () => { server = createCommandBoardServer(); await new Promise((resolve) => server.listen(0, resolve)); const address = server.address(); if (!address || typeof address === "string") { throw new Error("Expected API server to bind to a local port"); } baseUrl = `http://127.0.0.1:${address.port}`; }); afterAll(async () => { await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); }); describe("CommandBoard API contracts", () => { it("exposes health contract", async () => { const response = await fetch(`${baseUrl}/health`); const body = await response.json() as { ok: boolean; service: string }; expect(response.status).toBe(200); expect(body).toEqual({ ok: true, service: "commandboard-api" }); }); it("exposes default plugin contract including sh1pt", async () => { const response = await fetch(`${baseUrl}/api/plugins`); const body = await response.json() as { plugins: Array<{ id: string; enabled: boolean; capabilities: string[] }>; capabilities: Record; }; expect(response.status).toBe(200); expect(body.plugins.map((plugin) => plugin.id)).toEqual(["coinpay", "ugig", "sh1pt"]); expect(body.plugins.find((plugin) => plugin.id === "sh1pt")).toMatchObject({ enabled: true, capabilities: expect.arrayContaining(["projects.sync", "actions.publish", "deployments.status"]) }); expect(body.capabilities["actions.publish"]).toEqual(["sh1pt"]); }); it("exposes sh1pt project and action contracts", async () => { const projectsResponse = await fetch(`${baseUrl}/api/plugins/sh1pt/projects`); const projectsBody = await projectsResponse.json() as { projects: Array<{ id: string; board: string; status: string; actions: number }> }; const actionsResponse = await fetch(`${baseUrl}/api/plugins/sh1pt/actions`); const actionsBody = await actionsResponse.json() as { actions: Array<{ id: string; title: string; publishable: boolean }> }; expect(projectsResponse.status).toBe(200); expect(projectsBody.projects[0]).toMatchObject({ id: "sh1pt_project_1", board: "/projects/sh1pt", status: "active" }); expect(actionsResponse.status).toBe(200); expect(actionsBody.actions[0]).toMatchObject({ id: "action_release_checklist", publishable: true }); }); it("accepts sh1pt action publish requests", async () => { const response = await fetch(`${baseUrl}/api/plugins/sh1pt/actions/publish`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action_id: "action_release_checklist" }) }); const body = await response.json() as { accepted: boolean; action_id: string; board: string }; expect(response.status).toBe(202); expect(body).toEqual({ accepted: true, action_id: "action_release_checklist", board: "/projects/sh1pt" }); }); it("rejects invalid LogicSRC task payloads", async () => { const response = await fetch(`${baseUrl}/api/tasks`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ type: "logicsrc.task", title: "Incomplete" }) }); const body = await response.json() as { errors: unknown[] }; expect(response.status).toBe(422); expect(Array.isArray(body.errors)).toBe(true); }); });