diff --git a/backend/src/pi/pi-process-manager.ts b/backend/src/pi/pi-process-manager.ts index a3fc079f..d0b0a5d7 100644 --- a/backend/src/pi/pi-process-manager.ts +++ b/backend/src/pi/pi-process-manager.ts @@ -10,13 +10,14 @@ export interface SessionRuntime { child: ChildProcessWithoutNullStreams; } +/** Injected test double signature: produce a child process, no args needed. */ type SpawnFn = () => ChildProcessWithoutNullStreams; export class PiProcessManager { private runtimes = new Map(); private spawnFn: (sessionId: string, author: string) => ChildProcessWithoutNullStreams; - constructor(private cfg: AppConfig, opts?: { spawnFn?: () => ChildProcessWithoutNullStreams }) { + constructor(private cfg: AppConfig, opts?: { spawnFn?: SpawnFn }) { if (opts?.spawnFn) { this.spawnFn = () => opts.spawnFn!(); } else { diff --git a/backend/test/pi-process-manager.test.ts b/backend/test/pi-process-manager.test.ts index 3aa3907b..1b0edee7 100644 --- a/backend/test/pi-process-manager.test.ts +++ b/backend/test/pi-process-manager.test.ts @@ -19,6 +19,21 @@ test("spawnFor avvia un runtime e il bridge emette il widget F1", async () => { expect(mgr.count()).toBe(0); }); +test("l'exit del child rimuove il runtime dalla mappa (exit handler)", async () => { + const cfg = loadConfig({ THT_HARNESS_DIR: "../harness" }); + const mgr = new PiProcessManager(cfg, { spawnFn: () => spawn("node", [FAKE, SCRIPT]) as any }); + const rt = await mgr.spawnFor("exit-test", {}); + expect(mgr.count()).toBe(1); + // Cause the child to exit on its own and await the 'exit' event (no teardown call). + const exited = new Promise((res) => rt.child.on("exit", () => res())); + rt.child.kill(); + await exited; + // Let the manager's registered exit handler run. + await new Promise((res) => setImmediate(res)); + expect(mgr.count()).toBe(0); + expect(mgr.get("exit-test")).toBeUndefined(); +}); + test("oltre maxPiProcesses solleva errore", async () => { const cfg = { ...loadConfig({}), maxPiProcesses: 1 }; const mgr = new PiProcessManager(cfg, { spawnFn: () => spawn("node", [FAKE, SCRIPT]) as any });