Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/nushell/__serial__/runtime.test.ts

Raw
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");
	});
});