





















@@ -0,0 +1,100 @@
1+import { beforeEach, describe, expect, it, vi } from "vitest";
2+import type { RuntimeEnv } from "../runtime.js";
3+4+const runCommandWithTimeout = vi.fn();
5+const hasBinary = vi.fn();
6+7+vi.mock("../process/exec.js", () => ({
8+ runCommandWithTimeout,
9+}));
10+11+vi.mock("../agents/skills.js", () => ({
12+ hasBinary,
13+}));
14+15+vi.mock("../terminal/theme.js", () => ({
16+isRich: () => false,
17+theme: {
18+heading: (s: string) => s,
19+info: (s: string) => s,
20+muted: (s: string) => s,
21+command: (s: string) => s,
22+},
23+}));
24+25+vi.mock("../terminal/links.js", () => ({
26+formatDocsLink: (path: string, label: string) => `${label}${path}`,
27+}));
28+29+vi.mock("../cli/command-format.js", () => ({
30+formatCliCommand: (s: string) => s,
31+}));
32+33+const { docsSearchCommand } = await import("./docs.js");
34+35+function makeRuntime() {
36+return {
37+log: vi.fn(),
38+error: vi.fn(),
39+exit: vi.fn(),
40+} as unknown as RuntimeEnv & {
41+log: ReturnType<typeof vi.fn>;
42+error: ReturnType<typeof vi.fn>;
43+exit: ReturnType<typeof vi.fn>;
44+};
45+}
46+47+describe("docsSearchCommand", () => {
48+beforeEach(() => {
49+runCommandWithTimeout.mockReset();
50+hasBinary.mockReset();
51+hasBinary.mockReturnValue(true);
52+});
53+54+it("invokes the correct lowercase docs MCP tool id", async () => {
55+runCommandWithTimeout.mockResolvedValueOnce({
56+code: 0,
57+stdout: "",
58+stderr: "",
59+});
60+const runtime = makeRuntime();
61+62+await docsSearchCommand(["plugin", "allowlist"], runtime);
63+64+expect(runCommandWithTimeout).toHaveBeenCalledTimes(1);
65+const argv = runCommandWithTimeout.mock.calls[0][0] as string[];
66+const toolUrl = argv.find((arg) => arg.includes("docs.openclaw.ai/mcp."));
67+expect(toolUrl).toBe("https://docs.openclaw.ai/mcp.search_open_claw");
68+expect(toolUrl).not.toMatch(/SearchOpenClaw/);
69+});
70+71+it("fails loudly when mcporter returns a JSON-RPC MCP error on stdout with exit 0", async () => {
72+runCommandWithTimeout.mockResolvedValueOnce({
73+code: 0,
74+stdout: "MCP error -32602: Tool SearchOpenClaw not found",
75+stderr: "",
76+});
77+const runtime = makeRuntime();
78+79+await docsSearchCommand(["browser", "existing-session"], runtime);
80+81+expect(runtime.error).toHaveBeenCalledWith(expect.stringContaining("MCP error -32602"));
82+expect(runtime.exit).toHaveBeenCalledWith(1);
83+});
84+85+it("renders successful results when no MCP error is present", async () => {
86+runCommandWithTimeout.mockResolvedValueOnce({
87+code: 0,
88+stdout:
89+"Title: Plugin allowlist\nLink: https://docs.openclaw.ai/plugins/allowlist\nContent: How to configure the allowlist.",
90+stderr: "",
91+});
92+const runtime = makeRuntime();
93+94+await docsSearchCommand(["plugin", "allowlist"], runtime);
95+96+expect(runtime.error).not.toHaveBeenCalled();
97+expect(runtime.exit).not.toHaveBeenCalled();
98+expect(runtime.log).toHaveBeenCalled();
99+});
100+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。