test: speed up cli and command suites

This commit is contained in:
Peter Steinberger 2026-03-31 02:12:23 +01:00
parent 6b6ddcd2a6
commit 3f1d6fe147
No known key found for this signature in database
83 changed files with 1161 additions and 1054 deletions

View File

@ -1,36 +1,38 @@
import { Command } from "commander";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { runRegisteredCli } from "../test-utils/command-runner.js";
import { withTempSecretFiles } from "../test-utils/secret-file-fixture.js";
import { registerAcpCli } from "./acp-cli.js";
const runAcpClientInteractive = vi.fn(async (_opts: unknown) => {});
const serveAcpGateway = vi.fn(async (_opts: unknown) => {});
const mocks = vi.hoisted(() => ({
runAcpClientInteractive: vi.fn(async (_opts: unknown) => {}),
serveAcpGateway: vi.fn(async (_opts: unknown) => {}),
defaultRuntime: {
log: vi.fn(),
error: vi.fn(),
writeStdout: vi.fn(),
writeJson: vi.fn(),
exit: vi.fn(),
},
}));
const defaultRuntime = {
log: vi.fn(),
error: vi.fn(),
writeStdout: vi.fn(),
writeJson: vi.fn(),
exit: vi.fn(),
};
const { runAcpClientInteractive, serveAcpGateway, defaultRuntime } = mocks;
const passwordKey = () => ["pass", "word"].join("");
vi.mock("../acp/client.js", () => ({
runAcpClientInteractive: (opts: unknown) => runAcpClientInteractive(opts),
runAcpClientInteractive: (opts: unknown) => mocks.runAcpClientInteractive(opts),
}));
vi.mock("../acp/server.js", () => ({
serveAcpGateway: (opts: unknown) => serveAcpGateway(opts),
serveAcpGateway: (opts: unknown) => mocks.serveAcpGateway(opts),
}));
vi.mock("../runtime.js", () => ({
defaultRuntime,
defaultRuntime: mocks.defaultRuntime,
}));
describe("acp cli option collisions", () => {
let registerAcpCli: typeof import("./acp-cli.js").registerAcpCli;
function createAcpProgram() {
const program = new Command();
registerAcpCli(program);
@ -48,10 +50,6 @@ describe("acp cli option collisions", () => {
expect(defaultRuntime.exit).toHaveBeenCalledWith(1);
}
beforeAll(async () => {
({ registerAcpCli } = await import("./acp-cli.js"));
});
beforeEach(() => {
runAcpClientInteractive.mockClear();
serveAcpGateway.mockClear();

View File

@ -1,17 +1,12 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { formatCliBannerLine } from "./banner.js";
const readCliBannerTaglineModeMock = vi.fn();
const readCliBannerTaglineModeMock = vi.hoisted(() => vi.fn());
vi.mock("./banner-config-lite.js", () => ({
readCliBannerTaglineMode: readCliBannerTaglineModeMock,
}));
let formatCliBannerLine: typeof import("./banner.js").formatCliBannerLine;
beforeAll(async () => {
({ formatCliBannerLine } = await import("./banner.js"));
});
beforeEach(() => {
readCliBannerTaglineModeMock.mockReset();
readCliBannerTaglineModeMock.mockReturnValue(undefined);

View File

@ -1,4 +1,5 @@
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import { __testing, resolveCliChannelOptions } from "./channel-options.js";
const readFileSyncMock = vi.hoisted(() => vi.fn());
@ -19,13 +20,6 @@ vi.mock("../channels/registry.js", () => ({
CHAT_CHANNEL_ORDER: ["telegram", "discord"],
}));
let resolveCliChannelOptions: typeof import("./channel-options.js").resolveCliChannelOptions;
let __testing: typeof import("./channel-options.js").__testing;
beforeAll(async () => {
({ resolveCliChannelOptions, __testing } = await import("./channel-options.js"));
});
describe("resolveCliChannelOptions", () => {
afterEach(() => {
__testing.resetPrecomputedChannelOptionsForTests();

View File

@ -1,13 +1,12 @@
import { Command } from "commander";
import { describe, expect, it, vi } from "vitest";
import { registerDnsCli } from "./dns-cli.js";
import { parseCanvasSnapshotPayload } from "./nodes-canvas.js";
import { parseByteSize } from "./parse-bytes.js";
import { parseDurationMs } from "./parse-duration.js";
import { shouldSkipRespawnForArgv } from "./respawn-policy.js";
import { waitForever } from "./wait.js";
const { registerDnsCli } = await import("./dns-cli.js");
describe("waitForever", () => {
it("creates an unref'ed interval and returns a pending promise", () => {
const setIntervalSpy = vi.spyOn(global, "setInterval");

View File

@ -1,10 +1,15 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { resolveCommandSecretRefsViaGateway } from "./command-secret-gateway.js";
const callGateway = vi.fn();
const mocks = vi.hoisted(() => ({
callGateway: vi.fn(),
}));
const { callGateway } = mocks;
vi.mock("../gateway/call.js", () => ({
callGateway,
callGateway: mocks.callGateway,
}));
vi.mock("../secrets/runtime-web-tools.js", () => ({
@ -16,13 +21,6 @@ vi.mock("../utils/message-channel.js", () => ({
GATEWAY_CLIENT_NAMES: { CLI: "cli" },
}));
let resolveCommandSecretRefsViaGateway: typeof import("./command-secret-gateway.js").resolveCommandSecretRefsViaGateway;
beforeAll(async () => {
vi.resetModules();
({ resolveCommandSecretRefsViaGateway } = await import("./command-secret-gateway.js"));
});
beforeEach(() => {
callGateway.mockReset();
});

View File

@ -1,9 +1,29 @@
import { Command } from "commander";
import { describe, expect, it, vi } from "vitest";
import { createCliRuntimeCapture } from "./test-runtime-capture.js";
import { registerCronCli } from "./cron-cli.js";
const CRON_CLI_TEST_TIMEOUT_MS = 15_000;
const { defaultRuntime, resetRuntimeCapture } = createCliRuntimeCapture();
const mocks = vi.hoisted(() => {
const defaultRuntime = {
log: vi.fn(),
error: vi.fn(),
writeStdout: vi.fn((value: string) => {
defaultRuntime.log(value.endsWith("\n") ? value.slice(0, -1) : value);
}),
writeJson: vi.fn((value: unknown, space = 2) => {
defaultRuntime.log(JSON.stringify(value, null, space > 0 ? space : undefined));
}),
exit: vi.fn((code: number) => {
throw new Error(`__exit__:${code}`);
}),
};
return {
defaultRuntime,
callGatewayFromCli: vi.fn(),
};
});
const { defaultRuntime, callGatewayFromCli } = mocks;
const defaultGatewayMock = async (
method: string,
@ -16,23 +36,21 @@ const defaultGatewayMock = async (
}
return { ok: true, params };
};
const callGatewayFromCli = vi.fn(defaultGatewayMock);
callGatewayFromCli.mockImplementation(defaultGatewayMock);
vi.mock("./gateway-rpc.js", async () => {
const actual = await vi.importActual<typeof import("./gateway-rpc.js")>("./gateway-rpc.js");
return {
...actual,
callGatewayFromCli: (method: string, opts: unknown, params?: unknown, extra?: unknown) =>
callGatewayFromCli(method, opts, params, extra as number | undefined),
mocks.callGatewayFromCli(method, opts, params, extra as number | undefined),
};
});
vi.mock("../runtime.js", () => ({
defaultRuntime,
defaultRuntime: mocks.defaultRuntime,
}));
const { registerCronCli } = await import("./cron-cli.js");
type CronUpdatePatch = {
patch?: {
schedule?: { kind?: string; expr?: string; tz?: string; staggerMs?: number };
@ -72,7 +90,11 @@ function buildProgram() {
function resetGatewayMock() {
callGatewayFromCli.mockClear();
callGatewayFromCli.mockImplementation(defaultGatewayMock);
resetRuntimeCapture();
defaultRuntime.log.mockClear();
defaultRuntime.error.mockClear();
defaultRuntime.writeStdout.mockClear();
defaultRuntime.writeJson.mockClear();
defaultRuntime.exit.mockClear();
}
async function runCronCommand(args: string[]): Promise<void> {
@ -176,8 +198,7 @@ async function runCronRunAndCaptureExit(params: {
},
);
const runtimeModule = await import("../runtime.js");
const runtime = runtimeModule.defaultRuntime as { exit: (code: number) => void };
const runtime = defaultRuntime as { exit: (code: number) => void };
const originalExit = runtime.exit;
const exitSpy = vi.fn();
runtime.exit = exitSpy;

View File

@ -1,7 +1,7 @@
import { Command } from "commander";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { captureEnv } from "../test-utils/env.js";
import { createCliRuntimeCapture } from "./test-runtime-capture.js";
import { registerDaemonCli } from "./daemon-cli.js";
const probeGatewayStatus = vi.fn(async (..._args: unknown[]) => ({ ok: true }));
const resolveGatewayProgramArguments = vi.fn(async (_opts?: unknown) => ({
@ -34,7 +34,28 @@ const buildGatewayInstallPlan = vi.fn(
}),
);
const { runtimeLogs, defaultRuntime, resetRuntimeCapture } = createCliRuntimeCapture();
const mocks = vi.hoisted(() => {
const runtimeLogs: string[] = [];
const stringifyArgs = (args: unknown[]) => args.map((value) => String(value)).join(" ");
const defaultRuntime = {
log: vi.fn((...args: unknown[]) => {
runtimeLogs.push(stringifyArgs(args));
}),
error: vi.fn(),
writeStdout: vi.fn((value: string) => {
defaultRuntime.log(value.endsWith("\n") ? value.slice(0, -1) : value);
}),
writeJson: vi.fn((value: unknown, space = 2) => {
defaultRuntime.log(JSON.stringify(value, null, space > 0 ? space : undefined));
}),
exit: vi.fn((code: number) => {
throw new Error(`__exit__:${code}`);
}),
};
return { runtimeLogs, defaultRuntime };
});
const { runtimeLogs } = mocks;
vi.mock("./daemon-cli/probe.js", () => ({
probeGatewayStatus: (opts: unknown) => probeGatewayStatus(opts),
@ -85,7 +106,7 @@ vi.mock("../infra/ports.js", () => ({
vi.mock("../runtime.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../runtime.js")>()),
defaultRuntime,
defaultRuntime: mocks.defaultRuntime,
}));
vi.mock("../commands/daemon-install-helpers.js", () => ({
@ -101,7 +122,6 @@ vi.mock("./progress.js", () => ({
withProgress: async (_opts: unknown, fn: () => Promise<unknown>) => await fn(),
}));
const { registerDaemonCli } = await import("./daemon-cli.js");
let daemonProgram: Command;
function createDaemonProgram() {
@ -145,7 +165,7 @@ describe("daemon-cli coverage", () => {
});
it("probes gateway status by default", async () => {
resetRuntimeCapture();
runtimeLogs.length = 0;
probeGatewayStatus.mockClear();
await runDaemonCommand(["daemon", "status"]);
@ -159,7 +179,7 @@ describe("daemon-cli coverage", () => {
});
it("derives probe URL from service args + env (json)", async () => {
resetRuntimeCapture();
runtimeLogs.length = 0;
probeGatewayStatus.mockClear();
inspectPortUsage.mockClear();
@ -208,7 +228,7 @@ describe("daemon-cli coverage", () => {
});
it("installs the daemon (json output)", async () => {
resetRuntimeCapture();
runtimeLogs.length = 0;
serviceIsLoaded.mockResolvedValueOnce(false);
serviceInstall.mockClear();
@ -234,7 +254,7 @@ describe("daemon-cli coverage", () => {
});
it("starts and stops daemon (json output)", async () => {
resetRuntimeCapture();
runtimeLogs.length = 0;
serviceRestart.mockClear();
serviceStop.mockClear();
serviceIsLoaded.mockResolvedValue(true);

View File

@ -1,4 +1,5 @@
import { describe, expect, it, vi } from "vitest";
import { probeGatewayStatus } from "./probe.js";
const callGatewayMock = vi.hoisted(() => vi.fn());
const probeGatewayMock = vi.hoisted(() => vi.fn());
@ -15,8 +16,6 @@ vi.mock("../progress.js", () => ({
withProgress: async (_opts: unknown, fn: () => Promise<unknown>) => await fn(),
}));
const { probeGatewayStatus } = await import("./probe.js");
describe("probeGatewayStatus", () => {
it("uses lightweight token-only probing for daemon status", async () => {
callGatewayMock.mockReset();

View File

@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMockGatewayService } from "../../daemon/service.test-helpers.js";
import { captureEnv } from "../../test-utils/env.js";
import type { GatewayRestartSnapshot } from "./restart-health.js";
import { gatherDaemonStatus } from "./status.gather.js";
const callGatewayStatusProbe = vi.fn<
(opts?: unknown) => Promise<{ ok: boolean; url?: string; error?: string | null }>
@ -136,8 +137,6 @@ vi.mock("./restart-health.js", () => ({
inspectGatewayRestart: (opts: unknown) => inspectGatewayRestart(opts),
}));
const { gatherDaemonStatus } = await import("./status.gather.js");
describe("gatherDaemonStatus", () => {
let envSnapshot: ReturnType<typeof captureEnv>;

View File

@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { formatCliCommand } from "../command-format.js";
import { printDaemonStatus } from "./status.print.js";
const runtime = vi.hoisted(() => ({
log: vi.fn<(line: string) => void>(),
@ -69,8 +70,6 @@ vi.mock("./status.gather.js", () => ({
resolvePortListeningAddresses: () => ["127.0.0.1:18789"],
}));
const { printDaemonStatus } = await import("./status.print.js");
describe("printDaemonStatus", () => {
beforeEach(() => {
runtime.log.mockReset();

View File

@ -1,37 +1,53 @@
import { Command } from "commander";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { createCliRuntimeCapture } from "./test-runtime-capture.js";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { registerDevicesCli } from "./devices-cli.js";
const { defaultRuntime: runtime, resetRuntimeCapture } = createCliRuntimeCapture();
runtime.exit.mockImplementation(() => {});
const callGateway = vi.fn();
const buildGatewayConnectionDetails = vi.fn(() => ({
url: "ws://127.0.0.1:18789",
urlSource: "local loopback",
message: "",
const mocks = vi.hoisted(() => ({
runtime: {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn(),
writeJson: vi.fn(),
},
callGateway: vi.fn(),
buildGatewayConnectionDetails: vi.fn(() => ({
url: "ws://127.0.0.1:18789",
urlSource: "local loopback",
message: "",
})),
listDevicePairing: vi.fn(),
approveDevicePairing: vi.fn(),
summarizeDeviceTokens: vi.fn(),
withProgress: vi.fn(async (_opts: unknown, fn: () => Promise<unknown>) => await fn()),
}));
const listDevicePairing = vi.fn();
const approveDevicePairing = vi.fn();
const summarizeDeviceTokens = vi.fn();
const withProgress = vi.fn(async (_opts: unknown, fn: () => Promise<unknown>) => await fn());
vi.mock("../gateway/call.js", () => ({
const {
runtime,
callGateway,
buildGatewayConnectionDetails,
}));
vi.mock("./progress.js", () => ({
withProgress,
}));
vi.mock("../infra/device-pairing.js", () => ({
listDevicePairing,
approveDevicePairing,
summarizeDeviceTokens,
withProgress,
} = mocks;
vi.mock("../gateway/call.js", () => ({
callGateway: mocks.callGateway,
buildGatewayConnectionDetails: mocks.buildGatewayConnectionDetails,
}));
vi.mock("../runtime.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../runtime.js")>()),
defaultRuntime: runtime,
vi.mock("./progress.js", () => ({
withProgress: mocks.withProgress,
}));
vi.mock("../infra/device-pairing.js", () => ({
listDevicePairing: mocks.listDevicePairing,
approveDevicePairing: mocks.approveDevicePairing,
summarizeDeviceTokens: mocks.summarizeDeviceTokens,
}));
vi.mock("../runtime.js", () => ({
defaultRuntime: mocks.runtime,
writeRuntimeJson: (
targetRuntime: { log: (...args: unknown[]) => void },
value: unknown,
@ -39,12 +55,6 @@ vi.mock("../runtime.js", async (importOriginal) => ({
) => targetRuntime.log(JSON.stringify(value, null, space > 0 ? space : undefined)),
}));
let registerDevicesCli: typeof import("./devices-cli.js").registerDevicesCli;
beforeAll(async () => {
({ registerDevicesCli } = await import("./devices-cli.js"));
});
async function runDevicesApprove(argv: string[]) {
await runDevicesCommand(["approve", ...argv]);
}
@ -321,9 +331,12 @@ describe("devices cli list", () => {
});
});
beforeEach(() => {
vi.clearAllMocks();
runtime.exit.mockImplementation(() => {});
});
afterEach(() => {
resetRuntimeCapture();
callGateway.mockClear();
buildGatewayConnectionDetails.mockClear();
buildGatewayConnectionDetails.mockReturnValue({
url: "ws://127.0.0.1:18789",

View File

@ -1,16 +1,30 @@
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 runtimeState = vi.hoisted(() => {
const runtimeLogs: string[] = [];
const runtimeErrors: string[] = [];
const stringifyArgs = (args: unknown[]) => args.map((value) => String(value)).join(" ");
const defaultRuntime = {
log: vi.fn((...args: unknown[]) => {
runtimeLogs.push(stringifyArgs(args));
}),
error: vi.fn((...args: unknown[]) => {
runtimeErrors.push(stringifyArgs(args));
}),
writeStdout: vi.fn((value: string) => {
defaultRuntime.log(value.endsWith("\n") ? value.slice(0, -1) : value);
}),
writeJson: vi.fn((value: unknown, space = 2) => {
defaultRuntime.log(JSON.stringify(value, null, space > 0 ? space : undefined));
}),
exit: vi.fn((code: number) => {
throw new Error(`exit:${code}`);
}),
};
return { defaultRuntime, runtimeLogs, runtimeErrors };
});
const mocks = vi.hoisted(() => ({
loadConfig: vi.fn(),
@ -49,16 +63,15 @@ 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 };
});
vi.mock("../runtime.js", () => ({
defaultRuntime: runtimeState.defaultRuntime,
}));
describe("registerDirectoryCli", () => {
beforeEach(() => {
vi.clearAllMocks();
getRuntimeCapture().resetRuntimeCapture();
runtimeState.runtimeLogs.length = 0;
runtimeState.runtimeErrors.length = 0;
mocks.loadConfig.mockReturnValue({ channels: {} });
mocks.readConfigFileSnapshot.mockResolvedValue({ hash: "config-1" });
mocks.applyPluginAutoEnable.mockImplementation(({ config }) => ({ config, changes: [] }));
@ -69,7 +82,12 @@ describe("registerDirectoryCli", () => {
configured: ["demo-channel"],
source: "explicit",
});
getRuntimeCapture().defaultRuntime.exit.mockImplementation((code: number) => {
runtimeState.defaultRuntime.log.mockClear();
runtimeState.defaultRuntime.error.mockClear();
runtimeState.defaultRuntime.writeStdout.mockClear();
runtimeState.defaultRuntime.writeJson.mockClear();
runtimeState.defaultRuntime.exit.mockClear();
runtimeState.defaultRuntime.exit.mockImplementation((code: number) => {
throw new Error(`exit:${code}`);
});
});
@ -113,10 +131,10 @@ describe("registerDirectoryCli", () => {
accountId: "default",
}),
);
expect(getRuntimeCapture().defaultRuntime.log).toHaveBeenCalledWith(
expect(runtimeState.defaultRuntime.log).toHaveBeenCalledWith(
JSON.stringify({ id: "self-1", name: "Family Phone" }, null, 2),
);
expect(getRuntimeCapture().defaultRuntime.error).not.toHaveBeenCalled();
expect(runtimeState.defaultRuntime.error).not.toHaveBeenCalled();
});
it("uses the auto-enabled config snapshot for omitted channel selection", async () => {

View File

@ -1,20 +1,44 @@
import { Command } from "commander";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createCliRuntimeCapture } from "./test-runtime-capture.js";
import * as execApprovals from "../infra/exec-approvals.js";
import { registerExecApprovalsCli } from "./exec-approvals-cli.js";
const callGatewayFromCli = vi.fn(async (method: string, _opts: unknown, params?: unknown) => {
if (method.endsWith(".get")) {
return {
path: "/tmp/exec-approvals.json",
exists: true,
hash: "hash-1",
file: { version: 1, agents: {} },
};
}
return { method, params };
const mocks = vi.hoisted(() => {
const runtimeErrors: string[] = [];
const stringifyArgs = (args: unknown[]) => args.map((value) => String(value)).join(" ");
const defaultRuntime = {
log: vi.fn(),
error: vi.fn((...args: unknown[]) => {
runtimeErrors.push(stringifyArgs(args));
}),
writeStdout: vi.fn((value: string) => {
defaultRuntime.log(value.endsWith("\n") ? value.slice(0, -1) : value);
}),
writeJson: vi.fn((value: unknown, space = 2) => {
defaultRuntime.log(JSON.stringify(value, null, space > 0 ? space : undefined));
}),
exit: vi.fn((code: number) => {
throw new Error(`__exit__:${code}`);
}),
};
return {
callGatewayFromCli: vi.fn(async (method: string, _opts: unknown, params?: unknown) => {
if (method.endsWith(".get")) {
return {
path: "/tmp/exec-approvals.json",
exists: true,
hash: "hash-1",
file: { version: 1, agents: {} },
};
}
return { method, params };
}),
defaultRuntime,
runtimeErrors,
};
});
const { runtimeErrors, defaultRuntime, resetRuntimeCapture } = createCliRuntimeCapture();
const { callGatewayFromCli, defaultRuntime, runtimeErrors } = mocks;
const localSnapshot = {
path: "/tmp/local-exec-approvals.json",
@ -30,7 +54,7 @@ function resetLocalSnapshot() {
vi.mock("./gateway-rpc.js", () => ({
callGatewayFromCli: (method: string, opts: unknown, params?: unknown) =>
callGatewayFromCli(method, opts, params),
mocks.callGatewayFromCli(method, opts, params),
}));
vi.mock("./nodes-cli/rpc.js", async () => {
@ -42,7 +66,7 @@ vi.mock("./nodes-cli/rpc.js", async () => {
});
vi.mock("../runtime.js", () => ({
defaultRuntime,
defaultRuntime: mocks.defaultRuntime,
}));
vi.mock("../infra/exec-approvals.js", async () => {
@ -56,9 +80,6 @@ vi.mock("../infra/exec-approvals.js", async () => {
};
});
const { registerExecApprovalsCli } = await import("./exec-approvals-cli.js");
const execApprovals = await import("../infra/exec-approvals.js");
describe("exec approvals CLI", () => {
const createProgram = () => {
const program = new Command();
@ -74,8 +95,13 @@ describe("exec approvals CLI", () => {
beforeEach(() => {
resetLocalSnapshot();
resetRuntimeCapture();
runtimeErrors.length = 0;
callGatewayFromCli.mockClear();
defaultRuntime.log.mockClear();
defaultRuntime.error.mockClear();
defaultRuntime.writeStdout.mockClear();
defaultRuntime.writeJson.mockClear();
defaultRuntime.exit.mockClear();
});
it("routes get command to local, gateway, and node modes", async () => {

View File

@ -2,7 +2,7 @@ import { Command } from "commander";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { withEnvOverride } from "../config/test-helpers.js";
import { GatewayLockError } from "../infra/gateway-lock.js";
import { createCliRuntimeCapture } from "./test-runtime-capture.js";
import { registerGatewayCli } from "./gateway-cli.js";
type DiscoveredBeacon = Awaited<
ReturnType<typeof import("../infra/bonjour-discovery.js").discoverGatewayBeacons>
@ -30,8 +30,31 @@ const gatewayStatusCommand = vi.fn<(opts: unknown) => Promise<void>>(async () =>
const inspectPortUsage = vi.fn(async (_port: number) => ({ status: "free" as const }));
const formatPortDiagnostics = vi.fn((_diagnostics: unknown) => [] as string[]);
const { runtimeLogs, runtimeErrors, defaultRuntime, resetRuntimeCapture } =
createCliRuntimeCapture();
const mocks = vi.hoisted(() => {
const runtimeLogs: string[] = [];
const runtimeErrors: string[] = [];
const stringifyArgs = (args: unknown[]) => args.map((value) => String(value)).join(" ");
const defaultRuntime = {
log: vi.fn((...args: unknown[]) => {
runtimeLogs.push(stringifyArgs(args));
}),
error: vi.fn((...args: unknown[]) => {
runtimeErrors.push(stringifyArgs(args));
}),
writeStdout: vi.fn((value: string) => {
defaultRuntime.log(value.endsWith("\n") ? value.slice(0, -1) : value);
}),
writeJson: vi.fn((value: unknown, space = 2) => {
defaultRuntime.log(JSON.stringify(value, null, space > 0 ? space : undefined));
}),
exit: vi.fn((code: number) => {
throw new Error(`__exit__:${code}`);
}),
};
return { runtimeLogs, runtimeErrors, defaultRuntime };
});
const { runtimeLogs, runtimeErrors, defaultRuntime } = mocks;
vi.mock(
new URL("../../gateway/call.ts", new URL("./gateway-cli/call.ts", import.meta.url)).href,
@ -53,7 +76,7 @@ vi.mock("../globals.js", () => ({
vi.mock("../runtime.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../runtime.js")>()),
defaultRuntime,
defaultRuntime: mocks.defaultRuntime,
}));
vi.mock("./ports.js", () => ({
@ -96,7 +119,6 @@ vi.mock("../infra/ports.js", () => ({
formatPortDiagnostics: (diagnostics: unknown) => formatPortDiagnostics(diagnostics),
}));
const { registerGatewayCli } = await import("./gateway-cli.js");
let gatewayProgram: Command;
function createGatewayProgram() {
@ -117,12 +139,18 @@ async function expectGatewayExit(args: string[]) {
describe("gateway-cli coverage", () => {
beforeEach(() => {
gatewayProgram = createGatewayProgram();
runtimeLogs.length = 0;
runtimeErrors.length = 0;
defaultRuntime.log.mockClear();
defaultRuntime.error.mockClear();
defaultRuntime.writeStdout.mockClear();
defaultRuntime.writeJson.mockClear();
defaultRuntime.exit.mockClear();
inspectPortUsage.mockClear();
formatPortDiagnostics.mockClear();
});
it("registers call/health commands and routes to callGateway", async () => {
resetRuntimeCapture();
callGateway.mockClear();
await runGatewayCommand(["gateway", "call", "health", "--params", '{"x":1}', "--json"]);
@ -132,7 +160,6 @@ describe("gateway-cli coverage", () => {
});
it("registers gateway probe and routes to gatewayStatusCommand", async () => {
resetRuntimeCapture();
gatewayStatusCommand.mockClear();
await runGatewayCommand(["gateway", "probe", "--json"]);
@ -141,7 +168,6 @@ describe("gateway-cli coverage", () => {
});
it("registers gateway discover and prints json output", async () => {
resetRuntimeCapture();
discoverGatewayBeacons.mockClear();
discoverGatewayBeacons.mockResolvedValueOnce([
{
@ -166,7 +192,6 @@ describe("gateway-cli coverage", () => {
});
it("validates gateway discover timeout", async () => {
resetRuntimeCapture();
discoverGatewayBeacons.mockClear();
await expectGatewayExit(["gateway", "discover", "--timeout", "0"]);
@ -175,7 +200,6 @@ describe("gateway-cli coverage", () => {
});
it("fails gateway call on invalid params JSON", async () => {
resetRuntimeCapture();
callGateway.mockClear();
await expectGatewayExit(["gateway", "call", "status", "--params", "not-json"]);
@ -184,8 +208,6 @@ describe("gateway-cli coverage", () => {
});
it("validates gateway ports and handles force/start errors", async () => {
resetRuntimeCapture();
// Invalid port
await expectGatewayExit(["gateway", "--port", "0", "--token", "test-token"]);
@ -243,7 +265,6 @@ describe("gateway-cli coverage", () => {
OPENCLAW_SERVICE_KIND: undefined,
},
async () => {
resetRuntimeCapture();
serviceIsLoaded.mockResolvedValue(true);
startGatewayServer.mockRejectedValueOnce(
new GatewayLockError("another gateway instance is already listening"),
@ -260,7 +281,8 @@ describe("gateway-cli coverage", () => {
});
it("keeps exit 1 for gateway bind failures wrapped as GatewayLockError", async () => {
resetRuntimeCapture();
runtimeLogs.length = 0;
runtimeErrors.length = 0;
serviceIsLoaded.mockResolvedValue(true);
startGatewayServer.mockRejectedValueOnce(
new GatewayLockError("failed to bind gateway socket on ws://127.0.0.1:18789: Error: boom"),
@ -272,7 +294,8 @@ describe("gateway-cli coverage", () => {
});
it("keeps exit 1 for gateway lock acquisition failures", async () => {
resetRuntimeCapture();
runtimeLogs.length = 0;
runtimeErrors.length = 0;
serviceIsLoaded.mockResolvedValue(true);
startGatewayServer.mockRejectedValueOnce(
new GatewayLockError("failed to acquire gateway lock at /tmp/openclaw/gateway.lock"),
@ -285,7 +308,8 @@ describe("gateway-cli coverage", () => {
it("uses env/config port when --port is omitted", async () => {
await withEnvOverride({ OPENCLAW_GATEWAY_PORT: "19001" }, async () => {
resetRuntimeCapture();
runtimeLogs.length = 0;
runtimeErrors.length = 0;
startGatewayServer.mockClear();
startGatewayServer.mockRejectedValueOnce(new Error("nope"));

View File

@ -1,13 +1,22 @@
import { Command } from "commander";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createCliRuntimeCapture } from "../test-runtime-capture.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { registerGatewayCli } from "./register.js";
const callGatewayCli = vi.fn(async (_method: string, _opts: unknown, _params?: unknown) => ({
ok: true,
const mocks = vi.hoisted(() => ({
callGatewayCli: vi.fn(async (_method: string, _opts: unknown, _params?: unknown) => ({
ok: true,
})),
gatewayStatusCommand: vi.fn(async (_opts: unknown, _runtime: unknown) => {}),
defaultRuntime: {
log: vi.fn(),
error: vi.fn(),
writeStdout: vi.fn(),
writeJson: vi.fn(),
exit: vi.fn(),
},
}));
const gatewayStatusCommand = vi.fn(async (_opts: unknown, _runtime: unknown) => {});
const { defaultRuntime, resetRuntimeCapture } = createCliRuntimeCapture();
const { callGatewayCli, gatewayStatusCommand, defaultRuntime } = mocks;
vi.mock("../cli-utils.js", () => ({
runCommandWithRuntime: async (
@ -25,11 +34,12 @@ vi.mock("../cli-utils.js", () => ({
vi.mock("../../runtime.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../../runtime.js")>()),
defaultRuntime,
defaultRuntime: mocks.defaultRuntime,
}));
vi.mock("../../commands/gateway-status.js", () => ({
gatewayStatusCommand: (opts: unknown, runtime: unknown) => gatewayStatusCommand(opts, runtime),
gatewayStatusCommand: (opts: unknown, runtime: unknown) =>
mocks.gatewayStatusCommand(opts, runtime),
}));
vi.mock("./call.js", () => ({
@ -42,7 +52,7 @@ vi.mock("./call.js", () => ({
.option("--expect-final", "Wait for final response (agent)", false)
.option("--json", "Output JSON", false),
callGatewayCli: (method: string, opts: unknown, params?: unknown) =>
callGatewayCli(method, opts, params),
mocks.callGatewayCli(method, opts, params),
}));
vi.mock("./run.js", () => ({
@ -113,20 +123,21 @@ vi.mock("./discover.js", () => ({
}));
describe("gateway register option collisions", () => {
let registerGatewayCli: typeof import("./register.js").registerGatewayCli;
let sharedProgram: Command;
let sharedProgram: Command = new Command();
beforeAll(async () => {
({ registerGatewayCli } = await import("./register.js"));
sharedProgram = new Command();
if (sharedProgram.commands.length === 0) {
sharedProgram.exitOverride();
registerGatewayCli(sharedProgram);
});
}
beforeEach(() => {
resetRuntimeCapture();
callGatewayCli.mockClear();
gatewayStatusCommand.mockClear();
defaultRuntime.log.mockClear();
defaultRuntime.error.mockClear();
defaultRuntime.writeStdout.mockClear();
defaultRuntime.writeJson.mockClear();
defaultRuntime.exit.mockClear();
});
it.each([

View File

@ -1,6 +1,6 @@
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import { runRegisteredCli } from "../test-utils/command-runner.js";
import { formatLogTimestamp } from "./logs-cli.js";
import { formatLogTimestamp, registerLogsCli } from "./logs-cli.js";
const callGatewayFromCli = vi.fn();
@ -12,12 +12,6 @@ vi.mock("./gateway-rpc.js", async () => {
};
});
let registerLogsCli: typeof import("./logs-cli.js").registerLogsCli;
beforeAll(async () => {
({ registerLogsCli } = await import("./logs-cli.js"));
});
async function runLogsCli(argv: string[]) {
await runRegisteredCli({
register: registerLogsCli as (program: import("commander").Command) => void,

View File

@ -2,21 +2,38 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { Command } from "commander";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { withTempHome } from "../config/home-env.test-harness.js";
import { createCliRuntimeCapture } from "./test-runtime-capture.js";
import { registerMcpCli } from "./mcp-cli.js";
const { defaultRuntime, resetRuntimeCapture } = createCliRuntimeCapture();
const mocks = vi.hoisted(() => {
const runtime = {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn((code: number) => {
throw new Error(`__exit__:${code}`);
}),
writeJson: vi.fn((value: unknown, space = 2) => {
runtime.log(JSON.stringify(value, null, space > 0 ? space : undefined));
}),
};
return {
runtime,
serveOpenClawChannelMcp: vi.fn(),
};
});
const defaultRuntime = mocks.runtime;
const mockLog = defaultRuntime.log;
const mockError = defaultRuntime.error;
const serveOpenClawChannelMcp = vi.fn();
const serveOpenClawChannelMcp = mocks.serveOpenClawChannelMcp;
vi.mock("../runtime.js", () => ({
defaultRuntime,
defaultRuntime: mocks.runtime,
}));
vi.mock("../mcp/channel-server.js", () => ({
serveOpenClawChannelMcp,
serveOpenClawChannelMcp: mocks.serveOpenClawChannelMcp,
}));
const tempDirs: string[] = [];
@ -27,7 +44,6 @@ async function createWorkspace(): Promise<string> {
return dir;
}
let registerMcpCli: typeof import("./mcp-cli.js").registerMcpCli;
let sharedProgram: Command;
async function runMcpCommand(args: string[]) {
@ -35,16 +51,14 @@ async function runMcpCommand(args: string[]) {
}
describe("mcp cli", () => {
beforeAll(async () => {
({ registerMcpCli } = await import("./mcp-cli.js"));
if (!sharedProgram) {
sharedProgram = new Command();
sharedProgram.exitOverride();
registerMcpCli(sharedProgram);
}, 300_000);
}
beforeEach(() => {
vi.clearAllMocks();
resetRuntimeCapture();
});
afterEach(async () => {

View File

@ -1,45 +1,43 @@
import { Command } from "commander";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { runRegisteredCli } from "../test-utils/command-runner.js";
import { registerModelsCli } from "./models-cli.js";
const modelsStatusCommand = vi.fn().mockResolvedValue(undefined);
const noopAsync = vi.fn(async () => undefined);
const modelsAuthLoginCommand = vi.fn().mockResolvedValue(undefined);
const mocks = vi.hoisted(() => ({
modelsStatusCommand: vi.fn().mockResolvedValue(undefined),
noopAsync: vi.fn(async () => undefined),
modelsAuthLoginCommand: vi.fn().mockResolvedValue(undefined),
}));
const { modelsStatusCommand, modelsAuthLoginCommand } = mocks;
vi.mock("../commands/models.js", () => ({
modelsStatusCommand,
modelsAliasesAddCommand: noopAsync,
modelsAliasesListCommand: noopAsync,
modelsAliasesRemoveCommand: noopAsync,
modelsAuthAddCommand: noopAsync,
modelsAuthLoginCommand,
modelsAuthOrderClearCommand: noopAsync,
modelsAuthOrderGetCommand: noopAsync,
modelsAuthOrderSetCommand: noopAsync,
modelsAuthPasteTokenCommand: noopAsync,
modelsAuthSetupTokenCommand: noopAsync,
modelsFallbacksAddCommand: noopAsync,
modelsFallbacksClearCommand: noopAsync,
modelsFallbacksListCommand: noopAsync,
modelsFallbacksRemoveCommand: noopAsync,
modelsImageFallbacksAddCommand: noopAsync,
modelsImageFallbacksClearCommand: noopAsync,
modelsImageFallbacksListCommand: noopAsync,
modelsImageFallbacksRemoveCommand: noopAsync,
modelsListCommand: noopAsync,
modelsScanCommand: noopAsync,
modelsSetCommand: noopAsync,
modelsSetImageCommand: noopAsync,
modelsStatusCommand: mocks.modelsStatusCommand,
modelsAliasesAddCommand: mocks.noopAsync,
modelsAliasesListCommand: mocks.noopAsync,
modelsAliasesRemoveCommand: mocks.noopAsync,
modelsAuthAddCommand: mocks.noopAsync,
modelsAuthLoginCommand: mocks.modelsAuthLoginCommand,
modelsAuthOrderClearCommand: mocks.noopAsync,
modelsAuthOrderGetCommand: mocks.noopAsync,
modelsAuthOrderSetCommand: mocks.noopAsync,
modelsAuthPasteTokenCommand: mocks.noopAsync,
modelsAuthSetupTokenCommand: mocks.noopAsync,
modelsFallbacksAddCommand: mocks.noopAsync,
modelsFallbacksClearCommand: mocks.noopAsync,
modelsFallbacksListCommand: mocks.noopAsync,
modelsFallbacksRemoveCommand: mocks.noopAsync,
modelsImageFallbacksAddCommand: mocks.noopAsync,
modelsImageFallbacksClearCommand: mocks.noopAsync,
modelsImageFallbacksListCommand: mocks.noopAsync,
modelsImageFallbacksRemoveCommand: mocks.noopAsync,
modelsListCommand: mocks.noopAsync,
modelsScanCommand: mocks.noopAsync,
modelsSetCommand: mocks.noopAsync,
modelsSetImageCommand: mocks.noopAsync,
}));
describe("models cli", () => {
let registerModelsCli: (typeof import("./models-cli.js"))["registerModelsCli"];
beforeAll(async () => {
// Load once; vi.mock above ensures command handlers are already mocked.
({ registerModelsCli } = await import("./models-cli.js"));
});
beforeEach(() => {
modelsAuthLoginCommand.mockClear();
modelsStatusCommand.mockClear();

View File

@ -1,6 +1,6 @@
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import {
readFileUtf8AndCleanup,
stubFetchResponse,
@ -35,9 +35,7 @@ async function withCameraTempDir<T>(run: (dir: string) => Promise<T>): Promise<T
}
describe("nodes camera helpers", () => {
beforeEach(async () => {
vi.resetModules();
vi.clearAllMocks();
beforeAll(async () => {
({
cameraTempPath,
parseCameraClipPayload,
@ -49,6 +47,10 @@ describe("nodes camera helpers", () => {
({ parseScreenRecordPayload, screenRecordTempPath } = await import("./nodes-screen.js"));
});
beforeEach(() => {
vi.clearAllMocks();
});
it("parses camera.snap payload", () => {
expect(
parseCameraSnapPayload({

View File

@ -1,6 +1,6 @@
import { Command } from "commander";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createCliRuntimeCapture } from "./test-runtime-capture.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { registerNodesCli } from "./nodes-cli.js";
type NodeInvokeCall = {
method?: string;
@ -46,7 +46,31 @@ const callGateway = vi.fn(async (opts: NodeInvokeCall) => {
const randomIdempotencyKey = vi.fn(() => "rk_test");
const { runtimeErrors, defaultRuntime, resetRuntimeCapture } = createCliRuntimeCapture();
const mocks = vi.hoisted(() => {
const runtimeErrors: string[] = [];
const stringifyArgs = (args: unknown[]) => args.map((value) => String(value)).join(" ");
const defaultRuntime = {
log: vi.fn(),
error: vi.fn((...args: unknown[]) => {
runtimeErrors.push(stringifyArgs(args));
}),
writeStdout: vi.fn((value: string) => {
defaultRuntime.log(value.endsWith("\n") ? value.slice(0, -1) : value);
}),
writeJson: vi.fn((value: unknown, space = 2) => {
defaultRuntime.log(JSON.stringify(value, null, space > 0 ? space : undefined));
}),
exit: vi.fn((code: number) => {
throw new Error(`__exit__:${code}`);
}),
};
return {
runtimeErrors,
defaultRuntime,
};
});
const { runtimeErrors, defaultRuntime } = mocks;
vi.mock("../gateway/call.js", () => ({
callGateway: (opts: unknown) => callGateway(opts as NodeInvokeCall),
@ -55,12 +79,11 @@ vi.mock("../gateway/call.js", () => ({
vi.mock("../runtime.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../runtime.js")>()),
defaultRuntime,
defaultRuntime: mocks.defaultRuntime,
}));
describe("nodes-cli coverage", () => {
let registerNodesCli: (program: Command) => void;
let sharedProgram: Command;
let sharedProgram: Command = new Command();
const getNodeInvokeCall = () => {
const last = lastNodeInvokeCall;
@ -75,17 +98,20 @@ describe("nodes-cli coverage", () => {
return getNodeInvokeCall();
};
beforeAll(async () => {
({ registerNodesCli } = await import("./nodes-cli.js"));
sharedProgram = new Command();
if (sharedProgram.commands.length === 0) {
sharedProgram.exitOverride();
registerNodesCli(sharedProgram);
});
}
beforeEach(() => {
resetRuntimeCapture();
runtimeErrors.length = 0;
callGateway.mockClear();
randomIdempotencyKey.mockClear();
defaultRuntime.log.mockClear();
defaultRuntime.error.mockClear();
defaultRuntime.writeStdout.mockClear();
defaultRuntime.writeJson.mockClear();
defaultRuntime.exit.mockClear();
lastNodeInvokeCall = null;
});

View File

@ -1,6 +1,7 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { DEFAULT_EXEC_APPROVAL_TIMEOUT_MS } from "../../infra/exec-approvals.js";
import { parseTimeoutMs } from "../parse-timeout.js";
import { callGatewayCli } from "./rpc.js";
/**
* Regression test for #12098:
@ -18,9 +19,11 @@ import { parseTimeoutMs } from "../parse-timeout.js";
* least approvalTimeoutMs + 10_000.
*/
const callGatewaySpy = vi.fn<
(opts: Record<string, unknown>) => Promise<{ decision: "allow-once" }>
>(async () => ({ decision: "allow-once" }));
const { callGatewaySpy } = vi.hoisted(() => ({
callGatewaySpy: vi.fn<(opts: Record<string, unknown>) => Promise<{ decision: "allow-once" }>>(
async () => ({ decision: "allow-once" }),
),
}));
vi.mock("../../gateway/call.js", () => ({
callGateway: callGatewaySpy,
@ -32,13 +35,8 @@ vi.mock("../progress.js", () => ({
}));
describe("exec approval transport timeout (#12098)", () => {
let callGatewayCli: typeof import("./rpc.js").callGatewayCli;
const approvalTransportFloorMs = DEFAULT_EXEC_APPROVAL_TIMEOUT_MS + 10_000;
beforeAll(async () => {
({ callGatewayCli } = await import("./rpc.js"));
});
beforeEach(() => {
callGatewaySpy.mockClear();
callGatewaySpy.mockResolvedValue({ decision: "allow-once" });

View File

@ -1,43 +1,56 @@
import { Command } from "commander";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { registerPairingCli } from "./pairing-cli.js";
const mocks = vi.hoisted(() => ({
listChannelPairingRequests: vi.fn(),
approveChannelPairingCode: vi.fn(),
notifyPairingApproved: vi.fn(),
normalizeChannelId: vi.fn((raw: string) => {
if (!raw) {
return null;
}
if (raw === "imsg") {
return "imessage";
}
if (["telegram", "discord", "imessage"].includes(raw)) {
return raw;
}
return null;
}),
getPairingAdapter: vi.fn((channel: string) => ({
idLabel: pairingIdLabels[channel] ?? "userId",
})),
listPairingChannels: vi.fn(() => ["telegram", "discord", "imessage"]),
}));
const {
listChannelPairingRequests,
approveChannelPairingCode,
notifyPairingApproved,
normalizeChannelId,
getPairingAdapter,
listPairingChannels,
} = mocks;
const listChannelPairingRequests = vi.fn();
const approveChannelPairingCode = vi.fn();
const notifyPairingApproved = vi.fn();
const pairingIdLabels: Record<string, string> = {
telegram: "telegramUserId",
discord: "discordUserId",
};
const normalizeChannelId = vi.fn((raw: string) => {
if (!raw) {
return null;
}
if (raw === "imsg") {
return "imessage";
}
if (["telegram", "discord", "imessage"].includes(raw)) {
return raw;
}
return null;
});
const getPairingAdapter = vi.fn((channel: string) => ({
idLabel: pairingIdLabels[channel] ?? "userId",
}));
const listPairingChannels = vi.fn(() => ["telegram", "discord", "imessage"]);
vi.mock("../pairing/pairing-store.js", () => ({
listChannelPairingRequests,
approveChannelPairingCode,
listChannelPairingRequests: mocks.listChannelPairingRequests,
approveChannelPairingCode: mocks.approveChannelPairingCode,
}));
vi.mock("../channels/plugins/pairing.js", () => ({
listPairingChannels,
notifyPairingApproved,
getPairingAdapter,
listPairingChannels: mocks.listPairingChannels,
notifyPairingApproved: mocks.notifyPairingApproved,
getPairingAdapter: mocks.getPairingAdapter,
}));
vi.mock("../channels/plugins/index.js", () => ({
normalizeChannelId,
normalizeChannelId: mocks.normalizeChannelId,
}));
vi.mock("../config/config.js", () => ({
@ -45,13 +58,6 @@ vi.mock("../config/config.js", () => ({
}));
describe("pairing cli", () => {
let registerPairingCli: typeof import("./pairing-cli.js").registerPairingCli;
beforeAll(async () => {
vi.resetModules();
({ registerPairingCli } = await import("./pairing-cli.js"));
});
beforeEach(() => {
listChannelPairingRequests.mockClear();
listChannelPairingRequests.mockResolvedValue([]);

View File

@ -1,4 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { __testing, ensurePluginRegistryLoaded } from "./plugin-registry.js";
const mocks = vi.hoisted(() => ({
applyPluginAutoEnable: vi.fn(),
@ -37,8 +38,8 @@ vi.mock("../plugins/runtime.js", () => ({
describe("ensurePluginRegistryLoaded", () => {
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();
__testing.resetPluginRegistryLoadedForTests();
mocks.getActivePluginRegistry.mockReturnValue({
plugins: [],
channels: [],
@ -73,8 +74,6 @@ describe("ensurePluginRegistryLoaded", () => {
diagnostics: [],
});
const { ensurePluginRegistryLoaded } = await import("./plugin-registry.js");
ensurePluginRegistryLoaded({ scope: "configured-channels" });
expect(mocks.applyPluginAutoEnable).toHaveBeenCalledWith({
@ -127,8 +126,6 @@ describe("ensurePluginRegistryLoaded", () => {
tools: [],
});
const { ensurePluginRegistryLoaded } = await import("./plugin-registry.js");
ensurePluginRegistryLoaded({ scope: "configured-channels" });
ensurePluginRegistryLoaded({ scope: "channels" });
@ -160,8 +157,6 @@ describe("ensurePluginRegistryLoaded", () => {
tools: [],
});
const { ensurePluginRegistryLoaded } = await import("./plugin-registry.js");
ensurePluginRegistryLoaded({ scope: "all" });
expect(mocks.loadOpenClawPlugins).toHaveBeenCalledTimes(1);
@ -188,8 +183,6 @@ describe("ensurePluginRegistryLoaded", () => {
tools: [{ pluginId: "demo-tool" }],
});
const { ensurePluginRegistryLoaded } = await import("./plugin-registry.js");
ensurePluginRegistryLoaded({ scope: "configured-channels" });
expect(mocks.loadOpenClawPlugins).toHaveBeenCalledTimes(1);

View File

@ -107,3 +107,9 @@ export function ensurePluginRegistryLoaded(options?: { scope?: PluginRegistrySco
});
pluginRegistryLoaded = scope;
}
export const __testing = {
resetPluginRegistryLoadedForTests(): void {
pluginRegistryLoaded = "none";
},
};

View File

@ -2,10 +2,17 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import { bundledPluginRootAt, repoInstallSpec } from "../../test/helpers/bundled-plugin-paths.js";
import type { OpenClawConfig } from "../config/config.js";
import type { ConfigFileSnapshot } from "../config/types.openclaw.js";
import { loadConfigForInstall } from "./plugins-install-command.js";
const loadConfigMock = vi.fn<() => OpenClawConfig>();
const readConfigFileSnapshotMock = vi.fn<() => Promise<ConfigFileSnapshot>>();
const cleanStaleMatrixPluginConfigMock = vi.fn();
const hoisted = vi.hoisted(() => ({
loadConfigMock: vi.fn<() => OpenClawConfig>(),
readConfigFileSnapshotMock: vi.fn<() => Promise<ConfigFileSnapshot>>(),
cleanStaleMatrixPluginConfigMock: vi.fn(),
}));
const loadConfigMock = hoisted.loadConfigMock;
const readConfigFileSnapshotMock = hoisted.readConfigFileSnapshotMock;
const cleanStaleMatrixPluginConfigMock = hoisted.cleanStaleMatrixPluginConfigMock;
vi.mock("../config/config.js", () => ({
loadConfig: () => loadConfigMock(),
@ -16,7 +23,6 @@ vi.mock("../commands/doctor/providers/matrix.js", () => ({
cleanStaleMatrixPluginConfig: (cfg: OpenClawConfig) => cleanStaleMatrixPluginConfigMock(cfg),
}));
const { loadConfigForInstall } = await import("./plugins-install-command.js");
const MATRIX_REPO_INSTALL_SPEC = repoInstallSpec("matrix");
function makeSnapshot(overrides: Partial<ConfigFileSnapshot> = {}): ConfigFileSnapshot {

View File

@ -1,10 +1,10 @@
import { Command } from "commander";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { registerNodesCli } from "./nodes-cli.js";
import { createIosNodeListResponse } from "./program.nodes-test-helpers.js";
import { callGateway, installBaseProgramMocks, runtime } from "./program.test-mocks.js";
installBaseProgramMocks();
let registerNodesCli: (program: Command) => void;
function formatRuntimeLogCallArg(value: unknown): string {
if (typeof value === "string") {
@ -26,12 +26,12 @@ function formatRuntimeLogCallArg(value: unknown): string {
describe("cli program (nodes basics)", () => {
let program: Command;
beforeAll(async () => {
({ registerNodesCli } = await import("./nodes-cli.js"));
program = new Command();
program.exitOverride();
registerNodesCli(program);
});
function createProgram() {
const next = new Command();
next.exitOverride();
registerNodesCli(next);
return next;
}
async function runProgram(argv: string[]) {
runtime.log.mockClear();
@ -57,6 +57,7 @@ describe("cli program (nodes basics)", () => {
beforeEach(() => {
vi.clearAllMocks();
program = createProgram();
});
it("runs nodes list --connected and filters to connected nodes", async () => {

View File

@ -1,4 +1,5 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { buildProgram } from "./program.js";
import {
configureCommand,
ensureConfigReady,
@ -23,8 +24,6 @@ vi.mock("./config-cli.js", () => ({
runConfigUnset: vi.fn(),
}));
const { buildProgram } = await import("./program.js");
describe("cli program (smoke)", () => {
let program = createProgram();
@ -36,11 +35,8 @@ describe("cli program (smoke)", () => {
await program.parseAsync(argv, { from: "user" });
}
beforeAll(() => {
program = createProgram();
});
beforeEach(() => {
program = createProgram();
vi.clearAllMocks();
runTui.mockResolvedValue(undefined);
ensureConfigReady.mockResolvedValue(undefined);

View File

@ -1,8 +1,9 @@
import { Command } from "commander";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { reparseProgramFromActionArgs } from "./action-reparse.js";
const buildParseArgvMock = vi.fn();
const resolveActionArgsMock = vi.fn();
const buildParseArgvMock = vi.hoisted(() => vi.fn());
const resolveActionArgsMock = vi.hoisted(() => vi.fn());
vi.mock("../argv.js", () => ({
buildParseArgv: buildParseArgvMock,
@ -12,8 +13,6 @@ vi.mock("./helpers.js", () => ({
resolveActionArgs: resolveActionArgsMock,
}));
const { reparseProgramFromActionArgs } = await import("./action-reparse.js");
describe("reparseProgramFromActionArgs", () => {
beforeEach(() => {
vi.clearAllMocks();

View File

@ -1,13 +1,14 @@
import process from "node:process";
import { Command } from "commander";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { buildProgram } from "./build-program.js";
import type { ProgramContext } from "./context.js";
const registerProgramCommandsMock = vi.fn();
const createProgramContextMock = vi.fn();
const configureProgramHelpMock = vi.fn();
const registerPreActionHooksMock = vi.fn();
const setProgramContextMock = vi.fn();
const registerProgramCommandsMock = vi.hoisted(() => vi.fn());
const createProgramContextMock = vi.hoisted(() => vi.fn());
const configureProgramHelpMock = vi.hoisted(() => vi.fn());
const registerPreActionHooksMock = vi.hoisted(() => vi.fn());
const setProgramContextMock = vi.hoisted(() => vi.fn());
vi.mock("./command-registry.js", () => ({
registerProgramCommands: registerProgramCommandsMock,
@ -29,8 +30,6 @@ vi.mock("./program-context.js", () => ({
setProgramContext: setProgramContextMock,
}));
const { buildProgram } = await import("./build-program.js");
describe("buildProgram", () => {
beforeEach(() => {
vi.clearAllMocks();

View File

@ -1,7 +1,6 @@
import process from "node:process";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const { buildProgram } = await import("./build-program.js");
import { buildProgram } from "./build-program.js";
describe("buildProgram version alias handling", () => {
let originalArgv: string[];

View File

@ -27,13 +27,6 @@ vi.mock("./register.maintenance.js", () => ({
},
}));
const {
getCoreCliCommandNames,
getCoreCliCommandsWithSubcommands,
registerCoreCliByName,
registerCoreCliCommands,
} = await import("./command-registry.js");
vi.mock("./register.status-health-sessions.js", () => ({
registerStatusHealthSessionsCommands: (program: Command) => {
program.command("status");
@ -44,6 +37,13 @@ vi.mock("./register.status-health-sessions.js", () => ({
},
}));
import {
getCoreCliCommandNames,
getCoreCliCommandsWithSubcommands,
registerCoreCliByName,
registerCoreCliCommands,
} from "./command-registry.js";
const testProgramContext: ProgramContext = {
programVersion: "0.0.0-test",
channelOptions: [],

View File

@ -1,5 +1,5 @@
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { RuntimeEnv } from "../../runtime.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { ensureConfigReady, __test__ } from "./config-guard.js";
const loadAndMaybeMigrateDoctorConfigMock = vi.hoisted(() => vi.fn());
const readConfigFileSnapshotMock = vi.hoisted(() => vi.fn());
@ -12,8 +12,6 @@ vi.mock("../../config/config.js", () => ({
readConfigFileSnapshot: readConfigFileSnapshotMock,
}));
const mockedModuleIds = ["../../commands/doctor-config-preflight.js", "../../config/config.js"];
function makeSnapshot() {
return {
exists: false,
@ -46,13 +44,7 @@ async function withCapturedStdout(run: () => Promise<void>): Promise<string> {
}
describe("ensureConfigReady", () => {
let ensureConfigReady: (params: {
runtime: RuntimeEnv;
commandPath?: string[];
suppressDoctorStdout?: boolean;
allowInvalid?: boolean;
}) => Promise<void>;
let resetConfigGuardStateForTests: () => void;
const resetConfigGuardStateForTests = __test__.resetConfigGuardStateForTests;
async function runEnsureConfigReady(commandPath: string[], suppressDoctorStdout = false) {
const runtime = makeRuntime();
@ -75,20 +67,6 @@ describe("ensureConfigReady", () => {
});
}
beforeAll(async () => {
({
ensureConfigReady,
__test__: { resetConfigGuardStateForTests },
} = await import("./config-guard.js"));
});
afterAll(() => {
for (const id of mockedModuleIds) {
vi.doUnmock(id);
}
vi.resetModules();
});
beforeEach(() => {
vi.clearAllMocks();
resetConfigGuardStateForTests();

View File

@ -1,6 +1,7 @@
import { describe, expect, it, vi } from "vitest";
import { createProgramContext } from "./context.js";
const resolveCliChannelOptionsMock = vi.fn(() => ["telegram", "whatsapp"]);
const resolveCliChannelOptionsMock = vi.hoisted(() => vi.fn(() => ["telegram", "whatsapp"]));
vi.mock("../../version.js", () => ({
VERSION: "9.9.9-test",
@ -10,8 +11,6 @@ vi.mock("../channel-options.js", () => ({
resolveCliChannelOptions: resolveCliChannelOptionsMock,
}));
const { createProgramContext } = await import("./context.js");
describe("createProgramContext", () => {
it("builds program context from version and resolved channel options", () => {
resolveCliChannelOptionsMock.mockClear().mockReturnValue(["telegram", "whatsapp"]);

View File

@ -1,11 +1,14 @@
import { Command } from "commander";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { ProgramContext } from "./context.js";
import { configureProgramHelp } from "./help.js";
const hasEmittedCliBannerMock = vi.fn(() => false);
const formatCliBannerLineMock = vi.fn(() => "BANNER-LINE");
const formatDocsLinkMock = vi.fn((_path: string, full: string) => `https://${full}`);
const resolveCommitHashMock = vi.fn<() => string | null>(() => "abc1234");
const hasEmittedCliBannerMock = vi.hoisted(() => vi.fn(() => false));
const formatCliBannerLineMock = vi.hoisted(() => vi.fn(() => "BANNER-LINE"));
const formatDocsLinkMock = vi.hoisted(() =>
vi.fn((_path: string, full: string) => `https://${full}`),
);
const resolveCommitHashMock = vi.hoisted(() => vi.fn<() => string | null>(() => "abc1234"));
vi.mock("../../terminal/links.js", () => ({
formatDocsLink: formatDocsLinkMock,
@ -44,8 +47,6 @@ vi.mock("./register.subclis.js", () => ({
getSubCliCommandsWithSubcommands: () => ["gateway"],
}));
const { configureProgramHelp } = await import("./help.js");
const testProgramContext: ProgramContext = {
programVersion: "9.9.9-test",
channelOptions: ["telegram"],

View File

@ -1,5 +1,5 @@
import { Command } from "commander";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { repoInstallSpec } from "../../../test/helpers/bundled-plugin-paths.js";
import { loggingState } from "../../logging/state.js";
import { setCommandJsonMode } from "./json-mode.js";
@ -48,15 +48,6 @@ vi.mock("../plugin-registry.js", () => ({
ensurePluginRegistryLoaded: ensurePluginRegistryLoadedMock,
}));
const mockedModuleIds = [
"../../globals.js",
"../../runtime.js",
"../banner.js",
"../cli-name.js",
"./config-guard.js",
"../plugin-registry.js",
];
let registerPreActionHooks: typeof import("./preaction.js").registerPreActionHooks;
let originalProcessArgv: string[];
let originalProcessTitle: string;
@ -70,13 +61,6 @@ beforeAll(async () => {
({ registerPreActionHooks } = await import("./preaction.js"));
});
afterAll(() => {
for (const id of mockedModuleIds) {
vi.doUnmock(id);
}
vi.resetModules();
});
beforeEach(() => {
vi.clearAllMocks();
originalProcessArgv = [...process.argv];

View File

@ -1,67 +1,63 @@
import { Command } from "commander";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createCliRuntimeCapture } from "../test-runtime-capture.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { registerAgentCommands } from "./register.agent.js";
const agentCliCommandMock = vi.fn();
const agentsAddCommandMock = vi.fn();
const agentsBindingsCommandMock = vi.fn();
const agentsBindCommandMock = vi.fn();
const agentsDeleteCommandMock = vi.fn();
const agentsListCommandMock = vi.fn();
const agentsSetIdentityCommandMock = vi.fn();
const agentsUnbindCommandMock = vi.fn();
const setVerboseMock = vi.fn();
const createDefaultDepsMock = vi.fn(() => ({ deps: true }));
const mocks = vi.hoisted(() => ({
agentCliCommandMock: vi.fn(),
agentsAddCommandMock: vi.fn(),
agentsBindingsCommandMock: vi.fn(),
agentsBindCommandMock: vi.fn(),
agentsDeleteCommandMock: vi.fn(),
agentsListCommandMock: vi.fn(),
agentsSetIdentityCommandMock: vi.fn(),
agentsUnbindCommandMock: vi.fn(),
setVerboseMock: vi.fn(),
createDefaultDepsMock: vi.fn(() => ({ deps: true })),
runtime: {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn(),
},
}));
const { defaultRuntime: runtime, resetRuntimeCapture } = createCliRuntimeCapture();
const agentCliCommandMock = mocks.agentCliCommandMock;
const agentsAddCommandMock = mocks.agentsAddCommandMock;
const agentsBindingsCommandMock = mocks.agentsBindingsCommandMock;
const agentsBindCommandMock = mocks.agentsBindCommandMock;
const agentsDeleteCommandMock = mocks.agentsDeleteCommandMock;
const agentsListCommandMock = mocks.agentsListCommandMock;
const agentsSetIdentityCommandMock = mocks.agentsSetIdentityCommandMock;
const agentsUnbindCommandMock = mocks.agentsUnbindCommandMock;
const setVerboseMock = mocks.setVerboseMock;
const createDefaultDepsMock = mocks.createDefaultDepsMock;
const runtime = mocks.runtime;
vi.mock("../../commands/agent-via-gateway.js", () => ({
agentCliCommand: agentCliCommandMock,
agentCliCommand: mocks.agentCliCommandMock,
}));
vi.mock("../../commands/agents.js", () => ({
agentsAddCommand: agentsAddCommandMock,
agentsBindingsCommand: agentsBindingsCommandMock,
agentsBindCommand: agentsBindCommandMock,
agentsDeleteCommand: agentsDeleteCommandMock,
agentsListCommand: agentsListCommandMock,
agentsSetIdentityCommand: agentsSetIdentityCommandMock,
agentsUnbindCommand: agentsUnbindCommandMock,
agentsAddCommand: mocks.agentsAddCommandMock,
agentsBindingsCommand: mocks.agentsBindingsCommandMock,
agentsBindCommand: mocks.agentsBindCommandMock,
agentsDeleteCommand: mocks.agentsDeleteCommandMock,
agentsListCommand: mocks.agentsListCommandMock,
agentsSetIdentityCommand: mocks.agentsSetIdentityCommandMock,
agentsUnbindCommand: mocks.agentsUnbindCommandMock,
}));
vi.mock("../../globals.js", () => ({
setVerbose: setVerboseMock,
setVerbose: mocks.setVerboseMock,
}));
vi.mock("../deps.js", () => ({
createDefaultDeps: createDefaultDepsMock,
createDefaultDeps: mocks.createDefaultDepsMock,
}));
vi.mock("../../runtime.js", () => ({
defaultRuntime: runtime,
defaultRuntime: mocks.runtime,
}));
const mockedModuleIds = [
"../../commands/agent-via-gateway.js",
"../../commands/agents.js",
"../../globals.js",
"../deps.js",
"../../runtime.js",
];
let registerAgentCommands: typeof import("./register.agent.js").registerAgentCommands;
beforeAll(async () => {
({ registerAgentCommands } = await import("./register.agent.js"));
});
afterAll(() => {
for (const id of mockedModuleIds) {
vi.doUnmock(id);
}
vi.resetModules();
});
describe("registerAgentCommands", () => {
async function runCli(args: string[]) {
const program = new Command();
@ -71,7 +67,6 @@ describe("registerAgentCommands", () => {
beforeEach(() => {
vi.clearAllMocks();
resetRuntimeCapture();
runtime.exit.mockImplementation(() => {});
agentCliCommandMock.mockResolvedValue(undefined);
agentsAddCommandMock.mockResolvedValue(undefined);

View File

@ -1,43 +1,33 @@
import { Command } from "commander";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createCliRuntimeCapture } from "../test-runtime-capture.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { registerBackupCommand } from "./register.backup.js";
const backupCreateCommand = vi.fn();
const backupVerifyCommand = vi.fn();
const mocks = vi.hoisted(() => ({
backupCreateCommand: vi.fn(),
backupVerifyCommand: vi.fn(),
runtime: {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn(),
},
}));
const { defaultRuntime: runtime, resetRuntimeCapture } = createCliRuntimeCapture();
const backupCreateCommand = mocks.backupCreateCommand;
const backupVerifyCommand = mocks.backupVerifyCommand;
const runtime = mocks.runtime;
vi.mock("../../commands/backup.js", () => ({
backupCreateCommand,
backupCreateCommand: mocks.backupCreateCommand,
}));
vi.mock("../../commands/backup-verify.js", () => ({
backupVerifyCommand,
backupVerifyCommand: mocks.backupVerifyCommand,
}));
vi.mock("../../runtime.js", () => ({
defaultRuntime: runtime,
defaultRuntime: mocks.runtime,
}));
const mockedModuleIds = [
"../../commands/backup.js",
"../../commands/backup-verify.js",
"../../runtime.js",
];
let registerBackupCommand: typeof import("./register.backup.js").registerBackupCommand;
beforeAll(async () => {
({ registerBackupCommand } = await import("./register.backup.js"));
});
afterAll(() => {
for (const id of mockedModuleIds) {
vi.doUnmock(id);
}
vi.resetModules();
});
describe("registerBackupCommand", () => {
async function runCli(args: string[]) {
const program = new Command();
@ -47,7 +37,6 @@ describe("registerBackupCommand", () => {
beforeEach(() => {
vi.clearAllMocks();
resetRuntimeCapture();
backupCreateCommand.mockResolvedValue(undefined);
backupVerifyCommand.mockResolvedValue(undefined);
});

View File

@ -1,37 +1,27 @@
import { Command } from "commander";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { registerConfigureCommand } from "./register.configure.js";
const configureCommandFromSectionsArgMock = vi.fn();
const runtime = {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn(),
};
const mocks = vi.hoisted(() => ({
configureCommandFromSectionsArgMock: vi.fn(),
runtime: {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn(),
},
}));
const { configureCommandFromSectionsArgMock, runtime } = mocks;
vi.mock("../../commands/configure.js", () => ({
CONFIGURE_WIZARD_SECTIONS: ["auth", "channels", "gateway", "agent"],
configureCommandFromSectionsArg: configureCommandFromSectionsArgMock,
configureCommandFromSectionsArg: mocks.configureCommandFromSectionsArgMock,
}));
vi.mock("../../runtime.js", () => ({
defaultRuntime: runtime,
defaultRuntime: mocks.runtime,
}));
const mockedModuleIds = ["../../commands/configure.js", "../../runtime.js"];
let registerConfigureCommand: typeof import("./register.configure.js").registerConfigureCommand;
beforeAll(async () => {
({ registerConfigureCommand } = await import("./register.configure.js"));
});
afterAll(() => {
for (const id of mockedModuleIds) {
vi.doUnmock(id);
}
vi.resetModules();
});
describe("registerConfigureCommand", () => {
async function runCli(args: string[]) {
const program = new Command();

View File

@ -1,58 +1,41 @@
import { Command } from "commander";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { registerMaintenanceCommands } from "./register.maintenance.js";
const doctorCommand = vi.fn();
const dashboardCommand = vi.fn();
const resetCommand = vi.fn();
const uninstallCommand = vi.fn();
const mocks = vi.hoisted(() => ({
doctorCommand: vi.fn(),
dashboardCommand: vi.fn(),
resetCommand: vi.fn(),
uninstallCommand: vi.fn(),
runtime: {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn(),
},
}));
const runtime = {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn(),
};
const { doctorCommand, dashboardCommand, resetCommand, uninstallCommand, runtime } = mocks;
vi.mock("../../commands/doctor.js", () => ({
doctorCommand,
doctorCommand: mocks.doctorCommand,
}));
vi.mock("../../commands/dashboard.js", () => ({
dashboardCommand,
dashboardCommand: mocks.dashboardCommand,
}));
vi.mock("../../commands/reset.js", () => ({
resetCommand,
resetCommand: mocks.resetCommand,
}));
vi.mock("../../commands/uninstall.js", () => ({
uninstallCommand,
uninstallCommand: mocks.uninstallCommand,
}));
vi.mock("../../runtime.js", () => ({
defaultRuntime: runtime,
defaultRuntime: mocks.runtime,
}));
const mockedModuleIds = [
"../../commands/doctor.js",
"../../commands/dashboard.js",
"../../commands/reset.js",
"../../commands/uninstall.js",
"../../runtime.js",
];
let registerMaintenanceCommands: typeof import("./register.maintenance.js").registerMaintenanceCommands;
beforeAll(async () => {
({ registerMaintenanceCommands } = await import("./register.maintenance.js"));
});
afterAll(() => {
for (const id of mockedModuleIds) {
vi.doUnmock(id);
}
vi.resetModules();
});
describe("registerMaintenanceCommands doctor action", () => {
async function runMaintenanceCli(args: string[]) {
const program = new Command();

View File

@ -1,94 +1,84 @@
import { Command } from "commander";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ProgramContext } from "./context.js";
import { registerMessageCommands } from "./register.message.js";
const createMessageCliHelpersMock = vi.fn(() => ({ helper: true }));
const registerMessageSendCommandMock = vi.fn();
const registerMessageBroadcastCommandMock = vi.fn();
const registerMessagePollCommandMock = vi.fn();
const registerMessageReactionsCommandsMock = vi.fn();
const registerMessageReadEditDeleteCommandsMock = vi.fn();
const registerMessagePinCommandsMock = vi.fn();
const registerMessagePermissionsCommandMock = vi.fn();
const registerMessageSearchCommandMock = vi.fn();
const registerMessageThreadCommandsMock = vi.fn();
const registerMessageEmojiCommandsMock = vi.fn();
const registerMessageStickerCommandsMock = vi.fn();
const registerMessageDiscordAdminCommandsMock = vi.fn();
const mocks = vi.hoisted(() => ({
createMessageCliHelpersMock: vi.fn(() => ({ helper: true })),
registerMessageSendCommandMock: vi.fn(),
registerMessageBroadcastCommandMock: vi.fn(),
registerMessagePollCommandMock: vi.fn(),
registerMessageReactionsCommandsMock: vi.fn(),
registerMessageReadEditDeleteCommandsMock: vi.fn(),
registerMessagePinCommandsMock: vi.fn(),
registerMessagePermissionsCommandMock: vi.fn(),
registerMessageSearchCommandMock: vi.fn(),
registerMessageThreadCommandsMock: vi.fn(),
registerMessageEmojiCommandsMock: vi.fn(),
registerMessageStickerCommandsMock: vi.fn(),
registerMessageDiscordAdminCommandsMock: vi.fn(),
}));
const createMessageCliHelpersMock = mocks.createMessageCliHelpersMock;
const registerMessageSendCommandMock = mocks.registerMessageSendCommandMock;
const registerMessageBroadcastCommandMock = mocks.registerMessageBroadcastCommandMock;
const registerMessagePollCommandMock = mocks.registerMessagePollCommandMock;
const registerMessageReactionsCommandsMock = mocks.registerMessageReactionsCommandsMock;
const registerMessageReadEditDeleteCommandsMock = mocks.registerMessageReadEditDeleteCommandsMock;
const registerMessagePinCommandsMock = mocks.registerMessagePinCommandsMock;
const registerMessagePermissionsCommandMock = mocks.registerMessagePermissionsCommandMock;
const registerMessageSearchCommandMock = mocks.registerMessageSearchCommandMock;
const registerMessageThreadCommandsMock = mocks.registerMessageThreadCommandsMock;
const registerMessageEmojiCommandsMock = mocks.registerMessageEmojiCommandsMock;
const registerMessageStickerCommandsMock = mocks.registerMessageStickerCommandsMock;
const registerMessageDiscordAdminCommandsMock = mocks.registerMessageDiscordAdminCommandsMock;
vi.mock("./message/helpers.js", () => ({
createMessageCliHelpers: createMessageCliHelpersMock,
createMessageCliHelpers: mocks.createMessageCliHelpersMock,
}));
vi.mock("./message/register.send.js", () => ({
registerMessageSendCommand: registerMessageSendCommandMock,
registerMessageSendCommand: mocks.registerMessageSendCommandMock,
}));
vi.mock("./message/register.broadcast.js", () => ({
registerMessageBroadcastCommand: registerMessageBroadcastCommandMock,
registerMessageBroadcastCommand: mocks.registerMessageBroadcastCommandMock,
}));
vi.mock("./message/register.poll.js", () => ({
registerMessagePollCommand: registerMessagePollCommandMock,
registerMessagePollCommand: mocks.registerMessagePollCommandMock,
}));
vi.mock("./message/register.reactions.js", () => ({
registerMessageReactionsCommands: registerMessageReactionsCommandsMock,
registerMessageReactionsCommands: mocks.registerMessageReactionsCommandsMock,
}));
vi.mock("./message/register.read-edit-delete.js", () => ({
registerMessageReadEditDeleteCommands: registerMessageReadEditDeleteCommandsMock,
registerMessageReadEditDeleteCommands: mocks.registerMessageReadEditDeleteCommandsMock,
}));
vi.mock("./message/register.pins.js", () => ({
registerMessagePinCommands: registerMessagePinCommandsMock,
registerMessagePinCommands: mocks.registerMessagePinCommandsMock,
}));
vi.mock("./message/register.permissions-search.js", () => ({
registerMessagePermissionsCommand: registerMessagePermissionsCommandMock,
registerMessageSearchCommand: registerMessageSearchCommandMock,
registerMessagePermissionsCommand: mocks.registerMessagePermissionsCommandMock,
registerMessageSearchCommand: mocks.registerMessageSearchCommandMock,
}));
vi.mock("./message/register.thread.js", () => ({
registerMessageThreadCommands: registerMessageThreadCommandsMock,
registerMessageThreadCommands: mocks.registerMessageThreadCommandsMock,
}));
vi.mock("./message/register.emoji-sticker.js", () => ({
registerMessageEmojiCommands: registerMessageEmojiCommandsMock,
registerMessageStickerCommands: registerMessageStickerCommandsMock,
registerMessageEmojiCommands: mocks.registerMessageEmojiCommandsMock,
registerMessageStickerCommands: mocks.registerMessageStickerCommandsMock,
}));
vi.mock("./message/register.discord-admin.js", () => ({
registerMessageDiscordAdminCommands: registerMessageDiscordAdminCommandsMock,
registerMessageDiscordAdminCommands: mocks.registerMessageDiscordAdminCommandsMock,
}));
const mockedModuleIds = [
"./message/helpers.js",
"./message/register.send.js",
"./message/register.broadcast.js",
"./message/register.poll.js",
"./message/register.reactions.js",
"./message/register.read-edit-delete.js",
"./message/register.pins.js",
"./message/register.permissions-search.js",
"./message/register.thread.js",
"./message/register.emoji-sticker.js",
"./message/register.discord-admin.js",
];
let registerMessageCommands: typeof import("./register.message.js").registerMessageCommands;
beforeAll(async () => {
({ registerMessageCommands } = await import("./register.message.js"));
});
afterAll(() => {
for (const id of mockedModuleIds) {
vi.doUnmock(id);
}
vi.resetModules();
});
describe("registerMessageCommands", () => {
const ctx: ProgramContext = {
programVersion: "9.9.9-test",

View File

@ -1,13 +1,18 @@
import { Command } from "commander";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { registerOnboardCommand } from "./register.onboard.js";
const setupWizardCommandMock = vi.fn();
const mocks = vi.hoisted(() => ({
setupWizardCommandMock: vi.fn(),
runtime: {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn(),
},
}));
const runtime = {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn(),
};
const setupWizardCommandMock = mocks.setupWizardCommandMock;
const runtime = mocks.runtime;
vi.mock("../../commands/auth-choice-options.static.js", () => ({
formatStaticAuthChoiceChoicesForCli: () => "token|oauth",
@ -38,35 +43,13 @@ vi.mock("../../plugins/provider-auth-choices.js", () => ({
}));
vi.mock("../../commands/onboard.js", () => ({
setupWizardCommand: setupWizardCommandMock,
setupWizardCommand: mocks.setupWizardCommandMock,
}));
vi.mock("../../runtime.js", () => ({
defaultRuntime: runtime,
defaultRuntime: mocks.runtime,
}));
const mockedModuleIds = [
"../../commands/auth-choice-options.static.js",
"../../commands/auth-choice-options.js",
"../../commands/onboard-core-auth-flags.js",
"../../plugins/provider-auth-choices.js",
"../../commands/onboard.js",
"../../runtime.js",
];
let registerOnboardCommand: typeof import("./register.onboard.js").registerOnboardCommand;
beforeAll(async () => {
({ registerOnboardCommand } = await import("./register.onboard.js"));
});
afterAll(() => {
for (const id of mockedModuleIds) {
vi.doUnmock(id);
}
vi.resetModules();
});
describe("registerOnboardCommand", () => {
async function runCli(args: string[]) {
const program = new Command();

View File

@ -1,45 +1,33 @@
import { Command } from "commander";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { registerSetupCommand } from "./register.setup.js";
const setupCommandMock = vi.fn();
const setupWizardCommandMock = vi.fn();
const runtime = {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn(),
};
const mocks = vi.hoisted(() => ({
setupCommandMock: vi.fn(),
setupWizardCommandMock: vi.fn(),
runtime: {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn(),
},
}));
const setupCommandMock = mocks.setupCommandMock;
const setupWizardCommandMock = mocks.setupWizardCommandMock;
const runtime = mocks.runtime;
vi.mock("../../commands/setup.js", () => ({
setupCommand: setupCommandMock,
setupCommand: mocks.setupCommandMock,
}));
vi.mock("../../commands/onboard.js", () => ({
setupWizardCommand: setupWizardCommandMock,
setupWizardCommand: mocks.setupWizardCommandMock,
}));
vi.mock("../../runtime.js", () => ({
defaultRuntime: runtime,
defaultRuntime: mocks.runtime,
}));
const mockedModuleIds = [
"../../commands/setup.js",
"../../commands/onboard.js",
"../../runtime.js",
];
let registerSetupCommand: typeof import("./register.setup.js").registerSetupCommand;
beforeAll(async () => {
({ registerSetupCommand } = await import("./register.setup.js"));
});
afterAll(() => {
for (const id of mockedModuleIds) {
vi.doUnmock(id);
}
vi.resetModules();
});
describe("registerSetupCommand", () => {
async function runCli(args: string[]) {
const program = new Command();

View File

@ -1,60 +1,72 @@
import { Command } from "commander";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createCliRuntimeCapture } from "../test-runtime-capture.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { registerStatusHealthSessionsCommands } from "./register.status-health-sessions.js";
const statusCommand = vi.fn();
const healthCommand = vi.fn();
const sessionsCommand = vi.fn();
const sessionsCleanupCommand = vi.fn();
const tasksListCommand = vi.fn();
const tasksAuditCommand = vi.fn();
const tasksMaintenanceCommand = vi.fn();
const tasksShowCommand = vi.fn();
const tasksNotifyCommand = vi.fn();
const tasksCancelCommand = vi.fn();
const setVerbose = vi.fn();
const mocks = vi.hoisted(() => ({
statusCommand: vi.fn(),
healthCommand: vi.fn(),
sessionsCommand: vi.fn(),
sessionsCleanupCommand: vi.fn(),
tasksListCommand: vi.fn(),
tasksAuditCommand: vi.fn(),
tasksMaintenanceCommand: vi.fn(),
tasksShowCommand: vi.fn(),
tasksNotifyCommand: vi.fn(),
tasksCancelCommand: vi.fn(),
setVerbose: vi.fn(),
runtime: {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn(),
},
}));
const { defaultRuntime: runtime, resetRuntimeCapture } = createCliRuntimeCapture();
const statusCommand = mocks.statusCommand;
const healthCommand = mocks.healthCommand;
const sessionsCommand = mocks.sessionsCommand;
const sessionsCleanupCommand = mocks.sessionsCleanupCommand;
const tasksListCommand = mocks.tasksListCommand;
const tasksAuditCommand = mocks.tasksAuditCommand;
const tasksMaintenanceCommand = mocks.tasksMaintenanceCommand;
const tasksShowCommand = mocks.tasksShowCommand;
const tasksNotifyCommand = mocks.tasksNotifyCommand;
const tasksCancelCommand = mocks.tasksCancelCommand;
const setVerbose = mocks.setVerbose;
const runtime = mocks.runtime;
vi.mock("../../commands/status.js", () => ({
statusCommand,
statusCommand: mocks.statusCommand,
}));
vi.mock("../../commands/health.js", () => ({
healthCommand,
healthCommand: mocks.healthCommand,
}));
vi.mock("../../commands/sessions.js", () => ({
sessionsCommand,
sessionsCommand: mocks.sessionsCommand,
}));
vi.mock("../../commands/sessions-cleanup.js", () => ({
sessionsCleanupCommand,
sessionsCleanupCommand: mocks.sessionsCleanupCommand,
}));
vi.mock("../../commands/tasks.js", () => ({
tasksListCommand,
tasksAuditCommand,
tasksMaintenanceCommand,
tasksShowCommand,
tasksNotifyCommand,
tasksCancelCommand,
tasksListCommand: mocks.tasksListCommand,
tasksAuditCommand: mocks.tasksAuditCommand,
tasksMaintenanceCommand: mocks.tasksMaintenanceCommand,
tasksShowCommand: mocks.tasksShowCommand,
tasksNotifyCommand: mocks.tasksNotifyCommand,
tasksCancelCommand: mocks.tasksCancelCommand,
}));
vi.mock("../../globals.js", () => ({
setVerbose,
setVerbose: mocks.setVerbose,
}));
vi.mock("../../runtime.js", () => ({
defaultRuntime: runtime,
defaultRuntime: mocks.runtime,
}));
let registerStatusHealthSessionsCommands: typeof import("./register.status-health-sessions.js").registerStatusHealthSessionsCommands;
beforeAll(async () => {
({ registerStatusHealthSessionsCommands } = await import("./register.status-health-sessions.js"));
});
describe("registerStatusHealthSessionsCommands", () => {
async function runCli(args: string[]) {
const program = new Command();
@ -64,7 +76,6 @@ describe("registerStatusHealthSessionsCommands", () => {
beforeEach(() => {
vi.clearAllMocks();
resetRuntimeCapture();
runtime.exit.mockImplementation(() => {});
statusCommand.mockResolvedValue(undefined);
healthCommand.mockResolvedValue(undefined);

View File

@ -1,5 +1,10 @@
import { Command } from "commander";
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
loadValidatedConfigForPluginRegistration,
registerSubCliByName,
registerSubCliCommands,
} from "./register.subclis.js";
const { acpAction, registerAcpCli } = vi.hoisted(() => {
const action = vi.fn();
@ -27,18 +32,6 @@ vi.mock("../acp-cli.js", () => ({ registerAcpCli }));
vi.mock("../nodes-cli.js", () => ({ registerNodesCli }));
vi.mock("../../config/config.js", () => configModule);
const mockedModuleIds = ["../acp-cli.js", "../nodes-cli.js", "../../config/config.js"];
const { loadValidatedConfigForPluginRegistration, registerSubCliByName, registerSubCliCommands } =
await import("./register.subclis.js");
afterAll(() => {
for (const id of mockedModuleIds) {
vi.doUnmock(id);
}
vi.resetModules();
});
describe("registerSubCliCommands", () => {
const originalArgv = process.argv;
const originalDisableLazySubcommands = process.env.OPENCLAW_DISABLE_LAZY_SUBCOMMANDS;

View File

@ -1,4 +1,5 @@
import { describe, expect, it, vi } from "vitest";
import { renderRootHelpText } from "./root-help.js";
vi.mock("./core-command-descriptors.js", () => ({
getCoreCliCommandDescriptors: () => [
@ -32,8 +33,6 @@ vi.mock("../../plugins/cli.js", () => ({
],
}));
const { renderRootHelpText } = await import("./root-help.js");
describe("root help", () => {
it("includes plugin CLI descriptors alongside core and sub-CLI commands", async () => {
const text = await renderRootHelpText();

View File

@ -1,34 +1,26 @@
import readline from "node:readline/promises";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { isYes, setVerbose, setYes } from "../globals.js";
import { promptYesNo } from "./prompt.js";
type ReadlineMock = {
default: {
createInterface: () => {
question: ReturnType<typeof vi.fn>;
close: ReturnType<typeof vi.fn>;
};
};
};
const readlineState = vi.hoisted(() => {
const question = vi.fn(async () => "");
const close = vi.fn();
const createInterface = vi.fn(() => ({ question, close }));
return { question, close, createInterface };
});
type PromptModule = typeof import("./prompt.js");
type GlobalsModule = typeof import("../globals.js");
vi.mock("node:readline/promises", () => ({
default: { createInterface: readlineState.createInterface },
}));
let promptYesNo: PromptModule["promptYesNo"];
let readline: ReadlineMock;
let isYes: GlobalsModule["isYes"];
let setVerbose: GlobalsModule["setVerbose"];
let setYes: GlobalsModule["setYes"];
beforeEach(async () => {
vi.resetModules();
vi.doMock("node:readline/promises", () => {
const question = vi.fn(async () => "");
const close = vi.fn();
const createInterface = vi.fn(() => ({ question, close }));
return { default: { createInterface } };
});
({ promptYesNo } = await import("./prompt.js"));
({ isYes, setVerbose, setYes } = await import("../globals.js"));
readline = (await import("node:readline/promises")) as unknown as ReadlineMock;
beforeEach(() => {
setYes(false);
setVerbose(false);
readlineState.question.mockReset();
readlineState.question.mockResolvedValue("");
readlineState.close.mockClear();
readlineState.createInterface.mockClear();
});
describe("promptYesNo", () => {
@ -43,16 +35,16 @@ describe("promptYesNo", () => {
it("asks the question and respects default", async () => {
setYes(false);
setVerbose(false);
const { question: questionMock } = readline.default.createInterface();
questionMock.mockResolvedValueOnce("");
expect(readline).toBeTruthy();
readlineState.question.mockResolvedValueOnce("");
const resultDefaultYes = await promptYesNo("Continue?", true);
expect(resultDefaultYes).toBe(true);
questionMock.mockResolvedValueOnce("n");
readlineState.question.mockResolvedValueOnce("n");
const resultNo = await promptYesNo("Continue?", true);
expect(resultNo).toBe(false);
questionMock.mockResolvedValueOnce("y");
readlineState.question.mockResolvedValueOnce("y");
const resultYes = await promptYesNo("Continue?", false);
expect(resultYes).toBe(true);
});

View File

@ -1,5 +1,6 @@
import process from "node:process";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { runCli } from "./run-main.js";
const tryRouteCliMock = vi.hoisted(() => vi.fn());
const loadDotEnvMock = vi.hoisted(() => vi.fn());
@ -67,8 +68,6 @@ vi.mock("./program.js", () => ({
buildProgram: buildProgramMock,
}));
const { runCli } = await import("./run-main.js");
describe("runCli exit behavior", () => {
beforeEach(() => {
vi.clearAllMocks();

View File

@ -3,48 +3,83 @@ import os from "node:os";
import path from "node:path";
import { Command } from "commander";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createCliRuntimeCapture } from "./test-runtime-capture.js";
import { registerSecretsCli } from "./secrets-cli.js";
const callGatewayFromCli = vi.fn();
const runSecretsAudit = vi.fn();
const resolveSecretsAuditExitCode = vi.fn();
const runSecretsConfigureInteractive = vi.fn();
const runSecretsApply = vi.fn();
const confirm = vi.fn();
const mocks = vi.hoisted(() => {
const runtimeLogs: string[] = [];
const runtimeErrors: string[] = [];
const stringifyArgs = (args: unknown[]) => args.map((value) => String(value)).join(" ");
const defaultRuntime = {
log: vi.fn((...args: unknown[]) => {
runtimeLogs.push(stringifyArgs(args));
}),
error: vi.fn((...args: unknown[]) => {
runtimeErrors.push(stringifyArgs(args));
}),
writeStdout: vi.fn((value: string) => {
defaultRuntime.log(value.endsWith("\n") ? value.slice(0, -1) : value);
}),
writeJson: vi.fn((value: unknown, space = 2) => {
defaultRuntime.log(JSON.stringify(value, null, space > 0 ? space : undefined));
}),
exit: vi.fn((code: number) => {
throw new Error(`__exit__:${code}`);
}),
};
return {
callGatewayFromCli: vi.fn(),
runSecretsAudit: vi.fn(),
resolveSecretsAuditExitCode: vi.fn(),
runSecretsConfigureInteractive: vi.fn(),
runSecretsApply: vi.fn(),
confirm: vi.fn(),
defaultRuntime,
runtimeLogs,
runtimeErrors,
};
});
const { defaultRuntime, runtimeLogs, runtimeErrors, resetRuntimeCapture } =
createCliRuntimeCapture();
const {
callGatewayFromCli,
runSecretsAudit,
resolveSecretsAuditExitCode,
runSecretsConfigureInteractive,
runSecretsApply,
confirm,
defaultRuntime,
runtimeLogs,
runtimeErrors,
} = mocks;
vi.mock("./gateway-rpc.js", () => ({
addGatewayClientOptions: (cmd: Command) => cmd,
callGatewayFromCli: (method: string, opts: unknown, params?: unknown, extra?: unknown) =>
callGatewayFromCli(method, opts, params, extra),
mocks.callGatewayFromCli(method, opts, params, extra),
}));
vi.mock("../runtime.js", () => ({
defaultRuntime,
defaultRuntime: mocks.defaultRuntime,
}));
vi.mock("../secrets/audit.js", () => ({
runSecretsAudit: (options: unknown) => runSecretsAudit(options),
runSecretsAudit: (options: unknown) => mocks.runSecretsAudit(options),
resolveSecretsAuditExitCode: (report: unknown, check: boolean) =>
resolveSecretsAuditExitCode(report, check),
mocks.resolveSecretsAuditExitCode(report, check),
}));
vi.mock("../secrets/configure.js", () => ({
runSecretsConfigureInteractive: (options: unknown) => runSecretsConfigureInteractive(options),
runSecretsConfigureInteractive: (options: unknown) =>
mocks.runSecretsConfigureInteractive(options),
}));
vi.mock("../secrets/apply.js", () => ({
runSecretsApply: (options: unknown) => runSecretsApply(options),
runSecretsApply: (options: unknown) => mocks.runSecretsApply(options),
}));
vi.mock("@clack/prompts", () => ({
confirm: (options: unknown) => confirm(options),
confirm: (options: unknown) => mocks.confirm(options),
}));
const { registerSecretsCli } = await import("./secrets-cli.js");
function createManualSecretsPlan() {
return {
version: 1,
@ -126,13 +161,19 @@ describe("secrets CLI", () => {
};
beforeEach(() => {
resetRuntimeCapture();
runtimeLogs.length = 0;
runtimeErrors.length = 0;
callGatewayFromCli.mockReset();
runSecretsAudit.mockReset();
resolveSecretsAuditExitCode.mockReset();
runSecretsConfigureInteractive.mockReset();
runSecretsApply.mockReset();
confirm.mockReset();
defaultRuntime.log.mockClear();
defaultRuntime.error.mockClear();
defaultRuntime.writeStdout.mockClear();
defaultRuntime.writeJson.mockClear();
defaultRuntime.exit.mockClear();
});
it("calls secrets.reload and prints human output", async () => {

View File

@ -1,43 +1,72 @@
import { Command } from "commander";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createCliRuntimeCapture } from "./test-runtime-capture.js";
import { registerSecurityCli } from "./security-cli.js";
const loadConfig = vi.fn();
const runSecurityAudit = vi.fn();
const fixSecurityFootguns = vi.fn();
const resolveCommandSecretRefsViaGateway = vi.fn();
const getSecurityAuditCommandSecretTargetIds = vi.fn(
() => new Set(["gateway.auth.token", "gateway.auth.password"]),
);
const mocks = vi.hoisted(() => {
const runtimeLogs: string[] = [];
const stringifyArgs = (args: unknown[]) => args.map((value) => String(value)).join(" ");
const defaultRuntime = {
log: vi.fn((...args: unknown[]) => {
runtimeLogs.push(stringifyArgs(args));
}),
error: vi.fn(),
writeStdout: vi.fn((value: string) => {
defaultRuntime.log(value.endsWith("\n") ? value.slice(0, -1) : value);
}),
writeJson: vi.fn((value: unknown, space = 2) => {
defaultRuntime.log(JSON.stringify(value, null, space > 0 ? space : undefined));
}),
exit: vi.fn((code: number) => {
throw new Error(`__exit__:${code}`);
}),
};
return {
loadConfig: vi.fn(),
runSecurityAudit: vi.fn(),
fixSecurityFootguns: vi.fn(),
resolveCommandSecretRefsViaGateway: vi.fn(),
getSecurityAuditCommandSecretTargetIds: vi.fn(
() => new Set(["gateway.auth.token", "gateway.auth.password"]),
),
defaultRuntime,
runtimeLogs,
};
});
const { defaultRuntime, runtimeLogs, resetRuntimeCapture } = createCliRuntimeCapture();
const {
loadConfig,
runSecurityAudit,
fixSecurityFootguns,
resolveCommandSecretRefsViaGateway,
getSecurityAuditCommandSecretTargetIds,
runtimeLogs,
} = mocks;
vi.mock("../config/config.js", () => ({
loadConfig: () => loadConfig(),
loadConfig: () => mocks.loadConfig(),
}));
vi.mock("../runtime.js", () => ({
defaultRuntime,
defaultRuntime: mocks.defaultRuntime,
}));
vi.mock("../security/audit.js", () => ({
runSecurityAudit: (opts: unknown) => runSecurityAudit(opts),
runSecurityAudit: (opts: unknown) => mocks.runSecurityAudit(opts),
}));
vi.mock("../security/fix.js", () => ({
fixSecurityFootguns: () => fixSecurityFootguns(),
fixSecurityFootguns: () => mocks.fixSecurityFootguns(),
}));
vi.mock("./command-secret-gateway.js", () => ({
resolveCommandSecretRefsViaGateway: (opts: unknown) => resolveCommandSecretRefsViaGateway(opts),
resolveCommandSecretRefsViaGateway: (opts: unknown) =>
mocks.resolveCommandSecretRefsViaGateway(opts),
}));
vi.mock("./command-secret-targets.js", () => ({
getSecurityAuditCommandSecretTargetIds: () => getSecurityAuditCommandSecretTargetIds(),
getSecurityAuditCommandSecretTargetIds: () => mocks.getSecurityAuditCommandSecretTargetIds(),
}));
const { registerSecurityCli } = await import("./security-cli.js");
function createProgram() {
const program = new Command();
program.exitOverride();
@ -63,7 +92,7 @@ function primeDeepAuditConfig(sourceConfig = { gateway: { mode: "local" } }) {
describe("security CLI", () => {
beforeEach(() => {
resetRuntimeCapture();
runtimeLogs.length = 0;
loadConfig.mockReset();
runSecurityAudit.mockReset();
fixSecurityFootguns.mockReset();

View File

@ -1,40 +1,76 @@
import { Command } from "commander";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createCliRuntimeCapture } from "./test-runtime-capture.js";
import { registerSkillsCli } from "./skills-cli.js";
const loadConfigMock = vi.fn(() => ({}));
const resolveDefaultAgentIdMock = vi.fn(() => "main");
const resolveAgentWorkspaceDirMock = vi.fn(() => "/tmp/workspace");
const searchSkillsFromClawHubMock = vi.fn();
const installSkillFromClawHubMock = vi.fn();
const updateSkillsFromClawHubMock = vi.fn();
const readTrackedClawHubSkillSlugsMock = vi.fn();
const mocks = vi.hoisted(() => {
const runtimeLogs: string[] = [];
const runtimeErrors: string[] = [];
const stringifyArgs = (args: unknown[]) => args.map((value) => String(value)).join(" ");
const defaultRuntime = {
log: vi.fn((...args: unknown[]) => {
runtimeLogs.push(stringifyArgs(args));
}),
error: vi.fn((...args: unknown[]) => {
runtimeErrors.push(stringifyArgs(args));
}),
writeStdout: vi.fn((value: string) => {
defaultRuntime.log(value.endsWith("\n") ? value.slice(0, -1) : value);
}),
writeJson: vi.fn((value: unknown, space = 2) => {
defaultRuntime.log(JSON.stringify(value, null, space > 0 ? space : undefined));
}),
exit: vi.fn((code: number) => {
throw new Error(`__exit__:${code}`);
}),
};
return {
loadConfigMock: vi.fn(() => ({})),
resolveDefaultAgentIdMock: vi.fn(() => "main"),
resolveAgentWorkspaceDirMock: vi.fn(() => "/tmp/workspace"),
searchSkillsFromClawHubMock: vi.fn(),
installSkillFromClawHubMock: vi.fn(),
updateSkillsFromClawHubMock: vi.fn(),
readTrackedClawHubSkillSlugsMock: vi.fn(),
defaultRuntime,
runtimeLogs,
runtimeErrors,
};
});
const { defaultRuntime, runtimeLogs, runtimeErrors, resetRuntimeCapture } =
createCliRuntimeCapture();
const {
loadConfigMock,
resolveDefaultAgentIdMock,
resolveAgentWorkspaceDirMock,
searchSkillsFromClawHubMock,
installSkillFromClawHubMock,
updateSkillsFromClawHubMock,
readTrackedClawHubSkillSlugsMock,
defaultRuntime,
runtimeLogs,
runtimeErrors,
} = mocks;
vi.mock("../runtime.js", () => ({
defaultRuntime,
defaultRuntime: mocks.defaultRuntime,
}));
vi.mock("../config/config.js", () => ({
loadConfig: () => loadConfigMock(),
loadConfig: () => mocks.loadConfigMock(),
}));
vi.mock("../agents/agent-scope.js", () => ({
resolveDefaultAgentId: () => resolveDefaultAgentIdMock(),
resolveAgentWorkspaceDir: () => resolveAgentWorkspaceDirMock(),
resolveDefaultAgentId: () => mocks.resolveDefaultAgentIdMock(),
resolveAgentWorkspaceDir: () => mocks.resolveAgentWorkspaceDirMock(),
}));
vi.mock("../agents/skills-clawhub.js", () => ({
searchSkillsFromClawHub: (...args: unknown[]) => searchSkillsFromClawHubMock(...args),
installSkillFromClawHub: (...args: unknown[]) => installSkillFromClawHubMock(...args),
updateSkillsFromClawHub: (...args: unknown[]) => updateSkillsFromClawHubMock(...args),
readTrackedClawHubSkillSlugs: (...args: unknown[]) => readTrackedClawHubSkillSlugsMock(...args),
searchSkillsFromClawHub: (...args: unknown[]) => mocks.searchSkillsFromClawHubMock(...args),
installSkillFromClawHub: (...args: unknown[]) => mocks.installSkillFromClawHubMock(...args),
updateSkillsFromClawHub: (...args: unknown[]) => mocks.updateSkillsFromClawHubMock(...args),
readTrackedClawHubSkillSlugs: (...args: unknown[]) =>
mocks.readTrackedClawHubSkillSlugsMock(...args),
}));
const { registerSkillsCli } = await import("./skills-cli.js");
describe("skills cli commands", () => {
const createProgram = () => {
const program = new Command();
@ -46,7 +82,8 @@ describe("skills cli commands", () => {
const runCommand = (argv: string[]) => createProgram().parseAsync(argv, { from: "user" });
beforeEach(() => {
resetRuntimeCapture();
runtimeLogs.length = 0;
runtimeErrors.length = 0;
loadConfigMock.mockReset();
resolveDefaultAgentIdMock.mockReset();
resolveAgentWorkspaceDirMock.mockReset();
@ -65,6 +102,11 @@ describe("skills cli commands", () => {
});
updateSkillsFromClawHubMock.mockResolvedValue([]);
readTrackedClawHubSkillSlugsMock.mockResolvedValue([]);
defaultRuntime.log.mockClear();
defaultRuntime.error.mockClear();
defaultRuntime.writeStdout.mockClear();
defaultRuntime.writeJson.mockClear();
defaultRuntime.exit.mockClear();
});
it("searches ClawHub skills from the native CLI", async () => {

View File

@ -1,42 +1,44 @@
import { Command } from "commander";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { runRegisteredCli } from "../test-utils/command-runner.js";
import { createCliRuntimeCapture } from "./test-runtime-capture.js";
import { registerUpdateCli } from "./update-cli.js";
const updateCommand = vi.fn(async (_opts: unknown) => {});
const updateStatusCommand = vi.fn(async (_opts: unknown) => {});
const updateWizardCommand = vi.fn(async (_opts: unknown) => {});
const mocks = vi.hoisted(() => ({
updateCommand: vi.fn(async (_opts: unknown) => {}),
updateStatusCommand: vi.fn(async (_opts: unknown) => {}),
updateWizardCommand: vi.fn(async (_opts: unknown) => {}),
defaultRuntime: {
log: vi.fn(),
error: vi.fn(),
writeStdout: vi.fn(),
writeJson: vi.fn(),
exit: vi.fn(),
},
}));
const { defaultRuntime, resetRuntimeCapture } = createCliRuntimeCapture();
const { updateCommand, updateStatusCommand, updateWizardCommand, defaultRuntime } = mocks;
vi.mock("./update-cli/update-command.js", () => ({
updateCommand: (opts: unknown) => updateCommand(opts),
updateCommand: (opts: unknown) => mocks.updateCommand(opts),
}));
vi.mock("./update-cli/status.js", () => ({
updateStatusCommand: (opts: unknown) => updateStatusCommand(opts),
updateStatusCommand: (opts: unknown) => mocks.updateStatusCommand(opts),
}));
vi.mock("./update-cli/wizard.js", () => ({
updateWizardCommand: (opts: unknown) => updateWizardCommand(opts),
updateWizardCommand: (opts: unknown) => mocks.updateWizardCommand(opts),
}));
vi.mock("../runtime.js", () => ({
defaultRuntime,
defaultRuntime: mocks.defaultRuntime,
}));
describe("update cli option collisions", () => {
let registerUpdateCli: typeof import("./update-cli.js").registerUpdateCli;
beforeAll(async () => {
({ registerUpdateCli } = await import("./update-cli.js"));
});
beforeEach(() => {
updateCommand.mockClear();
updateStatusCommand.mockClear();
updateWizardCommand.mockClear();
resetRuntimeCapture();
defaultRuntime.log.mockClear();
defaultRuntime.error.mockClear();
defaultRuntime.writeStdout.mockClear();

View File

@ -1,13 +1,12 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createGlobalCommandRunner } from "./shared.js";
const runCommandWithTimeout = vi.fn();
const runCommandWithTimeout = vi.hoisted(() => vi.fn());
vi.mock("../../process/exec.js", () => ({
runCommandWithTimeout,
}));
const { createGlobalCommandRunner } = await import("./shared.js");
describe("createGlobalCommandRunner", () => {
beforeEach(() => {
vi.clearAllMocks();

View File

@ -1,9 +1,10 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ReplyPayload } from "../auto-reply/types.js";
import type { CliDeps } from "../cli/deps.js";
import type { OpenClawConfig } from "../config/config.js";
import type { SessionEntry } from "../config/sessions.js";
import type { RuntimeEnv } from "../runtime.js";
import { deliverAgentCommandResult } from "./agent/delivery.js";
const mocks = vi.hoisted(() => ({
deliverOutboundPayloads: vi.fn(async () => []),
@ -30,14 +31,7 @@ vi.mock("../infra/outbound/targets.js", async () => {
};
});
let deliverAgentCommandResult: typeof import("./agent/delivery.js").deliverAgentCommandResult;
describe("deliverAgentCommandResult", () => {
beforeAll(async () => {
vi.resetModules();
({ deliverAgentCommandResult } = await import("./agent/delivery.js"));
});
function createRuntime(): RuntimeEnv {
return {
log: vi.fn(),

View File

@ -1,5 +1,6 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
import { resolveSessionKeyForRequest } from "./session.js";
const mocks = vi.hoisted(() => ({
loadSessionStore: vi.fn(),
@ -22,8 +23,6 @@ vi.mock("../../agents/agent-scope.js", () => ({
listAgentIds: mocks.listAgentIds,
}));
let resolveSessionKeyForRequest: typeof import("./session.js").resolveSessionKeyForRequest;
describe("resolveSessionKeyForRequest", () => {
const MAIN_STORE_PATH = "/tmp/main-store.json";
const MYBOT_STORE_PATH = "/tmp/mybot-store.json";
@ -46,11 +45,6 @@ describe("resolveSessionKeyForRequest", () => {
mocks.loadSessionStore.mockImplementation((storePath: string) => stores[storePath] ?? {});
};
beforeAll(async () => {
vi.resetModules();
({ resolveSessionKeyForRequest } = await import("./session.js"));
});
beforeEach(() => {
vi.clearAllMocks();
mocks.listAgentIds.mockReturnValue(["main"]);

View File

@ -34,9 +34,11 @@ vi.mock("../config/config.js", async (importOriginal) => {
export const runtime = createTestRuntime();
let agentsCommandModulePromise: Promise<typeof import("./agents.js")> | undefined;
export async function loadFreshAgentsCommandModuleForTest() {
vi.resetModules();
return await import("./agents.js");
agentsCommandModulePromise ??= import("./agents.js");
return await agentsCommandModulePromise;
}
export function resetAgentsBindTestHarness(): void {

View File

@ -3,6 +3,7 @@ import os from "node:os";
import path from "node:path";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createTempHomeEnv, type TempHomeEnv } from "../test-utils/temp-home.js";
import { backupCreateCommand } from "./backup.js";
const tarCreateMock = vi.hoisted(() => vi.fn());
const backupVerifyCommandMock = vi.hoisted(() => vi.fn());
@ -15,8 +16,6 @@ vi.mock("./backup-verify.js", () => ({
backupVerifyCommand: backupVerifyCommandMock,
}));
const { backupCreateCommand } = await import("./backup.js");
describe("backupCreateCommand atomic archive write", () => {
let tempHome: TempHomeEnv;

View File

@ -1,4 +1,4 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ChannelPluginCatalogEntry } from "../channels/plugins/catalog.js";
import type { ChannelPlugin } from "../channels/plugins/types.js";
import { setActivePluginRegistry } from "../plugins/runtime.js";
@ -8,6 +8,7 @@ import {
ensureChannelSetupPluginInstalled,
loadChannelSetupPluginRegistrySnapshotForChannel,
} from "./channel-setup/plugin-install.js";
import { channelsAddCommand } from "./channels.js";
import { configMocks, offsetMocks } from "./channels.mock-harness.js";
import {
createMSTeamsCatalogEntry,
@ -47,7 +48,6 @@ vi.mock("./channel-setup/plugin-install.js", async (importOriginal) => {
});
const runtime = createTestRuntime();
let channelsAddCommand: typeof import("./channels.js").channelsAddCommand;
function listConfiguredAccountIds(
channelConfig: { accounts?: Record<string, unknown>; botToken?: string } | undefined,
@ -219,10 +219,6 @@ async function runSignalAddCommand(afterAccountConfigWritten: SignalAfterAccount
}
describe("channelsAddCommand", () => {
beforeAll(async () => {
({ channelsAddCommand } = await import("./channels.js"));
});
beforeEach(async () => {
configMocks.readConfigFileSnapshot.mockClear();
configMocks.writeConfigFile.mockClear();

View File

@ -1,4 +1,4 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ChannelPluginCatalogEntry } from "../channels/plugins/catalog.js";
import { setActivePluginRegistry } from "../plugins/runtime.js";
import { createTestRegistry } from "../test-utils/channel-plugins.js";
@ -6,6 +6,7 @@ import {
ensureChannelSetupPluginInstalled,
loadChannelSetupPluginRegistrySnapshotForChannel,
} from "./channel-setup/plugin-install.js";
import { channelsRemoveCommand } from "./channels.js";
import { configMocks } from "./channels.mock-harness.js";
import {
createMSTeamsCatalogEntry,
@ -33,13 +34,8 @@ vi.mock("./channel-setup/plugin-install.js", async (importOriginal) => {
});
const runtime = createTestRuntime();
let channelsRemoveCommand: typeof import("./channels.js").channelsRemoveCommand;
describe("channelsRemoveCommand", () => {
beforeAll(async () => {
({ channelsRemoveCommand } = await import("./channels.js"));
});
beforeEach(() => {
configMocks.readConfigFileSnapshot.mockClear();
configMocks.writeConfigFile.mockClear();

View File

@ -1,4 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { channelsResolveCommand } from "./channels/resolve.js";
const mocks = vi.hoisted(() => ({
resolveCommandSecretRefsViaGateway: vi.fn(),
@ -42,8 +43,6 @@ vi.mock("../channels/plugins/index.js", () => ({
getChannelPlugin: mocks.getChannelPlugin,
}));
const { channelsResolveCommand } = await import("./channels/resolve.js");
describe("channelsResolveCommand", () => {
const runtime = {
log: vi.fn(),

View File

@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
import { channelsStatusCommand } from "./channels/status.js";
const resolveDefaultAccountId = () => DEFAULT_ACCOUNT_ID;
@ -39,8 +40,6 @@ vi.mock("../cli/progress.js", () => ({
withProgress: (opts: unknown, run: () => Promise<unknown>) => withProgress(opts, run),
}));
const { channelsStatusCommand } = await import("./channels/status.js");
function createTokenOnlyPlugin() {
return {
id: "discord",

View File

@ -1,11 +1,14 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { PluginCompatibilityNotice } from "../plugins/status.js";
import { createCompatibilityNotice } from "../plugins/status.test-helpers.js";
import { requireValidConfigSnapshot } from "./config-validation.js";
const readConfigFileSnapshot = vi.fn();
const buildPluginCompatibilityNotices = vi.fn<(_params?: unknown) => PluginCompatibilityNotice[]>(
() => [],
);
const { readConfigFileSnapshot, buildPluginCompatibilityNotices } = vi.hoisted(() => ({
readConfigFileSnapshot: vi.fn(),
buildPluginCompatibilityNotices: vi.fn<(_params?: unknown) => PluginCompatibilityNotice[]>(
() => [],
),
}));
vi.mock("../config/config.js", () => ({
readConfigFileSnapshot,
@ -46,7 +49,6 @@ describe("requireValidConfigSnapshot", () => {
createValidSnapshot();
const runtime = createRuntime();
const { requireValidConfigSnapshot } = await import("./config-validation.js");
const config = await requireValidConfigSnapshot(runtime);
expect(config).toEqual({ plugins: {} });
@ -60,7 +62,6 @@ describe("requireValidConfigSnapshot", () => {
createValidSnapshot();
const runtime = createRuntime();
const { requireValidConfigSnapshot } = await import("./config-validation.js");
const config = await requireValidConfigSnapshot(runtime, {
includeCompatibilityAdvisory: true,
});
@ -83,7 +84,6 @@ describe("requireValidConfigSnapshot", () => {
});
const runtime = createRuntime();
const { requireValidConfigSnapshot } = await import("./config-validation.js");
const config = await requireValidConfigSnapshot(runtime, {
includeCompatibilityAdvisory: true,
});

View File

@ -1,4 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { maybeInstallDaemon } from "./configure.daemon.js";
const progressSetLabel = vi.hoisted(() => vi.fn());
const withProgress = vi.hoisted(() =>
@ -69,8 +70,6 @@ vi.mock("./systemd-linger.js", () => ({
ensureSystemdUserLingerInteractive,
}));
const { maybeInstallDaemon } = await import("./configure.daemon.js");
describe("maybeInstallDaemon", () => {
beforeEach(() => {
vi.clearAllMocks();

View File

@ -1,4 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { dashboardCommand } from "./dashboard.js";
const readConfigFileSnapshotMock = vi.hoisted(() => vi.fn());
const resolveGatewayPortMock = vi.hoisted(() => vi.fn());
@ -29,8 +30,6 @@ vi.mock("../secrets/resolve.js", () => ({
resolveSecretRefValues: resolveSecretRefValuesMock,
}));
let dashboardCommand: typeof import("./dashboard.js").dashboardCommand;
const runtime = {
log: vi.fn(),
error: vi.fn(),
@ -63,9 +62,7 @@ function mockSnapshot(token: unknown = "abc") {
}
describe("dashboardCommand", () => {
beforeEach(async () => {
vi.resetModules();
({ dashboardCommand } = await import("./dashboard.js"));
beforeEach(() => {
resetRuntime();
readConfigFileSnapshotMock.mockClear();
resolveGatewayPortMock.mockClear();

View File

@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { GatewayBindMode } from "../config/types.gateway.js";
import { dashboardCommand } from "./dashboard.js";
const mocks = vi.hoisted(() => ({
readConfigFileSnapshot: vi.fn(),
@ -29,7 +30,6 @@ const runtime = {
error: vi.fn(),
exit: vi.fn(),
};
let dashboardCommand: typeof import("./dashboard.js").dashboardCommand;
function mockSnapshot(params?: {
token?: string;
@ -62,9 +62,7 @@ function mockSnapshot(params?: {
}
describe("dashboardCommand bind selection", () => {
beforeEach(async () => {
vi.resetModules();
({ dashboardCommand } = await import("./dashboard.js"));
beforeEach(() => {
mocks.readConfigFileSnapshot.mockClear();
mocks.resolveGatewayPort.mockClear();
mocks.resolveControlUiLinks.mockClear();

View File

@ -5,45 +5,47 @@ import {
createTypedHook,
} from "../plugins/status.test-helpers.js";
import * as noteModule from "../terminal/note.js";
import { noteWorkspaceStatus } from "./doctor-workspace-status.js";
const resolveAgentWorkspaceDirMock = vi.fn();
const resolveDefaultAgentIdMock = vi.fn();
const buildWorkspaceSkillStatusMock = vi.fn();
const buildPluginStatusReportMock = vi.fn();
const buildPluginCompatibilityWarningsMock = vi.fn();
const mocks = vi.hoisted(() => ({
resolveAgentWorkspaceDir: vi.fn(),
resolveDefaultAgentId: vi.fn(),
buildWorkspaceSkillStatus: vi.fn(),
buildPluginStatusReport: vi.fn(),
buildPluginCompatibilityWarnings: vi.fn(),
}));
vi.mock("../agents/agent-scope.js", () => ({
resolveAgentWorkspaceDir: (...args: unknown[]) => resolveAgentWorkspaceDirMock(...args),
resolveDefaultAgentId: (...args: unknown[]) => resolveDefaultAgentIdMock(...args),
resolveAgentWorkspaceDir: (...args: unknown[]) => mocks.resolveAgentWorkspaceDir(...args),
resolveDefaultAgentId: (...args: unknown[]) => mocks.resolveDefaultAgentId(...args),
}));
vi.mock("../agents/skills-status.js", () => ({
buildWorkspaceSkillStatus: (...args: unknown[]) => buildWorkspaceSkillStatusMock(...args),
buildWorkspaceSkillStatus: (...args: unknown[]) => mocks.buildWorkspaceSkillStatus(...args),
}));
vi.mock("../plugins/status.js", () => ({
buildPluginStatusReport: (...args: unknown[]) => buildPluginStatusReportMock(...args),
buildPluginStatusReport: (...args: unknown[]) => mocks.buildPluginStatusReport(...args),
buildPluginCompatibilityWarnings: (...args: unknown[]) =>
buildPluginCompatibilityWarningsMock(...args),
mocks.buildPluginCompatibilityWarnings(...args),
}));
async function runNoteWorkspaceStatusForTest(
loadResult: ReturnType<typeof createPluginLoadResult>,
compatibilityWarnings: string[] = [],
) {
resolveDefaultAgentIdMock.mockReturnValue("default");
resolveAgentWorkspaceDirMock.mockReturnValue("/workspace");
buildWorkspaceSkillStatusMock.mockReturnValue({
mocks.resolveDefaultAgentId.mockReturnValue("default");
mocks.resolveAgentWorkspaceDir.mockReturnValue("/workspace");
mocks.buildWorkspaceSkillStatus.mockReturnValue({
skills: [],
});
buildPluginStatusReportMock.mockReturnValue({
mocks.buildPluginStatusReport.mockReturnValue({
workspaceDir: "/workspace",
...loadResult,
});
buildPluginCompatibilityWarningsMock.mockReturnValue(compatibilityWarnings);
mocks.buildPluginCompatibilityWarnings.mockReturnValue(compatibilityWarnings);
const noteSpy = vi.spyOn(noteModule, "note").mockImplementation(() => {});
const { noteWorkspaceStatus } = await import("./doctor-workspace-status.js");
noteWorkspaceStatus({});
return noteSpy;
}
@ -65,7 +67,7 @@ describe("noteWorkspaceStatus", () => {
}),
);
try {
expect(buildPluginStatusReportMock).toHaveBeenCalledWith({
expect(mocks.buildPluginStatusReport).toHaveBeenCalledWith({
config: {},
workspaceDir: "/workspace",
});
@ -138,7 +140,7 @@ describe("noteWorkspaceStatus", () => {
"legacy-plugin still uses legacy before_agent_start",
]);
try {
expect(buildPluginCompatibilityWarningsMock).toHaveBeenCalledWith({
expect(mocks.buildPluginCompatibilityWarnings).toHaveBeenCalledWith({
config: {},
workspaceDir: "/workspace",
report: {

View File

@ -1,23 +1,19 @@
import { beforeAll, describe, expect, it, vi } from "vitest";
import { describe, expect, it, vi } from "vitest";
import {
createDoctorRuntime,
mockDoctorConfigSnapshot,
runStartupMatrixMigration,
} from "./doctor.e2e-harness.js";
import "./doctor.fast-path-mocks.js";
import { doctorCommand } from "./doctor.js";
vi.mock("../plugins/providers.runtime.js", () => ({
resolvePluginProviders: vi.fn(() => []),
}));
const DOCTOR_MIGRATION_TIMEOUT_MS = process.platform === "win32" ? 60_000 : 45_000;
let doctorCommand: typeof import("./doctor.js").doctorCommand;
describe("doctor command", () => {
beforeAll(async () => {
({ doctorCommand } = await import("./doctor.js"));
});
it(
"runs Matrix startup migration during repair flows",
{ timeout: DOCTOR_MIGRATION_TIMEOUT_MS },

View File

@ -1,4 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { maybeRepairAllowlistPolicyAllowFrom } from "./allowlist-policy-repair.js";
const { readChannelAllowFromStoreMock } = vi.hoisted(() => ({
readChannelAllowFromStoreMock: vi.fn(),
@ -8,12 +9,8 @@ vi.mock("../../../pairing/pairing-store.js", () => ({
readChannelAllowFromStore: readChannelAllowFromStoreMock,
}));
let maybeRepairAllowlistPolicyAllowFrom: typeof import("./allowlist-policy-repair.js").maybeRepairAllowlistPolicyAllowFrom;
describe("doctor allowlist-policy repair", () => {
beforeEach(async () => {
vi.resetModules();
({ maybeRepairAllowlistPolicyAllowFrom } = await import("./allowlist-policy-repair.js"));
beforeEach(() => {
readChannelAllowFromStoreMock.mockReset();
});

View File

@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { resolveGatewayInstallToken } from "./gateway-install-token.js";
const readConfigFileSnapshotMock = vi.hoisted(() => vi.fn());
const replaceConfigFileMock = vi.hoisted(() => vi.fn());
@ -57,8 +58,6 @@ vi.mock("./onboard-helpers.js", () => ({
randomToken: randomTokenMock,
}));
const { resolveGatewayInstallToken } = await import("./gateway-install-token.js");
describe("resolveGatewayInstallToken", () => {
beforeEach(() => {
vi.clearAllMocks();

View File

@ -3,147 +3,161 @@ import type { GatewayProbeResult } from "../gateway/probe.js";
import type { GatewayBonjourBeacon } from "../infra/bonjour-discovery.js";
import type { RuntimeEnv } from "../runtime.js";
import { withEnvAsync } from "../test-utils/env.js";
import { gatewayStatusCommand } from "./gateway-status.js";
const readBestEffortConfig = vi.fn(async () => ({
gateway: {
mode: "remote",
remote: { url: "wss://remote.example:18789", token: "rtok" },
auth: { token: "ltok" },
},
}));
const resolveGatewayPort = vi.fn((_cfg?: unknown) => 18789);
const discoverGatewayBeacons = vi.fn(
async (_opts?: unknown): Promise<GatewayBonjourBeacon[]> => [],
);
const pickPrimaryTailnetIPv4 = vi.fn(() => "100.64.0.10");
const sshStop = vi.fn(async () => {});
const resolveSshConfig = vi.fn(
async (
_opts?: unknown,
): Promise<{
user: string;
host: string;
port: number;
identityFiles: string[];
} | null> => null,
);
const startSshPortForward = vi.fn(async (_opts?: unknown) => ({
parsedTarget: { user: "me", host: "studio", port: 22 },
localPort: 18789,
remotePort: 18789,
pid: 123,
stderr: [],
stop: sshStop,
}));
const probeGateway = vi.fn(async (opts: { url: string }): Promise<GatewayProbeResult> => {
const { url } = opts;
if (url.includes("127.0.0.1")) {
return {
ok: true,
url,
connectLatencyMs: 12,
error: null,
close: null,
health: { ok: true },
status: {
linkChannel: {
id: "whatsapp",
label: "WhatsApp",
linked: false,
authAgeMs: null,
},
sessions: { count: 0 },
},
presence: [
{
mode: "gateway",
reason: "self",
host: "local",
ip: "127.0.0.1",
text: "Gateway: local (127.0.0.1) · app test · mode gateway · reason self",
ts: Date.now(),
},
],
configSnapshot: {
path: "/tmp/cfg.json",
exists: true,
valid: true,
config: {
gateway: { mode: "local" },
},
issues: [],
legacyIssues: [],
},
};
}
const mocks = vi.hoisted(() => {
const sshStop = vi.fn(async () => {});
return {
ok: true,
url,
connectLatencyMs: 34,
error: null,
close: null,
health: { ok: true },
status: {
linkChannel: {
id: "whatsapp",
label: "WhatsApp",
linked: true,
authAgeMs: 5_000,
readBestEffortConfig: vi.fn(async () => ({
gateway: {
mode: "remote",
remote: { url: "wss://remote.example:18789", token: "rtok" },
auth: { token: "ltok" },
},
sessions: { count: 2 },
},
presence: [
{
mode: "gateway",
reason: "self",
host: "remote",
ip: "100.64.0.2",
text: "Gateway: remote (100.64.0.2) · app test · mode gateway · reason self",
ts: Date.now(),
},
],
configSnapshot: {
path: "/tmp/remote.json",
exists: true,
valid: true,
config: { gateway: { mode: "remote" } },
issues: [],
legacyIssues: [],
},
})),
resolveGatewayPort: vi.fn((_cfg?: unknown) => 18789),
discoverGatewayBeacons: vi.fn(async (_opts?: unknown): Promise<GatewayBonjourBeacon[]> => []),
pickPrimaryTailnetIPv4: vi.fn(() => "100.64.0.10"),
sshStop,
resolveSshConfig: vi.fn(
async (
_opts?: unknown,
): Promise<{
user: string;
host: string;
port: number;
identityFiles: string[];
} | null> => null,
),
startSshPortForward: vi.fn(async (_opts?: unknown) => ({
parsedTarget: { user: "me", host: "studio", port: 22 },
localPort: 18789,
remotePort: 18789,
pid: 123,
stderr: [],
stop: sshStop,
})),
probeGateway: vi.fn(async (opts: { url: string }): Promise<GatewayProbeResult> => {
const { url } = opts;
if (url.includes("127.0.0.1")) {
return {
ok: true,
url,
connectLatencyMs: 12,
error: null,
close: null,
health: { ok: true },
status: {
linkChannel: {
id: "whatsapp",
label: "WhatsApp",
linked: false,
authAgeMs: null,
},
sessions: { count: 0 },
},
presence: [
{
mode: "gateway",
reason: "self",
host: "local",
ip: "127.0.0.1",
text: "Gateway: local (127.0.0.1) · app test · mode gateway · reason self",
ts: Date.now(),
},
],
configSnapshot: {
path: "/tmp/cfg.json",
exists: true,
valid: true,
config: {
gateway: { mode: "local" },
},
issues: [],
legacyIssues: [],
},
};
}
return {
ok: true,
url,
connectLatencyMs: 34,
error: null,
close: null,
health: { ok: true },
status: {
linkChannel: {
id: "whatsapp",
label: "WhatsApp",
linked: true,
authAgeMs: 5_000,
},
sessions: { count: 2 },
},
presence: [
{
mode: "gateway",
reason: "self",
host: "remote",
ip: "100.64.0.2",
text: "Gateway: remote (100.64.0.2) · app test · mode gateway · reason self",
ts: Date.now(),
},
],
configSnapshot: {
path: "/tmp/remote.json",
exists: true,
valid: true,
config: { gateway: { mode: "remote" } },
issues: [],
legacyIssues: [],
},
};
}),
};
});
vi.mock("../config/config.js", () => ({
const {
readBestEffortConfig,
resolveGatewayPort,
discoverGatewayBeacons,
pickPrimaryTailnetIPv4,
sshStop,
resolveSshConfig,
startSshPortForward,
probeGateway,
} = mocks;
vi.mock("../config/config.js", () => ({
readBestEffortConfig: mocks.readBestEffortConfig,
resolveGatewayPort: mocks.resolveGatewayPort,
}));
vi.mock("../infra/bonjour-discovery.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../infra/bonjour-discovery.js")>();
return {
...actual,
discoverGatewayBeacons,
discoverGatewayBeacons: mocks.discoverGatewayBeacons,
};
});
vi.mock("../infra/tailnet.js", () => ({
pickPrimaryTailnetIPv4,
pickPrimaryTailnetIPv4: mocks.pickPrimaryTailnetIPv4,
}));
vi.mock("../infra/ssh-tunnel.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../infra/ssh-tunnel.js")>();
return {
...actual,
startSshPortForward,
startSshPortForward: mocks.startSshPortForward,
};
});
vi.mock("../infra/ssh-config.js", () => ({
resolveSshConfig,
resolveSshConfig: mocks.resolveSshConfig,
}));
vi.mock("../gateway/probe.js", () => ({
probeGateway,
probeGateway: mocks.probeGateway,
}));
function createRuntimeCapture() {
@ -194,7 +208,6 @@ async function runGatewayStatus(
runtime: ReturnType<typeof createRuntimeCapture>["runtime"],
opts: { timeout: string; json?: boolean; ssh?: string; sshAuto?: boolean; sshIdentity?: string },
) {
const { gatewayStatusCommand } = await import("./gateway-status.js");
await gatewayStatusCommand(opts, asRuntimeEnv(runtime));
}

View File

@ -25,7 +25,6 @@ type TelegramHealthAccount = {
};
async function loadFreshHealthModulesForTest() {
vi.resetModules();
vi.doMock("../config/config.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../config/config.js")>();
return {

View File

@ -1,6 +1,7 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { CliDeps } from "../cli/outbound-send-deps.js";
import type { RuntimeEnv } from "../runtime.js";
import { messageCommand } from "./message.js";
let testConfig: Record<string, unknown> = {};
@ -57,9 +58,6 @@ describe("messageCommand agent routing", () => {
error: vi.fn(),
exit: vi.fn(),
};
const { messageCommand } = await import("./message.js");
await messageCommand(
{
action: "send",

View File

@ -56,7 +56,6 @@ let envSnapshot: ReturnType<typeof captureEnv>;
const EMPTY_TEST_REGISTRY = createTestRegistry([]);
beforeAll(async () => {
vi.resetModules();
({ messageCommand } = await import("./message.js"));
});

View File

@ -177,7 +177,6 @@ function installModelsListCommandForwardCompatMocks() {
}
beforeAll(async () => {
vi.resetModules();
installModelsListCommandForwardCompatMocks();
({ modelsListCommand } = await import("./list.list-command.js"));
});

View File

@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
import { loadValidConfigOrThrow, updateConfig } from "./shared.js";
const mocks = vi.hoisted(() => ({
readConfigFileSnapshot: vi.fn(),
@ -11,15 +12,10 @@ vi.mock("../../config/config.js", () => ({
replaceConfigFile: (...args: unknown[]) => mocks.replaceConfigFile(...args),
}));
let loadValidConfigOrThrow: typeof import("./shared.js").loadValidConfigOrThrow;
let updateConfig: typeof import("./shared.js").updateConfig;
describe("models/shared", () => {
beforeEach(async () => {
vi.resetModules();
beforeEach(() => {
mocks.readConfigFileSnapshot.mockClear();
mocks.replaceConfigFile.mockClear();
({ loadValidConfigOrThrow, updateConfig } = await import("./shared.js"));
});
it("returns config when snapshot is valid", async () => {

View File

@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../config/config.js";
import { installGatewayDaemonNonInteractive } from "./daemon-install.js";
const buildGatewayInstallPlan = vi.hoisted(() => vi.fn());
const gatewayInstallErrorHint = vi.hoisted(() => vi.fn(() => "hint"));
@ -36,8 +37,6 @@ vi.mock("../../systemd-linger.js", () => ({
ensureSystemdUserLingerNonInteractive,
}));
const { installGatewayDaemonNonInteractive } = await import("./daemon-install.js");
describe("installGatewayDaemonNonInteractive", () => {
beforeEach(() => {
vi.clearAllMocks();

View File

@ -3,6 +3,7 @@ import type { OpenClawConfig } from "../config/config.js";
import type { GatewayBonjourBeacon } from "../infra/bonjour-discovery.js";
import { captureEnv } from "../test-utils/env.js";
import type { WizardPrompter } from "../wizard/prompts.js";
import { promptRemoteGatewayConfig } from "./onboard-remote.js";
import { createWizardPrompter } from "./test-wizard-helpers.js";
const discoverGatewayBeacons = vi.hoisted(() => vi.fn<() => Promise<GatewayBonjourBeacon[]>>());
@ -25,8 +26,6 @@ vi.mock("./onboard-helpers.js", () => ({
detectBinary,
}));
const { promptRemoteGatewayConfig } = await import("./onboard-remote.js");
function createPrompter(overrides: Partial<WizardPrompter>): WizardPrompter {
return createWizardPrompter(overrides, { defaultSelect: "" });
}

View File

@ -98,7 +98,6 @@ describe("onboard-search provider resolution", () => {
let mod: typeof import("./onboard-search.js");
beforeAll(async () => {
vi.resetModules();
mod = await import("./onboard-search.js");
});

View File

@ -1,6 +1,7 @@
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { RuntimeEnv } from "../runtime.js";
import { onboardCommand, setupWizardCommand } from "./onboard.js";
const mocks = vi.hoisted(() => ({
runInteractiveSetup: vi.fn(async () => {}),
@ -26,8 +27,6 @@ vi.mock("./onboard-helpers.js", () => ({
handleReset: mocks.handleReset,
}));
const { onboardCommand, setupWizardCommand } = await import("./onboard.js");
function makeRuntime(): RuntimeEnv {
return {
log: vi.fn(),

View File

@ -1,6 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createNonExitingRuntime } from "../runtime.js";
const resolveCleanupPlanFromDisk = vi.fn();
const removePath = vi.fn();
const listAgentSessionDirs = vi.fn();
@ -22,10 +21,13 @@ vi.mock("./cleanup-utils.js", () => ({
removeWorkspaceDirs,
}));
const { resetCommand } = await import("./reset.js");
describe("resetCommand", () => {
const runtime = createNonExitingRuntime();
let resetCommand: typeof import("./reset.js").resetCommand;
beforeAll(async () => {
({ resetCommand } = await import("./reset.js"));
});
beforeEach(() => {
vi.clearAllMocks();

View File

@ -1,4 +1,5 @@
import { describe, expect, it, vi } from "vitest";
import { sandboxExplainCommand } from "./sandbox-explain.js";
const SANDBOX_EXPLAIN_TEST_TIMEOUT_MS = process.platform === "win32" ? 45_000 : 30_000;
@ -12,8 +13,6 @@ vi.mock("../config/config.js", async (importOriginal) => {
};
});
const { sandboxExplainCommand } = await import("./sandbox-explain.js");
describe("sandbox explain command", () => {
it("prints JSON shape + fix-it keys", { timeout: SANDBOX_EXPLAIN_TEST_TIMEOUT_MS }, async () => {
mockCfg = {

View File

@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { RuntimeEnv } from "../runtime.js";
import { statusJsonCommand } from "./status-json.js";
const mocks = vi.hoisted(() => ({
scanStatusJsonFast: vi.fn(),
@ -41,8 +42,6 @@ vi.mock("../infra/update-channels.js", () => ({
resolveUpdateChannelDisplay: mocks.resolveUpdateChannelDisplay,
}));
const { statusJsonCommand } = await import("./status-json.js");
function createRuntimeCapture() {
const logs: string[] = [];
const runtime: RuntimeEnv = {

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../channels/config-presence.js", () => ({
hasPotentialConfiguredChannels: vi.fn(() => true),
@ -113,21 +113,20 @@ vi.mock("./status.link-channel.js", () => ({
const { hasPotentialConfiguredChannels } = await import("../channels/config-presence.js");
const { buildChannelSummary } = await import("../infra/channel-summary.js");
const { resolveLinkChannelContext } = await import("./status.link-channel.js");
async function loadStatusSummaryForTest() {
vi.resetModules();
const { getStatusSummary } = await import("./status.summary.js");
const { statusSummaryRuntime } = await import("./status.summary.runtime.js");
return { getStatusSummary, statusSummaryRuntime };
}
let getStatusSummary: typeof import("./status.summary.js").getStatusSummary;
let statusSummaryRuntime: typeof import("./status.summary.runtime.js").statusSummaryRuntime;
describe("getStatusSummary", () => {
beforeAll(async () => {
({ getStatusSummary } = await import("./status.summary.js"));
({ statusSummaryRuntime } = await import("./status.summary.runtime.js"));
});
beforeEach(() => {
vi.clearAllMocks();
});
it("includes runtimeVersion in the status payload", async () => {
const { getStatusSummary } = await loadStatusSummaryForTest();
const summary = await getStatusSummary();
expect(summary.runtimeVersion).toBe("2026.3.8");
@ -138,7 +137,6 @@ describe("getStatusSummary", () => {
});
it("skips channel summary imports when no channels are configured", async () => {
const { getStatusSummary } = await loadStatusSummaryForTest();
vi.mocked(hasPotentialConfiguredChannels).mockReturnValue(false);
const summary = await getStatusSummary();
@ -150,7 +148,6 @@ describe("getStatusSummary", () => {
});
it("does not trigger async context warmup while building status summaries", async () => {
const { getStatusSummary, statusSummaryRuntime } = await loadStatusSummaryForTest();
await getStatusSummary();
expect(vi.mocked(statusSummaryRuntime.resolveContextTokensForModel)).toHaveBeenCalledWith(

View File

@ -1,18 +1,41 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createCliRuntimeCapture } from "../cli/test-runtime-capture.js";
import {
tasksAuditCommand,
tasksCancelCommand,
tasksListCommand,
tasksMaintenanceCommand,
tasksNotifyCommand,
tasksShowCommand,
} from "./tasks.js";
const reconcileInspectableTasksMock = vi.fn();
const reconcileTaskLookupTokenMock = vi.fn();
const listTaskAuditFindingsMock = vi.fn();
const summarizeTaskAuditFindingsMock = vi.fn();
const previewTaskRegistryMaintenanceMock = vi.fn();
const runTaskRegistryMaintenanceMock = vi.fn();
const getInspectableTaskRegistrySummaryMock = vi.fn();
const getInspectableTaskAuditSummaryMock = vi.fn();
const updateTaskNotifyPolicyByIdMock = vi.fn();
const cancelTaskByIdMock = vi.fn();
const getTaskByIdMock = vi.fn();
const loadConfigMock = vi.fn(() => ({ loaded: true }));
const mocks = vi.hoisted(() => ({
reconcileInspectableTasksMock: vi.fn(),
reconcileTaskLookupTokenMock: vi.fn(),
listTaskAuditFindingsMock: vi.fn(),
summarizeTaskAuditFindingsMock: vi.fn(),
previewTaskRegistryMaintenanceMock: vi.fn(),
runTaskRegistryMaintenanceMock: vi.fn(),
getInspectableTaskRegistrySummaryMock: vi.fn(),
getInspectableTaskAuditSummaryMock: vi.fn(),
updateTaskNotifyPolicyByIdMock: vi.fn(),
cancelTaskByIdMock: vi.fn(),
getTaskByIdMock: vi.fn(),
loadConfigMock: vi.fn(() => ({ loaded: true })),
}));
const reconcileInspectableTasksMock = mocks.reconcileInspectableTasksMock;
const reconcileTaskLookupTokenMock = mocks.reconcileTaskLookupTokenMock;
const listTaskAuditFindingsMock = mocks.listTaskAuditFindingsMock;
const summarizeTaskAuditFindingsMock = mocks.summarizeTaskAuditFindingsMock;
const previewTaskRegistryMaintenanceMock = mocks.previewTaskRegistryMaintenanceMock;
const runTaskRegistryMaintenanceMock = mocks.runTaskRegistryMaintenanceMock;
const getInspectableTaskRegistrySummaryMock = mocks.getInspectableTaskRegistrySummaryMock;
const getInspectableTaskAuditSummaryMock = mocks.getInspectableTaskAuditSummaryMock;
const updateTaskNotifyPolicyByIdMock = mocks.updateTaskNotifyPolicyByIdMock;
const cancelTaskByIdMock = mocks.cancelTaskByIdMock;
const getTaskByIdMock = mocks.getTaskByIdMock;
const loadConfigMock = mocks.loadConfigMock;
vi.mock("../tasks/task-registry.reconcile.js", () => ({
reconcileInspectableTasks: (...args: unknown[]) => reconcileInspectableTasksMock(...args),
@ -51,13 +74,6 @@ const {
resetRuntimeCapture,
} = createCliRuntimeCapture();
let tasksListCommand: typeof import("./tasks.js").tasksListCommand;
let tasksShowCommand: typeof import("./tasks.js").tasksShowCommand;
let tasksNotifyCommand: typeof import("./tasks.js").tasksNotifyCommand;
let tasksCancelCommand: typeof import("./tasks.js").tasksCancelCommand;
let tasksAuditCommand: typeof import("./tasks.js").tasksAuditCommand;
let tasksMaintenanceCommand: typeof import("./tasks.js").tasksMaintenanceCommand;
const taskFixture = {
taskId: "task-12345678",
runtime: "acp",
@ -74,17 +90,6 @@ const taskFixture = {
progressSummary: "No output for 60s. It may be waiting for input.",
} as const;
beforeAll(async () => {
({
tasksListCommand,
tasksShowCommand,
tasksNotifyCommand,
tasksCancelCommand,
tasksAuditCommand,
tasksMaintenanceCommand,
} = await import("./tasks.js"));
});
describe("tasks commands", () => {
beforeEach(() => {
vi.clearAllMocks();