import { Command } from "commander"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { registerDirectoryCli } from "./directory-cli.js"; import type { CliRuntimeCapture } from "./test-runtime-capture.js"; const runtimeState = vi.hoisted(() => ({ capture: null as CliRuntimeCapture | null })); function getRuntimeCapture(): CliRuntimeCapture { if (!runtimeState.capture) { throw new Error("runtime capture not initialized"); } return runtimeState.capture; } const mocks = vi.hoisted(() => ({ loadConfig: vi.fn(), writeConfigFile: vi.fn(), resolveInstallableChannelPlugin: vi.fn(), resolveMessageChannelSelection: vi.fn(), getChannelPlugin: vi.fn(), resolveChannelDefaultAccountId: vi.fn(), })); vi.mock("../config/config.js", () => ({ loadConfig: mocks.loadConfig, writeConfigFile: mocks.writeConfigFile, })); vi.mock("../commands/channel-setup/channel-plugin-resolution.js", () => ({ resolveInstallableChannelPlugin: mocks.resolveInstallableChannelPlugin, })); vi.mock("../infra/outbound/channel-selection.js", () => ({ resolveMessageChannelSelection: mocks.resolveMessageChannelSelection, })); vi.mock("../channels/plugins/index.js", () => ({ getChannelPlugin: mocks.getChannelPlugin, })); vi.mock("../channels/plugins/helpers.js", () => ({ resolveChannelDefaultAccountId: mocks.resolveChannelDefaultAccountId, })); vi.mock("../runtime.js", async () => { const { createCliRuntimeCapture } = await import("./test-runtime-capture.js"); runtimeState.capture ??= createCliRuntimeCapture(); return { defaultRuntime: runtimeState.capture.defaultRuntime }; }); describe("registerDirectoryCli", () => { beforeEach(() => { vi.clearAllMocks(); getRuntimeCapture().resetRuntimeCapture(); mocks.loadConfig.mockReturnValue({ channels: {} }); mocks.writeConfigFile.mockResolvedValue(undefined); mocks.resolveChannelDefaultAccountId.mockReturnValue("default"); mocks.resolveMessageChannelSelection.mockResolvedValue({ channel: "slack", configured: ["slack"], source: "explicit", }); getRuntimeCapture().defaultRuntime.exit.mockImplementation((code: number) => { throw new Error(`exit:${code}`); }); }); it("installs an explicit optional directory channel on demand", async () => { const self = vi.fn().mockResolvedValue({ id: "self-1", name: "Family Phone" }); mocks.resolveInstallableChannelPlugin.mockResolvedValue({ cfg: { channels: {}, plugins: { entries: { whatsapp: { enabled: true } } }, }, channelId: "whatsapp", plugin: { id: "whatsapp", directory: { self }, }, configChanged: true, }); const program = new Command().name("openclaw"); registerDirectoryCli(program); await program.parseAsync(["directory", "self", "--channel", "whatsapp", "--json"], { from: "user", }); expect(mocks.resolveInstallableChannelPlugin).toHaveBeenCalledWith( expect.objectContaining({ rawChannel: "whatsapp", allowInstall: true, }), ); expect(mocks.writeConfigFile).toHaveBeenCalledWith( expect.objectContaining({ plugins: { entries: { whatsapp: { enabled: true } } }, }), ); expect(self).toHaveBeenCalledWith( expect.objectContaining({ accountId: "default", }), ); expect(getRuntimeCapture().defaultRuntime.log).toHaveBeenCalledWith( JSON.stringify({ id: "self-1", name: "Family Phone" }, null, 2), ); expect(getRuntimeCapture().defaultRuntime.error).not.toHaveBeenCalled(); }); });