Files
hermes-agent/tests/pm/test_install_state_permissions.py
2026-09-28 06:42:00 -07:00

101 lines
3.8 KiB
Python

"""Permission failures in install state must explain the actual access problem."""
from __future__ import annotations
import os
from pathlib import Path
import subprocess
import sys
from types import ModuleType
import pytest
from pm.environments import install_state_dir, install_state_permission_message
@pytest.mark.parametrize("phase", ["preparation", "activation"])
def test_bootstrap_reports_unwritable_install_once(tmp_path, monkeypatch, phase):
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "home"))
root = Path(__file__).resolve().parents[2]
target = install_state_dir(root) / (
"pm-runtime/.prepare.lock" if phase == "preparation" else ".install.lock"
)
code = """
import errno
import sys
import pm.environments as environments
target, phase = sys.argv[1:3]
from hermes_cli import _early_recovery, venv_sync
def denied(*args, **kwargs):
raise PermissionError(errno.EACCES, "Permission denied", target)
venv_sync.prepare_launch = denied if phase == "preparation" else lambda *_: None
_early_recovery.recover_if_needed = lambda *_: False
environments.activate_dependencies = denied if phase == "activation" else lambda *_: None
sys.argv = ["hermes", "-z", "hi"]
import hermes_bootstrap
"""
result = subprocess.run(
[sys.executable, "-S", "-c", code, str(target), phase],
env={**os.environ, "PYTHONPATH": str(root)},
capture_output=True, text=True, timeout=30,
)
assert result.returncode == 1, result.stderr
assert result.stderr.count("hermes: ") == 1
assert "install state is not writable by this user" in result.stderr
assert str(target) in result.stderr
assert "source-update completion failed" not in result.stderr
assert "run `hermes update`" not in result.stderr
assert "hermes pm repair" not in result.stderr
assert "Traceback" not in result.stderr
def test_pm_repair_reports_unwritable_runtime_without_traceback(tmp_path, monkeypatch, capsys):
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "home"))
root = Path(__file__).resolve().parents[2]
target = install_state_dir(root) / "pm-runtime" / ".prepare.lock"
code = """
import errno
import sys
from pm import runtime
runtime.is_runtime = lambda: False
def denied(*args):
raise PermissionError(errno.EACCES, "Permission denied", sys.argv[1])
runtime.run_cli = denied
from pm.cli import main
raise SystemExit(main(["repair"]))
"""
result = subprocess.run(
[sys.executable, "-c", code, str(target)],
env={**os.environ, "PYTHONPATH": str(root)},
capture_output=True, text=True, timeout=30,
)
assert result.returncode == 1, result.stderr
assert "install state is not writable by this user" in result.stderr
assert str(target) in result.stderr
assert "Traceback" not in result.stderr
lease = install_state_dir(root) / "environments" / "generation" / ".leases" / "reader"
assert install_state_permission_message(root, PermissionError(13, "Permission denied", str(lease)))
outside = tmp_path / "other" / ".prepare.lock"
exc = PermissionError(13, "Permission denied", str(outside))
assert install_state_permission_message(root, exc) is None
from hermes_cli import _early_recovery
project = tmp_path / "source"
project.mkdir()
(project / "pyproject.toml").write_text("[project]\n", encoding="utf-8")
denied_path = install_state_dir(project) / ".install.lock"
recovery = ModuleType("pm.recovery")
def fail_repair(*_):
raise PermissionError(13, "Permission denied", str(denied_path))
recovery.repair_dependencies = fail_repair
monkeypatch.setitem(sys.modules, "pm.recovery", recovery)
with pytest.raises(PermissionError):
_early_recovery.recover_if_needed(project, argv=["pm", "repair"], explicit=True)
assert "run `hermes pm repair`" not in capsys.readouterr().err