| import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; |
| import { DEFAULT_EXEC_APPROVAL_TIMEOUT_MS } from "../../infra/exec-approvals.js"; |
| import { parseTimeoutMs } from "../nodes-run.js"; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const callGatewaySpy = vi.fn< |
| (opts: Record<string, unknown>) => Promise<{ decision: "allow-once" }> |
| >(async () => ({ decision: "allow-once" })); |
|
|
| vi.mock("../../gateway/call.js", () => ({ |
| callGateway: callGatewaySpy, |
| randomIdempotencyKey: () => "mock-key", |
| })); |
|
|
| vi.mock("../progress.js", () => ({ |
| withProgress: (_opts: unknown, fn: () => unknown) => fn(), |
| })); |
|
|
| describe("nodes run: approval transport timeout (#12098)", () => { |
| let callGatewayCli: typeof import("./rpc.js").callGatewayCli; |
|
|
| beforeAll(async () => { |
| ({ callGatewayCli } = await import("./rpc.js")); |
| }); |
|
|
| beforeEach(() => { |
| callGatewaySpy.mockClear(); |
| callGatewaySpy.mockResolvedValue({ decision: "allow-once" }); |
| }); |
|
|
| it("callGatewayCli forwards opts.timeout as the transport timeoutMs", async () => { |
| await callGatewayCli("exec.approval.request", { timeout: "35000" } as never, { |
| timeoutMs: 120_000, |
| }); |
|
|
| expect(callGatewaySpy).toHaveBeenCalledTimes(1); |
| const callOpts = callGatewaySpy.mock.calls[0][0]; |
| expect(callOpts.method).toBe("exec.approval.request"); |
| expect(callOpts.timeoutMs).toBe(35_000); |
| }); |
|
|
| it("fix: overriding transportTimeoutMs gives the approval enough transport time", async () => { |
| const approvalTimeoutMs = 120_000; |
| |
| const transportTimeoutMs = Math.max(parseTimeoutMs("35000") ?? 0, approvalTimeoutMs + 10_000); |
| expect(transportTimeoutMs).toBe(130_000); |
|
|
| await callGatewayCli( |
| "exec.approval.request", |
| { timeout: "35000" } as never, |
| { timeoutMs: approvalTimeoutMs }, |
| { transportTimeoutMs }, |
| ); |
|
|
| expect(callGatewaySpy).toHaveBeenCalledTimes(1); |
| const callOpts = callGatewaySpy.mock.calls[0][0]; |
| expect(callOpts.timeoutMs).toBeGreaterThanOrEqual(approvalTimeoutMs); |
| expect(callOpts.timeoutMs).toBe(130_000); |
| }); |
|
|
| it("fix: user-specified timeout larger than approval is preserved", async () => { |
| const approvalTimeoutMs = 120_000; |
| const userTimeout = 200_000; |
| |
| const transportTimeoutMs = Math.max( |
| parseTimeoutMs(String(userTimeout)) ?? 0, |
| approvalTimeoutMs + 10_000, |
| ); |
| expect(transportTimeoutMs).toBe(200_000); |
|
|
| await callGatewayCli( |
| "exec.approval.request", |
| { timeout: String(userTimeout) } as never, |
| { timeoutMs: approvalTimeoutMs }, |
| { transportTimeoutMs }, |
| ); |
|
|
| const callOpts = callGatewaySpy.mock.calls[0][0]; |
| expect(callOpts.timeoutMs).toBe(200_000); |
| }); |
|
|
| it("fix: non-numeric timeout falls back to approval floor", async () => { |
| const approvalTimeoutMs = DEFAULT_EXEC_APPROVAL_TIMEOUT_MS; |
| |
| |
| const transportTimeoutMs = Math.max(parseTimeoutMs("foo") ?? 0, approvalTimeoutMs + 10_000); |
| expect(transportTimeoutMs).toBe(130_000); |
|
|
| await callGatewayCli( |
| "exec.approval.request", |
| { timeout: "foo" } as never, |
| { timeoutMs: approvalTimeoutMs }, |
| { transportTimeoutMs }, |
| ); |
|
|
| const callOpts = callGatewaySpy.mock.calls[0][0]; |
| expect(callOpts.timeoutMs).toBe(130_000); |
| }); |
| }); |
|
|