import type { ChildProcess } from "node:child_process"; import { EventEmitter } from "node:events"; import { afterEach, describe, expect, it, vi } from "vitest"; const { spawnMock } = vi.hoisted(() => ({ spawnMock: vi.fn() })); vi.mock("node:child_process", () => ({ spawn: spawnMock })); vi.mock("node:fs/promises", () => ({ access: vi.fn().mockResolvedValue(undefined), })); import { freshOperations } from "../runtime.ts"; afterEach(() => { vi.useRealTimers(); vi.clearAllMocks(); }); function childProcess(): ChildProcess { const child = new EventEmitter() as unknown as ChildProcess; Object.defineProperty(child, "stdout", { value: new EventEmitter() }); Object.defineProperty(child, "stderr", { value: new EventEmitter() }); Object.defineProperty(child, "pid", { value: undefined }); Object.defineProperty(child, "unref", { value: vi.fn() }); return child; } describe("fresh Nushell process completion", () => { it("waits for close and forwards final stdout/stderr chunks after exit", async () => { const child = childProcess(); spawnMock.mockReturnValue(child); const chunks: Buffer[] = []; let completed = false; const resultPromise = freshOperations("nu").exec("true", process.cwd(), { onData: (chunk) => chunks.push(chunk), }); await Promise.resolve(); void resultPromise.then(() => { completed = true; }); child.emit("exit", 0, null); await Promise.resolve(); await Promise.resolve(); expect(completed).toBe(false); const stdout = Buffer.from("stdout-tail"); const stderr = Buffer.from("stderr-tail"); child.stdout?.emit("data", stdout); child.stderr?.emit("data", stderr); child.emit("close", 0, null); await expect(resultPromise).resolves.toEqual({ exitCode: 0 }); expect(chunks).toEqual([stdout, stderr]); }); it("rejects spawn errors", async () => { const child = childProcess(); spawnMock.mockReturnValue(child); const resultPromise = freshOperations("nu").exec("true", process.cwd(), { onData: () => {}, }); await Promise.resolve(); child.emit("error", new Error("spawn failed")); await expect(resultPromise).rejects.toThrow("spawn failed"); }); it("rejects aborted executions after process close", async () => { const child = childProcess(); spawnMock.mockReturnValue(child); const controller = new AbortController(); const resultPromise = freshOperations("nu").exec("true", process.cwd(), { onData: () => {}, signal: controller.signal, }); await Promise.resolve(); controller.abort(); child.emit("close", null, "SIGKILL"); await expect(resultPromise).rejects.toThrow("aborted"); }); it("rejects timed-out executions after process close", async () => { vi.useFakeTimers(); const child = childProcess(); spawnMock.mockReturnValue(child); const resultPromise = freshOperations("nu").exec("true", process.cwd(), { onData: () => {}, timeout: 0.01, }); await Promise.resolve(); await vi.advanceTimersByTimeAsync(10); child.emit("close", null, "SIGKILL"); await expect(resultPromise).rejects.toThrow("timeout:0.01"); }); });