Files
openclaw/src/cli/command-path-policy.test.ts
Mariano 3b347d1c7e Add agent visibility to skills check (#75983)
Merged via squash.

Prepared head SHA: 63bac4340f
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
2026-05-02 20:50:38 +02:00

265 lines
9.5 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import type { CliCommandCatalogEntry, CliCommandPathPolicy } from "./command-catalog.js";
import {
resolveCliCatalogCommandPath,
resolveCliCommandPathPolicy,
resolveCliNetworkProxyPolicy,
} from "./command-path-policy.js";
const DEFAULT_EXPECTED_POLICY: CliCommandPathPolicy = {
bypassConfigGuard: false,
routeConfigGuard: "never",
loadPlugins: "never",
pluginRegistry: { scope: "all" },
hideBanner: false,
ensureCliPath: true,
networkProxy: "default",
};
function expectResolvedPolicy(
commandPath: string[],
expected: Partial<CliCommandPathPolicy>,
): void {
expect(resolveCliCommandPathPolicy(commandPath)).toEqual({
...DEFAULT_EXPECTED_POLICY,
...expected,
});
}
describe("command-path-policy", () => {
afterEach(() => {
vi.doUnmock("./command-catalog.js");
vi.resetModules();
});
it("resolves status policy with shared startup semantics", () => {
expectResolvedPolicy(["status"], {
routeConfigGuard: "when-suppressed",
loadPlugins: "never",
pluginRegistry: { scope: "channels" },
ensureCliPath: false,
networkProxy: "bypass",
});
});
it("applies exact overrides after broader channel plugin rules", () => {
expectResolvedPolicy(["channels", "send"], {
loadPlugins: "always",
pluginRegistry: { scope: "configured-channels" },
});
expectResolvedPolicy(["channels", "login"], {
loadPlugins: "always",
pluginRegistry: { scope: "configured-channels" },
});
expectResolvedPolicy(["channels", "capabilities"], {
loadPlugins: "always",
pluginRegistry: { scope: "configured-channels" },
});
expectResolvedPolicy(["channels", "add"], {
loadPlugins: "never",
pluginRegistry: { scope: "configured-channels" },
networkProxy: "bypass",
});
expectResolvedPolicy(["channels", "status"], {
loadPlugins: "never",
pluginRegistry: { scope: "configured-channels" },
networkProxy: expect.any(Function),
});
expectResolvedPolicy(["channels", "list"], {
loadPlugins: "never",
pluginRegistry: { scope: "configured-channels" },
networkProxy: "bypass",
});
expectResolvedPolicy(["channels", "logs"], {
loadPlugins: "never",
pluginRegistry: { scope: "configured-channels" },
networkProxy: "bypass",
});
expectResolvedPolicy(["channels", "remove"], {
loadPlugins: "always",
pluginRegistry: { scope: "configured-channels" },
networkProxy: "bypass",
});
expectResolvedPolicy(["channels", "resolve"], {
loadPlugins: "always",
pluginRegistry: { scope: "configured-channels" },
networkProxy: "bypass",
});
});
it("keeps config-only agent commands on config-only startup", () => {
expectResolvedPolicy(["agent"], {
loadPlugins: expect.any(Function),
pluginRegistry: { scope: "all" },
networkProxy: expect.any(Function),
});
for (const commandPath of [
["agents"],
["agents", "list"],
["agents", "bind"],
["agents", "bindings"],
["agents", "unbind"],
["agents", "set-identity"],
["agents", "delete"],
]) {
expectResolvedPolicy(commandPath, {
loadPlugins: "never",
networkProxy: "bypass",
});
}
});
it("resolves mixed startup-only rules", () => {
expectResolvedPolicy(["configure"], {
bypassConfigGuard: true,
loadPlugins: "never",
});
expectResolvedPolicy(["config", "validate"], {
bypassConfigGuard: true,
loadPlugins: "never",
networkProxy: "bypass",
});
expectResolvedPolicy(["gateway", "status"], {
routeConfigGuard: "always",
loadPlugins: "never",
networkProxy: "bypass",
});
expectResolvedPolicy(["plugins", "update"], {
loadPlugins: "never",
hideBanner: true,
});
for (const commandPath of [
["plugins", "install"],
["plugins", "list"],
["plugins", "inspect"],
["plugins", "registry"],
["plugins", "doctor"],
]) {
expectResolvedPolicy(commandPath, {
loadPlugins: "never",
});
}
expectResolvedPolicy(["cron", "list"], {
bypassConfigGuard: true,
loadPlugins: "never",
networkProxy: "bypass",
});
});
it("defaults unknown command paths to network proxy routing", () => {
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "googlemeet", "login"])).toBe(
"default",
);
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "tool", "image_generate"])).toBe(
"bypass",
);
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "tools", "effective"])).toBe("bypass");
});
it("resolves static network proxy bypass policies from the catalog", () => {
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "status"])).toBe("bypass");
expect(
resolveCliNetworkProxyPolicy(["node", "openclaw", "config", "get", "proxy.enabled"]),
).toBe("bypass");
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "proxy", "start"])).toBe("bypass");
});
it("resolves mixed network proxy policies from argv-sensitive catalog entries", () => {
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "gateway"])).toBe("default");
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "gateway", "run"])).toBe("default");
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "gateway", "health"])).toBe("bypass");
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "node", "run"])).toBe("default");
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "node", "status"])).toBe("bypass");
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "agent", "--local"])).toBe("default");
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "agent", "run"])).toBe("bypass");
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "channels", "status"])).toBe("bypass");
expect(
resolveCliNetworkProxyPolicy(["node", "openclaw", "channels", "status", "--probe"]),
).toBe("default");
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "models", "status"])).toBe("bypass");
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "models", "status", "--probe"])).toBe(
"default",
);
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "skills", "info", "browser"])).toBe(
"bypass",
);
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "skills", "check"])).toBe("bypass");
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "skills", "list"])).toBe("bypass");
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "skills", "search", "browser"])).toBe(
"default",
);
});
it("uses the longest catalog command path for deep network proxy overrides", async () => {
const catalog: readonly CliCommandCatalogEntry[] = [
{ commandPath: ["nodes"], policy: { networkProxy: "bypass" } },
{
commandPath: ["nodes", "camera", "snap"],
exact: true,
policy: { networkProxy: "default" },
},
];
vi.resetModules();
vi.doMock("./command-catalog.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./command-catalog.js")>();
return { ...actual, cliCommandCatalog: catalog };
});
const { resolveCliCatalogCommandPath, resolveCliNetworkProxyPolicy } =
await import("./command-path-policy.js");
expect(resolveCliCatalogCommandPath(["node", "openclaw", "nodes", "camera", "snap"])).toEqual([
"nodes",
"camera",
"snap",
]);
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "nodes", "camera", "snap"])).toBe(
"default",
);
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "nodes", "camera", "list"])).toBe(
"bypass",
);
});
it("stops catalog command path resolution before positional arguments", () => {
expect(
resolveCliCatalogCommandPath(["node", "openclaw", "config", "get", "proxy.enabled"]),
).toEqual(["config", "get"]);
expect(
resolveCliCatalogCommandPath(["node", "openclaw", "message", "send", "--to", "demo"]),
).toEqual(["message"]);
});
it("treats bare gateway invocations with options as the gateway runtime", () => {
const argv = ["node", "openclaw", "gateway", "--port", "1234"];
expect(resolveCliCatalogCommandPath(argv)).toEqual(["gateway"]);
expect(resolveCliNetworkProxyPolicy(argv)).toBe("default");
});
it("does not let gateway run option values spoof bypass subcommands", () => {
for (const argv of [
["node", "openclaw", "gateway", "--token", "status"],
["node", "openclaw", "gateway", "--token=status"],
["node", "openclaw", "gateway", "--password", "health"],
["node", "openclaw", "gateway", "--password-file", "status"],
["node", "openclaw", "gateway", "--ws-log", "compact"],
]) {
expect(resolveCliCatalogCommandPath(argv), argv.join(" ")).toEqual(["gateway"]);
expect(resolveCliNetworkProxyPolicy(argv), argv.join(" ")).toBe("default");
}
});
it("still resolves real gateway bypass subcommands after their command token", () => {
expect(resolveCliCatalogCommandPath(["node", "openclaw", "gateway", "status"])).toEqual([
"gateway",
"status",
]);
expect(
resolveCliCatalogCommandPath(["node", "openclaw", "gateway", "status", "--token", "secret"]),
).toEqual(["gateway", "status"]);
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "gateway", "status"])).toBe("bypass");
});
});