Files
openclaw/src/cli/daemon-cli/lifecycle-core.config-guard.test.ts
merlin 335223af32 test: add runServiceStart config pre-flight tests (#35862)
Address Greptile review: add test coverage for runServiceStart path.
The error message copy-paste issue was already fixed in the DRY refactor
(uses params.serviceNoun instead of hardcoded 'restart').
2026-03-09 05:53:52 +00:00

207 lines
5.3 KiB
TypeScript

import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const readConfigFileSnapshotMock = vi.fn();
const loadConfig = vi.fn(() => ({}));
const runtimeLogs: string[] = [];
const defaultRuntime = {
log: (message: string) => runtimeLogs.push(message),
error: vi.fn(),
exit: (code: number) => {
throw new Error(`__exit__:${code}`);
},
};
const service = {
label: "TestService",
loadedText: "loaded",
notLoadedText: "not loaded",
install: vi.fn(),
uninstall: vi.fn(),
stop: vi.fn(),
isLoaded: vi.fn(),
readCommand: vi.fn(),
readRuntime: vi.fn(),
restart: vi.fn(),
};
vi.mock("../../config/config.js", () => ({
loadConfig: () => loadConfig(),
readConfigFileSnapshot: () => readConfigFileSnapshotMock(),
}));
vi.mock("../../config/issue-format.js", () => ({
formatConfigIssueLines: (
issues: Array<{ path: string; message: string }>,
_prefix: string,
_opts?: unknown,
) => issues.map((i) => `${i.path}: ${i.message}`),
}));
vi.mock("../../runtime.js", () => ({
defaultRuntime,
}));
describe("runServiceRestart config pre-flight (#35862)", () => {
let runServiceRestart: typeof import("./lifecycle-core.js").runServiceRestart;
beforeAll(async () => {
({ runServiceRestart } = await import("./lifecycle-core.js"));
});
beforeEach(() => {
runtimeLogs.length = 0;
readConfigFileSnapshotMock.mockReset();
readConfigFileSnapshotMock.mockResolvedValue({
exists: true,
valid: true,
config: {},
issues: [],
});
loadConfig.mockReset();
loadConfig.mockReturnValue({});
service.isLoaded.mockClear();
service.readCommand.mockClear();
service.restart.mockClear();
service.isLoaded.mockResolvedValue(true);
service.readCommand.mockResolvedValue({ environment: {} });
service.restart.mockResolvedValue(undefined);
vi.unstubAllEnvs();
vi.stubEnv("OPENCLAW_GATEWAY_TOKEN", "");
vi.stubEnv("CLAWDBOT_GATEWAY_TOKEN", "");
});
it("aborts restart when config is invalid", async () => {
readConfigFileSnapshotMock.mockResolvedValue({
exists: true,
valid: false,
config: {},
issues: [{ path: "agents.defaults.pdfModel", message: "Unrecognized key" }],
});
await expect(
runServiceRestart({
serviceNoun: "Gateway",
service,
renderStartHints: () => [],
opts: { json: true },
}),
).rejects.toThrow("__exit__:1");
expect(service.restart).not.toHaveBeenCalled();
});
it("proceeds with restart when config is valid", async () => {
readConfigFileSnapshotMock.mockResolvedValue({
exists: true,
valid: true,
config: {},
issues: [],
});
const result = await runServiceRestart({
serviceNoun: "Gateway",
service,
renderStartHints: () => [],
opts: { json: true },
});
expect(result).toBe(true);
expect(service.restart).toHaveBeenCalledTimes(1);
});
it("proceeds with restart when config file does not exist", async () => {
readConfigFileSnapshotMock.mockResolvedValue({
exists: false,
valid: true,
config: {},
issues: [],
});
const result = await runServiceRestart({
serviceNoun: "Gateway",
service,
renderStartHints: () => [],
opts: { json: true },
});
expect(result).toBe(true);
expect(service.restart).toHaveBeenCalledTimes(1);
});
it("proceeds with restart when snapshot read throws", async () => {
readConfigFileSnapshotMock.mockRejectedValue(new Error("read failed"));
const result = await runServiceRestart({
serviceNoun: "Gateway",
service,
renderStartHints: () => [],
opts: { json: true },
});
expect(result).toBe(true);
expect(service.restart).toHaveBeenCalledTimes(1);
});
});
describe("runServiceStart config pre-flight (#35862)", () => {
let runServiceStart: typeof import("./lifecycle-core.js").runServiceStart;
beforeAll(async () => {
({ runServiceStart } = await import("./lifecycle-core.js"));
});
beforeEach(() => {
runtimeLogs.length = 0;
readConfigFileSnapshotMock.mockReset();
readConfigFileSnapshotMock.mockResolvedValue({
exists: true,
valid: true,
config: {},
issues: [],
});
service.isLoaded.mockClear();
service.restart.mockClear();
service.isLoaded.mockResolvedValue(true);
service.restart.mockResolvedValue(undefined);
});
it("aborts start when config is invalid", async () => {
readConfigFileSnapshotMock.mockResolvedValue({
exists: true,
valid: false,
config: {},
issues: [{ path: "agents.defaults.pdfModel", message: "Unrecognized key" }],
});
await expect(
runServiceStart({
serviceNoun: "Gateway",
service,
renderStartHints: () => [],
opts: { json: true },
}),
).rejects.toThrow("__exit__:1");
expect(service.restart).not.toHaveBeenCalled();
});
it("proceeds with start when config is valid", async () => {
readConfigFileSnapshotMock.mockResolvedValue({
exists: true,
valid: true,
config: {},
issues: [],
});
await runServiceStart({
serviceNoun: "Gateway",
service,
renderStartHints: () => [],
opts: { json: true },
});
expect(service.restart).toHaveBeenCalledTimes(1);
});
});