From c539782c095d78bf1d58966f7e2bb31e4abf93fa Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 22 Feb 2026 21:57:03 +0100 Subject: [PATCH] test(gateway-lock): stabilize port-probe liveness coverage --- src/infra/gateway-lock.test.ts | 75 ++++++++++------------------------ 1 file changed, 21 insertions(+), 54 deletions(-) diff --git a/src/infra/gateway-lock.test.ts b/src/infra/gateway-lock.test.ts index 072a6e86640..086d37552a5 100644 --- a/src/infra/gateway-lock.test.ts +++ b/src/infra/gateway-lock.test.ts @@ -1,4 +1,5 @@ import { createHash } from "node:crypto"; +import { EventEmitter } from "node:events"; import fsSync from "node:fs"; import fs from "node:fs/promises"; import net from "node:net"; @@ -114,51 +115,6 @@ function createEaccesProcStatSpy() { }); } -async function acquireStaleLinuxLock(env: NodeJS.ProcessEnv) { - await writeLockFile(env, { - startTime: 111, - createdAt: new Date(0).toISOString(), - }); - const staleProcSpy = createEaccesProcStatSpy(); - const lock = await acquireForTest(env, { - staleMs: 1, - platform: "linux", - }); - expect(lock).not.toBeNull(); - - await lock?.release(); - staleProcSpy.mockRestore(); -} - -async function listenOnLoopbackPort() { - const server = net.createServer(); - await new Promise((resolve, reject) => { - server.once("error", reject); - server.listen(0, "127.0.0.1", () => { - server.off("error", reject); - resolve(); - }); - }); - const address = server.address(); - if (!address || typeof address === "string") { - throw new Error("failed to resolve loopback test port"); - } - return { - port: address.port, - close: async () => { - await new Promise((resolve, reject) => { - server.close((err) => { - if (err) { - reject(err); - return; - } - resolve(); - }); - }); - }, - }; -} - describe("gateway lock", () => { beforeAll(async () => { fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-gateway-lock-")); @@ -233,7 +189,6 @@ describe("gateway lock", () => { await expect(pending).rejects.toBeInstanceOf(GatewayLockError); spy.mockRestore(); - await acquireStaleLinuxLock(env); }); it("keeps lock when fs.stat fails until payload is stale", async () => { @@ -253,7 +208,6 @@ describe("gateway lock", () => { await expect(pending).rejects.toBeInstanceOf(GatewayLockError); procSpy.mockRestore(); - await acquireStaleLinuxLock(env); statSpy.mockRestore(); }); @@ -264,19 +218,25 @@ describe("gateway lock", () => { startTime: 111, createdAt: new Date().toISOString(), }); - const listener = await listenOnLoopbackPort(); - const port = listener.port; - await listener.close(); + const connectSpy = vi.spyOn(net, "createConnection").mockImplementation(() => { + const socket = new EventEmitter() as net.Socket; + socket.destroy = vi.fn(); + setImmediate(() => { + socket.emit("error", Object.assign(new Error("ECONNREFUSED"), { code: "ECONNREFUSED" })); + }); + return socket; + }); const lock = await acquireForTest(env, { timeoutMs: 80, pollIntervalMs: 5, staleMs: 10_000, platform: "darwin", - port, + port: 18789, }); expect(lock).not.toBeNull(); await lock?.release(); + connectSpy.mockRestore(); }); it("keeps lock when configured port is busy and owner pid is alive", async () => { @@ -286,18 +246,25 @@ describe("gateway lock", () => { startTime: 111, createdAt: new Date().toISOString(), }); - const listener = await listenOnLoopbackPort(); + const connectSpy = vi.spyOn(net, "createConnection").mockImplementation(() => { + const socket = new EventEmitter() as net.Socket; + socket.destroy = vi.fn(); + setImmediate(() => { + socket.emit("connect"); + }); + return socket; + }); try { const pending = acquireForTest(env, { timeoutMs: 20, pollIntervalMs: 2, staleMs: 10_000, platform: "darwin", - port: listener.port, + port: 18789, }); await expect(pending).rejects.toBeInstanceOf(GatewayLockError); } finally { - await listener.close(); + connectSpy.mockRestore(); } });