Files
openclaw/src/cli/native-hook-relay-cli.test.ts

212 lines
6.3 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import {
createReadableTextStream,
createWritableTextBuffer,
runNativeHookRelayCli,
} from "./native-hook-relay-cli.js";
describe("native hook relay CLI", () => {
it("reads Codex hook JSON from stdin and forwards it to the gateway relay", async () => {
const callGateway = vi.fn(async () => ({ stdout: "", stderr: "", exitCode: 0 }));
const stdout = createWritableTextBuffer();
const stderr = createWritableTextBuffer();
const exitCode = await runNativeHookRelayCli(
{
provider: "codex",
relayId: "relay-1",
event: "pre_tool_use",
timeout: "1234",
},
{
stdin: createReadableTextStream(
JSON.stringify({
hook_event_name: "PreToolUse",
tool_name: "Bash",
tool_input: { command: "pnpm test" },
}),
),
stdout,
stderr,
callGateway: callGateway as never,
},
);
expect(exitCode).toBe(0);
expect(stdout.text()).toBe("");
expect(stderr.text()).toBe("");
expect(callGateway).toHaveBeenCalledWith({
method: "nativeHook.invoke",
params: {
provider: "codex",
relayId: "relay-1",
event: "pre_tool_use",
rawPayload: {
hook_event_name: "PreToolUse",
tool_name: "Bash",
tool_input: { command: "pnpm test" },
},
},
timeoutMs: 1234,
scopes: ["operator.admin"],
});
});
it("renders provider-compatible stdout, stderr, and exit code from the gateway response", async () => {
const callGateway = vi.fn(async () => ({ stdout: "out", stderr: "err", exitCode: 2 }));
const stdout = createWritableTextBuffer();
const stderr = createWritableTextBuffer();
const exitCode = await runNativeHookRelayCli(
{ provider: "codex", relayId: "relay-1", event: "permission_request" },
{
stdin: createReadableTextStream("{}"),
stdout,
stderr,
callGateway: callGateway as never,
},
);
expect(exitCode).toBe(2);
expect(stdout.text()).toBe("out");
expect(stderr.text()).toBe("err");
});
it("returns a nonzero code for malformed hook input without touching the gateway", async () => {
const callGateway = vi.fn();
const stderr = createWritableTextBuffer();
const exitCode = await runNativeHookRelayCli(
{ provider: "codex", relayId: "relay-1", event: "pre_tool_use" },
{
stdin: createReadableTextStream("{nope"),
stderr,
callGateway: callGateway as never,
},
);
expect(exitCode).toBe(1);
expect(stderr.text()).toContain("failed to read native hook input");
expect(callGateway).not.toHaveBeenCalled();
});
it("rejects oversized hook input without touching the gateway", async () => {
const callGateway = vi.fn();
const stderr = createWritableTextBuffer();
const exitCode = await runNativeHookRelayCli(
{ provider: "codex", relayId: "relay-1", event: "post_tool_use" },
{
stdin: createReadableTextStream("x".repeat(1024 * 1024 + 1)),
stderr,
callGateway: callGateway as never,
},
);
expect(exitCode).toBe(1);
expect(stderr.text()).toContain("native hook input exceeds");
expect(callGateway).not.toHaveBeenCalled();
});
it("fails closed for PreToolUse when the gateway relay is unavailable", async () => {
const callGateway = vi.fn(async () => {
throw new Error("gateway closed");
});
const stdout = createWritableTextBuffer();
const stderr = createWritableTextBuffer();
const exitCode = await runNativeHookRelayCli(
{ provider: "codex", relayId: "relay-1", event: "pre_tool_use" },
{
stdin: createReadableTextStream("{}"),
stdout,
stderr,
callGateway: callGateway as never,
},
);
expect(exitCode).toBe(0);
expect(JSON.parse(stdout.text())).toEqual({
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Native hook relay unavailable",
},
});
expect(stderr.text()).toContain("native hook relay unavailable");
});
it("fails closed for PermissionRequest when the gateway relay is unavailable", async () => {
const callGateway = vi.fn(async () => {
throw new Error("gateway closed");
});
const stdout = createWritableTextBuffer();
const stderr = createWritableTextBuffer();
const exitCode = await runNativeHookRelayCli(
{ provider: "codex", relayId: "relay-1", event: "permission_request" },
{
stdin: createReadableTextStream("{}"),
stdout,
stderr,
callGateway: callGateway as never,
},
);
expect(exitCode).toBe(0);
expect(JSON.parse(stdout.text())).toEqual({
hookSpecificOutput: {
hookEventName: "PermissionRequest",
decision: {
behavior: "deny",
message: "Native hook relay unavailable",
},
},
});
});
it("keeps PostToolUse unavailable handling observational", async () => {
const callGateway = vi.fn(async () => {
throw new Error("gateway closed");
});
const stdout = createWritableTextBuffer();
const stderr = createWritableTextBuffer();
const exitCode = await runNativeHookRelayCli(
{ provider: "codex", relayId: "relay-1", event: "post_tool_use" },
{
stdin: createReadableTextStream("{}"),
stdout,
stderr,
callGateway: callGateway as never,
},
);
expect(exitCode).toBe(0);
expect(stdout.text()).toBe("");
expect(stderr.text()).toContain("native hook relay unavailable");
});
it("keeps before_agent_finalize unavailable handling observational", async () => {
const callGateway = vi.fn(async () => {
throw new Error("gateway closed");
});
const stdout = createWritableTextBuffer();
const stderr = createWritableTextBuffer();
const exitCode = await runNativeHookRelayCli(
{ provider: "codex", relayId: "relay-1", event: "before_agent_finalize" },
{
stdin: createReadableTextStream("{}"),
stdout,
stderr,
callGateway: callGateway as never,
},
);
expect(exitCode).toBe(0);
expect(stdout.text()).toBe("");
expect(stderr.text()).toContain("native hook relay unavailable");
});
});