openclaw / src /cli /daemon-cli /lifecycle.gateway-owner.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
f778c12 verified
Raw History Blame Contribute Delete
5.2 kB
import { afterEach, beforeEach, expect, it, vi } from "vitest";
import { mockSystemAccountHome } from "../../daemon/service.test-helpers.js";
import {
lifecycleTestRuntime,
resetLifecycleRuntimeLogs,
resetLifecycleServiceMocks,
service,
stubEmptyGatewayEnv,
} from "./test-helpers/lifecycle-core-harness.js";
const owner = vi.hoisted(() => ({
owner: "foreground-generation",
pid: 9472,
host: "fixture-host",
startedAt: 100,
port: 18789,
mode: "foreground" as const,
supervisor: null,
state: "live" as const,
expired: false,
}));
const mocks = vi.hoisted(() => ({
readOwner: vi.fn<(params?: { env?: NodeJS.ProcessEnv }) => typeof owner | undefined>(() => owner),
callGatewayCli: vi.fn(async () => ({ pid: owner.pid })),
waitForGatewayHealthyListener: vi.fn(async () => ({ healthy: true })),
}));
vi.mock("../../runtime.js", () => ({ defaultRuntime: lifecycleTestRuntime }));
vi.mock("../../daemon/service.js", async (original) => ({
...(await original<typeof import("../../daemon/service.js")>()),
resolveGatewayService: () => service,
}));
vi.mock("../../config/config.js", async (original) => ({
...(await original<typeof import("../../config/config.js")>()),
readBestEffortConfig: async () => ({ gateway: { port: 18789 } }),
}));
vi.mock("../../config/io.js", () => ({
createConfigIO: () => ({
readBestEffortConfig: async () => ({ gateway: { port: 18789 } }),
}),
}));
vi.mock("./lifecycle-action-preflight.js", () => ({
getServiceActionPreflightFailure: async () => null,
}));
vi.mock("../../infra/gateway-owner-lease.js", () => ({
readGatewayOwnerLease: mocks.readOwner,
}));
vi.mock("../../infra/gateway-lock.js", () => ({
readActiveGatewayLockPort: async () => owner.port,
readActiveGatewayLockIdentity: async () => ({
ownerId: owner.owner,
pid: owner.pid,
port: owner.port,
createdAt: "2026-09-13T02:27:25Z",
startTime: owner.startedAt,
}),
isSameGatewayLockIdentity: (a: { ownerId?: string }, b: { ownerId?: string }) =>
a.ownerId === b.ownerId,
}));
vi.mock("../../infra/gateway-processes.js", () => ({
findVerifiedGatewayListenerPidsOnPortSync: () => [owner.pid],
signalVerifiedGatewayPidSync: vi.fn(),
formatGatewayPidList: (pids: number[]) => pids.join(", "),
}));
vi.mock("../../gateway/probe.js", () => ({
probeGateway: async () => ({ ok: true, configSnapshot: { commands: { restart: true } } }),
}));
vi.mock("../../gateway/call.js", () => ({ callGatewayCli: mocks.callGatewayCli }));
vi.mock("./restart-health.js", async (original) => ({
...(await original<typeof import("./restart-health.js")>()),
waitForGatewayHealthyListener: mocks.waitForGatewayHealthyListener,
}));
vi.mock("./lifecycle-audit.js", () => ({
appendGatewayLifecycleAudit: vi.fn(),
createGatewayLifecycleMutationAudit: () => undefined,
createServiceLifecycleMutationAudit: () => undefined,
appendServiceLifecycleRepairAudit: vi.fn(),
}));
vi.mock("../../infra/restart-intent.js", async (original) => ({
...(await original<typeof import("../../infra/restart-intent.js")>()),
writeGatewayRestartIntentSync: () => true,
clearGatewayRestartIntentSync: vi.fn(),
}));
beforeEach(() => {
vi.clearAllMocks();
resetLifecycleRuntimeLogs();
resetLifecycleServiceMocks();
stubEmptyGatewayEnv();
mockSystemAccountHome();
vi.stubEnv("OPENCLAW_SUPERVISOR_MODE", "");
vi.stubEnv("OPENCLAW_CONTAINER_HINT", "");
vi.stubEnv("OPENCLAW_UPDATE_IN_PROGRESS", "");
mocks.readOwner.mockImplementation((params) =>
params?.env?.OPENCLAW_STATE_DIR === "foreign-service-state" ? undefined : owner,
);
vi.spyOn(process, "platform", "get").mockReturnValue("win32");
service.readCommand.mockResolvedValue({
programArguments: [
"node",
"C:\\OpenClaw\\dist\\index.js",
"gateway",
"--port",
"18789",
"--task-supervisor",
],
environment: {},
});
service.readRuntime.mockResolvedValue({ status: "stopped" });
service.restart.mockRejectedValue(new Error("gateway port 18789 is still busy before restart"));
});
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllEnvs();
});
it.each([false, true])(
"restarts the selected foreground owner despite an installed task (different state=%s)",
async (differentState) => {
if (differentState) {
const command = await service.readCommand(process.env);
if (!command) {
throw new Error("Expected installed task fixture");
}
service.readCommand.mockResolvedValue({
...command,
environment: { OPENCLAW_STATE_DIR: "foreign-service-state" },
});
}
const { runDaemonRestart } = await import("./lifecycle.js");
await expect(runDaemonRestart({ json: true })).resolves.toBe(true);
expect(mocks.callGatewayCli).toHaveBeenCalledWith(
expect.objectContaining({
method: "gateway.restart.request",
params: expect.objectContaining({
target: { pid: owner.pid, ownerId: owner.owner, port: owner.port },
}),
}),
);
expect(service.restart).not.toHaveBeenCalled();
expect(service.install).not.toHaveBeenCalled();
expect(mocks.waitForGatewayHealthyListener).toHaveBeenCalled();
},
);