Spaces:
Configuration error
Configuration error
| import { describe, expect, it, vi } from "vitest"; | |
| import { ExecApprovalManager } from "../exec-approval-manager.js"; | |
| import { createExecApprovalHandlers } from "./exec-approval.js"; | |
| import { validateExecApprovalRequestParams } from "../protocol/index.js"; | |
| const noop = () => {}; | |
| describe("exec approval handlers", () => { | |
| describe("ExecApprovalRequestParams validation", () => { | |
| it("accepts request with resolvedPath omitted", () => { | |
| const params = { | |
| command: "echo hi", | |
| cwd: "/tmp", | |
| host: "node", | |
| }; | |
| expect(validateExecApprovalRequestParams(params)).toBe(true); | |
| }); | |
| it("accepts request with resolvedPath as string", () => { | |
| const params = { | |
| command: "echo hi", | |
| cwd: "/tmp", | |
| host: "node", | |
| resolvedPath: "/usr/bin/echo", | |
| }; | |
| expect(validateExecApprovalRequestParams(params)).toBe(true); | |
| }); | |
| it("accepts request with resolvedPath as undefined", () => { | |
| const params = { | |
| command: "echo hi", | |
| cwd: "/tmp", | |
| host: "node", | |
| resolvedPath: undefined, | |
| }; | |
| expect(validateExecApprovalRequestParams(params)).toBe(true); | |
| }); | |
| // Fixed: null is now accepted (Type.Union([Type.String(), Type.Null()])) | |
| // This matches the calling code in bash-tools.exec.ts which passes null. | |
| it("accepts request with resolvedPath as null", () => { | |
| const params = { | |
| command: "echo hi", | |
| cwd: "/tmp", | |
| host: "node", | |
| resolvedPath: null, | |
| }; | |
| expect(validateExecApprovalRequestParams(params)).toBe(true); | |
| }); | |
| }); | |
| it("broadcasts request + resolve", async () => { | |
| const manager = new ExecApprovalManager(); | |
| const handlers = createExecApprovalHandlers(manager); | |
| const broadcasts: Array<{ event: string; payload: unknown }> = []; | |
| const respond = vi.fn(); | |
| const context = { | |
| broadcast: (event: string, payload: unknown) => { | |
| broadcasts.push({ event, payload }); | |
| }, | |
| }; | |
| const requestPromise = handlers["exec.approval.request"]({ | |
| params: { | |
| command: "echo ok", | |
| cwd: "/tmp", | |
| host: "node", | |
| timeoutMs: 2000, | |
| }, | |
| respond, | |
| context: context as unknown as Parameters< | |
| (typeof handlers)["exec.approval.request"] | |
| >[0]["context"], | |
| client: null, | |
| req: { id: "req-1", type: "req", method: "exec.approval.request" }, | |
| isWebchatConnect: noop, | |
| }); | |
| const requested = broadcasts.find((entry) => entry.event === "exec.approval.requested"); | |
| expect(requested).toBeTruthy(); | |
| const id = (requested?.payload as { id?: string })?.id ?? ""; | |
| expect(id).not.toBe(""); | |
| const resolveRespond = vi.fn(); | |
| await handlers["exec.approval.resolve"]({ | |
| params: { id, decision: "allow-once" }, | |
| respond: resolveRespond, | |
| context: context as unknown as Parameters< | |
| (typeof handlers)["exec.approval.resolve"] | |
| >[0]["context"], | |
| client: { connect: { client: { id: "cli", displayName: "CLI" } } }, | |
| req: { id: "req-2", type: "req", method: "exec.approval.resolve" }, | |
| isWebchatConnect: noop, | |
| }); | |
| await requestPromise; | |
| expect(resolveRespond).toHaveBeenCalledWith(true, { ok: true }, undefined); | |
| expect(respond).toHaveBeenCalledWith( | |
| true, | |
| expect.objectContaining({ id, decision: "allow-once" }), | |
| undefined, | |
| ); | |
| expect(broadcasts.some((entry) => entry.event === "exec.approval.resolved")).toBe(true); | |
| }); | |
| it("accepts resolve during broadcast", async () => { | |
| const manager = new ExecApprovalManager(); | |
| const handlers = createExecApprovalHandlers(manager); | |
| const respond = vi.fn(); | |
| const resolveRespond = vi.fn(); | |
| const resolveContext = { | |
| broadcast: () => {}, | |
| }; | |
| const context = { | |
| broadcast: (event: string, payload: unknown) => { | |
| if (event !== "exec.approval.requested") return; | |
| const id = (payload as { id?: string })?.id ?? ""; | |
| void handlers["exec.approval.resolve"]({ | |
| params: { id, decision: "allow-once" }, | |
| respond: resolveRespond, | |
| context: resolveContext as unknown as Parameters< | |
| (typeof handlers)["exec.approval.resolve"] | |
| >[0]["context"], | |
| client: { connect: { client: { id: "cli", displayName: "CLI" } } }, | |
| req: { id: "req-2", type: "req", method: "exec.approval.resolve" }, | |
| isWebchatConnect: noop, | |
| }); | |
| }, | |
| }; | |
| await handlers["exec.approval.request"]({ | |
| params: { | |
| command: "echo ok", | |
| cwd: "/tmp", | |
| host: "node", | |
| timeoutMs: 2000, | |
| }, | |
| respond, | |
| context: context as unknown as Parameters< | |
| (typeof handlers)["exec.approval.request"] | |
| >[0]["context"], | |
| client: null, | |
| req: { id: "req-1", type: "req", method: "exec.approval.request" }, | |
| isWebchatConnect: noop, | |
| }); | |
| expect(resolveRespond).toHaveBeenCalledWith(true, { ok: true }, undefined); | |
| expect(respond).toHaveBeenCalledWith( | |
| true, | |
| expect.objectContaining({ decision: "allow-once" }), | |
| undefined, | |
| ); | |
| }); | |
| it("accepts explicit approval ids", async () => { | |
| const manager = new ExecApprovalManager(); | |
| const handlers = createExecApprovalHandlers(manager); | |
| const broadcasts: Array<{ event: string; payload: unknown }> = []; | |
| const respond = vi.fn(); | |
| const context = { | |
| broadcast: (event: string, payload: unknown) => { | |
| broadcasts.push({ event, payload }); | |
| }, | |
| }; | |
| const requestPromise = handlers["exec.approval.request"]({ | |
| params: { | |
| id: "approval-123", | |
| command: "echo ok", | |
| cwd: "/tmp", | |
| host: "gateway", | |
| timeoutMs: 2000, | |
| }, | |
| respond, | |
| context: context as unknown as Parameters< | |
| (typeof handlers)["exec.approval.request"] | |
| >[0]["context"], | |
| client: null, | |
| req: { id: "req-1", type: "req", method: "exec.approval.request" }, | |
| isWebchatConnect: noop, | |
| }); | |
| const requested = broadcasts.find((entry) => entry.event === "exec.approval.requested"); | |
| const id = (requested?.payload as { id?: string })?.id ?? ""; | |
| expect(id).toBe("approval-123"); | |
| const resolveRespond = vi.fn(); | |
| await handlers["exec.approval.resolve"]({ | |
| params: { id, decision: "allow-once" }, | |
| respond: resolveRespond, | |
| context: context as unknown as Parameters< | |
| (typeof handlers)["exec.approval.resolve"] | |
| >[0]["context"], | |
| client: { connect: { client: { id: "cli", displayName: "CLI" } } }, | |
| req: { id: "req-2", type: "req", method: "exec.approval.resolve" }, | |
| isWebchatConnect: noop, | |
| }); | |
| await requestPromise; | |
| expect(respond).toHaveBeenCalledWith( | |
| true, | |
| expect.objectContaining({ id: "approval-123", decision: "allow-once" }), | |
| undefined, | |
| ); | |
| }); | |
| it("rejects duplicate approval ids", async () => { | |
| const manager = new ExecApprovalManager(); | |
| const handlers = createExecApprovalHandlers(manager); | |
| const respondA = vi.fn(); | |
| const respondB = vi.fn(); | |
| const broadcasts: Array<{ event: string; payload: unknown }> = []; | |
| const context = { | |
| broadcast: (event: string, payload: unknown) => { | |
| broadcasts.push({ event, payload }); | |
| }, | |
| }; | |
| const requestPromise = handlers["exec.approval.request"]({ | |
| params: { | |
| id: "dup-1", | |
| command: "echo ok", | |
| }, | |
| respond: respondA, | |
| context: context as unknown as Parameters< | |
| (typeof handlers)["exec.approval.request"] | |
| >[0]["context"], | |
| client: null, | |
| req: { id: "req-1", type: "req", method: "exec.approval.request" }, | |
| isWebchatConnect: noop, | |
| }); | |
| await handlers["exec.approval.request"]({ | |
| params: { | |
| id: "dup-1", | |
| command: "echo again", | |
| }, | |
| respond: respondB, | |
| context: context as unknown as Parameters< | |
| (typeof handlers)["exec.approval.request"] | |
| >[0]["context"], | |
| client: null, | |
| req: { id: "req-2", type: "req", method: "exec.approval.request" }, | |
| isWebchatConnect: noop, | |
| }); | |
| expect(respondB).toHaveBeenCalledWith( | |
| false, | |
| undefined, | |
| expect.objectContaining({ message: "approval id already pending" }), | |
| ); | |
| const requested = broadcasts.find((entry) => entry.event === "exec.approval.requested"); | |
| const id = (requested?.payload as { id?: string })?.id ?? ""; | |
| const resolveRespond = vi.fn(); | |
| await handlers["exec.approval.resolve"]({ | |
| params: { id, decision: "deny" }, | |
| respond: resolveRespond, | |
| context: context as unknown as Parameters< | |
| (typeof handlers)["exec.approval.resolve"] | |
| >[0]["context"], | |
| client: { connect: { client: { id: "cli", displayName: "CLI" } } }, | |
| req: { id: "req-3", type: "req", method: "exec.approval.resolve" }, | |
| isWebchatConnect: noop, | |
| }); | |
| await requestPromise; | |
| }); | |
| }); | |