/** * Agent Chat Tests * * Tests for /api/chat and /api/embed-chat routes: * - Input validation (missing messages) * - Missing HF token handling * - Model list endpoint */ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import request from "supertest"; import { mkdtempSync, rmSync } from "fs"; import { mkdirSync } from "fs"; import { tmpdir } from "os"; import { join } from "path"; import { setDataDir } from "../src/utils.js"; import { createApp, resetSaveTimers } from "../src/create-app.js"; let tmpDir: string; let app: ReturnType["app"]; let httpServer: ReturnType["httpServer"]; let hocuspocus: ReturnType["hocuspocus"]; beforeEach(() => { tmpDir = mkdtempSync(join(tmpdir(), "collab-agent-test-")); mkdirSync(tmpDir, { recursive: true }); setDataDir(tmpDir); const result = createApp(); app = result.app; httpServer = result.httpServer; hocuspocus = result.hocuspocus; }); afterEach(async () => { resetSaveTimers(); try { await hocuspocus.destroy(); } catch {} try { httpServer.close(); } catch {} setDataDir(undefined); try { rmSync(tmpDir, { recursive: true, force: true }); } catch {} vi.restoreAllMocks(); }); describe("/api/models", () => { it("returns a non-empty array of models", async () => { const res = await request(app).get("/api/models").expect(200); expect(Array.isArray(res.body)).toBe(true); expect(res.body.length).toBeGreaterThan(0); expect(res.body[0]).toHaveProperty("id"); expect(res.body[0]).toHaveProperty("label"); expect(res.body[0]).toHaveProperty("context"); expect(res.body[0]).toHaveProperty("cost"); }); }); describe("/api/chat - validation", () => { it("rejects request without messages", async () => { const res = await request(app) .post("/api/chat") .send({ context: {} }) .expect(400); expect(res.body).toHaveProperty("error"); expect(res.body.error).toContain("messages"); }); it("rejects request with non-array messages", async () => { const res = await request(app) .post("/api/chat") .send({ messages: "not-an-array" }) .expect(400); expect(res.body).toHaveProperty("error"); }); it("returns 500 when no HF token is available", async () => { const original = process.env.HF_TOKEN; delete process.env.HF_TOKEN; const res = await request(app) .post("/api/chat") .send({ messages: [{ id: "1", role: "user", parts: [{ type: "text", text: "hello" }] }], }) .expect(500); expect(res.body).toHaveProperty("error"); expect(res.body.error).toContain("Hugging Face token"); if (original) process.env.HF_TOKEN = original; }); }); describe("/api/embed-chat - validation", () => { it("rejects request without messages", async () => { const res = await request(app) .post("/api/embed-chat") .send({ context: {} }) .expect(400); expect(res.body).toHaveProperty("error"); expect(res.body.error).toContain("messages"); }); it("rejects request with non-array messages", async () => { const res = await request(app) .post("/api/embed-chat") .send({ messages: 42 }) .expect(400); expect(res.body).toHaveProperty("error"); }); it("returns 500 when no HF token is available", async () => { const original = process.env.HF_TOKEN; delete process.env.HF_TOKEN; const res = await request(app) .post("/api/embed-chat") .send({ messages: [{ id: "1", role: "user", parts: [{ type: "text", text: "make a chart" }] }], }) .expect(500); expect(res.body).toHaveProperty("error"); expect(res.body.error).toContain("Hugging Face token"); if (original) process.env.HF_TOKEN = original; }); });