diff --git a/src/infra/map-size.test.ts b/src/infra/map-size.test.ts new file mode 100644 index 00000000000..82fbe8a52ac --- /dev/null +++ b/src/infra/map-size.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from "vitest"; +import { pruneMapToMaxSize } from "./map-size.js"; + +describe("pruneMapToMaxSize", () => { + it("keeps the newest entries after flooring fractional limits", () => { + const map = new Map([ + ["a", 1], + ["b", 2], + ["c", 3], + ]); + + pruneMapToMaxSize(map, 2.9); + + expect([...map.entries()]).toEqual([ + ["b", 2], + ["c", 3], + ]); + }); + + it("clears maps for zero or negative limits and leaves undersized maps untouched", () => { + const cleared = new Map([ + ["a", 1], + ["b", 2], + ]); + pruneMapToMaxSize(cleared, 0); + expect([...cleared.entries()]).toEqual([]); + + const alsoCleared = new Map([ + ["a", 1], + ["b", 2], + ]); + pruneMapToMaxSize(alsoCleared, -4); + expect([...alsoCleared.entries()]).toEqual([]); + + const unchanged = new Map([["a", 1]]); + pruneMapToMaxSize(unchanged, 5); + expect([...unchanged.entries()]).toEqual([["a", 1]]); + }); +}); diff --git a/src/infra/ports.test.ts b/src/infra/ports.test.ts index f809662f1ac..9059d5e0c0f 100644 --- a/src/infra/ports.test.ts +++ b/src/infra/ports.test.ts @@ -7,6 +7,7 @@ const runCommandWithTimeoutMock = vi.hoisted(() => vi.fn()); vi.mock("../process/exec.js", () => ({ runCommandWithTimeout: (...args: unknown[]) => runCommandWithTimeoutMock(...args), })); +import { formatPortListener } from "./ports-format.js"; import { inspectPortUsage } from "./ports-inspect.js"; import { buildPortHints, @@ -62,29 +63,75 @@ describe("ports helpers", () => { expect(messages.join("\n")).toContain("another OpenClaw instance is already running"); }); - it("classifies ssh and gateway listeners", () => { - expect( - classifyPortListener({ commandLine: "ssh -N -L 18789:127.0.0.1:18789 user@host" }, 18789), - ).toBe("ssh"); - expect( - classifyPortListener( - { - commandLine: "node /Users/me/Projects/openclaw/dist/entry.js gateway", - }, - 18789, - ), - ).toBe("gateway"); + it("classifies port listeners across gateway, ssh, and unknown cases", () => { + const cases = [ + { + listener: { commandLine: "ssh -N -L 18789:127.0.0.1:18789 user@host" }, + expected: "ssh", + }, + { + listener: { command: "ssh" }, + expected: "ssh", + }, + { + listener: { commandLine: "node /Users/me/Projects/openclaw/dist/entry.js gateway" }, + expected: "gateway", + }, + { + listener: { commandLine: "python -m http.server 18789" }, + expected: "unknown", + }, + ] as const; + + for (const testCase of cases) { + expect( + classifyPortListener(testCase.listener, 18789), + JSON.stringify(testCase.listener), + ).toBe(testCase.expected); + } }); - it("formats port diagnostics with hints", () => { - const diagnostics = { + it("builds ordered hints for mixed listener kinds and multiple listeners", () => { + expect( + buildPortHints( + [ + { commandLine: "node dist/index.js openclaw gateway" }, + { commandLine: "ssh -N -L 18789:127.0.0.1:18789" }, + { commandLine: "python -m http.server 18789" }, + ], + 18789, + ), + ).toEqual([ + expect.stringContaining("Gateway already running locally."), + "SSH tunnel already bound to this port. Close the tunnel or use a different local port in -L.", + "Another process is listening on this port.", + expect.stringContaining("Multiple listeners detected"), + ]); + expect(buildPortHints([], 18789)).toEqual([]); + }); + + it("formats port listeners and diagnostics for free and busy ports", () => { + expect(formatPortListener({ command: "ssh", address: "127.0.0.1:18789" })).toBe( + "pid ?: ssh (127.0.0.1:18789)", + ); + + expect( + formatPortDiagnostics({ + port: 18789, + status: "free", + listeners: [], + hints: [], + }), + ).toEqual(["Port 18789 is free."]); + + const lines = formatPortDiagnostics({ port: 18789, - status: "busy" as const, - listeners: [{ pid: 123, commandLine: "ssh -N -L 18789:127.0.0.1:18789" }], + status: "busy", + listeners: [{ pid: 123, user: "alice", commandLine: "ssh -N -L 18789:127.0.0.1:18789" }], hints: buildPortHints([{ pid: 123, commandLine: "ssh -N -L 18789:127.0.0.1:18789" }], 18789), - }; - const lines = formatPortDiagnostics(diagnostics); + }); expect(lines[0]).toContain("Port 18789 is already in use"); + expect(lines).toContain("- pid 123 alice: ssh -N -L 18789:127.0.0.1:18789"); expect(lines.some((line) => line.includes("SSH tunnel"))).toBe(true); }); });