diff --git a/src/cli/program/register.agent.test.ts b/src/cli/program/register.agent.test.ts new file mode 100644 index 000000000..9ad1fa19d --- /dev/null +++ b/src/cli/program/register.agent.test.ts @@ -0,0 +1,216 @@ +import { Command } from "commander"; +import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +const agentCliCommandMock = vi.fn(); +const agentsAddCommandMock = vi.fn(); +const agentsDeleteCommandMock = vi.fn(); +const agentsListCommandMock = vi.fn(); +const agentsSetIdentityCommandMock = vi.fn(); +const setVerboseMock = vi.fn(); +const createDefaultDepsMock = vi.fn(() => ({ deps: true })); + +const runtime = { + log: vi.fn(), + error: vi.fn(), + exit: vi.fn(), +}; + +vi.mock("../../commands/agent-via-gateway.js", () => ({ + agentCliCommand: agentCliCommandMock, +})); + +vi.mock("../../commands/agents.js", () => ({ + agentsAddCommand: agentsAddCommandMock, + agentsDeleteCommand: agentsDeleteCommandMock, + agentsListCommand: agentsListCommandMock, + agentsSetIdentityCommand: agentsSetIdentityCommandMock, +})); + +vi.mock("../../globals.js", () => ({ + setVerbose: setVerboseMock, +})); + +vi.mock("../deps.js", () => ({ + createDefaultDeps: createDefaultDepsMock, +})); + +vi.mock("../../runtime.js", () => ({ + defaultRuntime: runtime, +})); + +let registerAgentCommands: typeof import("./register.agent.js").registerAgentCommands; + +beforeAll(async () => { + ({ registerAgentCommands } = await import("./register.agent.js")); +}); + +describe("registerAgentCommands", () => { + async function runCli(args: string[]) { + const program = new Command(); + registerAgentCommands(program, { agentChannelOptions: "last|telegram|discord" }); + await program.parseAsync(args, { from: "user" }); + } + + beforeEach(() => { + vi.clearAllMocks(); + agentCliCommandMock.mockResolvedValue(undefined); + agentsAddCommandMock.mockResolvedValue(undefined); + agentsDeleteCommandMock.mockResolvedValue(undefined); + agentsListCommandMock.mockResolvedValue(undefined); + agentsSetIdentityCommandMock.mockResolvedValue(undefined); + createDefaultDepsMock.mockReturnValue({ deps: true }); + }); + + it("runs agent command with deps and verbose enabled for --verbose on", async () => { + await runCli(["agent", "--message", "hi", "--verbose", "ON", "--json"]); + + expect(setVerboseMock).toHaveBeenCalledWith(true); + expect(createDefaultDepsMock).toHaveBeenCalledTimes(1); + expect(agentCliCommandMock).toHaveBeenCalledWith( + expect.objectContaining({ + message: "hi", + verbose: "ON", + json: true, + }), + runtime, + { deps: true }, + ); + }); + + it("runs agent command with verbose disabled for --verbose off", async () => { + await runCli(["agent", "--message", "hi", "--verbose", "off"]); + + expect(setVerboseMock).toHaveBeenCalledWith(false); + expect(agentCliCommandMock).toHaveBeenCalledWith( + expect.objectContaining({ + message: "hi", + verbose: "off", + }), + runtime, + { deps: true }, + ); + }); + + it("runs agents add and computes hasFlags based on explicit options", async () => { + await runCli(["agents", "add", "alpha"]); + expect(agentsAddCommandMock).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + name: "alpha", + workspace: undefined, + bind: [], + }), + runtime, + { hasFlags: false }, + ); + + await runCli([ + "agents", + "add", + "beta", + "--workspace", + "/tmp/ws", + "--bind", + "telegram", + "--bind", + "discord:acct", + "--non-interactive", + "--json", + ]); + expect(agentsAddCommandMock).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + name: "beta", + workspace: "/tmp/ws", + bind: ["telegram", "discord:acct"], + nonInteractive: true, + json: true, + }), + runtime, + { hasFlags: true }, + ); + }); + + it("runs agents list when root agents command is invoked", async () => { + await runCli(["agents"]); + expect(agentsListCommandMock).toHaveBeenCalledWith({}, runtime); + }); + + it("forwards agents list options", async () => { + await runCli(["agents", "list", "--json", "--bindings"]); + expect(agentsListCommandMock).toHaveBeenCalledWith( + { + json: true, + bindings: true, + }, + runtime, + ); + }); + + it("forwards agents delete options", async () => { + await runCli(["agents", "delete", "worker-a", "--force", "--json"]); + expect(agentsDeleteCommandMock).toHaveBeenCalledWith( + expect.objectContaining({ + id: "worker-a", + force: true, + json: true, + }), + runtime, + ); + }); + + it("forwards set-identity options", async () => { + await runCli([ + "agents", + "set-identity", + "--agent", + "main", + "--workspace", + "/tmp/ws", + "--identity-file", + "/tmp/ws/IDENTITY.md", + "--from-identity", + "--name", + "OpenClaw", + "--theme", + "ops", + "--emoji", + ":lobster:", + "--avatar", + "https://example.com/openclaw.png", + "--json", + ]); + expect(agentsSetIdentityCommandMock).toHaveBeenCalledWith( + { + agent: "main", + workspace: "/tmp/ws", + identityFile: "/tmp/ws/IDENTITY.md", + fromIdentity: true, + name: "OpenClaw", + theme: "ops", + emoji: ":lobster:", + avatar: "https://example.com/openclaw.png", + json: true, + }, + runtime, + ); + }); + + it("reports errors via runtime when a command fails", async () => { + agentsListCommandMock.mockRejectedValueOnce(new Error("list failed")); + + await runCli(["agents"]); + + expect(runtime.error).toHaveBeenCalledWith("Error: list failed"); + expect(runtime.exit).toHaveBeenCalledWith(1); + }); + + it("reports errors via runtime when agent command fails", async () => { + agentCliCommandMock.mockRejectedValueOnce(new Error("agent failed")); + + await runCli(["agent", "--message", "hello"]); + + expect(runtime.error).toHaveBeenCalledWith("Error: agent failed"); + expect(runtime.exit).toHaveBeenCalledWith(1); + }); +});