import { describe, expect, it } from "bun:test"; import { createSession, deleteSession, listSessions, sendMessage } from "./assistant-client.ts"; // ── Helper ───────────────────────────────────────────────────────────── function withMockFetch(mock: typeof fetch, fn: () => Promise): Promise { const original = globalThis.fetch; globalThis.fetch = mock; return fn().finally(() => { globalThis.fetch = original; }); } // ── createSession tests ──────────────────────────────────────────────── describe("createSession", () => { it("returns session id on success", async () => { const result = await withMockFetch( (async () => new Response(JSON.stringify({ id: "sess_42" }), { status: 200 })) as typeof fetch, () => createSession({ baseUrl: "http://assistant" }, "title"), ); expect(result).toBe("sess_42"); }); it("throws on non-200 response", async () => { await withMockFetch( (async () => new Response("bad", { status: 500 })) as typeof fetch, async () => { await expect(createSession({ baseUrl: "http://assistant" }, "t")).rejects.toThrow("session creation failed"); }, ); }); it("throws on invalid session id", async () => { await withMockFetch( (async () => new Response(JSON.stringify({ id: "../bad" }), { status: 200 })) as typeof fetch, async () => { await expect(createSession({ baseUrl: "http://assistant" }, "t")).rejects.toThrow("Invalid session ID"); }, ); }); }); describe("listSessions", () => { it("returns valid session summaries", async () => { const result = await withMockFetch( (async () => new Response(JSON.stringify([ { id: "sess_1", title: "discord/thread-1" }, { id: "bad/id", title: "ignored" }, { id: "sess_2", title: 123 }, ]), { status: 200 })) as typeof fetch, () => listSessions({ baseUrl: "http://assistant" }), ); expect(result).toEqual([{ id: "sess_1", title: "discord/thread-1" }]); }); it("throws on invalid response body", async () => { await withMockFetch( (async () => new Response(JSON.stringify({ nope: true }), { status: 200 })) as typeof fetch, async () => { await expect(listSessions({ baseUrl: "http://assistant" })).rejects.toThrow("Invalid session list"); }, ); }); }); describe("deleteSession", () => { it("returns true on success", async () => { const result = await withMockFetch( (async () => new Response("true", { status: 200 })) as typeof fetch, () => deleteSession({ baseUrl: "http://assistant" }, "sess_1"), ); expect(result).toBe(true); }); it("returns false on 404", async () => { const result = await withMockFetch( (async () => new Response("missing", { status: 404 })) as typeof fetch, () => deleteSession({ baseUrl: "http://assistant" }, "sess_1"), ); expect(result).toBe(false); }); it("throws on invalid session id", async () => { await expect( deleteSession({ baseUrl: "http://assistant" }, "../escape"), ).rejects.toThrow("Invalid session ID"); }); }); // ── sendMessage tests ────────────────────────────────────────────────── describe("sendMessage", () => { it("returns joined text parts", async () => { const result = await withMockFetch( (async () => new Response(JSON.stringify({ parts: [{ type: "text", text: "a" }, { type: "text", text: "b" }], }), { status: 200 })) as typeof fetch, () => sendMessage({ baseUrl: "http://assistant" }, "sess1", "prompt"), ); expect(result).toBe("a\nb"); }); it("returns (no response) when parts are empty", async () => { const result = await withMockFetch( (async () => new Response(JSON.stringify({ parts: [] }), { status: 200 })) as typeof fetch, () => sendMessage({ baseUrl: "http://assistant" }, "sess1", "prompt"), ); expect(result).toBe("(no response)"); }); it("throws on non-200 response", async () => { await withMockFetch( (async () => new Response("err", { status: 502 })) as typeof fetch, async () => { await expect(sendMessage({ baseUrl: "http://assistant" }, "sess1", "p")).rejects.toThrow("message failed"); }, ); }); });