Files
next-tjwater-drainage-frontend/features/agent/api/client.test.ts
T

149 lines
4.5 KiB
TypeScript

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 }
}
]);
});
});