mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 11:08:37 +00:00
test: share systemd service test helpers
This commit is contained in:
@@ -25,6 +25,10 @@ type ExecFileError = Error & {
|
|||||||
code?: string | number;
|
code?: string | number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const TEST_SERVICE_HOME = "/home/test";
|
||||||
|
const TEST_MANAGED_HOME = "/tmp/openclaw-test-home";
|
||||||
|
const GATEWAY_SERVICE = "openclaw-gateway.service";
|
||||||
|
|
||||||
const createExecFileError = (
|
const createExecFileError = (
|
||||||
message: string,
|
message: string,
|
||||||
options: { stderr?: string; code?: string | number } = {},
|
options: { stderr?: string; code?: string | number } = {},
|
||||||
@@ -58,6 +62,48 @@ function pathLikeToString(pathname: unknown): string {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assertUserSystemctlArgs(args: string[], ...command: string[]) {
|
||||||
|
expect(args).toEqual(["--user", ...command]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertMachineUserSystemctlArgs(args: string[], user: string, ...command: string[]) {
|
||||||
|
expect(args).toEqual(["--machine", `${user}@`, "--user", ...command]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readManagedServiceEnabled(env: NodeJS.ProcessEnv = { HOME: TEST_MANAGED_HOME }) {
|
||||||
|
const { isSystemdServiceEnabled } = await import("./systemd.js");
|
||||||
|
vi.spyOn(fs, "access").mockResolvedValue(undefined);
|
||||||
|
return isSystemdServiceEnabled({ env });
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockReadGatewayServiceFile(
|
||||||
|
unitLines: string[],
|
||||||
|
extraFiles: Record<string, string | Error> = {},
|
||||||
|
) {
|
||||||
|
return vi.spyOn(fs, "readFile").mockImplementation(async (pathname) => {
|
||||||
|
const pathValue = pathLikeToString(pathname);
|
||||||
|
if (pathValue.endsWith(`/${GATEWAY_SERVICE}`)) {
|
||||||
|
return unitLines.join("\n");
|
||||||
|
}
|
||||||
|
const extraFile = extraFiles[pathValue];
|
||||||
|
if (typeof extraFile === "string") {
|
||||||
|
return extraFile;
|
||||||
|
}
|
||||||
|
if (extraFile instanceof Error) {
|
||||||
|
throw extraFile;
|
||||||
|
}
|
||||||
|
throw new Error(`unexpected readFile path: ${pathValue}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function expectExecStartWithoutEnvironment(envFileLine: string) {
|
||||||
|
mockReadGatewayServiceFile(["[Service]", "ExecStart=/usr/bin/openclaw gateway run", envFileLine]);
|
||||||
|
|
||||||
|
const command = await readSystemdServiceExecStart({ HOME: TEST_SERVICE_HOME });
|
||||||
|
expect(command?.programArguments).toEqual(["/usr/bin/openclaw", "gateway", "run"]);
|
||||||
|
expect(command?.environment).toBeUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
const assertRestartSuccess = async (env: NodeJS.ProcessEnv) => {
|
const assertRestartSuccess = async (env: NodeJS.ProcessEnv) => {
|
||||||
const { write, stdout } = createWritableStreamMock();
|
const { write, stdout } = createWritableStreamMock();
|
||||||
await restartSystemdService({ stdout, env });
|
await restartSystemdService({ stdout, env });
|
||||||
@@ -118,24 +164,18 @@ describe("systemd availability", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("isSystemdServiceEnabled", () => {
|
describe("isSystemdServiceEnabled", () => {
|
||||||
const mockManagedUnitPresent = () => {
|
|
||||||
vi.spyOn(fs, "access").mockResolvedValue(undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
execFileMock.mockReset();
|
execFileMock.mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns false when systemctl is not present", async () => {
|
it("returns false when systemctl is not present", async () => {
|
||||||
const { isSystemdServiceEnabled } = await import("./systemd.js");
|
|
||||||
mockManagedUnitPresent();
|
|
||||||
execFileMock.mockImplementation((_cmd, _args, _opts, cb) => {
|
execFileMock.mockImplementation((_cmd, _args, _opts, cb) => {
|
||||||
const err = new Error("spawn systemctl EACCES") as Error & { code?: string };
|
const err = new Error("spawn systemctl EACCES") as Error & { code?: string };
|
||||||
err.code = "EACCES";
|
err.code = "EACCES";
|
||||||
cb(err, "", "");
|
cb(err, "", "");
|
||||||
});
|
});
|
||||||
const result = await isSystemdServiceEnabled({ env: { HOME: "/tmp/openclaw-test-home" } });
|
const result = await readManagedServiceEnabled();
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -152,55 +192,45 @@ describe("isSystemdServiceEnabled", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("calls systemctl is-enabled when systemctl is present", async () => {
|
it("calls systemctl is-enabled when systemctl is present", async () => {
|
||||||
const { isSystemdServiceEnabled } = await import("./systemd.js");
|
|
||||||
mockManagedUnitPresent();
|
|
||||||
execFileMock.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
execFileMock.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
expect(args).toEqual(["--user", "is-enabled", "openclaw-gateway.service"]);
|
assertUserSystemctlArgs(args, "is-enabled", GATEWAY_SERVICE);
|
||||||
cb(null, "enabled", "");
|
cb(null, "enabled", "");
|
||||||
});
|
});
|
||||||
const result = await isSystemdServiceEnabled({ env: { HOME: "/tmp/openclaw-test-home" } });
|
const result = await readManagedServiceEnabled();
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns false when systemctl reports disabled", async () => {
|
it("returns false when systemctl reports disabled", async () => {
|
||||||
const { isSystemdServiceEnabled } = await import("./systemd.js");
|
|
||||||
mockManagedUnitPresent();
|
|
||||||
execFileMock.mockImplementationOnce((_cmd, _args, _opts, cb) => {
|
execFileMock.mockImplementationOnce((_cmd, _args, _opts, cb) => {
|
||||||
const err = new Error("disabled") as Error & { code?: number };
|
const err = new Error("disabled") as Error & { code?: number };
|
||||||
err.code = 1;
|
err.code = 1;
|
||||||
cb(err, "disabled", "");
|
cb(err, "disabled", "");
|
||||||
});
|
});
|
||||||
const result = await isSystemdServiceEnabled({ env: { HOME: "/tmp/openclaw-test-home" } });
|
const result = await readManagedServiceEnabled();
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns false for the WSL2 Ubuntu 24.04 wrapper-only is-enabled failure", async () => {
|
it("returns false for the WSL2 Ubuntu 24.04 wrapper-only is-enabled failure", async () => {
|
||||||
const { isSystemdServiceEnabled } = await import("./systemd.js");
|
|
||||||
mockManagedUnitPresent();
|
|
||||||
execFileMock.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
execFileMock.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
expect(args).toEqual(["--user", "is-enabled", "openclaw-gateway.service"]);
|
assertUserSystemctlArgs(args, "is-enabled", GATEWAY_SERVICE);
|
||||||
const err = new Error(
|
const err = new Error(
|
||||||
"Command failed: systemctl --user is-enabled openclaw-gateway.service",
|
`Command failed: systemctl --user is-enabled ${GATEWAY_SERVICE}`,
|
||||||
) as Error & { code?: number };
|
) as Error & { code?: number };
|
||||||
err.code = 1;
|
err.code = 1;
|
||||||
cb(err, "", "");
|
cb(err, "", "");
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(
|
await expect(readManagedServiceEnabled()).rejects.toThrow(
|
||||||
isSystemdServiceEnabled({ env: { HOME: "/tmp/openclaw-test-home" } }),
|
`systemctl is-enabled unavailable: Command failed: systemctl --user is-enabled ${GATEWAY_SERVICE}`,
|
||||||
).rejects.toThrow(
|
|
||||||
"systemctl is-enabled unavailable: Command failed: systemctl --user is-enabled openclaw-gateway.service",
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns false when is-enabled cannot connect to the user bus without machine fallback", async () => {
|
it("returns false when is-enabled cannot connect to the user bus without machine fallback", async () => {
|
||||||
const { isSystemdServiceEnabled } = await import("./systemd.js");
|
|
||||||
mockManagedUnitPresent();
|
|
||||||
vi.spyOn(os, "userInfo").mockImplementationOnce(() => {
|
vi.spyOn(os, "userInfo").mockImplementationOnce(() => {
|
||||||
throw new Error("no user info");
|
throw new Error("no user info");
|
||||||
});
|
});
|
||||||
execFileMock.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
execFileMock.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
expect(args).toEqual(["--user", "is-enabled", "openclaw-gateway.service"]);
|
assertUserSystemctlArgs(args, "is-enabled", GATEWAY_SERVICE);
|
||||||
cb(
|
cb(
|
||||||
createExecFileError("Failed to connect to bus", { stderr: "Failed to connect to bus" }),
|
createExecFileError("Failed to connect to bus", { stderr: "Failed to connect to bus" }),
|
||||||
"",
|
"",
|
||||||
@@ -209,18 +239,14 @@ describe("isSystemdServiceEnabled", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
isSystemdServiceEnabled({
|
readManagedServiceEnabled({ HOME: TEST_MANAGED_HOME, USER: "", LOGNAME: "" }),
|
||||||
env: { HOME: "/tmp/openclaw-test-home", USER: "", LOGNAME: "" },
|
|
||||||
}),
|
|
||||||
).rejects.toThrow("systemctl is-enabled unavailable: Failed to connect to bus");
|
).rejects.toThrow("systemctl is-enabled unavailable: Failed to connect to bus");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns false when both direct and machine-scope is-enabled checks report bus unavailability", async () => {
|
it("returns false when both direct and machine-scope is-enabled checks report bus unavailability", async () => {
|
||||||
const { isSystemdServiceEnabled } = await import("./systemd.js");
|
|
||||||
mockManagedUnitPresent();
|
|
||||||
execFileMock
|
execFileMock
|
||||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
expect(args).toEqual(["--user", "is-enabled", "openclaw-gateway.service"]);
|
assertUserSystemctlArgs(args, "is-enabled", GATEWAY_SERVICE);
|
||||||
cb(
|
cb(
|
||||||
createExecFileError("Failed to connect to bus", { stderr: "Failed to connect to bus" }),
|
createExecFileError("Failed to connect to bus", { stderr: "Failed to connect to bus" }),
|
||||||
"",
|
"",
|
||||||
@@ -228,13 +254,7 @@ describe("isSystemdServiceEnabled", () => {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
expect(args).toEqual([
|
assertMachineUserSystemctlArgs(args, "debian", "is-enabled", GATEWAY_SERVICE);
|
||||||
"--machine",
|
|
||||||
"debian@",
|
|
||||||
"--user",
|
|
||||||
"is-enabled",
|
|
||||||
"openclaw-gateway.service",
|
|
||||||
]);
|
|
||||||
cb(
|
cb(
|
||||||
createExecFileError("Failed to connect to user scope bus via local transport", {
|
createExecFileError("Failed to connect to user scope bus via local transport", {
|
||||||
stderr:
|
stderr:
|
||||||
@@ -246,32 +266,28 @@ describe("isSystemdServiceEnabled", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
isSystemdServiceEnabled({
|
readManagedServiceEnabled({ HOME: TEST_MANAGED_HOME, USER: "debian" }),
|
||||||
env: { HOME: "/tmp/openclaw-test-home", USER: "debian" },
|
|
||||||
}),
|
|
||||||
).rejects.toThrow("systemctl is-enabled unavailable: Failed to connect to user scope bus");
|
).rejects.toThrow("systemctl is-enabled unavailable: Failed to connect to user scope bus");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws when generic wrapper errors report infrastructure failures", async () => {
|
it("throws when generic wrapper errors report infrastructure failures", async () => {
|
||||||
const { isSystemdServiceEnabled } = await import("./systemd.js");
|
|
||||||
mockManagedUnitPresent();
|
|
||||||
execFileMock.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
execFileMock.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
expect(args).toEqual(["--user", "is-enabled", "openclaw-gateway.service"]);
|
assertUserSystemctlArgs(args, "is-enabled", GATEWAY_SERVICE);
|
||||||
const err = new Error(
|
const err = new Error(
|
||||||
"Command failed: systemctl --user is-enabled openclaw-gateway.service",
|
`Command failed: systemctl --user is-enabled ${GATEWAY_SERVICE}`,
|
||||||
) as Error & { code?: number };
|
) as Error & { code?: number };
|
||||||
err.code = 1;
|
err.code = 1;
|
||||||
cb(err, "", "read-only file system");
|
cb(err, "", "read-only file system");
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(
|
await expect(readManagedServiceEnabled()).rejects.toThrow(
|
||||||
isSystemdServiceEnabled({ env: { HOME: "/tmp/openclaw-test-home" } }),
|
"systemctl is-enabled unavailable: read-only file system",
|
||||||
).rejects.toThrow("systemctl is-enabled unavailable: read-only file system");
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws when systemctl is-enabled fails for non-state errors", async () => {
|
it("throws when systemctl is-enabled fails for non-state errors", async () => {
|
||||||
const { isSystemdServiceEnabled } = await import("./systemd.js");
|
const { isSystemdServiceEnabled } = await import("./systemd.js");
|
||||||
mockManagedUnitPresent();
|
vi.spyOn(fs, "access").mockResolvedValue(undefined);
|
||||||
execFileMock
|
execFileMock
|
||||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
expect(args).toEqual(["--user", "is-enabled", "openclaw-gateway.service"]);
|
expect(args).toEqual(["--user", "is-enabled", "openclaw-gateway.service"]);
|
||||||
@@ -294,7 +310,7 @@ describe("isSystemdServiceEnabled", () => {
|
|||||||
|
|
||||||
it("returns false when systemctl is-enabled exits with code 4 (not-found)", async () => {
|
it("returns false when systemctl is-enabled exits with code 4 (not-found)", async () => {
|
||||||
const { isSystemdServiceEnabled } = await import("./systemd.js");
|
const { isSystemdServiceEnabled } = await import("./systemd.js");
|
||||||
mockManagedUnitPresent();
|
vi.spyOn(fs, "access").mockResolvedValue(undefined);
|
||||||
execFileMock.mockImplementationOnce((_cmd, _args, _opts, cb) => {
|
execFileMock.mockImplementationOnce((_cmd, _args, _opts, cb) => {
|
||||||
// On Ubuntu 24.04, `systemctl --user is-enabled <unit>` exits with
|
// On Ubuntu 24.04, `systemctl --user is-enabled <unit>` exits with
|
||||||
// code 4 and prints "not-found" to stdout when the unit doesn't exist.
|
// code 4 and prints "not-found" to stdout when the unit doesn't exist.
|
||||||
@@ -463,82 +479,38 @@ describe("readSystemdServiceExecStart", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("loads OPENCLAW_GATEWAY_TOKEN from EnvironmentFile", async () => {
|
it("loads OPENCLAW_GATEWAY_TOKEN from EnvironmentFile", async () => {
|
||||||
const readFileSpy = vi.spyOn(fs, "readFile").mockImplementation(async (pathname) => {
|
const readFileSpy = mockReadGatewayServiceFile(
|
||||||
const pathValue = pathLikeToString(pathname);
|
["[Service]", "ExecStart=/usr/bin/openclaw gateway run", "EnvironmentFile=%h/.openclaw/.env"],
|
||||||
if (pathValue.endsWith("/openclaw-gateway.service")) {
|
{ [`${TEST_SERVICE_HOME}/.openclaw/.env`]: "OPENCLAW_GATEWAY_TOKEN=env-file-token\n" },
|
||||||
return [
|
);
|
||||||
"[Service]",
|
|
||||||
"ExecStart=/usr/bin/openclaw gateway run",
|
|
||||||
"EnvironmentFile=%h/.openclaw/.env",
|
|
||||||
].join("\n");
|
|
||||||
}
|
|
||||||
if (pathValue === "/home/test/.openclaw/.env") {
|
|
||||||
return "OPENCLAW_GATEWAY_TOKEN=env-file-token\n";
|
|
||||||
}
|
|
||||||
throw new Error(`unexpected readFile path: ${pathValue}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
const command = await readSystemdServiceExecStart({ HOME: "/home/test" });
|
const command = await readSystemdServiceExecStart({ HOME: TEST_SERVICE_HOME });
|
||||||
expect(command?.environment?.OPENCLAW_GATEWAY_TOKEN).toBe("env-file-token");
|
expect(command?.environment?.OPENCLAW_GATEWAY_TOKEN).toBe("env-file-token");
|
||||||
expect(readFileSpy).toHaveBeenCalledTimes(2);
|
expect(readFileSpy).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("lets EnvironmentFile override inline Environment values", async () => {
|
it("lets EnvironmentFile override inline Environment values", async () => {
|
||||||
vi.spyOn(fs, "readFile").mockImplementation(async (pathname) => {
|
mockReadGatewayServiceFile(
|
||||||
const pathValue = pathLikeToString(pathname);
|
[
|
||||||
if (pathValue.endsWith("/openclaw-gateway.service")) {
|
"[Service]",
|
||||||
return [
|
"ExecStart=/usr/bin/openclaw gateway run",
|
||||||
"[Service]",
|
"EnvironmentFile=%h/.openclaw/.env",
|
||||||
"ExecStart=/usr/bin/openclaw gateway run",
|
'Environment="OPENCLAW_GATEWAY_TOKEN=inline-token"',
|
||||||
"EnvironmentFile=%h/.openclaw/.env",
|
],
|
||||||
'Environment="OPENCLAW_GATEWAY_TOKEN=inline-token"',
|
{ [`${TEST_SERVICE_HOME}/.openclaw/.env`]: "OPENCLAW_GATEWAY_TOKEN=env-file-token\n" },
|
||||||
].join("\n");
|
);
|
||||||
}
|
|
||||||
if (pathValue === "/home/test/.openclaw/.env") {
|
|
||||||
return "OPENCLAW_GATEWAY_TOKEN=env-file-token\n";
|
|
||||||
}
|
|
||||||
throw new Error(`unexpected readFile path: ${pathValue}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
const command = await readSystemdServiceExecStart({ HOME: "/home/test" });
|
const command = await readSystemdServiceExecStart({ HOME: TEST_SERVICE_HOME });
|
||||||
expect(command?.environment?.OPENCLAW_GATEWAY_TOKEN).toBe("env-file-token");
|
expect(command?.environment?.OPENCLAW_GATEWAY_TOKEN).toBe("env-file-token");
|
||||||
expect(command?.environmentValueSources?.OPENCLAW_GATEWAY_TOKEN).toBe("file");
|
expect(command?.environmentValueSources?.OPENCLAW_GATEWAY_TOKEN).toBe("file");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ignores missing optional EnvironmentFile entries", async () => {
|
it("ignores missing optional EnvironmentFile entries", async () => {
|
||||||
vi.spyOn(fs, "readFile").mockImplementation(async (pathname) => {
|
await expectExecStartWithoutEnvironment("EnvironmentFile=-%h/.openclaw/missing.env");
|
||||||
const pathValue = pathLikeToString(pathname);
|
|
||||||
if (pathValue.endsWith("/openclaw-gateway.service")) {
|
|
||||||
return [
|
|
||||||
"[Service]",
|
|
||||||
"ExecStart=/usr/bin/openclaw gateway run",
|
|
||||||
"EnvironmentFile=-%h/.openclaw/missing.env",
|
|
||||||
].join("\n");
|
|
||||||
}
|
|
||||||
throw new Error(`missing: ${pathValue}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
const command = await readSystemdServiceExecStart({ HOME: "/home/test" });
|
|
||||||
expect(command?.programArguments).toEqual(["/usr/bin/openclaw", "gateway", "run"]);
|
|
||||||
expect(command?.environment).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps parsing when non-optional EnvironmentFile entries are missing", async () => {
|
it("keeps parsing when non-optional EnvironmentFile entries are missing", async () => {
|
||||||
vi.spyOn(fs, "readFile").mockImplementation(async (pathname) => {
|
await expectExecStartWithoutEnvironment("EnvironmentFile=%h/.openclaw/missing.env");
|
||||||
const pathValue = pathLikeToString(pathname);
|
|
||||||
if (pathValue.endsWith("/openclaw-gateway.service")) {
|
|
||||||
return [
|
|
||||||
"[Service]",
|
|
||||||
"ExecStart=/usr/bin/openclaw gateway run",
|
|
||||||
"EnvironmentFile=%h/.openclaw/missing.env",
|
|
||||||
].join("\n");
|
|
||||||
}
|
|
||||||
throw new Error(`missing: ${pathValue}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
const command = await readSystemdServiceExecStart({ HOME: "/home/test" });
|
|
||||||
expect(command?.programArguments).toEqual(["/usr/bin/openclaw", "gateway", "run"]);
|
|
||||||
expect(command?.environment).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("supports multiple EnvironmentFile entries and quoted paths", async () => {
|
it("supports multiple EnvironmentFile entries and quoted paths", async () => {
|
||||||
@@ -631,7 +603,7 @@ describe("readSystemdServiceExecStart", () => {
|
|||||||
|
|
||||||
describe("systemd service control", () => {
|
describe("systemd service control", () => {
|
||||||
const assertMachineRestartArgs = (args: string[]) => {
|
const assertMachineRestartArgs = (args: string[]) => {
|
||||||
expect(args).toEqual(["--machine", "debian@", "--user", "restart", "openclaw-gateway.service"]);
|
assertMachineUserSystemctlArgs(args, "debian", "restart", GATEWAY_SERVICE);
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -642,7 +614,7 @@ describe("systemd service control", () => {
|
|||||||
execFileMock
|
execFileMock
|
||||||
.mockImplementationOnce((_cmd, _args, _opts, cb) => cb(null, "", ""))
|
.mockImplementationOnce((_cmd, _args, _opts, cb) => cb(null, "", ""))
|
||||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
expect(args).toEqual(["--user", "stop", "openclaw-gateway.service"]);
|
assertUserSystemctlArgs(args, "stop", GATEWAY_SERVICE);
|
||||||
cb(null, "", "");
|
cb(null, "", "");
|
||||||
});
|
});
|
||||||
const write = vi.fn();
|
const write = vi.fn();
|
||||||
@@ -664,7 +636,7 @@ describe("systemd service control", () => {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
expect(args).toEqual(["--user", "stop", "openclaw-gateway.service"]);
|
assertUserSystemctlArgs(args, "stop", GATEWAY_SERVICE);
|
||||||
cb(null, "", "");
|
cb(null, "", "");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -678,7 +650,7 @@ describe("systemd service control", () => {
|
|||||||
execFileMock
|
execFileMock
|
||||||
.mockImplementationOnce((_cmd, _args, _opts, cb) => cb(null, "", ""))
|
.mockImplementationOnce((_cmd, _args, _opts, cb) => cb(null, "", ""))
|
||||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
expect(args).toEqual(["--user", "restart", "openclaw-gateway-work.service"]);
|
assertUserSystemctlArgs(args, "restart", "openclaw-gateway-work.service");
|
||||||
cb(null, "", "");
|
cb(null, "", "");
|
||||||
});
|
});
|
||||||
await assertRestartSuccess({ OPENCLAW_PROFILE: "work" });
|
await assertRestartSuccess({ OPENCLAW_PROFILE: "work" });
|
||||||
@@ -724,7 +696,7 @@ describe("systemd service control", () => {
|
|||||||
it("targets the sudo caller's user scope when SUDO_USER is set", async () => {
|
it("targets the sudo caller's user scope when SUDO_USER is set", async () => {
|
||||||
execFileMock
|
execFileMock
|
||||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
expect(args).toEqual(["--machine", "debian@", "--user", "status"]);
|
assertMachineUserSystemctlArgs(args, "debian", "status");
|
||||||
cb(null, "", "");
|
cb(null, "", "");
|
||||||
})
|
})
|
||||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
@@ -737,11 +709,11 @@ describe("systemd service control", () => {
|
|||||||
it("keeps direct --user scope when SUDO_USER is root", async () => {
|
it("keeps direct --user scope when SUDO_USER is root", async () => {
|
||||||
execFileMock
|
execFileMock
|
||||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
expect(args).toEqual(["--user", "status"]);
|
assertUserSystemctlArgs(args, "status");
|
||||||
cb(null, "", "");
|
cb(null, "", "");
|
||||||
})
|
})
|
||||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
expect(args).toEqual(["--user", "restart", "openclaw-gateway.service"]);
|
assertUserSystemctlArgs(args, "restart", GATEWAY_SERVICE);
|
||||||
cb(null, "", "");
|
cb(null, "", "");
|
||||||
});
|
});
|
||||||
await assertRestartSuccess({ SUDO_USER: "root", USER: "root" });
|
await assertRestartSuccess({ SUDO_USER: "root", USER: "root" });
|
||||||
@@ -750,7 +722,7 @@ describe("systemd service control", () => {
|
|||||||
it("falls back to machine user scope for restart when user bus env is missing", async () => {
|
it("falls back to machine user scope for restart when user bus env is missing", async () => {
|
||||||
execFileMock
|
execFileMock
|
||||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
expect(args).toEqual(["--user", "status"]);
|
assertUserSystemctlArgs(args, "status");
|
||||||
const err = createExecFileError("Failed to connect to user scope bus", {
|
const err = createExecFileError("Failed to connect to user scope bus", {
|
||||||
stderr:
|
stderr:
|
||||||
"Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined",
|
"Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined",
|
||||||
@@ -758,11 +730,11 @@ describe("systemd service control", () => {
|
|||||||
cb(err, "", "");
|
cb(err, "", "");
|
||||||
})
|
})
|
||||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
expect(args).toEqual(["--machine", "debian@", "--user", "status"]);
|
assertMachineUserSystemctlArgs(args, "debian", "status");
|
||||||
cb(null, "", "");
|
cb(null, "", "");
|
||||||
})
|
})
|
||||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||||
expect(args).toEqual(["--user", "restart", "openclaw-gateway.service"]);
|
assertUserSystemctlArgs(args, "restart", GATEWAY_SERVICE);
|
||||||
const err = createExecFileError("Failed to connect to user scope bus", {
|
const err = createExecFileError("Failed to connect to user scope bus", {
|
||||||
stderr: "Failed to connect to user scope bus",
|
stderr: "Failed to connect to user scope bus",
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user