mirror of https://github.com/openclaw/openclaw.git
test: add socket and ssh helper coverage
This commit is contained in:
parent
dbef3dfef0
commit
c3fadff0ce
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue