test: strengthen ports, tool policy, and note wrapping

This commit is contained in:
Peter Steinberger
2026-02-16 01:31:06 +00:00
parent f50e1e8015
commit 4d9e310dad
3 changed files with 102 additions and 4 deletions

View File

@@ -1,6 +1,14 @@
import { describe, expect, it } from "vitest";
import type { AnyAgentTool } from "./tools/common.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", () => {
it("expands groups and normalizes aliases", () => {
@@ -28,6 +36,53 @@ describe("tool-policy", () => {
expect(group).toContain("subagents");
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", () => {

View File

@@ -1,5 +1,6 @@
import net from "node:net";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { stripAnsi } from "../terminal/ansi.js";
const runCommandWithTimeoutMock = vi.hoisted(() => vi.fn());
@@ -24,7 +25,7 @@ describe("ports helpers", () => {
await new Promise((resolve) => server.listen(0, resolve));
const port = (server.address() as net.AddressInfo).port;
await expect(ensurePortAvailable(port)).rejects.toBeInstanceOf(PortInUseError);
server.close();
await new Promise<void>((resolve) => server.close(() => resolve()));
});
it("handlePortError exits nicely on EADDRINUSE", async () => {
@@ -37,10 +38,30 @@ describe("ports helpers", () => {
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);
});
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", () => {
expect(
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.errors?.some((err) => err.includes("ENOENT"))).toBe(true);
} finally {
server.close();
await new Promise<void>((resolve) => server.close(() => resolve()));
}
});
});

View File

@@ -163,4 +163,26 @@ describe("wrapNoteMessage", () => {
expect(wrapped).toContain("\n");
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);
});
});