import { createEmptyChatState, deleteChatSession, listChatSessions, loadChatSessionById, updateChatSessionTitle, } from "./chatStorage"; const apiFetch = jest.fn(); jest.mock("@/lib/apiFetch", () => ({ apiFetch: (...args: unknown[]) => apiFetch(...args), })); describe("chatStorage backend session operations", () => { beforeEach(() => { apiFetch.mockReset(); }); it("creates an empty initial conversation state without backend calls", () => { const loaded = createEmptyChatState(); expect(loaded).toMatchObject({ title: undefined, messages: [], sessionId: undefined, }); expect(apiFetch).not.toHaveBeenCalled(); }); it("lists backend sessions sorted by created time", async () => { apiFetch.mockResolvedValueOnce({ ok: true, json: async () => ({ sessions: [ { id: "session-old", title: "旧会话", created_at: "2026-01-01T00:00:00.000Z", updated_at: "2026-01-02T00:00:00.000Z", }, { id: "session-new", title: "新会话", created_at: "2026-01-03T00:00:00.000Z", updated_at: "2026-01-03T00:00:00.000Z", is_streaming: true, run_status: "running", }, ], }), }); await expect(listChatSessions()).resolves.toEqual([ expect.objectContaining({ id: "session-new", title: "新会话", isStreaming: true, runStatus: "running", }), expect.objectContaining({ id: "session-old", title: "旧会话", }), ]); expect(apiFetch.mock.calls[0][1]).toMatchObject({ method: "GET" }); }); it("loads a backend session state", async () => { apiFetch.mockResolvedValueOnce({ ok: true, json: async () => ({ id: "session-1", title: "管网分析", is_title_manually_edited: true, messages: [{ id: "message-1", role: "user", content: "查压力" }], is_streaming: false, }), }); await expect(loadChatSessionById("session-1")).resolves.toMatchObject({ title: "管网分析", isTitleManuallyEdited: true, sessionId: "session-1", messages: [{ id: "message-1", role: "user", content: "查压力" }], }); expect(String(apiFetch.mock.calls[0][0])).toContain("/sessions/session-1"); expect(apiFetch.mock.calls[0][1]).toMatchObject({ method: "GET" }); }); it("updates a backend session title through the title endpoint", async () => { apiFetch.mockResolvedValueOnce({ ok: true, text: async () => "", }); await updateChatSessionTitle("session-1", " 新标题 ", { isTitleManuallyEdited: true, }); expect(String(apiFetch.mock.calls[0][0])).toContain("/sessions/session-1"); expect(apiFetch.mock.calls[0][1]).toMatchObject({ method: "PATCH" }); expect(JSON.parse(String(apiFetch.mock.calls[0][1]?.body))).toEqual({ title: "新标题", is_title_manually_edited: true, }); }); it("deletes a backend session and returns the next active session id", async () => { apiFetch .mockResolvedValueOnce({ ok: true, text: async () => "", }) .mockResolvedValueOnce({ ok: true, json: async () => ({ sessions: [ { id: "session-next", title: "下一会话", created_at: "2026-01-01T00:00:00.000Z", updated_at: "2026-01-01T00:00:00.000Z", }, ], }), }); await expect(deleteChatSession("session-1")).resolves.toBe("session-next"); expect(apiFetch.mock.calls[0][1]).toMatchObject({ method: "DELETE" }); expect(apiFetch.mock.calls[1][1]).toMatchObject({ method: "GET" }); }); });