import { afterEach, describe, expect, it, vi } from "vitest"; import { createAgentApiClient } from "./client"; describe("Agent API client sessions", () => { afterEach(() => { vi.unstubAllGlobals(); }); it("lists normalized sessions in newest-first order", async () => { vi.stubGlobal( "fetch", vi.fn(async () => new Response( JSON.stringify({ sessions: [ { id: "old", title: "", created_at: 100, updated_at: 120 }, { id: "new", title: " 新会话 ", created_at: 200, updated_at: 210, run_status: "running" } ] }), { status: 200 } ) ) ); const sessions = await createAgentApiClient("http://agent.local").listSessions(); expect(sessions).toEqual([ { id: "new", title: "新会话", createdAt: 200, updatedAt: 210, status: undefined, parentSessionId: undefined, isStreaming: undefined, runStatus: "running" }, { id: "old", title: "新对话", createdAt: 100, updatedAt: 120, status: undefined, parentSessionId: undefined, isStreaming: undefined, runStatus: undefined } ]); }); it("returns null when loading a missing session", async () => { vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({ message: "missing" }), { status: 404 }))); await expect(createAgentApiClient("http://agent.local").loadSession("missing")).resolves.toBeNull(); }); it("falls back when a candidate is not the agent service", async () => { const fetchMock = vi .fn() .mockResolvedValueOnce(new Response(JSON.stringify({ message: "not found" }), { status: 404 })) .mockResolvedValueOnce( new Response( JSON.stringify({ schema_version: "agent-ui-registry@1", chart_grammars: [], components: [], actions: [] }), { status: 200 } ) ); vi.stubGlobal("fetch", fetchMock); await expect(createAgentApiClient(["http://not-agent.local", "http://agent.local"]).getUiRegistry()).resolves.toEqual({ schema_version: "agent-ui-registry@1", chart_grammars: [], components: [], actions: [] }); expect(fetchMock).toHaveBeenNthCalledWith( 1, "http://not-agent.local/api/v1/agent/chat/ui-registry", undefined ); expect(fetchMock).toHaveBeenNthCalledWith(2, "http://agent.local/api/v1/agent/chat/ui-registry", undefined); }); it("sends title update payloads using backend field names", async () => { const fetchMock = vi.fn(async () => new Response(JSON.stringify({ ok: true }), { status: 200 })); vi.stubGlobal("fetch", fetchMock); await createAgentApiClient("http://agent.local").updateSessionTitle("session-1", " 标题 ", { isTitleManuallyEdited: true }); expect(fetchMock).toHaveBeenCalledWith( "http://agent.local/api/v1/agent/chat/session/session-1/title", expect.objectContaining({ method: "PATCH", body: JSON.stringify({ title: "标题", is_title_manually_edited: true }) }) ); }); it("streams session events from the backend SSE endpoint", async () => { vi.stubGlobal( "fetch", vi.fn(async () => new Response( [ 'event: state\ndata: {"session_id":"session-1","messages":[{"id":"u1","role":"user","parts":[{"type":"text","text":"检查压力"}]}],"is_streaming":true,"run_status":"running"}\n\n', 'event: token\ndata: {"session_id":"session-1","content":"处理中"}\n\n', 'event: done\ndata: {"session_id":"session-1","duration_ms":20}\n\n' ].join(""), { status: 200 } ) ) ); const events: unknown[] = []; await createAgentApiClient("http://agent.local").streamSession("session-1", { onEvent: (event) => events.push(event) }); expect(events).toEqual([ { type: "state", sessionId: "session-1", messages: [{ id: "u1", role: "user", parts: [{ type: "text", text: "检查压力" }] }], isStreaming: true, runStatus: "running" }, { type: "token", sessionId: "session-1", data: { session_id: "session-1", content: "处理中" } }, { type: "done", sessionId: "session-1", data: { session_id: "session-1", duration_ms: 20 } } ]); }); });