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,
) {
const args = {
action: "poll" ,
sessionId,
...(timeout === undefined ? {} : { timeout }),
} as unknown as Parameters<ReturnType<typeof createProcessTool>["execute" ]>[1 ];
return processTool.execute(callId, args);
}
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;
}
async function expectCompletedPollWithTimeout(params: {
sessionId: string;
callId: string;
timeout: number | string;
advanceMs: number;
assertUnresolvedAtMs?: number;
}) {
vi.useFakeTimers();
try {
const { processTool, session } = createProcessSessionHarness(params.sessionId);
setTimeout(() => {
appendOutput(session, "stdout" , "done\n" );
markExited(session, 0 , null , "completed" );
}, 10 );
const pollPromise = pollSession(processTool, params.callId, params.sessionId, params.timeout);
if (params.assertUnresolvedAtMs !== undefined) {
let resolved = false ;
void pollPromise.finally (() => {
resolved = true ;
});
await vi.advanceTimersByTimeAsync(params.assertUnresolvedAtMs);
expect(resolved).toBe(false );
}
await vi.advanceTimersByTimeAsync(params.advanceMs);
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 waits for completion when timeout is provided" , async () => {
await expectCompletedPollWithTimeout({
sessionId: "sess" ,
callId: "toolcall" ,
timeout: 2000 ,
assertUnresolvedAtMs: 200 ,
advanceMs: 100 ,
});
});
test("process poll accepts string timeout values" , async () => {
await expectCompletedPollWithTimeout({
sessionId: "sess-2" ,
callId: "toolcall" ,
timeout: "2000" ,
advanceMs: 350 ,
});
});
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();
});
Messung V0.5 in Prozent C=99 H=98 G=98
¤ Dauer der Verarbeitung: 0.4 Sekunden
¤
*© Formatika GbR, Deutschland