Files
hermes-agent/tests/pm/test_update_artifact_refresh.py
2026-10-08 17:22:54 +02: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
@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