// acceptance grid for brew-install.ts.
//
// Mirror of apt-resolve.test.ts shape: pure decision logic exercised against
// fixture spawnSync results. The wrapper itself is a thin orchestration layer
// over `brew list --versions <pkg>` (post-check) and `brew install <pkgs...>`
// (install). Tests inject the spawn function so no real brew is invoked —
// these run identically on Linux CI and Mac developer boxes.
//
// Branches covered:
//   1. ensureBrewAvailable — `which brew` exits 0 → ok; non-zero → loud refusal.
//   2. classifyBrewState — per-pkg installed/missing decision from
//      `brew list --versions` exit codes + parsed versions.
//   3. installAll — given a classification, decide what to feed `brew install`
//      and what to log. Already-installed pkgs are skipped (no install line).
//   4. verifyAll — post-install, every pkg must have at least one version token
//      from `brew list --versions`. Partial-install case (exit 0 but pkg
//      missing) throws with the install-still-missing diagnostic.
import test from "node:test";
import assert from "node:assert/strict";
import { ensureBrewAvailable, classifyBrewState, decideBrewInstallPlan, verifyBrewInstalled, } from "../brew-install.js";
function ok(stdout) {
    return { status: 0, stdout };
}
function fail(stdout = "") {
    return { status: 1, stdout };
}
// ---------------------------------------------------------------------------
// ensureBrewAvailable — pre-flight refusal.
// ---------------------------------------------------------------------------
test("ensureBrewAvailable: brew on PATH (which brew exits 0) → returns silently", () => {
    let called = false;
    ensureBrewAvailable((cmd, args) => {
        called = true;
        assert.equal(cmd, "which");
        assert.deepEqual(args, ["brew"]);
        return ok("/opt/homebrew/bin/brew\n");
    });
    assert.equal(called, true);
});
test("ensureBrewAvailable: brew not on PATH → throws loud refusal", () => {
    assert.throws(() => ensureBrewAvailable(() => fail()), /Homebrew not found\. Install from https:\/\/brew\.sh and re-run\./);
});
// ---------------------------------------------------------------------------
// classifyBrewState — per-pkg installed-vs-missing classification.
// ---------------------------------------------------------------------------
test("classifyBrewState: installed pkg with versions returns installed=true and version list", () => {
    const r = classifyBrewState("node@22", ok("node@22 22.18.1 22.18.0\n"));
    assert.equal(r.installed, true);
    assert.deepEqual(r.versions, ["22.18.1", "22.18.0"]);
});
test("classifyBrewState: missing pkg (brew exit 1, empty stdout) returns installed=false", () => {
    const r = classifyBrewState("neo4j", fail());
    assert.equal(r.installed, false);
    assert.deepEqual(r.versions, []);
});
test("classifyBrewState: name-only stdout (cellar half-uninstalled) treated as not-installed", () => {
    // brew exited 0 but reported only the package name with no version tokens.
    // Treat as uninstalled so the install pass re-runs and the post-check fails
    // loudly if it still doesn't land — matches the apt-resolve fail-loud contract.
    const r = classifyBrewState("node@22", ok("node@22\n"));
    assert.equal(r.installed, false);
    assert.deepEqual(r.versions, []);
});
// ---------------------------------------------------------------------------
// decideBrewInstallPlan — given the apt-side input names + per-name installed
// state, produce the resolved formula list to install plus per-pkg log lines.
// ---------------------------------------------------------------------------
test("decideBrewInstallPlan: all pkgs already installed → empty install list, only 'already installed' logs", () => {
    const plan = decideBrewInstallPlan({
        pkgs: [
            { original: "nodejs", resolvedFormula: "node@22", installed: true, versions: ["22.18.1"] },
            { original: "neo4j", resolvedFormula: "neo4j", installed: true, versions: ["5.20.0"] },
        ],
    });
    assert.deepEqual(plan.toInstall, []);
    // Each pkg emits the legacy `[homebrew]` line plus the uniform-prefix
    // `[create-maxy] brew-formula=… action=…` mirror Task 297 added for the
    // grep recipe.
    assert.deepEqual(plan.logs, [
        "  [homebrew] node@22 already installed (v22.18.1)",
        "  [create-maxy] brew-formula=node@22 action=present",
        "  [homebrew] neo4j already installed (v5.20.0)",
        "  [create-maxy] brew-formula=neo4j action=present",
    ]);
});
test("decideBrewInstallPlan: some missing → install list contains only missing formulas, install + already logs interleaved", () => {
    const plan = decideBrewInstallPlan({
        pkgs: [
            { original: "nodejs", resolvedFormula: "node@22", installed: true, versions: ["22.18.1"] },
            { original: "neo4j", resolvedFormula: "neo4j", installed: false, versions: [] },
            { original: "cloudflared", resolvedFormula: "cloudflared", installed: false, versions: [] },
        ],
    });
    assert.deepEqual(plan.toInstall, ["neo4j", "cloudflared"]);
    assert.deepEqual(plan.logs, [
        "  [homebrew] node@22 already installed (v22.18.1)",
        "  [create-maxy] brew-formula=node@22 action=present",
        "  [homebrew] installing neo4j …",
        "  [create-maxy] brew-formula=neo4j action=install",
        "  [homebrew] installing cloudflared …",
        "  [create-maxy] brew-formula=cloudflared action=install",
    ]);
});
test("decideBrewInstallPlan: all missing → install list = every formula, only install logs", () => {
    const plan = decideBrewInstallPlan({
        pkgs: [
            { original: "nodejs", resolvedFormula: "node@22", installed: false, versions: [] },
            { original: "neo4j", resolvedFormula: "neo4j", installed: false, versions: [] },
        ],
    });
    assert.deepEqual(plan.toInstall, ["node@22", "neo4j"]);
    assert.deepEqual(plan.logs, [
        "  [homebrew] installing node@22 …",
        "  [create-maxy] brew-formula=node@22 action=install",
        "  [homebrew] installing neo4j …",
        "  [create-maxy] brew-formula=neo4j action=install",
    ]);
});
// ---------------------------------------------------------------------------
// verifyBrewInstalled — post-install check. Every formula must have ≥1 version
// token from `brew list --versions`. Pure logic over an injected probe.
// ---------------------------------------------------------------------------
test("verifyBrewInstalled: every pkg present per brew list --versions → returns verified logs", () => {
    const versionResults = {
        "node@22": ok("node@22 22.18.1\n"),
        "neo4j": ok("neo4j 5.20.0\n"),
    };
    const verified = verifyBrewInstalled(["node@22", "neo4j"], (pkg) => versionResults[pkg]);
    assert.deepEqual(verified, [
        "  [homebrew] node@22 verified post-install",
        "  [homebrew] neo4j verified post-install",
    ]);
});
test("verifyBrewInstalled: install returned 0 but pkg still missing → throws loud install-still-missing diagnostic", () => {
    // The exact failure-mode mirror of installAptGroup — apt-get install exits 0
    // but dpkg -s says the package isn't there. The brew analogue: brew install
    // exits 0 but `brew list --versions` reports nothing.
    const versionResults = {
        "node@22": ok("node@22 22.18.1\n"),
        "neo4j": fail(),
    };
    assert.throws(() => verifyBrewInstalled(["node@22", "neo4j"], (pkg) => versionResults[pkg]), /\[homebrew\] installing neo4j exited 0 but brew list --versions reports missing: neo4j/);
});
test("verifyBrewInstalled: multiple still-missing → all listed in diagnostic", () => {
    const versionResults = {
        "node@22": fail(),
        "neo4j": ok("neo4j 5.20.0\n"),
        "cloudflared": fail(),
    };
    assert.throws(() => verifyBrewInstalled(["node@22", "neo4j", "cloudflared"], (pkg) => versionResults[pkg]), /reports missing: node@22, cloudflared/);
});
