import { afterEach, describe, expect, it, vi } from "vitest";

/**
 * Pins the security-relevant contract of the `--yes` install path: the detected
 * manager binary is spawned via execFileSync with `shell: false` and the exact
 * argv from installInvocation — no shell, so a version can never be re-parsed
 * as shell syntax. installInvocation's argv correctness is covered separately
 * in installerDetection.test.ts; this locks how it's executed.
 */
describe("runDetectedInstall", () => {
  afterEach(() => {
    vi.doUnmock("node:child_process");
    vi.resetModules();
  });

  it("spawns the manager binary with shell:false and inherited stdio", async () => {
    const execSpy = vi.fn();
    vi.resetModules();
    vi.doMock("node:child_process", () => ({ execFileSync: execSpy }));

    const { runDetectedInstall } = await import("./upgrade.js");
    runDetectedInstall(
      { bin: "bun", args: ["add", "-g", "hyperframes@1.2.3"] },
      "bun add -g hyperframes@1.2.3",
      "1.2.3",
    );

    expect(execSpy).toHaveBeenCalledTimes(1);
    expect(execSpy).toHaveBeenCalledWith("bun", ["add", "-g", "hyperframes@1.2.3"], {
      stdio: "inherit",
      shell: false,
    });
  });

  it("sets a non-zero exit code when the install fails, without throwing", async () => {
    const execSpy = vi.fn(() => {
      throw new Error("install boom");
    });
    vi.resetModules();
    vi.doMock("node:child_process", () => ({ execFileSync: execSpy }));

    const { runDetectedInstall } = await import("./upgrade.js");
    const original = process.exitCode;
    try {
      expect(() =>
        runDetectedInstall(
          { bin: "npm", args: ["install", "-g", "hyperframes@1.2.3"] },
          "npm install -g hyperframes@1.2.3",
          "1.2.3",
        ),
      ).not.toThrow();
      expect(process.exitCode).toBe(1);
    } finally {
      process.exitCode = original;
    }
  });
});
