Tests: cover gateway --force helpers
parent
6afcf43ff2
commit
dc8f9e043d
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
|
||||||
|
vi.mock("node:child_process", async () => {
|
||||||
|
const actual = await vi.importActual<typeof import("node:child_process")>(
|
||||||
|
"node:child_process",
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
execFileSync: vi.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
import { execFileSync } from "node:child_process";
|
||||||
|
import {
|
||||||
|
forceFreePort,
|
||||||
|
listPortListeners,
|
||||||
|
parseLsofOutput,
|
||||||
|
type PortProcess,
|
||||||
|
} from "./program.js";
|
||||||
|
|
||||||
|
describe("gateway --force helpers", () => {
|
||||||
|
let originalKill: typeof process.kill;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
originalKill = process.kill;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.kill = originalKill;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses lsof output into pid/command pairs", () => {
|
||||||
|
const sample = ["p123", "cnode", "p456", "cpython", ""].join("\n");
|
||||||
|
const parsed = parseLsofOutput(sample);
|
||||||
|
expect(parsed).toEqual<PortProcess[]>([
|
||||||
|
{ pid: 123, command: "node" },
|
||||||
|
{ pid: 456, command: "python" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns empty list when lsof finds nothing", () => {
|
||||||
|
(execFileSync as unknown as vi.Mock).mockImplementation(() => {
|
||||||
|
const err = new Error("no matches");
|
||||||
|
// @ts-expect-error partial
|
||||||
|
err.status = 1; // lsof uses exit 1 for no matches
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
expect(listPortListeners(18789)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when lsof missing", () => {
|
||||||
|
(execFileSync as unknown as vi.Mock).mockImplementation(() => {
|
||||||
|
const err = new Error("not found");
|
||||||
|
// @ts-expect-error partial
|
||||||
|
err.code = "ENOENT";
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
expect(() => listPortListeners(18789)).toThrow(/lsof not found/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("kills each listener and returns metadata", () => {
|
||||||
|
(execFileSync as unknown as vi.Mock).mockReturnValue(
|
||||||
|
["p42", "cnode", "p99", "cssh", ""].join("\n"),
|
||||||
|
);
|
||||||
|
const killMock = vi.fn();
|
||||||
|
// @ts-expect-error override for test
|
||||||
|
process.kill = killMock;
|
||||||
|
|
||||||
|
const killed = forceFreePort(18789);
|
||||||
|
|
||||||
|
expect(execFileSync).toHaveBeenCalled();
|
||||||
|
expect(killMock).toHaveBeenCalledTimes(2);
|
||||||
|
expect(killMock).toHaveBeenCalledWith(42, "SIGTERM");
|
||||||
|
expect(killMock).toHaveBeenCalledWith(99, "SIGTERM");
|
||||||
|
expect(killed).toEqual<PortProcess[]>([
|
||||||
|
{ pid: 42, command: "node" },
|
||||||
|
{ pid: 99, command: "ssh" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -17,9 +17,9 @@ import { VERSION } from "../version.js";
|
||||||
import { startWebChatServer } from "../webchat/server.js";
|
import { startWebChatServer } from "../webchat/server.js";
|
||||||
import { createDefaultDeps } from "./deps.js";
|
import { createDefaultDeps } from "./deps.js";
|
||||||
|
|
||||||
type PortProcess = { pid: number; command?: string };
|
export type PortProcess = { pid: number; command?: string };
|
||||||
|
|
||||||
function parseLsofOutput(output: string): PortProcess[] {
|
export function parseLsofOutput(output: string): PortProcess[] {
|
||||||
const lines = output.split(/\r?\n/).filter(Boolean);
|
const lines = output.split(/\r?\n/).filter(Boolean);
|
||||||
const results: PortProcess[] = [];
|
const results: PortProcess[] = [];
|
||||||
let current: Partial<PortProcess> = {};
|
let current: Partial<PortProcess> = {};
|
||||||
|
|
@ -35,7 +35,7 @@ function parseLsofOutput(output: string): PortProcess[] {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
function listPortListeners(port: number): PortProcess[] {
|
export function listPortListeners(port: number): PortProcess[] {
|
||||||
try {
|
try {
|
||||||
const out = execFileSync(
|
const out = execFileSync(
|
||||||
"lsof",
|
"lsof",
|
||||||
|
|
@ -55,7 +55,7 @@ function listPortListeners(port: number): PortProcess[] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function forceFreePort(port: number): PortProcess[] {
|
export function forceFreePort(port: number): PortProcess[] {
|
||||||
const listeners = listPortListeners(port);
|
const listeners = listPortListeners(port);
|
||||||
for (const proc of listeners) {
|
for (const proc of listeners) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue