test: restore runtime-aware cli mocks

This commit is contained in:
Peter Steinberger 2026-03-22 18:28:42 -07:00
parent c43bfcbbec
commit 75835fc664
No known key found for this signature in database
16 changed files with 110 additions and 81 deletions

View File

@ -42,10 +42,10 @@ vi.mock("../runtime.js", async (importOriginal) => {
...actual.defaultRuntime,
log: (...args: unknown[]) => mockLog(...args),
error: (...args: unknown[]) => mockError(...args),
exit: (code: number) => mockExit(code),
writeStdout: (value: string) => mockLog(value.endsWith("\n") ? value.slice(0, -1) : value),
writeJson: (value: unknown, space = 2) =>
mockLog(JSON.stringify(value, null, space > 0 ? space : undefined)),
exit: (code: number) => mockExit(code),
},
};
});

View File

@ -25,17 +25,24 @@ vi.mock("./gateway-rpc.js", async () => {
};
});
vi.mock("../runtime.js", () => ({
defaultRuntime: {
log: vi.fn(),
error: vi.fn(),
writeStdout: vi.fn(),
writeJson: vi.fn(),
exit: (code: number) => {
throw new Error(`__exit__:${code}`);
vi.mock("../runtime.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../runtime.js")>();
const log = vi.fn();
return {
...actual,
defaultRuntime: {
...actual.defaultRuntime,
log,
error: vi.fn(),
writeStdout: (value: string) => log(value.endsWith("\n") ? value.slice(0, -1) : value),
writeJson: (value: unknown, space = 2) =>
log(JSON.stringify(value, null, space > 0 ? space : undefined)),
exit: (code: number) => {
throw new Error(`__exit__:${code}`);
},
},
},
}));
};
});
const { registerCronCli } = await import("./cron-cli.js");

View File

@ -81,7 +81,8 @@ vi.mock("../infra/ports.js", () => ({
formatPortDiagnostics: () => ["Port 18789 is already in use."],
}));
vi.mock("../runtime.js", () => ({
vi.mock("../runtime.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../runtime.js")>()),
defaultRuntime,
}));

View File

@ -24,21 +24,24 @@ vi.mock("../../daemon/service.js", () => ({
resolveGatewayService: () => serviceMock,
}));
vi.mock("../../runtime.js", () => ({
defaultRuntime: {
log: (message: string) => runtimeLogs.push(message),
error: (message: string) => runtimeErrors.push(message),
writeStdout: (value: string) => {
runtimeLogs.push(value.endsWith("\n") ? value.slice(0, -1) : value);
vi.mock("../../runtime.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../../runtime.js")>();
return {
...actual,
defaultRuntime: {
...actual.defaultRuntime,
log: (message: string) => runtimeLogs.push(message),
error: (message: string) => runtimeErrors.push(message),
writeStdout: (value: string) =>
runtimeLogs.push(value.endsWith("\n") ? value.slice(0, -1) : value),
writeJson: (value: unknown, space = 2) =>
runtimeLogs.push(JSON.stringify(value, null, space > 0 ? space : undefined)),
exit: (code: number) => {
throw new Error(`__exit__:${code}`);
},
},
writeJson: (value: unknown, space = 2) => {
runtimeLogs.push(JSON.stringify(value, null, space > 0 ? space : undefined));
},
exit: (code: number) => {
throw new Error(`__exit__:${code}`);
},
},
}));
};
});
const { runDaemonInstall } = await import("./install.js");
const { clearConfigCache } = await import("../../config/config.js");

View File

@ -8,8 +8,6 @@ export const runtimeLogs: string[] = [];
type LifecycleRuntimeHarness = OutputRuntimeEnv & {
error: MockFn<OutputRuntimeEnv["error"]>;
exit: MockFn<OutputRuntimeEnv["exit"]>;
writeStdout: MockFn<(value: string) => void>;
writeJson: MockFn<(value: unknown, space?: number) => void>;
};
type LifecycleServiceHarness = GatewayService & {
@ -26,16 +24,16 @@ export const defaultRuntime: LifecycleRuntimeHarness = {
log: (...args: unknown[]) => {
runtimeLogs.push(args.map((arg) => String(arg)).join(" "));
},
writeStdout: (value: string) => {
runtimeLogs.push(value.endsWith("\n") ? value.slice(0, -1) : value);
},
writeJson: (value: unknown, space = 2) => {
runtimeLogs.push(JSON.stringify(value, null, space > 0 ? space : undefined));
},
error: vi.fn(),
exit: vi.fn((code: number) => {
throw new Error(`__exit__:${code}`);
}),
writeStdout: vi.fn((value: string) => {
runtimeLogs.push(value.endsWith("\n") ? value.slice(0, -1) : value);
}),
writeJson: vi.fn((value: unknown, space = 2) => {
runtimeLogs.push(JSON.stringify(value, null, space > 0 ? space : undefined));
}),
};
export const service: LifecycleServiceHarness = {

View File

@ -38,7 +38,8 @@ vi.mock("../infra/device-pairing.js", () => ({
summarizeDeviceTokens,
}));
vi.mock("../runtime.js", () => ({
vi.mock("../runtime.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../runtime.js")>()),
defaultRuntime: runtime,
}));

View File

@ -35,16 +35,21 @@ vi.mock("../channels/plugins/helpers.js", () => ({
resolveChannelDefaultAccountId: mocks.resolveChannelDefaultAccountId,
}));
vi.mock("../runtime.js", () => ({
defaultRuntime: {
log: (...args: unknown[]) => mocks.log(...args),
error: (...args: unknown[]) => mocks.error(...args),
writeStdout: (value: string) => mocks.log(value.endsWith("\n") ? value.slice(0, -1) : value),
writeJson: (value: unknown, space = 2) =>
mocks.log(JSON.stringify(value, null, space > 0 ? space : undefined)),
exit: (...args: unknown[]) => mocks.exit(...args),
},
}));
vi.mock("../runtime.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../runtime.js")>();
return {
...actual,
defaultRuntime: {
...actual.defaultRuntime,
log: (...args: unknown[]) => mocks.log(...args),
error: (...args: unknown[]) => mocks.error(...args),
writeStdout: (value: string) => mocks.log(value.endsWith("\n") ? value.slice(0, -1) : value),
writeJson: (value: unknown, space = 2) =>
mocks.log(JSON.stringify(value, null, space > 0 ? space : undefined)),
exit: (...args: unknown[]) => mocks.exit(...args),
},
};
});
describe("registerDirectoryCli", () => {
beforeEach(() => {

View File

@ -51,7 +51,8 @@ vi.mock("../globals.js", () => ({
setVerbose: (enabled: boolean) => setVerbose(enabled),
}));
vi.mock("../runtime.js", () => ({
vi.mock("../runtime.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../runtime.js")>()),
defaultRuntime,
}));

View File

@ -23,7 +23,8 @@ vi.mock("../cli-utils.js", () => ({
},
}));
vi.mock("../../runtime.js", () => ({
vi.mock("../../runtime.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../../runtime.js")>()),
defaultRuntime,
}));

View File

@ -11,16 +11,21 @@ const mockExit = vi.fn((code: number) => {
throw new Error(`__exit__:${code}`);
});
vi.mock("../runtime.js", () => ({
defaultRuntime: {
log: (...args: unknown[]) => mockLog(...args),
error: (...args: unknown[]) => mockError(...args),
writeStdout: (value: string) => mockLog(value.endsWith("\n") ? value.slice(0, -1) : value),
writeJson: (value: unknown, space = 2) =>
mockLog(JSON.stringify(value, null, space > 0 ? space : undefined)),
exit: (code: number) => mockExit(code),
},
}));
vi.mock("../runtime.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../runtime.js")>();
return {
...actual,
defaultRuntime: {
...actual.defaultRuntime,
log: (...args: unknown[]) => mockLog(...args),
error: (...args: unknown[]) => mockError(...args),
writeStdout: (value: string) => mockLog(value.endsWith("\n") ? value.slice(0, -1) : value),
writeJson: (value: unknown, space = 2) =>
mockLog(JSON.stringify(value, null, space > 0 ? space : undefined)),
exit: (code: number) => mockExit(code),
},
};
});
const tempDirs: string[] = [];

View File

@ -61,7 +61,7 @@ describe("memory cli", () => {
function spyRuntimeLogs() {
const logSpy = vi.spyOn(defaultRuntime, "log").mockImplementation(() => {});
vi.spyOn(defaultRuntime, "writeJson").mockImplementation((value: unknown, space = 2) => {
logSpy(JSON.stringify(value, null, space));
logSpy(JSON.stringify(value, null, space > 0 ? space : undefined));
});
return logSpy;
}

View File

@ -88,7 +88,8 @@ vi.mock("../gateway/call.js", () => ({
randomIdempotencyKey: () => randomIdempotencyKey(),
}));
vi.mock("../runtime.js", () => ({
vi.mock("../runtime.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../runtime.js")>()),
defaultRuntime,
}));

View File

@ -27,7 +27,10 @@ const mocks = vi.hoisted(() => ({
}),
}));
vi.mock("../runtime.js", () => ({ defaultRuntime: mocks.runtime }));
vi.mock("../runtime.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../runtime.js")>()),
defaultRuntime: mocks.runtime,
}));
vi.mock("../config/config.js", () => ({ loadConfig: mocks.loadConfig }));
vi.mock("../process/exec.js", () => ({ runCommandWithTimeout: mocks.runCommandWithTimeout }));
vi.mock("./command-secret-gateway.js", () => ({

View File

@ -13,7 +13,8 @@ vi.mock("./gateway-rpc.js", () => ({
callGatewayFromCli,
}));
vi.mock("../runtime.js", () => ({
vi.mock("../runtime.js", async (importOriginal) => ({
...(await importOriginal<typeof import("../runtime.js")>()),
defaultRuntime,
}));

View File

@ -3,7 +3,7 @@ import type { OutputRuntimeEnv } from "../runtime.js";
export type CliRuntimeCapture = {
runtimeLogs: string[];
runtimeErrors: string[];
defaultRuntime: Pick<OutputRuntimeEnv, "log" | "error" | "exit" | "writeJson" | "writeStdout">;
defaultRuntime: Pick<OutputRuntimeEnv, "log" | "error" | "exit" | "writeStdout" | "writeJson">;
resetRuntimeCapture: () => void;
};
@ -11,6 +11,9 @@ export function createCliRuntimeCapture(): CliRuntimeCapture {
const runtimeLogs: string[] = [];
const runtimeErrors: string[] = [];
const stringifyArgs = (args: unknown[]) => args.map((value) => String(value)).join(" ");
const writeLine = (value: string) => {
runtimeLogs.push(value.endsWith("\n") ? value.slice(0, -1) : value);
};
return {
runtimeLogs,
runtimeErrors,
@ -22,10 +25,10 @@ export function createCliRuntimeCapture(): CliRuntimeCapture {
runtimeErrors.push(stringifyArgs(args));
},
writeStdout: (value: string) => {
runtimeLogs.push(value.endsWith("\n") ? value.slice(0, -1) : value);
writeLine(value);
},
writeJson: (value: unknown, space = 2) => {
runtimeLogs.push(JSON.stringify(value, null, space > 0 ? space : undefined));
writeLine(JSON.stringify(value, null, space > 0 ? space : undefined));
},
exit: (code: number) => {
throw new Error(`__exit__:${code}`);

View File

@ -25,12 +25,6 @@ const formatPortDiagnostics = vi.fn();
const pathExists = vi.fn();
const syncPluginsForUpdateChannel = vi.fn();
const updateNpmInstalledPlugins = vi.fn();
const runtimeLog = vi.fn();
const runtimeError = vi.fn();
const runtimeExit = vi.fn();
const runtimeWriteJson = vi.fn((value: unknown, space = 2) =>
runtimeLog(JSON.stringify(value, null, space > 0 ? space : undefined)),
);
vi.mock("@clack/prompts", () => ({
confirm,
@ -135,17 +129,22 @@ vi.mock("./daemon-cli.js", () => ({
}));
// Mock the runtime
vi.mock("../runtime.js", () => ({
defaultRuntime: {
log: runtimeLog,
error: runtimeError,
exit: runtimeExit,
writeStdout: vi.fn((value: string) =>
runtimeLog(value.endsWith("\n") ? value.slice(0, -1) : value),
),
writeJson: runtimeWriteJson,
},
}));
vi.mock("../runtime.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../runtime.js")>();
const log = vi.fn();
return {
...actual,
defaultRuntime: {
...actual.defaultRuntime,
log,
error: vi.fn(),
writeStdout: (value: string) => log(value.endsWith("\n") ? value.slice(0, -1) : value),
writeJson: (value: unknown, space = 2) =>
log(JSON.stringify(value, null, space > 0 ? space : undefined)),
exit: vi.fn(),
},
};
});
const { runGatewayUpdate } = await import("../infra/update-runner.js");
const { resolveOpenClawPackageRoot } = await import("../infra/openclaw-root.js");