import type { ChildProcess } from "node:child_process"; import { EventEmitter } from "node:events"; import { afterEach, describe, expect, it, vi } from "vitest"; import { ReplKernelManager } from "../src/core/kernel/repl-manager.js"; vi.mock("node:child_process", () => ({ spawn: vi.fn(), })); import { spawn } from "node:child_process"; const mockedSpawn = vi.mocked(spawn); interface FakeStream extends EventEmitter { destroy: () => void; } interface FakeChild extends EventEmitter { stdout: FakeStream; stderr: FakeStream; stdin: { destroyed: boolean; write: (line: string, callback: (error?: Error) => void) => void; destroy: () => void; }; kill: () => boolean; exitCode: number | null; signalCode: NodeJS.Signals | null; } function createReadyChild(): FakeChild { const child = new EventEmitter() as FakeChild; child.stdout = Object.assign(new EventEmitter(), { destroy: vi.fn() }); child.stderr = Object.assign(new EventEmitter(), { destroy: vi.fn() }); child.stdin = { destroyed: false, write: vi.fn((_line: string, callback: (error?: Error) => void) => callback()), destroy: vi.fn(), }; child.kill = vi.fn(() => false); child.exitCode = null; child.signalCode = null; process.nextTick(() => child.stdout.emit("data", Buffer.from('{"event":"ready","protocol":3}\n'))); return child; } afterEach(() => { vi.clearAllMocks(); }); describe("remote REPL startup", () => { it("quotes hostile remote cwd and Python executable paths", async () => { const child = createReadyChild(); mockedSpawn.mockReturnValueOnce(child as unknown as ChildProcess); const manager = new ReplKernelManager({ cwd: "/remote cwd/it's $cwd\\path", remote: { host: "remote-host", pythonCmd: "/remote python/it's $python\\bin", }, }); try { await manager.start(); expect(mockedSpawn).toHaveBeenCalledWith( "ssh", expect.arrayContaining([ "(cd '/remote cwd/it'\\''s $cwd\\path' 2>/dev/null || cd ~) && '/remote python/it'\\''s $python\\bin' -m rlm.repl", ]), expect.anything(), ); } finally { child.emit("exit", 0, null); await manager.shutdown(); } }); });