"""The default `pm install` closure must stage the interpreter boot requires. A fresh source install emits boot launchers (the source completion's publish_launchers, hermes_cli/_launchers.py) that exec the pm STORE interpreter. The `python` package is marked optional (dev installs use their own venv; sealed bundles adopt a shipped one), so the old default closure — every non-optional lockfile package — skipped it and left `hermes` unbootable (audit C05). Behavioral: drive cmd_install with the real lockfile/registry and stub installers, asserting on the requested package names, not on script text. """ from __future__ import annotations import argparse import importlib import pytest import pm.cli @pytest.fixture() def install_spy(monkeypatch): calls = {"names": None, "verified": None, "sync_extras": None, "activated": [], "fail": set()} def fake_install_names(names, target=None, *, verify=True): # Accumulate: optional defaults install in their own call after the # required closure. calls["names"] = [*(calls["names"] or []), *names] calls["verified"] = verify return len(calls["fail"] & set(names)) def fake_sync_venv(extras=None, **kwargs): calls["sync_extras"] = list(extras or []) return None def fake_activate(**kwargs): calls["activated"].append(kwargs) return [] monkeypatch.setattr(pm.cli, "_install_names", fake_install_names) monkeypatch.setattr(importlib.import_module("pm.install"), "sync_venv", fake_sync_venv) monkeypatch.setattr(importlib.import_module("pm.install"), "activate", fake_activate) return calls def test_default_closure_includes_the_boot_interpreter(install_spy): assert pm.cli.cmd_install(argparse.Namespace(names=None, tools_only=False)) == 0 assert "python" in install_spy["names"] assert "venv" not in install_spy["names"] assert all(not pm.cli.get_package(name).internal for name in install_spy["names"]) assert install_spy["sync_extras"] == ["all"] assert install_spy["activated"] == [{"allow_incomplete": True}] @pytest.mark.parametrize("names", [["npm", "ripgrep"], ["dmgbuild"]]) def test_explicit_names_pass_through_untouched(install_spy, names) -> None: assert pm.cli.cmd_install(argparse.Namespace(names=names, tools_only=False)) == 0 assert install_spy["names"] == names assert install_spy["sync_extras"] is None assert install_spy["activated"] == [] def test_tools_only_publishes_tools_and_stops_before_the_venv(install_spy): assert pm.cli.cmd_install(argparse.Namespace(names=None, extra=[], target=None, tools_only=True)) == 0 assert "python" in install_spy["names"] assert "venv" not in install_spy["names"] assert install_spy["sync_extras"] is None assert install_spy["activated"] == [{"allow_incomplete": True}] def test_trust_recorded_skips_the_byte_check_and_still_syncs(install_spy): assert pm.cli.cmd_install(argparse.Namespace( names=None, extra=[], target=None, tools_only=False, trust_recorded=True)) == 0 assert "python" in install_spy["names"] assert install_spy["verified"] is False assert install_spy["sync_extras"] == ["all"] @pytest.mark.parametrize("kwargs, message", [ ({"names": ["ripgrep"], "extra": [], "target": None, "tools_only": False, "trust_recorded": True}, "--trust-recorded"), ({"names": None, "extra": ["anthropic"], "target": None, "tools_only": False, "trust_recorded": True}, "--trust-recorded"), ({"names": None, "extra": [], "target": "linux-x64", "tools_only": False, "trust_recorded": True}, "--target"), ]) def test_trust_recorded_refuses_a_narrowed_install(install_spy, capsys, kwargs, message): assert pm.cli.cmd_install(argparse.Namespace(**kwargs)) == 1 assert message in capsys.readouterr().out assert install_spy["names"] is None assert install_spy["sync_extras"] is None def test_a_missing_tool_blocks_the_venv_sync(install_spy, monkeypatch, capsys): monkeypatch.setattr( importlib.import_module("pm.install"), "activate", lambda **kwargs: ["git: not installed or outdated"], ) assert pm.cli.cmd_install(argparse.Namespace(names=None, tools_only=False)) == 1 assert install_spy["sync_extras"] is None assert "git: not installed or outdated" in capsys.readouterr().out def _bare_install(install_spy, **kwargs) -> list[str]: install_spy["names"] = None assert pm.cli.cmd_install(argparse.Namespace(names=None, tools_only=False, **kwargs)) == 0 return install_spy["names"] def test_browser_tools_are_a_default_the_user_can_decline_and_restore(install_spy): assert "agent-browser" in _bare_install(install_spy) assert "agent-browser" not in _bare_install(install_spy, without=["agent-browser"]) # The opt-out is recorded: a later bare install (the updater's shape) keeps it. assert "agent-browser" not in _bare_install(install_spy) install_spy["names"] = None assert pm.cli.cmd_install(argparse.Namespace(names=["agent-browser"], tools_only=False)) == 0 assert "agent-browser" in _bare_install(install_spy) def test_a_failed_default_download_does_not_fail_the_install(install_spy, capsys): install_spy["fail"] = {"agent-browser"} assert "agent-browser" in _bare_install(install_spy) assert install_spy["sync_extras"] == ["all"] assert "hermes pm install agent-browser" in capsys.readouterr().out def test_termux_has_no_pm_browser_default(): from pm.defaults import default_packages names = pm.cli._lockfile().names() assert "agent-browser" in default_packages(names, target="linux-x64", declined_names=frozenset()) assert default_packages(names, target="linux-arm64-bionic", declined_names=frozenset()) == [] def test_without_refuses_a_required_package(install_spy, capsys): assert pm.cli.cmd_install(argparse.Namespace(names=None, tools_only=False, without=["git"])) == 1 assert "--without accepts only" in capsys.readouterr().out assert install_spy["names"] is None def test_default_install_carries_the_computer_use_driver_wherever_it_builds(): """Computer use's only OS path ships in the box; a default claimed for a target with no pinned artifact would fail every install there (the bionic KeyError).""" from pm.defaults import default_packages from pm.lock import Lockfile from pm.paths import lockfile_path from pm.store import ALL_TARGETS lock = Lockfile(lockfile_path()) for target in ALL_TARGETS: defaults = default_packages(lock.names(), target=target, declined_names=frozenset()) assert all(lock.artifacts(name, target) for name in defaults), target driver = pm.cli.get_package("cua-driver") assert ("cua-driver" in defaults) == (driver.missing_reason(target) is None), target assert "cua-driver" not in default_packages( lock.names(), target="linux-x64", declined_names=frozenset({"cua-driver"}))