zerobyte/app/server/modules/agents/__tests__/agents-manager.test.ts
2026-04-07 19:08:26 +02:00

82 lines
1.9 KiB
TypeScript

import { EventEmitter } from "node:events";
import * as childProcess from "node:child_process";
import { PassThrough } from "node:stream";
import { afterEach, expect, mock, test, vi } from "bun:test";
const spawnMock = mock();
await mock.module("node:child_process", () => ({
...childProcess,
spawn: spawnMock,
}));
import { spawnLocalAgent, stopLocalAgent } from "../agents-manager";
const flushMicrotasks = async () => {
await Promise.resolve();
};
type FakeChildProcess = EventEmitter & {
stdout: PassThrough;
stderr: PassThrough;
exitCode: number | null;
signalCode: NodeJS.Signals | null;
kill: ReturnType<typeof mock>;
};
const createFakeChild = () => {
const child = new EventEmitter() as FakeChildProcess;
child.stdout = new PassThrough();
child.stderr = new PassThrough();
child.exitCode = null;
child.signalCode = null;
child.kill = mock(() => {
child.exitCode = 0;
child.emit("exit", 0, null);
return true;
});
return child;
};
afterEach(async () => {
await stopLocalAgent();
spawnMock.mockReset();
mock.restore();
vi.useRealTimers();
});
test("respawns the local agent after an unexpected exit", async () => {
vi.useFakeTimers();
const firstChild = createFakeChild();
const secondChild = createFakeChild();
spawnMock.mockReturnValueOnce(firstChild).mockReturnValueOnce(secondChild);
await spawnLocalAgent();
firstChild.exitCode = 1;
firstChild.emit("exit", 1, null);
vi.advanceTimersByTime(1_000);
await flushMicrotasks();
expect(spawnMock).toHaveBeenCalledTimes(2);
});
test("does not respawn the local agent after an intentional stop", async () => {
vi.useFakeTimers();
const child = createFakeChild();
spawnMock.mockReturnValue(child);
await spawnLocalAgent();
await stopLocalAgent();
vi.advanceTimersByTime(1_000);
await flushMicrotasks();
expect(spawnMock).toHaveBeenCalledTimes(1);
expect(child.kill).toHaveBeenCalledTimes(1);
});