import { Hono } from "hono"; import { describe, expect, it, vi } from "vitest"; // Mock @vercel/sandbox so agent turns don't create real sandboxes vi.mock("@vercel/sandbox", () => ({ Sandbox: { create: vi.fn().mockResolvedValue({ runCommand: vi.fn().mockImplementation(({ cmd, args }) => { if (cmd === "node" && args?.[0] === "agent.mjs") { return { exitCode: 0, stdout: async () => JSON.stringify({ success: true, summary: "Test", actions: [], }), stderr: async () => "", }; } return { exitCode: 0, stdout: async () => "", stderr: async () => "", }; }), writeFiles: vi.fn().mockResolvedValue(undefined), stop: vi.fn().mockResolvedValue(undefined), }), }, })); import app from "../src/index"; describe("API", () => { it("GET / returns status", async () => { const res = await app.request("/"); expect(res.status).toBe(200); const body = await res.json(); expect(body.name).toBe("clawbook-bot"); expect(body.status).toBe("ok"); }); it("POST /api/cron/heartbeat returns 200 (no CRON_SECRET = dev mode)", async () => { const res = await app.request("/api/cron/heartbeat", { method: "POST" }); expect(res.status).toBe(200); }); it("POST /api/cron/post returns 200 (no CRON_SECRET = dev mode)", async () => { const res = await app.request("/api/cron/post", { method: "POST" }); expect(res.status).toBe(200); }); it("POST /api/agent requires Sigma Auth", async () => { const res = await app.request("/api/agent", { method: "POST" }); expect(res.status).toBe(401); }); it("POST /api/hooks/wake requires Sigma Auth", async () => { const res = await app.request("/api/hooks/wake", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: "hello" }), }); expect(res.status).toBe(401); }); it("POST /api/hooks/agent requires Sigma Auth", async () => { const res = await app.request("/api/hooks/agent", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: "hello" }), }); expect(res.status).toBe(401); }); it("POST /v1/chat/completions requires Sigma Auth", async () => { const res = await app.request("/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ messages: [{ role: "user", content: "hello" }], }), }); expect(res.status).toBe(401); }); it("GET /api/runs/:runId returns 404 for unknown run", async () => { const res = await app.request("/api/runs/nonexistent-id"); expect(res.status).toBe(404); }); it("global error handler catches unhandled throws and returns 500 JSON", async () => { // Use a fresh Hono app to test onError pattern (can't add routes after router is built) const testApp = new Hono(); testApp.onError((_err, c) => { return c.json({ error: "Internal server error" }, 500); }); testApp.get("/boom", () => { throw new Error("unhandled boom"); }); const res = await testApp.request("/boom"); expect(res.status).toBe(500); const body = await res.json(); expect(body.error).toBe("Internal server error"); }); });