refactor(daemon): dedupe systemd restart test scaffolding

This commit is contained in:
Peter Steinberger
2026-03-07 17:05:26 +00:00
parent a31d3cad96
commit 1fc11ea7d8

View File

@@ -17,6 +17,38 @@ import {
stopSystemdService,
} from "./systemd.js";
type ExecFileError = Error & {
stderr?: string;
code?: string | number;
};
const createExecFileError = (
message: string,
options: { stderr?: string; code?: string | number } = {},
): ExecFileError => {
const err = new Error(message) as ExecFileError;
err.code = options.code ?? 1;
if (options.stderr) {
err.stderr = options.stderr;
}
return err;
};
const createWritableStreamMock = () => {
const write = vi.fn();
return {
write,
stdout: { write } as unknown as NodeJS.WritableStream,
};
};
const assertRestartSuccess = async (env: NodeJS.ProcessEnv) => {
const { write, stdout } = createWritableStreamMock();
await restartSystemdService({ stdout, env });
expect(write).toHaveBeenCalledTimes(1);
expect(String(write.mock.calls[0]?.[0])).toContain("Restarted systemd service");
};
describe("systemd availability", () => {
beforeEach(() => {
execFileMock.mockReset();
@@ -46,15 +78,10 @@ describe("systemd availability", () => {
execFileMock
.mockImplementationOnce((_cmd, args, _opts, cb) => {
expect(args).toEqual(["--user", "status"]);
const err = new Error(
"Failed to connect to user scope bus via local transport",
) as Error & {
stderr?: string;
code?: number;
};
err.stderr =
"Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined";
err.code = 1;
const err = createExecFileError("Failed to connect to user scope bus via local transport", {
stderr:
"Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined",
});
cb(err, "", "");
})
.mockImplementationOnce((_cmd, args, _opts, cb) => {
@@ -271,6 +298,10 @@ describe("parseSystemdExecStart", () => {
});
describe("systemd service control", () => {
const assertMachineRestartArgs = (args: string[]) => {
expect(args).toEqual(["--machine", "debian@", "--user", "restart", "openclaw-gateway.service"]);
};
beforeEach(() => {
execFileMock.mockReset();
});
@@ -298,13 +329,7 @@ describe("systemd service control", () => {
expect(args).toEqual(["--user", "restart", "openclaw-gateway-work.service"]);
cb(null, "", "");
});
const write = vi.fn();
const stdout = { write } as unknown as NodeJS.WritableStream;
await restartSystemdService({ stdout, env: { OPENCLAW_PROFILE: "work" } });
expect(write).toHaveBeenCalledTimes(1);
expect(String(write.mock.calls[0]?.[0])).toContain("Restarted systemd service");
await assertRestartSuccess({ OPENCLAW_PROFILE: "work" });
});
it("surfaces stop failures with systemctl detail", async () => {
@@ -331,22 +356,10 @@ describe("systemd service control", () => {
cb(null, "", "");
})
.mockImplementationOnce((_cmd, args, _opts, cb) => {
expect(args).toEqual([
"--machine",
"debian@",
"--user",
"restart",
"openclaw-gateway.service",
]);
assertMachineRestartArgs(args);
cb(null, "", "");
});
const write = vi.fn();
const stdout = { write } as unknown as NodeJS.WritableStream;
await restartSystemdService({ stdout, env: { SUDO_USER: "debian" } });
expect(write).toHaveBeenCalledTimes(1);
expect(String(write.mock.calls[0]?.[0])).toContain("Restarted systemd service");
await assertRestartSuccess({ SUDO_USER: "debian" });
});
it("keeps direct --user scope when SUDO_USER is root", async () => {
@@ -359,26 +372,17 @@ describe("systemd service control", () => {
expect(args).toEqual(["--user", "restart", "openclaw-gateway.service"]);
cb(null, "", "");
});
const write = vi.fn();
const stdout = { write } as unknown as NodeJS.WritableStream;
await restartSystemdService({ stdout, env: { SUDO_USER: "root", USER: "root" } });
expect(write).toHaveBeenCalledTimes(1);
expect(String(write.mock.calls[0]?.[0])).toContain("Restarted systemd service");
await assertRestartSuccess({ SUDO_USER: "root", USER: "root" });
});
it("falls back to machine user scope for restart when user bus env is missing", async () => {
execFileMock
.mockImplementationOnce((_cmd, args, _opts, cb) => {
expect(args).toEqual(["--user", "status"]);
const err = new Error("Failed to connect to user scope bus") as Error & {
stderr?: string;
code?: number;
};
err.stderr =
"Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined";
err.code = 1;
const err = createExecFileError("Failed to connect to user scope bus", {
stderr:
"Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined",
});
cb(err, "", "");
})
.mockImplementationOnce((_cmd, args, _opts, cb) => {
@@ -387,30 +391,15 @@ describe("systemd service control", () => {
})
.mockImplementationOnce((_cmd, args, _opts, cb) => {
expect(args).toEqual(["--user", "restart", "openclaw-gateway.service"]);
const err = new Error("Failed to connect to user scope bus") as Error & {
stderr?: string;
code?: number;
};
err.stderr = "Failed to connect to user scope bus";
err.code = 1;
const err = createExecFileError("Failed to connect to user scope bus", {
stderr: "Failed to connect to user scope bus",
});
cb(err, "", "");
})
.mockImplementationOnce((_cmd, args, _opts, cb) => {
expect(args).toEqual([
"--machine",
"debian@",
"--user",
"restart",
"openclaw-gateway.service",
]);
assertMachineRestartArgs(args);
cb(null, "", "");
});
const write = vi.fn();
const stdout = { write } as unknown as NodeJS.WritableStream;
await restartSystemdService({ stdout, env: { USER: "debian" } });
expect(write).toHaveBeenCalledTimes(1);
expect(String(write.mock.calls[0]?.[0])).toContain("Restarted systemd service");
await assertRestartSuccess({ USER: "debian" });
});
});