Files
hermes-agent/tests/pm/test_update_artifact_refresh.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

96 lines
3.7 KiB
Python

"""Minor-style updates follow advertised artifacts without dropping other pins."""
from __future__ import annotations
from argparse import Namespace
import hashlib
import importlib
import io
import json
import zipfile
import pytest
from pm import cli, paths, registry
from pm.lock import Facts, Lockfile
from pm.package import Package
from pm.store import current_target
from tests.pm._range_server import RangeHandler, dl_server, url # noqa: F401
@pytest.mark.parametrize("next_version", ["1.2.3", "1.2.4"])
def test_same_minor_refresh_updates_real_bytes_and_preserves_unresolved_targets(
tmp_path, dl_server, monkeypatch, next_version,
):
class RollingPackage(Package):
name = "rolling-tool"
version_style = "minor"
def latest_versions(self, target, locked=None):
return [next_version] if target == current_target() else []
def fetch_url(self, version, target):
assert version == next_version
assert target == current_target()
return url(dl_server, "/new-build.zip")
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, "w") as archive:
archive.writestr("tool.txt", b"new rolling build")
body = buffer.getvalue()
RangeHandler.payloads["/new-build.zip"] = body
original_get = RangeHandler.do_GET
requests = []
def record(handler):
requests.append(handler.path)
original_get(handler)
monkeypatch.setattr(RangeHandler, "do_GET", record)
target = current_target()
held_target = "linux-arm64-bionic" if target != "linux-arm64-bionic" else "win32-arm64"
held = {"url": "https://upstream.invalid/held.deb", "sha256": "b" * 64}
lock = Lockfile(tmp_path / "lock.json")
lock.set_pin("rolling-tool", "1.2.3", {
target: {"url": url(dl_server, "/old-build.zip"), "sha256": "a" * 64},
held_target: held,
})
lock.set_pin("unrelated", "2.0", {"any": held})
lock.save()
store = tmp_path / "tools"
monkeypatch.setitem(registry._packages, "rolling-tool", RollingPackage())
monkeypatch.setattr(paths, "lockfile_path", lambda: lock.path)
monkeypatch.setattr(paths, "repo_root", lambda: tmp_path)
monkeypatch.setattr(paths, "partials_root", lambda: tmp_path / "partials")
monkeypatch.setenv("HERMES_RUNTIME_DIR", str(store))
syncs = []
engine = importlib.import_module("pm.install")
monkeypatch.setattr(engine, "sync_venv", lambda **kwargs: syncs.append(kwargs))
before = lock.path.read_bytes()
args = Namespace(names=["rolling-tool"], target=None, check=True, uv=False, npm=False, termux=False)
assert cli.cmd_update(args) == 1
assert lock.path.read_bytes() == before and not requests and not syncs
assert not store.exists()
args.check = False
assert cli.cmd_update(args) == 0
after = json.loads(lock.path.read_text(encoding="utf-8"))["packages"]
original = json.loads(before)["packages"]
assert after["unrelated"] == original["unrelated"]
assert after["rolling-tool"]["version"] == original["rolling-tool"]["version"]
assert after["rolling-tool"]["artifacts"][held_target] == held
assert Lockfile(lock.path).artifacts("rolling-tool", target) == [{
"url": url(dl_server, "/new-build.zip"), "sha256": hashlib.sha256(body).hexdigest(),
}]
fact = Facts(store / "facts.json").get("rolling-tool")
assert (store / fact["entry"] / "tool.txt").read_bytes() == b"new rolling build"
assert syncs == [{"explicit": True}]
assert requests and all(path == "/new-build.zip" for path in requests)
pinned = lock.path.read_bytes()
requests.clear()
syncs.clear()
for check in (True, False):
args.check = check
assert cli.cmd_update(args) == 0
assert lock.path.read_bytes() == pinned and not requests and not syncs