Files
agent/packages/harness/tests/approval-broker.test.ts

125 lines
4.3 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import * as harness from "../src/index.js";
type Resolution = { approvalId: string; status: string };
type Request = {
approvalId: string;
sessionId: string;
runId: string;
toolCallId: string;
toolName: string;
arguments: unknown;
riskLevel: "low" | "medium" | "high";
};
type Broker = {
request(input: Request, signal?: AbortSignal): Promise<Resolution>;
approve(approvalId: string): Resolution;
deny(approvalId: string): Resolution;
cancelForSession(sessionId: string): number;
cancelForRun(runId: string): number;
};
function createBroker(timeoutMs = 1_000): {
broker: Broker;
store: { listAfter(sessionId: string): { events: Array<{ type: string; payload: unknown }> } };
} {
const Store = Reflect.get(harness, "InMemoryEventStore") as new () => {
listAfter(sessionId: string): { events: Array<{ type: string; payload: unknown }> };
};
const Broker = Reflect.get(harness, "InMemoryApprovalBroker") as unknown as
| (new (options: Record<string, unknown>) => Broker)
| undefined;
expect(Broker, "InMemoryApprovalBroker must be exported").toBeDefined();
const store = new Store();
return { broker: new Broker!({ eventStore: store, timeoutMs }), store };
}
function request(overrides: Partial<Request> = {}): Request {
return {
approvalId: "ap_1",
sessionId: "ses_1",
runId: "run_1",
toolCallId: "call_1",
toolName: "save_report_draft",
arguments: { body: "report", apiKey: "never-log-me" },
riskLevel: "medium",
...overrides,
};
}
afterEach(() => {
vi.useRealTimers();
});
describe("InMemoryApprovalBroker", () => {
it("resolves an approved request and treats the same repeated decision as idempotent", async () => {
const { broker, store } = createBroker();
const waiting = broker.request(request());
const first = broker.approve("ap_1");
const second = broker.approve("ap_1");
await expect(waiting).resolves.toEqual({ approvalId: "ap_1", status: "approved" });
expect(second).toEqual(first);
expect(store.listAfter("ses_1").events.map((event) => event.type)).toEqual([
"approval.required",
"approval.resolved",
]);
expect(JSON.stringify(store.listAfter("ses_1"))).not.toContain("never-log-me");
});
it("rejects a conflicting second decision", async () => {
const { broker } = createBroker();
const waiting = broker.request(request());
broker.approve("ap_1");
await waiting;
expect(() => broker.deny("ap_1")).toThrowError(
expect.objectContaining({ code: "APPROVAL_ALREADY_RESOLVED" }),
);
});
it("expires pending approval once when its deadline passes", async () => {
vi.useFakeTimers();
const { broker, store } = createBroker(50);
const waiting = broker.request(request());
await vi.advanceTimersByTimeAsync(50);
await expect(waiting).resolves.toEqual({ approvalId: "ap_1", status: "expired" });
expect(store.listAfter("ses_1").events.map((event) => event.type)).toEqual([
"approval.required",
"approval.resolved",
]);
});
it("cancels on abort and removes the abort listener after resolution", async () => {
const { broker, store } = createBroker();
const controller = new AbortController();
const waiting = broker.request(request(), controller.signal);
controller.abort();
await expect(waiting).resolves.toEqual({ approvalId: "ap_1", status: "cancelled" });
expect(store.listAfter("ses_1").events).toHaveLength(2);
controller.abort();
expect(store.listAfter("ses_1").events).toHaveLength(2);
});
it("cancels all pending approvals for a session or run", async () => {
const { broker } = createBroker();
const first = broker.request(request());
const second = broker.request(request({ approvalId: "ap_2", runId: "run_2", toolCallId: "call_2" }));
const third = broker.request(request({ approvalId: "ap_3", sessionId: "ses_2", toolCallId: "call_3" }));
expect(broker.cancelForRun("run_2")).toBe(1);
expect(broker.cancelForSession("ses_1")).toBe(1);
expect(broker.cancelForSession("ses_2")).toBe(1);
await expect(Promise.all([first, second, third])).resolves.toEqual([
{ approvalId: "ap_1", status: "cancelled" },
{ approvalId: "ap_2", status: "cancelled" },
{ approvalId: "ap_3", status: "cancelled" },
]);
});
});