import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { EventEmitter } from "node:events";
EventEmitter.captureRejections = true;
import { PassThrough } from "node:stream";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { projectDir } from "./goal-store.js";
const spawnMock = vi.hoisted(() => vi.fn());
const killProcessTreeMock = vi.hoisted(() => vi.fn());
vi.mock("node:child_process", () => ({ spawn: spawnMock }));
vi.mock("../utils/process.js", () => ({ killProcessTree: killProcessTreeMock }));
vi.mock("node:fs/promises", async () => {
    const actual = await vi.importActual("node:fs/promises");
    const writeFile = vi.fn((file, data, options) => actual.writeFile(file, data, options));
    return { ...actual, writeFile };
});
class FakeChild extends EventEmitter {
    stdout = new PassThrough();
    stderr = new PassThrough();
    pid = 4242;
    kill = vi.fn();
}
let tmpBase;
let tmpProject;
let child;
beforeEach(async () => {
    vi.useFakeTimers();
    tmpBase = await fs.mkdtemp(path.join(os.tmpdir(), "goal-verifier-test-base-"));
    tmpProject = await fs.mkdtemp(path.join(os.tmpdir(), "goal-verifier-test-project-"));
    process.env.GG_GOALS_BASE = tmpBase;
    child = new FakeChild();
    spawnMock.mockReturnValue(child);
    killProcessTreeMock.mockResolvedValue(undefined);
});
afterEach(async () => {
    vi.useRealTimers();
    vi.clearAllMocks();
    delete process.env.GG_GOALS_BASE;
    await fs.rm(tmpBase, { recursive: true, force: true });
    await fs.rm(tmpProject, { recursive: true, force: true });
});
describe("runGoalVerifierCommand", () => {
    it("records spawn errors as failed verifier artifacts", async () => {
        const { runGoalVerifierCommand } = await import("./goal-verifier.js");
        const resultPromise = runGoalVerifierCommand({
            cwd: tmpProject,
            runId: "goal-a",
            command: "missing-verifier",
            now: () => 1234,
        });
        await vi.waitFor(() => expect(child.listenerCount("error")).toBeGreaterThan(0));
        child.listeners("error").forEach((listener) => listener.call(child, new Error("spawn ENOENT")));
        child.emit("close", 1);
        const result = await resultPromise;
        expect(result.failureClass).toBe("verifier_spawn_error");
        expect(result.verification).toMatchObject({
            status: "fail",
            command: "missing-verifier",
            exitCode: 1,
            summary: "Verifier process error: spawn ENOENT",
        });
        expect(result.verification.outputPath).toBe(path.join(projectDir(tmpProject), "verifiers", "goal-a-1234.log"));
        await expect(fs.readFile(result.verification.outputPath, "utf-8")).resolves.toContain("Verifier process error: spawn ENOENT");
    });
    it("times out hung verifiers, kills them, and writes stable output artifacts", async () => {
        const { runGoalVerifierCommand } = await import("./goal-verifier.js");
        const resultPromise = runGoalVerifierCommand({
            cwd: tmpProject,
            runId: "goal-a",
            command: "sleep forever",
            timeoutMs: 25,
            now: () => 2000,
        });
        await vi.waitFor(() => expect(child.listenerCount("close")).toBeGreaterThan(0));
        await vi.waitFor(() => expect(child.stdout.listenerCount("data")).toBeGreaterThan(0));
        child.stdout
            .listeners("data")
            .forEach((listener) => listener.call(child.stdout, Buffer.from("partial output")));
        await vi.advanceTimersByTimeAsync(25);
        child.emit("close", 124);
        const result = await resultPromise;
        expect(killProcessTreeMock).toHaveBeenCalledWith(4242);
        expect(child.kill).toHaveBeenCalledWith("SIGTERM");
        expect(result.failureClass).toBe("verifier_timeout");
        expect(result.verification).toMatchObject({ status: "fail", exitCode: 124 });
        expect(result.verification.summary).toContain("Verifier timed out after 25ms");
        expect(result.verification.outputPath).toBe(path.join(projectDir(tmpProject), "verifiers", "goal-a-2000.log"));
        await expect(fs.readFile(result.verification.outputPath, "utf-8")).resolves.toContain("Verifier timed out after 25ms");
    });
    it("records pass/fail output artifacts with stable failure classes", async () => {
        const { runGoalVerifierCommand } = await import("./goal-verifier.js");
        const passPromise = runGoalVerifierCommand({
            cwd: tmpProject,
            runId: "goal-a",
            command: "pnpm test",
            now: () => 3000,
        });
        await vi.waitFor(() => expect(child.stdout.listenerCount("data")).toBeGreaterThan(0));
        child.stdout
            .listeners("data")
            .forEach((listener) => listener.call(child.stdout, Buffer.from("ok")));
        child.emit("close", 0);
        const pass = await passPromise;
        expect(pass.failureClass).toBe("verifier_pass");
        expect(pass.verification).toMatchObject({ status: "pass", exitCode: 0, summary: "ok" });
        await expect(fs.readFile(pass.verification.outputPath, "utf-8")).resolves.toBe("ok\n");
        child = new FakeChild();
        spawnMock.mockReturnValue(child);
        const failPromise = runGoalVerifierCommand({
            cwd: tmpProject,
            runId: "goal-a",
            command: "pnpm test",
            now: () => 4000,
        });
        await vi.waitFor(() => expect(child.stderr.listenerCount("data")).toBeGreaterThan(0));
        child.stderr
            .listeners("data")
            .forEach((listener) => listener.call(child.stderr, Buffer.from("assertion failed")));
        child.emit("close", 1);
        const fail = await failPromise;
        expect(fail.failureClass).toBe("verifier_failure");
        expect(fail.verification).toMatchObject({
            status: "fail",
            exitCode: 1,
            summary: "assertion failed",
        });
        await expect(fs.readFile(fail.verification.outputPath, "utf-8")).resolves.toBe("assertion failed\n");
    });
});
//# sourceMappingURL=goal-verifier.test.js.map