Files
hermes-agent/tests/pm/test_update_failure_status.py
ethernet 6a5a6a05d2 refactor(pm): rename the pm.ensure submodule to pm.install; lazy_deps back to the shim
`import pm.ensure` bound the submodule onto the package, shadowing the facade's
`pm.ensure()` function for every later caller in the process (photon's sidecar
start hit `'module' object is not callable`). The module is pm.install now; the
function keeps its name. The facade resolves through `__import__` rather than
`importlib.import_module` so a test that patches import_module globally does not
break attribute access on pm.

tools/lazy_deps.py returns to the 16-line stop_for_relaunch shim the branch wrote
(an origin/main merge had replaced it with main's 775-line implementation); the
project-metadata tests follow. update_cmd re-exports the four old_updater_deps
names the shim tests resolve through hermes_cli.update_cmd.
2026-09-18 23:27:05 -04:00

76 lines
2.9 KiB
Python

"""A failed update lookup is not a current result or permission to apply."""
import importlib
from argparse import Namespace
import pytest
from pm import cli, paths, registry
from pm.lock import Lockfile
from pm.package import Package
from pm.store import current_target
class UpdateFixture(Package):
def __init__(self, name, versions):
self.name = name
self.versions = versions
self.lookups = 0
def missing_reason(self, target):
return None if target == current_target() else "not a fixture target"
def latest_versions(self, target, locked=None):
self.lookups += 1
if isinstance(self.versions, Exception):
raise self.versions
return self.versions
def prepare(tmp_path, monkeypatch, packages):
lock = Lockfile(tmp_path / "lock.json")
for package in packages:
lock.set_pin(package.name, "1.0", {})
monkeypatch.setitem(registry._packages, package.name, package)
lock.save()
monkeypatch.setattr(paths, "lockfile_path", lambda: lock.path)
monkeypatch.setattr(paths, "repo_root", lambda: tmp_path)
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "home"))
monkeypatch.setenv("HERMES_RUNTIME_DIR", str(tmp_path / "tools"))
def forbidden(*args, **kwargs):
pytest.fail("lookup-only result reached a mutation or dependency refresh")
for name in ("_pin_artifacts", "_install_names", "_run_live", "lock_project"):
monkeypatch.setattr(cli, name, forbidden)
monkeypatch.setattr(importlib.import_module("pm.install"), "sync_venv", forbidden)
return lock
@pytest.mark.parametrize("check", [True, False])
@pytest.mark.parametrize("mixed", [True, False])
def test_failed_resolution_stops_every_apply_path(tmp_path, monkeypatch, capsys, check, mixed):
failed = UpdateFixture("failed-lookup", TimeoutError("fixture index unavailable"))
healthy = UpdateFixture("healthy-lookup", ["2.0"])
packages = [failed, healthy] if mixed else [failed]
lock = prepare(tmp_path, monkeypatch, packages)
before = lock.path.read_bytes()
args = Namespace(names=[p.name for p in packages], target=None, check=check, uv=True, npm=True, termux=False)
assert cli.cmd_update(args) == 1
assert "fixture index unavailable" in capsys.readouterr().out
assert lock.path.read_bytes() == before
assert not (tmp_path / "tools").exists()
if mixed:
assert healthy.lookups == 1
@pytest.mark.parametrize("versions", [["1.0"], []])
def test_current_and_manual_results_are_successful_without_writes(tmp_path, monkeypatch, versions):
package = UpdateFixture("no-update", versions)
lock = prepare(tmp_path, monkeypatch, [package])
before = lock.path.read_bytes()
for check in (True, False):
args = Namespace(names=[package.name], target=None, check=check, uv=False, npm=False, termux=False)
assert cli.cmd_update(args) == 0
assert lock.path.read_bytes() == before
assert not (tmp_path / "tools").exists()