mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:40:44 +00:00
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
265 lines
9.5 KiB
TypeScript
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");
|
|
});
|
|
});
|