138 lines
4.6 KiB
TypeScript
138 lines
4.6 KiB
TypeScript
import { afterEach, expect, test, vi } from "vitest";
|
|
import { resetDiagnosticSessionStateForTest } from "../logging/diagnostic-session-state.js";
|
|
import {
|
|
addSession,
|
|
appendOutput,
|
|
markExited,
|
|
resetProcessRegistryForTests,
|
|
} from "./bash-process-registry.js";
|
|
import { createProcessSessionFixture } from "./bash-process-registry.test-helpers.js";
|
|
import { createProcessTool } from "./bash-tools.process.js";
|
|
|
|
afterEach(() => {
|
|
resetProcessRegistryForTests();
|
|
resetDiagnosticSessionStateForTest();
|
|
});
|
|
|
|
function createProcessSessionHarness(sessionId: string) {
|
|
const processTool = createProcessTool();
|
|
const session = createProcessSessionFixture({
|
|
id: sessionId,
|
|
command: "test",
|
|
backgrounded: true,
|
|
});
|
|
addSession(session);
|
|
return { processTool, session };
|
|
}
|
|
|
|
async function pollSession(
|
|
processTool: ReturnType<typeof createProcessTool>,
|
|
callId: string,
|
|
sessionId: string,
|
|
timeout?: number | string,
|
|
) {
|
|
return processTool.execute(callId, {
|
|
action: "poll",
|
|
sessionId,
|
|
...(timeout === undefined ? {} : { timeout }),
|
|
});
|
|
}
|
|
|
|
function retryMs(result: Awaited<ReturnType<ReturnType<typeof createProcessTool>["execute"]>>) {
|
|
return (result.details as { retryInMs?: number }).retryInMs;
|
|
}
|
|
|
|
function pollStatus(result: Awaited<ReturnType<ReturnType<typeof createProcessTool>["execute"]>>) {
|
|
return (result.details as { status?: string }).status;
|
|
}
|
|
|
|
test("process poll waits for completion when timeout is provided", async () => {
|
|
vi.useFakeTimers();
|
|
try {
|
|
const sessionId = "sess";
|
|
const { processTool, session } = createProcessSessionHarness(sessionId);
|
|
|
|
setTimeout(() => {
|
|
appendOutput(session, "stdout", "done\n");
|
|
markExited(session, 0, null, "completed");
|
|
}, 10);
|
|
|
|
const pollPromise = pollSession(processTool, "toolcall", sessionId, 2000);
|
|
|
|
let resolved = false;
|
|
void pollPromise.finally(() => {
|
|
resolved = true;
|
|
});
|
|
|
|
await vi.advanceTimersByTimeAsync(200);
|
|
expect(resolved).toBe(false);
|
|
|
|
await vi.advanceTimersByTimeAsync(100);
|
|
const poll = await pollPromise;
|
|
const details = poll.details as { status?: string; aggregated?: string };
|
|
expect(details.status).toBe("completed");
|
|
expect(details.aggregated ?? "").toContain("done");
|
|
} finally {
|
|
vi.useRealTimers();
|
|
}
|
|
});
|
|
|
|
test("process poll accepts string timeout values", async () => {
|
|
vi.useFakeTimers();
|
|
try {
|
|
const sessionId = "sess-2";
|
|
const { processTool, session } = createProcessSessionHarness(sessionId);
|
|
setTimeout(() => {
|
|
appendOutput(session, "stdout", "done\n");
|
|
markExited(session, 0, null, "completed");
|
|
}, 10);
|
|
|
|
const pollPromise = pollSession(processTool, "toolcall", sessionId, "2000");
|
|
await vi.advanceTimersByTimeAsync(350);
|
|
const poll = await pollPromise;
|
|
const details = poll.details as { status?: string; aggregated?: string };
|
|
expect(details.status).toBe("completed");
|
|
expect(details.aggregated ?? "").toContain("done");
|
|
} finally {
|
|
vi.useRealTimers();
|
|
}
|
|
});
|
|
|
|
test("process poll exposes adaptive retryInMs for repeated no-output polls", async () => {
|
|
const sessionId = "sess-retry";
|
|
const { processTool } = createProcessSessionHarness(sessionId);
|
|
|
|
const polls = await Promise.all([
|
|
pollSession(processTool, "toolcall-1", sessionId),
|
|
pollSession(processTool, "toolcall-2", sessionId),
|
|
pollSession(processTool, "toolcall-3", sessionId),
|
|
pollSession(processTool, "toolcall-4", sessionId),
|
|
pollSession(processTool, "toolcall-5", sessionId),
|
|
]);
|
|
|
|
expect(polls.map((poll) => retryMs(poll))).toEqual([5000, 10000, 30000, 60000, 60000]);
|
|
});
|
|
|
|
test("process poll resets retryInMs when output appears and clears on completion", async () => {
|
|
const sessionId = "sess-reset";
|
|
const { processTool, session } = createProcessSessionHarness(sessionId);
|
|
|
|
const poll1 = await pollSession(processTool, "toolcall-1", sessionId);
|
|
const poll2 = await pollSession(processTool, "toolcall-2", sessionId);
|
|
expect(retryMs(poll1)).toBe(5000);
|
|
expect(retryMs(poll2)).toBe(10000);
|
|
|
|
appendOutput(session, "stdout", "step complete\n");
|
|
const pollWithOutput = await pollSession(processTool, "toolcall-output", sessionId);
|
|
expect(retryMs(pollWithOutput)).toBe(5000);
|
|
|
|
markExited(session, 0, null, "completed");
|
|
const pollCompleted = await pollSession(processTool, "toolcall-completed", sessionId);
|
|
expect(pollStatus(pollCompleted)).toBe("completed");
|
|
expect(retryMs(pollCompleted)).toBeUndefined();
|
|
|
|
const pollFinished = await pollSession(processTool, "toolcall-finished", sessionId);
|
|
expect(pollStatus(pollFinished)).toBe("completed");
|
|
expect(retryMs(pollFinished)).toBeUndefined();
|
|
});
|