nanobot/webui/src/tests/api.test.ts
Xubin Ren 56eee06736 feat(webui): add BYOK web search settings
Let WebUI users configure the single web search provider credential from BYOK while keeping saved secrets masked and hot-reloaded for new searches.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 14:52:48 +08:00

158 lines
4.2 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import {
deleteSession,
fetchSessionMessages,
listSessions,
listSlashCommands,
updateProviderSettings,
updateSettings,
updateWebSearchSettings,
} from "@/lib/api";
describe("webui API helpers", () => {
beforeEach(() => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ deleted: true, key: "websocket:chat-1", messages: [] }),
}),
);
});
it("percent-encodes websocket keys when fetching session history", async () => {
await fetchSessionMessages("tok", "websocket:chat-1");
expect(fetch).toHaveBeenCalledWith(
"/api/sessions/websocket%3Achat-1/messages",
expect.objectContaining({
headers: { Authorization: "Bearer tok" },
}),
);
});
it("percent-encodes websocket keys when deleting a session", async () => {
await deleteSession("tok", "websocket:chat-1");
expect(fetch).toHaveBeenCalledWith(
"/api/sessions/websocket%3Achat-1/delete",
expect.objectContaining({
headers: { Authorization: "Bearer tok" },
}),
);
});
it("serializes settings updates as a narrow query string", async () => {
await updateSettings("tok", {
model: "openrouter/test",
provider: "openrouter",
});
expect(fetch).toHaveBeenCalledWith(
"/api/settings/update?model=openrouter%2Ftest&provider=openrouter",
expect.objectContaining({
headers: { Authorization: "Bearer tok" },
}),
);
});
it("serializes provider settings updates without returning secrets", async () => {
await updateProviderSettings("tok", {
provider: "openrouter",
apiKey: "sk-or-test",
apiBase: "https://openrouter.ai/api/v1",
});
expect(fetch).toHaveBeenCalledWith(
"/api/settings/provider/update?provider=openrouter&api_key=sk-or-test&api_base=https%3A%2F%2Fopenrouter.ai%2Fapi%2Fv1",
expect.objectContaining({
headers: { Authorization: "Bearer tok" },
}),
);
});
it("serializes web search settings updates", async () => {
await updateWebSearchSettings("tok", {
provider: "searxng",
baseUrl: "https://search.example.com",
});
expect(fetch).toHaveBeenCalledWith(
"/api/settings/web-search/update?provider=searxng&base_url=https%3A%2F%2Fsearch.example.com",
expect.objectContaining({
headers: { Authorization: "Bearer tok" },
}),
);
});
it("maps generated session titles from the sessions list", async () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
json: async () => ({
sessions: [
{
key: "websocket:chat-1",
created_at: "2026-05-01T10:00:00",
updated_at: "2026-05-01T10:01:00",
title: "优化 WebUI 标题",
},
],
}),
} as Response);
await expect(listSessions("tok")).resolves.toMatchObject([
{
key: "websocket:chat-1",
title: "优化 WebUI 标题",
preview: "",
},
]);
});
it("maps slash command metadata from the commands endpoint", async () => {
vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
json: async () => ({
commands: [
{
command: "/stop",
title: "Stop current task",
description: "Cancel the active task.",
icon: "square",
},
{
command: "/restart",
title: "Restart nanobot",
description: "Restart the bot process.",
icon: "rotate-cw",
},
{
command: "/history",
title: "Show conversation history",
description: "Print the last N messages.",
icon: "history",
arg_hint: "[n]",
},
],
}),
} as Response);
await expect(listSlashCommands("tok")).resolves.toEqual([
{
command: "/history",
title: "Show conversation history",
description: "Print the last N messages.",
icon: "history",
argHint: "[n]",
},
]);
expect(fetch).toHaveBeenCalledWith(
"/api/commands",
expect.objectContaining({
headers: { Authorization: "Bearer tok" },
}),
);
});
});