mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 21:10:43 +00:00
366 lines
12 KiB
TypeScript
366 lines
12 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { detectMarkerLineWithGateway, findExtraGatewayServices } from "./inspect.js";
|
|
|
|
const { execSchtasksMock } = vi.hoisted(() => ({
|
|
execSchtasksMock: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("./schtasks-exec.js", () => ({
|
|
execSchtasks: (...args: unknown[]) => execSchtasksMock(...args),
|
|
}));
|
|
|
|
// Real content from the openclaw-gateway.service unit file (the canonical gateway unit).
|
|
const GATEWAY_SERVICE_CONTENTS = `\
|
|
[Unit]
|
|
Description=OpenClaw Gateway (v2026.3.8)
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
ExecStart=/usr/bin/node /home/openclaw/.npm-global/lib/node_modules/openclaw/dist/entry.js gateway --port 18789
|
|
Restart=always
|
|
Environment=OPENCLAW_SERVICE_MARKER=openclaw
|
|
Environment=OPENCLAW_SERVICE_KIND=gateway
|
|
Environment=OPENCLAW_SERVICE_VERSION=2026.3.8
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
`;
|
|
|
|
// Real content from the openclaw-test.service unit file (a non-gateway openclaw service).
|
|
const TEST_SERVICE_CONTENTS = `\
|
|
[Unit]
|
|
Description=OpenClaw test service
|
|
After=default.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=/bin/sh -c 'while true; do sleep 60; done'
|
|
Restart=on-failure
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
`;
|
|
|
|
const CLAWDBOT_GATEWAY_CONTENTS = `\
|
|
[Unit]
|
|
Description=Clawdbot Gateway
|
|
[Service]
|
|
ExecStart=/usr/bin/node /opt/clawdbot/dist/entry.js gateway --port 18789
|
|
Environment=HOME=/home/clawdbot
|
|
`;
|
|
|
|
const COMPANION_SERVICE_CONTENTS = `\
|
|
[Unit]
|
|
Description=OpenClaw companion worker
|
|
After=openclaw-gateway.service
|
|
Requires=openclaw-gateway.service
|
|
|
|
[Service]
|
|
ExecStart=/usr/bin/node /opt/openclaw-worker/dist/index.js worker
|
|
`;
|
|
|
|
const CUSTOM_OPENCLAW_GATEWAY_CONTENTS = `\
|
|
[Unit]
|
|
Description=Custom OpenClaw gateway
|
|
|
|
[Service]
|
|
ExecStart=/usr/bin/node /opt/openclaw/dist/entry.js gateway --port 18888
|
|
`;
|
|
|
|
describe("detectMarkerLineWithGateway", () => {
|
|
it("returns null for openclaw-test.service (openclaw only in description, no gateway on same line)", () => {
|
|
expect(detectMarkerLineWithGateway(TEST_SERVICE_CONTENTS)).toBeNull();
|
|
});
|
|
|
|
it("returns openclaw for the canonical gateway unit (ExecStart has both openclaw and gateway)", () => {
|
|
expect(detectMarkerLineWithGateway(GATEWAY_SERVICE_CONTENTS)).toBe("openclaw");
|
|
});
|
|
|
|
it("returns clawdbot for a clawdbot gateway unit", () => {
|
|
expect(detectMarkerLineWithGateway(CLAWDBOT_GATEWAY_CONTENTS)).toBe("clawdbot");
|
|
});
|
|
|
|
it("handles line continuations — marker and gateway split across physical lines", () => {
|
|
const contents = `[Service]\nExecStart=/usr/bin/node /opt/openclaw/dist/entry.js \\\n gateway --port 18789\n`;
|
|
expect(detectMarkerLineWithGateway(contents)).toBe("openclaw");
|
|
});
|
|
|
|
it("ignores dependency-only references to the gateway unit", () => {
|
|
expect(detectMarkerLineWithGateway(COMPANION_SERVICE_CONTENTS)).toBeNull();
|
|
});
|
|
|
|
it("ignores non-gateway ExecStart commands that only pass gateway-named options", () => {
|
|
const contents = `[Service]\nExecStart=/usr/bin/openclaw-helper --gateway-url http://127.0.0.1:18789 sync\n`;
|
|
expect(detectMarkerLineWithGateway(contents)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("findExtraGatewayServices (linux / scanSystemdDir) — real filesystem", () => {
|
|
// These tests write real .service files to a temp dir and call findExtraGatewayServices
|
|
// with that dir as HOME. No platform mocking or fs mocking needed.
|
|
// Only runs on Linux/macOS where the linux branch of findExtraGatewayServices is active.
|
|
const isLinux = process.platform === "linux";
|
|
|
|
it.skipIf(!isLinux)("does not report openclaw-test.service as a gateway service", async () => {
|
|
const tmpHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-"));
|
|
const systemdDir = path.join(tmpHome, ".config", "systemd", "user");
|
|
try {
|
|
await fs.mkdir(systemdDir, { recursive: true });
|
|
await fs.writeFile(path.join(systemdDir, "openclaw-test.service"), TEST_SERVICE_CONTENTS);
|
|
const result = await findExtraGatewayServices({ HOME: tmpHome });
|
|
expect(result).toEqual([]);
|
|
} finally {
|
|
await fs.rm(tmpHome, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it.skipIf(!isLinux)(
|
|
"does not report the canonical openclaw-gateway.service as an extra service",
|
|
async () => {
|
|
const tmpHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-"));
|
|
const systemdDir = path.join(tmpHome, ".config", "systemd", "user");
|
|
try {
|
|
await fs.mkdir(systemdDir, { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(systemdDir, "openclaw-gateway.service"),
|
|
GATEWAY_SERVICE_CONTENTS,
|
|
);
|
|
const result = await findExtraGatewayServices({ HOME: tmpHome });
|
|
expect(result).toEqual([]);
|
|
} finally {
|
|
await fs.rm(tmpHome, { recursive: true, force: true });
|
|
}
|
|
},
|
|
);
|
|
|
|
it.skipIf(!isLinux)(
|
|
"reports a legacy clawdbot-gateway service as an extra gateway service",
|
|
async () => {
|
|
const tmpHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-"));
|
|
const systemdDir = path.join(tmpHome, ".config", "systemd", "user");
|
|
const unitPath = path.join(systemdDir, "clawdbot-gateway.service");
|
|
try {
|
|
await fs.mkdir(systemdDir, { recursive: true });
|
|
await fs.writeFile(unitPath, CLAWDBOT_GATEWAY_CONTENTS);
|
|
const result = await findExtraGatewayServices({ HOME: tmpHome });
|
|
expect(result).toEqual([
|
|
{
|
|
platform: "linux",
|
|
label: "clawdbot-gateway.service",
|
|
detail: `unit: ${unitPath}`,
|
|
scope: "user",
|
|
marker: "clawdbot",
|
|
legacy: true,
|
|
},
|
|
]);
|
|
} finally {
|
|
await fs.rm(tmpHome, { recursive: true, force: true });
|
|
}
|
|
},
|
|
);
|
|
|
|
it.skipIf(!isLinux)(
|
|
"does not report companion units that only depend on the gateway",
|
|
async () => {
|
|
const tmpHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-"));
|
|
const systemdDir = path.join(tmpHome, ".config", "systemd", "user");
|
|
try {
|
|
await fs.mkdir(systemdDir, { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(systemdDir, "openclaw-companion.service"),
|
|
COMPANION_SERVICE_CONTENTS,
|
|
);
|
|
const result = await findExtraGatewayServices({ HOME: tmpHome });
|
|
expect(result).toEqual([]);
|
|
} finally {
|
|
await fs.rm(tmpHome, { recursive: true, force: true });
|
|
}
|
|
},
|
|
);
|
|
|
|
it.skipIf(!isLinux)(
|
|
"reports custom-named gateway units that execute openclaw gateway",
|
|
async () => {
|
|
const tmpHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-"));
|
|
const systemdDir = path.join(tmpHome, ".config", "systemd", "user");
|
|
const unitPath = path.join(systemdDir, "custom-openclaw.service");
|
|
try {
|
|
await fs.mkdir(systemdDir, { recursive: true });
|
|
await fs.writeFile(unitPath, CUSTOM_OPENCLAW_GATEWAY_CONTENTS);
|
|
const result = await findExtraGatewayServices({ HOME: tmpHome });
|
|
expect(result).toEqual([
|
|
{
|
|
platform: "linux",
|
|
label: "custom-openclaw.service",
|
|
detail: `unit: ${unitPath}`,
|
|
scope: "user",
|
|
marker: "openclaw",
|
|
legacy: false,
|
|
},
|
|
]);
|
|
} finally {
|
|
await fs.rm(tmpHome, { recursive: true, force: true });
|
|
}
|
|
},
|
|
);
|
|
});
|
|
|
|
describe("findExtraGatewayServices (darwin / scanLaunchdDir) — real filesystem", () => {
|
|
const originalPlatform = process.platform;
|
|
|
|
beforeEach(() => {
|
|
Object.defineProperty(process, "platform", {
|
|
configurable: true,
|
|
value: "darwin",
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
Object.defineProperty(process, "platform", {
|
|
configurable: true,
|
|
value: originalPlatform,
|
|
});
|
|
});
|
|
|
|
it("does not report LaunchAgent companions that only mention the gateway label", async () => {
|
|
const tmpHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-"));
|
|
const launchdDir = path.join(tmpHome, "Library", "LaunchAgents");
|
|
try {
|
|
await fs.mkdir(launchdDir, { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(launchdDir, "com.example.companion.plist"),
|
|
`<?xml version="1.0" encoding="UTF-8"?>
|
|
<plist version="1.0"><dict>
|
|
<key>Label</key><string>com.example.companion</string>
|
|
<key>KeepAlive</key><dict><key>OtherJobEnabled</key><dict><key>ai.openclaw.gateway</key><true/></dict></dict>
|
|
<key>ProgramArguments</key><array><string>/usr/local/bin/openclaw-helper</string><string>sync</string></array>
|
|
</dict></plist>`,
|
|
);
|
|
const result = await findExtraGatewayServices({ HOME: tmpHome });
|
|
expect(result).toEqual([]);
|
|
} finally {
|
|
await fs.rm(tmpHome, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("does not report LaunchAgent companions that only pass gateway-named options", async () => {
|
|
const tmpHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-"));
|
|
const launchdDir = path.join(tmpHome, "Library", "LaunchAgents");
|
|
try {
|
|
await fs.mkdir(launchdDir, { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(launchdDir, "com.example.companion-options.plist"),
|
|
`<?xml version="1.0" encoding="UTF-8"?>
|
|
<plist version="1.0"><dict>
|
|
<key>Label</key><string>com.example.companion-options</string>
|
|
<key>ProgramArguments</key><array><string>/usr/local/bin/openclaw-helper</string><string>--gateway-url</string><string>http://127.0.0.1:18789</string><string>sync</string></array>
|
|
</dict></plist>`,
|
|
);
|
|
const result = await findExtraGatewayServices({ HOME: tmpHome });
|
|
expect(result).toEqual([]);
|
|
} finally {
|
|
await fs.rm(tmpHome, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("reports custom LaunchAgents that execute openclaw gateway", async () => {
|
|
const tmpHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-"));
|
|
const launchdDir = path.join(tmpHome, "Library", "LaunchAgents");
|
|
const plistPath = path.join(launchdDir, "com.example.openclaw-gateway.plist");
|
|
try {
|
|
await fs.mkdir(launchdDir, { recursive: true });
|
|
await fs.writeFile(
|
|
plistPath,
|
|
`<?xml version="1.0" encoding="UTF-8"?>
|
|
<plist version="1.0"><dict>
|
|
<key>Label</key><string>com.example.openclaw-gateway</string>
|
|
<key>ProgramArguments</key><array><string>/usr/local/bin/openclaw</string><string>gateway</string><string>--port</string><string>18888</string></array>
|
|
</dict></plist>`,
|
|
);
|
|
const result = await findExtraGatewayServices({ HOME: tmpHome });
|
|
expect(result).toEqual([
|
|
{
|
|
platform: "darwin",
|
|
label: "com.example.openclaw-gateway",
|
|
detail: `plist: ${plistPath}`,
|
|
scope: "user",
|
|
marker: "openclaw",
|
|
legacy: false,
|
|
},
|
|
]);
|
|
} finally {
|
|
await fs.rm(tmpHome, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("findExtraGatewayServices (win32)", () => {
|
|
const originalPlatform = process.platform;
|
|
|
|
beforeEach(() => {
|
|
Object.defineProperty(process, "platform", {
|
|
configurable: true,
|
|
value: "win32",
|
|
});
|
|
execSchtasksMock.mockReset();
|
|
});
|
|
|
|
afterEach(() => {
|
|
Object.defineProperty(process, "platform", {
|
|
configurable: true,
|
|
value: originalPlatform,
|
|
});
|
|
});
|
|
|
|
it("skips schtasks queries unless deep mode is enabled", async () => {
|
|
const result = await findExtraGatewayServices({});
|
|
expect(result).toEqual([]);
|
|
expect(execSchtasksMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("returns empty results when schtasks query fails", async () => {
|
|
execSchtasksMock.mockResolvedValueOnce({
|
|
code: 1,
|
|
stdout: "",
|
|
stderr: "error",
|
|
});
|
|
|
|
const result = await findExtraGatewayServices({}, { deep: true });
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it("collects only non-openclaw marker tasks from schtasks output", async () => {
|
|
execSchtasksMock.mockResolvedValueOnce({
|
|
code: 0,
|
|
stdout: [
|
|
"TaskName: OpenClaw Gateway",
|
|
"Task To Run: C:\\Program Files\\OpenClaw\\openclaw.exe gateway run",
|
|
"",
|
|
"TaskName: Clawdbot Legacy",
|
|
"Task To Run: C:\\clawdbot\\clawdbot.exe run",
|
|
"",
|
|
"TaskName: Other Task",
|
|
"Task To Run: C:\\tools\\helper.exe",
|
|
"",
|
|
].join("\n"),
|
|
stderr: "",
|
|
});
|
|
|
|
const result = await findExtraGatewayServices({}, { deep: true });
|
|
expect(result).toEqual([
|
|
{
|
|
platform: "win32",
|
|
label: "Clawdbot Legacy",
|
|
detail: "task: Clawdbot Legacy, run: C:\\clawdbot\\clawdbot.exe run",
|
|
scope: "system",
|
|
marker: "clawdbot",
|
|
legacy: true,
|
|
},
|
|
]);
|
|
});
|
|
});
|