test: add socket and ssh helper coverage

This commit is contained in:
Peter Steinberger 2026-03-13 20:11:57 +00:00
parent dbef3dfef0
commit c3fadff0ce
4 changed files with 207 additions and 28 deletions

109
src/infra/exec-host.test.ts Normal file
View File

@ -0,0 +1,109 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const requestJsonlSocketMock = vi.hoisted(() => vi.fn());
vi.mock("./jsonl-socket.js", () => ({
requestJsonlSocket: (...args: unknown[]) => requestJsonlSocketMock(...args),
}));
import { requestExecHostViaSocket } from "./exec-host.js";
describe("requestExecHostViaSocket", () => {
beforeEach(() => {
requestJsonlSocketMock.mockReset();
});
it("returns null when socket credentials are missing", async () => {
await expect(
requestExecHostViaSocket({
socketPath: "",
token: "secret",
request: { command: ["echo", "hi"] },
}),
).resolves.toBeNull();
await expect(
requestExecHostViaSocket({
socketPath: "/tmp/socket",
token: "",
request: { command: ["echo", "hi"] },
}),
).resolves.toBeNull();
expect(requestJsonlSocketMock).not.toHaveBeenCalled();
});
it("builds an exec payload and forwards the default timeout", async () => {
requestJsonlSocketMock.mockResolvedValueOnce({ ok: true, payload: { success: true } });
await expect(
requestExecHostViaSocket({
socketPath: "/tmp/socket",
token: "secret",
request: {
command: ["echo", "hi"],
cwd: "/tmp",
},
}),
).resolves.toEqual({ ok: true, payload: { success: true } });
const call = requestJsonlSocketMock.mock.calls[0]?.[0] as
| {
socketPath: string;
payload: string;
timeoutMs: number;
accept: (msg: unknown) => unknown;
}
| undefined;
if (!call) {
throw new Error("expected requestJsonlSocket call");
}
expect(call.socketPath).toBe("/tmp/socket");
expect(call.timeoutMs).toBe(20_000);
const payload = JSON.parse(call.payload) as {
type: string;
id: string;
nonce: string;
ts: number;
hmac: string;
requestJson: string;
};
expect(payload.type).toBe("exec");
expect(payload.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
expect(payload.nonce).toMatch(/^[0-9a-f]{32}$/);
expect(typeof payload.ts).toBe("number");
expect(payload.hmac).toMatch(/^[0-9a-f]{64}$/);
expect(JSON.parse(payload.requestJson)).toEqual({
command: ["echo", "hi"],
cwd: "/tmp",
});
});
it("accepts only exec response messages and maps malformed matches to null", async () => {
requestJsonlSocketMock.mockImplementationOnce(async ({ accept }) => {
expect(accept({ type: "ignore" })).toBeUndefined();
expect(accept({ type: "exec-res", ok: true, payload: { success: true } })).toEqual({
ok: true,
payload: { success: true },
});
expect(accept({ type: "exec-res", ok: false, error: { code: "DENIED" } })).toEqual({
ok: false,
error: { code: "DENIED" },
});
expect(accept({ type: "exec-res", ok: true })).toBeNull();
return null;
});
await expect(
requestExecHostViaSocket({
socketPath: "/tmp/socket",
token: "secret",
timeoutMs: 123,
request: { command: ["echo", "hi"] },
}),
).resolves.toBeNull();
expect(
(requestJsonlSocketMock.mock.calls[0]?.[0] as { timeoutMs?: number } | undefined)?.timeoutMs,
).toBe(123);
});
});

View File

@ -1,28 +0,0 @@
import { describe, expect, it } from "vitest";
import { parseSshTarget } from "./ssh-tunnel.js";
describe("infra parsing", () => {
describe("parseSshTarget", () => {
it("parses user@host:port targets", () => {
expect(parseSshTarget("me@example.com:2222")).toEqual({
user: "me",
host: "example.com",
port: 2222,
});
});
it("parses host-only targets with default port", () => {
expect(parseSshTarget("example.com")).toEqual({
user: undefined,
host: "example.com",
port: 22,
});
});
it("rejects hostnames that start with '-'", () => {
expect(parseSshTarget("-V")).toBeNull();
expect(parseSshTarget("me@-badhost")).toBeNull();
expect(parseSshTarget("-oProxyCommand=echo")).toBeNull();
});
});
});

View File

@ -0,0 +1,69 @@
import net from "node:net";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { withTempDir } from "../test-helpers/temp-dir.js";
import { requestJsonlSocket } from "./jsonl-socket.js";
describe.runIf(process.platform !== "win32")("requestJsonlSocket", () => {
it("ignores malformed and non-accepted lines until one is accepted", async () => {
await withTempDir({ prefix: "openclaw-jsonl-socket-" }, async (dir) => {
const socketPath = path.join(dir, "socket.sock");
const server = net.createServer((socket) => {
socket.on("data", () => {
socket.write("{bad json}\n");
socket.write('{"type":"ignore"}\n');
socket.write('{"type":"done","value":42}\n');
});
});
await new Promise<void>((resolve) => server.listen(socketPath, resolve));
try {
await expect(
requestJsonlSocket({
socketPath,
payload: '{"hello":"world"}',
timeoutMs: 500,
accept: (msg) => {
const value = msg as { type?: string; value?: number };
return value.type === "done" ? (value.value ?? null) : undefined;
},
}),
).resolves.toBe(42);
} finally {
server.close();
}
});
});
it("returns null on timeout and on socket errors", async () => {
await withTempDir({ prefix: "openclaw-jsonl-socket-" }, async (dir) => {
const socketPath = path.join(dir, "socket.sock");
const server = net.createServer(() => {
// Intentionally never reply.
});
await new Promise<void>((resolve) => server.listen(socketPath, resolve));
try {
await expect(
requestJsonlSocket({
socketPath,
payload: "{}",
timeoutMs: 50,
accept: () => undefined,
}),
).resolves.toBeNull();
} finally {
server.close();
}
await expect(
requestJsonlSocket({
socketPath,
payload: "{}",
timeoutMs: 50,
accept: () => undefined,
}),
).resolves.toBeNull();
});
});
});

View File

@ -0,0 +1,29 @@
import { describe, expect, it } from "vitest";
import { parseSshTarget } from "./ssh-tunnel.js";
describe("parseSshTarget", () => {
it("parses user@host:port targets", () => {
expect(parseSshTarget("me@example.com:2222")).toEqual({
user: "me",
host: "example.com",
port: 2222,
});
});
it("strips an ssh prefix and keeps the default port when missing", () => {
expect(parseSshTarget(" ssh alice@example.com ")).toEqual({
user: "alice",
host: "example.com",
port: 22,
});
});
it("rejects invalid hosts and ports", () => {
expect(parseSshTarget("")).toBeNull();
expect(parseSshTarget("me@example.com:0")).toBeNull();
expect(parseSshTarget("me@example.com:not-a-port")).toBeNull();
expect(parseSshTarget("-V")).toBeNull();
expect(parseSshTarget("me@-badhost")).toBeNull();
expect(parseSshTarget("-oProxyCommand=echo")).toBeNull();
});
});