test: strengthen ports, tool policy, and note wrapping
This commit is contained in:
@@ -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", () => {
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user