123 lines
3.3 KiB
TypeScript
123 lines
3.3 KiB
TypeScript
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);
|
|
});
|
|
});
|