Files
openclaw/src/gateway/server-methods/tools-catalog.test.ts
Tak Hoffman 9e1a13bf4c Gateway/UI: data-driven agents tools catalog with provenance (openclaw#24199) thanks @Takhoffman
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- gh pr checks 24199 --watch --fail-fast

Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-22 23:55:59 -06:00

121 lines
4.3 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { ErrorCodes } from "../protocol/index.js";
import { toolsCatalogHandlers } from "./tools-catalog.js";
vi.mock("../../config/config.js", () => ({
loadConfig: vi.fn(() => ({})),
}));
vi.mock("../../agents/agent-scope.js", () => ({
listAgentIds: vi.fn(() => ["main"]),
resolveDefaultAgentId: vi.fn(() => "main"),
resolveAgentWorkspaceDir: vi.fn(() => "/tmp/workspace-main"),
resolveAgentDir: vi.fn(() => "/tmp/agents/main/agent"),
}));
const pluginToolMetaState = new Map<string, { pluginId: string; optional: boolean }>();
vi.mock("../../plugins/tools.js", () => ({
resolvePluginTools: vi.fn(() => [
{ name: "voice_call", label: "voice_call", description: "Plugin calling tool" },
{ name: "matrix_room", label: "matrix_room", description: "Matrix room helper" },
]),
getPluginToolMeta: vi.fn((tool: { name: string }) => pluginToolMetaState.get(tool.name)),
}));
type RespondCall = [boolean, unknown?, { code: number; message: string }?];
function createInvokeParams(params: Record<string, unknown>) {
const respond = vi.fn();
return {
respond,
invoke: async () =>
await toolsCatalogHandlers["tools.catalog"]({
params,
respond: respond as never,
context: {} as never,
client: null,
req: { type: "req", id: "req-1", method: "tools.catalog" },
isWebchatConnect: () => false,
}),
};
}
describe("tools.catalog handler", () => {
beforeEach(() => {
pluginToolMetaState.clear();
pluginToolMetaState.set("voice_call", { pluginId: "voice-call", optional: true });
pluginToolMetaState.set("matrix_room", { pluginId: "matrix", optional: false });
});
it("rejects invalid params", async () => {
const { respond, invoke } = createInvokeParams({ extra: true });
await invoke();
const call = respond.mock.calls[0] as RespondCall | undefined;
expect(call?.[0]).toBe(false);
expect(call?.[2]?.code).toBe(ErrorCodes.INVALID_REQUEST);
expect(call?.[2]?.message).toContain("invalid tools.catalog params");
});
it("rejects unknown agent ids", async () => {
const { respond, invoke } = createInvokeParams({ agentId: "unknown-agent" });
await invoke();
const call = respond.mock.calls[0] as RespondCall | undefined;
expect(call?.[0]).toBe(false);
expect(call?.[2]?.code).toBe(ErrorCodes.INVALID_REQUEST);
expect(call?.[2]?.message).toContain("unknown agent id");
});
it("returns core groups including tts and excludes plugins when includePlugins=false", async () => {
const { respond, invoke } = createInvokeParams({ includePlugins: false });
await invoke();
const call = respond.mock.calls[0] as RespondCall | undefined;
expect(call?.[0]).toBe(true);
const payload = call?.[1] as
| {
agentId: string;
groups: Array<{
id: string;
source: "core" | "plugin";
tools: Array<{ id: string; source: "core" | "plugin" }>;
}>;
}
| undefined;
expect(payload?.agentId).toBe("main");
expect(payload?.groups.some((group) => group.source === "plugin")).toBe(false);
const media = payload?.groups.find((group) => group.id === "media");
expect(media?.tools.some((tool) => tool.id === "tts" && tool.source === "core")).toBe(true);
});
it("includes plugin groups with plugin metadata", async () => {
const { respond, invoke } = createInvokeParams({});
await invoke();
const call = respond.mock.calls[0] as RespondCall | undefined;
expect(call?.[0]).toBe(true);
const payload = call?.[1] as
| {
groups: Array<{
source: "core" | "plugin";
pluginId?: string;
tools: Array<{
id: string;
source: "core" | "plugin";
pluginId?: string;
optional?: boolean;
}>;
}>;
}
| undefined;
const pluginGroups = (payload?.groups ?? []).filter((group) => group.source === "plugin");
expect(pluginGroups.length).toBeGreaterThan(0);
const voiceCall = pluginGroups
.flatMap((group) => group.tools)
.find((tool) => tool.id === "voice_call");
expect(voiceCall).toMatchObject({
source: "plugin",
pluginId: "voice-call",
optional: true,
});
});
});