import { Hono } from "hono"; import { describe, expect, it, vi } from "vitest"; // Mock @vercel/sandbox 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: "Here is what happened on Clawbook today.", actions: [], }), stderr: async () => "", }; } return { exitCode: 0, stdout: async () => "", stderr: async () => "", }; }), writeFiles: vi.fn().mockResolvedValue(undefined), stop: vi.fn().mockResolvedValue(undefined), }), }, })); // Import handler directly and wire to a test app (bypassing auth middleware) import { handleChatCompletions } from "../../src/handlers/openai-compat"; function createTestApp() { const app = new Hono(); app.post("/v1/chat/completions", handleChatCompletions); return app; } describe("POST /v1/chat/completions", () => { const app = createTestApp(); it("returns 400 for missing messages", async () => { const res = await app.request("/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({}), }); expect(res.status).toBe(400); const body = await res.json(); expect(body.error).toContain("messages"); }); it("returns 400 for empty messages array", async () => { const res = await app.request("/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ messages: [] }), }); expect(res.status).toBe(400); }); it("returns 400 when no user message in array", async () => { const res = await app.request("/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ messages: [{ role: "system", content: "You are helpful" }], }), }); expect(res.status).toBe(400); const body = await res.json(); expect(body.error).toContain("user message"); }); it("returns non-streaming chat completion format", async () => { const res = await app.request("/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ messages: [{ role: "user", content: "What's on Clawbook?" }], }), }); expect(res.status).toBe(200); const body = await res.json(); expect(body.object).toBe("chat.completion"); expect(body.model).toBe("clarkling"); expect(body.choices).toHaveLength(1); expect(body.choices[0].message.role).toBe("assistant"); expect(body.choices[0].message.content).toContain("Clawbook"); expect(body.choices[0].finish_reason).toBe("stop"); expect(body.usage).toBeDefined(); }); it("extracts last user message from messages array", async () => { const res = await app.request("/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ messages: [ { role: "system", content: "You are helpful" }, { role: "user", content: "First question" }, { role: "assistant", content: "First answer" }, { role: "user", content: "Second question" }, ], }), }); expect(res.status).toBe(200); }); it("returns streaming SSE format", async () => { const res = await app.request("/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ messages: [{ role: "user", content: "Hello" }], stream: true, }), }); expect(res.status).toBe(200); const text = await res.text(); expect(text).toContain("data: "); expect(text).toContain("[DONE]"); expect(text).toContain("chat.completion.chunk"); }); });