import { describe, expect, test } from "bun:test"; import { fileURLToPath } from "node:url"; import { executeCliCommand } from "../../src/cli/executeCliCommand.js"; import { type RuntimeSessionContext } from "../../src/runtime/sessionContext.js"; const cliPath = fileURLToPath( new URL("../fixtures/fakeCli.mjs", import.meta.url), ); const context: RuntimeSessionContext = { accessToken: "test-token", actorKey: "actor-1", clientSessionId: "client-1", projectId: "project-1", projectKey: "project-1", sessionId: "session-1", traceId: "trace-1", }; const run = ( command: string, options: { maxOutputBytes?: number; terminationGraceMs?: number; timeoutSec?: number; } = {}, ) => executeCliCommand(context, command, options.timeoutSec ?? 1, { apiBaseUrl: "http://127.0.0.1:8000", cliPath, maxOutputBytes: options.maxOutputBytes ?? 64, terminationGraceMs: options.terminationGraceMs ?? 20, }); describe("executeCliCommand", () => { test("accepts output at the byte limit", async () => { await expect(run("stdout 123456", { maxOutputBytes: 6 })).resolves.toMatchObject({ outcome: "completed", exitCode: 0, status: 200, stdout: "123456", }); }); test("rejects multibyte output above the byte limit without returning a partial body", async () => { await expect(run("stdout 水水", { maxOutputBytes: 5 })).resolves.toMatchObject({ outcome: "output_limit", exceededStream: "stdout", status: 502, stderr: "", stdout: "", }); }); test("limits stderr independently", async () => { await expect(run("stderr 1234567", { maxOutputBytes: 6 })).resolves.toMatchObject({ outcome: "output_limit", exceededStream: "stderr", status: 502, stderr: "", stdout: "", }); }); test("waits for a SIGTERM-aware process to close after timeout", async () => { const startedAt = Date.now(); const result = await run("term", { terminationGraceMs: 100, timeoutSec: 0.25, }); expect(result).toMatchObject({ outcome: "timeout", status: 504 }); expect(Date.now() - startedAt).toBeGreaterThanOrEqual(270); }); test("uses SIGKILL when a timed-out process ignores SIGTERM", async () => { const result = await run("ignore-term", { timeoutSec: 0.25 }); expect(result).toMatchObject({ outcome: "timeout", signal: "SIGKILL", status: 504, }); }); test("keeps the timeout outcome when closing stdin also errors", async () => { const largeContext = { ...context, accessToken: "x".repeat(1024 * 1024), }; await expect( executeCliCommand(largeContext, "ignore-term", 0.25, { apiBaseUrl: "http://127.0.0.1:8000", cliPath, maxOutputBytes: 64, terminationGraceMs: 20, }), ).resolves.toMatchObject({ outcome: "timeout", signal: "SIGKILL", status: 504, }); }); test("rejects a deterministic stdin pipe error without crashing", async () => { const largeContext = { ...context, accessToken: "x".repeat(1024 * 1024), }; await expect( executeCliCommand(largeContext, "closed-stdin", 1, { apiBaseUrl: "http://127.0.0.1:8000", cliPath, maxOutputBytes: 64, terminationGraceMs: 20, }), ).rejects.toBeInstanceOf(Error); }); });