mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 23:14:32 +00:00
test: strengthen ports, tool policy, and note wrapping
This commit is contained in:
@@ -1,6 +1,14 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
import type { AnyAgentTool } from "./tools/common.js";
|
||||||
import { TOOL_POLICY_CONFORMANCE } from "./tool-policy.conformance.js";
|
import { TOOL_POLICY_CONFORMANCE } from "./tool-policy.conformance.js";
|
||||||
import { expandToolGroups, resolveToolProfilePolicy, TOOL_GROUPS } from "./tool-policy.js";
|
import {
|
||||||
|
applyOwnerOnlyToolPolicy,
|
||||||
|
expandToolGroups,
|
||||||
|
isOwnerOnlyToolName,
|
||||||
|
normalizeToolName,
|
||||||
|
resolveToolProfilePolicy,
|
||||||
|
TOOL_GROUPS,
|
||||||
|
} from "./tool-policy.js";
|
||||||
|
|
||||||
describe("tool-policy", () => {
|
describe("tool-policy", () => {
|
||||||
it("expands groups and normalizes aliases", () => {
|
it("expands groups and normalizes aliases", () => {
|
||||||
@@ -28,6 +36,53 @@ describe("tool-policy", () => {
|
|||||||
expect(group).toContain("subagents");
|
expect(group).toContain("subagents");
|
||||||
expect(group).toContain("session_status");
|
expect(group).toContain("session_status");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("normalizes tool names and aliases", () => {
|
||||||
|
expect(normalizeToolName(" BASH ")).toBe("exec");
|
||||||
|
expect(normalizeToolName("apply-patch")).toBe("apply_patch");
|
||||||
|
expect(normalizeToolName("READ")).toBe("read");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("identifies owner-only tools", () => {
|
||||||
|
expect(isOwnerOnlyToolName("whatsapp_login")).toBe(true);
|
||||||
|
expect(isOwnerOnlyToolName("read")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("strips owner-only tools for non-owner senders", async () => {
|
||||||
|
const tools = [
|
||||||
|
{
|
||||||
|
name: "read",
|
||||||
|
// oxlint-disable-next-line typescript/no-explicit-any
|
||||||
|
execute: async () => ({ content: [], details: {} }) as any,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "whatsapp_login",
|
||||||
|
// oxlint-disable-next-line typescript/no-explicit-any
|
||||||
|
execute: async () => ({ content: [], details: {} }) as any,
|
||||||
|
},
|
||||||
|
] as unknown as AnyAgentTool[];
|
||||||
|
|
||||||
|
const filtered = applyOwnerOnlyToolPolicy(tools, false);
|
||||||
|
expect(filtered.map((t) => t.name)).toEqual(["read"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps owner-only tools for the owner sender", async () => {
|
||||||
|
const tools = [
|
||||||
|
{
|
||||||
|
name: "read",
|
||||||
|
// oxlint-disable-next-line typescript/no-explicit-any
|
||||||
|
execute: async () => ({ content: [], details: {} }) as any,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "whatsapp_login",
|
||||||
|
// oxlint-disable-next-line typescript/no-explicit-any
|
||||||
|
execute: async () => ({ content: [], details: {} }) as any,
|
||||||
|
},
|
||||||
|
] as unknown as AnyAgentTool[];
|
||||||
|
|
||||||
|
const filtered = applyOwnerOnlyToolPolicy(tools, true);
|
||||||
|
expect(filtered.map((t) => t.name)).toEqual(["read", "whatsapp_login"]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("TOOL_POLICY_CONFORMANCE", () => {
|
describe("TOOL_POLICY_CONFORMANCE", () => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import net from "node:net";
|
import net from "node:net";
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { stripAnsi } from "../terminal/ansi.js";
|
||||||
|
|
||||||
const runCommandWithTimeoutMock = vi.hoisted(() => vi.fn());
|
const runCommandWithTimeoutMock = vi.hoisted(() => vi.fn());
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ describe("ports helpers", () => {
|
|||||||
await new Promise((resolve) => server.listen(0, resolve));
|
await new Promise((resolve) => server.listen(0, resolve));
|
||||||
const port = (server.address() as net.AddressInfo).port;
|
const port = (server.address() as net.AddressInfo).port;
|
||||||
await expect(ensurePortAvailable(port)).rejects.toBeInstanceOf(PortInUseError);
|
await expect(ensurePortAvailable(port)).rejects.toBeInstanceOf(PortInUseError);
|
||||||
server.close();
|
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handlePortError exits nicely on EADDRINUSE", async () => {
|
it("handlePortError exits nicely on EADDRINUSE", async () => {
|
||||||
@@ -37,10 +38,30 @@ describe("ports helpers", () => {
|
|||||||
await handlePortError(new PortInUseError(1234, "details"), 1234, "context", runtime).catch(
|
await handlePortError(new PortInUseError(1234, "details"), 1234, "context", runtime).catch(
|
||||||
() => {},
|
() => {},
|
||||||
);
|
);
|
||||||
expect(runtime.error).toHaveBeenCalled();
|
const messages = runtime.error.mock.calls.map((call) => stripAnsi(String(call[0] ?? "")));
|
||||||
|
expect(messages.join("\n")).toContain("context failed: port 1234 is already in use.");
|
||||||
|
expect(messages.join("\n")).toContain("Resolve by stopping the process");
|
||||||
expect(runtime.exit).toHaveBeenCalledWith(1);
|
expect(runtime.exit).toHaveBeenCalledWith(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("prints an OpenClaw-specific hint when port details look like another OpenClaw instance", async () => {
|
||||||
|
const runtime = {
|
||||||
|
error: vi.fn(),
|
||||||
|
log: vi.fn(),
|
||||||
|
exit: vi.fn() as unknown as (code: number) => never,
|
||||||
|
};
|
||||||
|
|
||||||
|
await handlePortError(
|
||||||
|
new PortInUseError(18789, "node dist/index.js openclaw gateway"),
|
||||||
|
18789,
|
||||||
|
"gateway start",
|
||||||
|
runtime,
|
||||||
|
).catch(() => {});
|
||||||
|
|
||||||
|
const messages = runtime.error.mock.calls.map((call) => stripAnsi(String(call[0] ?? "")));
|
||||||
|
expect(messages.join("\n")).toContain("another OpenClaw instance is already running");
|
||||||
|
});
|
||||||
|
|
||||||
it("classifies ssh and gateway listeners", () => {
|
it("classifies ssh and gateway listeners", () => {
|
||||||
expect(
|
expect(
|
||||||
classifyPortListener({ commandLine: "ssh -N -L 18789:127.0.0.1:18789 user@host" }, 18789),
|
classifyPortListener({ commandLine: "ssh -N -L 18789:127.0.0.1:18789 user@host" }, 18789),
|
||||||
@@ -87,7 +108,7 @@ describeUnix("inspectPortUsage", () => {
|
|||||||
expect(result.status).toBe("busy");
|
expect(result.status).toBe("busy");
|
||||||
expect(result.errors?.some((err) => err.includes("ENOENT"))).toBe(true);
|
expect(result.errors?.some((err) => err.includes("ENOENT"))).toBe(true);
|
||||||
} finally {
|
} finally {
|
||||||
server.close();
|
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -163,4 +163,26 @@ describe("wrapNoteMessage", () => {
|
|||||||
expect(wrapped).toContain("\n");
|
expect(wrapped).toContain("\n");
|
||||||
expect(wrapped.replace(/\n/g, "")).toBe(input);
|
expect(wrapped.replace(/\n/g, "")).toBe(input);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("wraps bullet lines while preserving bullet indentation", () => {
|
||||||
|
const input = "- one two three four five six seven eight nine ten";
|
||||||
|
const wrapped = wrapNoteMessage(input, { maxWidth: 18, columns: 80 });
|
||||||
|
const lines = wrapped.split("\n");
|
||||||
|
expect(lines.length).toBeGreaterThan(1);
|
||||||
|
expect(lines[0]?.startsWith("- ")).toBe(true);
|
||||||
|
expect(lines.slice(1).every((line) => line.startsWith(" "))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves long Windows paths without inserting spaces/newlines", () => {
|
||||||
|
// No spaces: wrapNoteMessage splits on whitespace, so a "Program Files" style path would wrap.
|
||||||
|
const input = "C:\\\\State\\\\OpenClaw\\\\bin\\\\openclaw.exe";
|
||||||
|
const wrapped = wrapNoteMessage(input, { maxWidth: 10, columns: 80 });
|
||||||
|
expect(wrapped).toBe(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves UNC paths without inserting spaces/newlines", () => {
|
||||||
|
const input = "\\\\\\\\server\\\\share\\\\some\\\\really\\\\long\\\\path\\\\file.txt";
|
||||||
|
const wrapped = wrapNoteMessage(input, { maxWidth: 12, columns: 80 });
|
||||||
|
expect(wrapped).toBe(input);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user