hermes-agent/tests/lints/test_uv_stale_exclude.py

393 lines
15 KiB
Python

"""Fixture tests for the uv-stale-exclude lint. No network — publish
dates come from uv.lock's upload-time fields in the fixtures."""
from __future__ import annotations
import sys
import tomllib
from datetime import datetime, timedelta, timezone
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO_ROOT / "scripts"))
from lints import uv_stale_exclude as mod # noqa: E402
NOW = datetime(2026, 8, 8, 12, 0, tzinfo=timezone.utc)
OLD = (NOW - timedelta(days=40)).strftime("%Y-%m-%dT%H:%M:%SZ")
FRESH = (NOW - timedelta(days=3)).strftime("%Y-%m-%dT%H:%M:%SZ")
BOUNDARY = (NOW - timedelta(days=14, hours=12)).strftime("%Y-%m-%dT%H:%M:%SZ")
def _pyproject(entries: str) -> str:
return (
"[project]\n"
'name = "fixture"\n'
'version = "0.0.0"\n'
"\n"
"[tool.uv]\n"
'exclude-newer = "14 days"\n'
"# h2: temporary exception for a CVE fix\n"
"exclude-newer-package = { " + entries + " }\n"
)
def _lock(packages: list[tuple[str, str, str | None]], mirror: str = "") -> str:
"""packages: (name, version, upload_time_or_None)."""
head = (
"version = 1\n"
"revision = 3\n"
"\n"
"[options]\n"
'exclude-newer = "2026-07-25T00:00:00Z"\n'
'exclude-newer-span = "P14D"\n'
)
if mirror:
head += "\n[options.exclude-newer-package]\n" + mirror + "\n"
body = ""
for name, version, upload in packages:
body += f'\n[[package]]\nname = "{name}"\nversion = "{version}"\n'
if upload:
body += (
f'sdist = {{ url = "https://x/{name}.tar.gz", '
f'hash = "sha256:0", size = 1, upload-time = "{upload}" }}\n'
)
return head + body
# ── plan ─────────────────────────────────────────────────────────────────
def test_false_replaced_with_stamp_from_lock():
py = _pyproject("h2 = false")
lock = _lock([("h2", "4.4.1", FRESH)])
actions = mod.plan(py, lock, now=NOW)
assert len(actions) == 1
key, action, reason, stamp = actions[0]
assert (key, action) == ("h2", "replace")
assert "= false" in reason or "false" in reason
# Stamp = newest upload-time + 1 day.
expected = (
datetime.fromisoformat(FRESH.replace("Z", "+00:00")) + timedelta(days=1)
).strftime("%Y-%m-%dT%H:%M:%SZ")
assert stamp == expected
def test_stale_dated_entry_removed():
py = _pyproject('h2 = "2026-08-04T00:00:00Z"')
lock = _lock([("h2", "4.4.1", OLD)])
actions = mod.plan(py, lock, now=NOW)
assert [(a[0], a[1]) for a in actions] == [("h2", "remove")]
def test_stale_false_entry_removed_not_replaced():
"""Removal wins over replacement when the package is already old —
replacing first would just leave a stamp for the next run to remove."""
py = _pyproject("h2 = false")
lock = _lock([("h2", "4.4.1", OLD)])
actions = mod.plan(py, lock, now=NOW)
assert [(a[0], a[1]) for a in actions] == [("h2", "remove")]
def test_fresh_dated_entry_kept():
py = _pyproject('h2 = "2026-08-04T00:00:00Z"')
lock = _lock([("h2", "4.4.1", FRESH)])
assert mod.plan(py, lock, now=NOW) == []
def test_boundary_grace_keeps_entry():
py = _pyproject('h2 = "2026-08-04T00:00:00Z"')
lock = _lock([("h2", "4.4.1", BOUNDARY)])
assert mod.plan(py, lock, now=NOW) == []
def test_unknown_upload_time_fails_open():
py = _pyproject('h2 = "2026-08-04T00:00:00Z"')
lock = _lock([("h2", "4.4.1", None)])
assert mod.plan(py, lock, now=NOW) == []
def test_false_with_no_dated_artifacts_left_alone():
py = _pyproject("h2 = false")
lock = _lock([("h2", "4.4.1", None)])
assert mod.plan(py, lock, now=NOW) == []
def test_entry_matching_no_locked_package_removed():
py = _pyproject("ghost = false")
lock = _lock([("h2", "4.4.1", FRESH)])
actions = mod.plan(py, lock, now=NOW)
assert [(a[0], a[1]) for a in actions] == [("ghost", "remove")]
assert "matches no package" in actions[0][2]
def test_name_normalization_bridges_underscore_and_dash():
"""pyproject says huggingface_hub, uv.lock says huggingface-hub."""
py = _pyproject("huggingface_hub = false")
lock = _lock([("huggingface-hub", "1.0.0", FRESH)])
actions = mod.plan(py, lock, now=NOW)
assert [(a[0], a[1]) for a in actions] == [("huggingface_hub", "replace")]
def test_multiple_locked_versions_any_fresh_keeps():
py = _pyproject('h2 = "2026-08-04T00:00:00Z"')
lock = _lock([("h2", "4.4.1", OLD), ("h2", "4.4.2", FRESH)])
assert mod.plan(py, lock, now=NOW) == []
def test_keep_marker_exempts_table():
py = _pyproject("h2 = false").replace(
"# h2: temporary exception for a CVE fix",
"# lint: keep\n# h2: standing exception",
)
lock = _lock([("h2", "4.4.1", OLD)])
assert mod.plan(py, lock, now=NOW) == []
_SECTION_PYPROJECT = (
"[project]\nname = \"fixture\"\nversion = \"0.0.0\"\n\n"
"[tool.uv]\nexclude-newer = \"14 days\"\n\n"
"# temporary exceptions\n"
"[tool.uv.exclude-newer-package]\nh2 = false\nvercel = \"2026-08-01T00:00:00Z\"\n"
)
def test_section_form_planned_same_as_inline():
"""The [tool.uv.exclude-newer-package] table form is read as plain
TOML and planned identically to the inline form."""
lock = _lock([("h2", "4.4.1", FRESH), ("vercel", "1.0.0", OLD)])
actions = mod.plan(_SECTION_PYPROJECT, lock, now=NOW)
assert {(a[0], a[1]) for a in actions} == {("h2", "replace"), ("vercel", "remove")}
def test_section_form_applied_in_pyproject():
lock = _lock([("h2", "4.4.1", FRESH), ("vercel", "1.0.0", OLD)])
actions = mod.plan(_SECTION_PYPROJECT, lock, now=NOW)
out = mod.apply_to_pyproject(_SECTION_PYPROJECT, actions)
table = tomllib.loads(out)["tool"]["uv"]["exclude-newer-package"]
assert "vercel" not in table
assert isinstance(table["h2"], str) # false -> stamp
mod.verify_only_age_changed(
_SECTION_PYPROJECT, out, ("tool", "uv", "exclude-newer-package"), "pyproject"
)
def test_section_form_emptied_drops_header_and_comment():
lock = _lock([("vercel", "1.0.0", OLD)])
py = (
"[project]\nname = \"fixture\"\nversion = \"0.0.0\"\n\n"
"[tool.uv]\nexclude-newer = \"14 days\"\n\n"
"# temporary exceptions\n"
"[tool.uv.exclude-newer-package]\nvercel = \"2026-08-01T00:00:00Z\"\n"
)
actions = mod.plan(py, lock, now=NOW)
out = mod.apply_to_pyproject(py, actions)
parsed = tomllib.loads(out)
assert "exclude-newer-package" not in parsed["tool"]["uv"]
assert "# temporary exceptions" not in out
assert parsed["tool"]["uv"]["exclude-newer"] == "14 days"
def test_section_form_keep_marker_respected():
py = _SECTION_PYPROJECT.replace(
"# temporary exceptions", "# lint: keep\n# standing exceptions"
)
lock = _lock([("h2", "4.4.1", OLD), ("vercel", "1.0.0", OLD)])
assert mod.plan(py, lock, now=NOW) == []
def test_real_repo_pyproject_parses():
"""The shipped pyproject/uv.lock must be plannable (invariant)."""
py = (REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8")
lock = (REPO_ROOT / "uv.lock").read_text(encoding="utf-8")
span = mod.parse_span(py)
assert span is not None and span.days >= 1
form, _, _ = mod.locate_table(py)
assert form is not None, "repo pyproject should carry the table"
mod.plan(py, lock) # must not raise
# ── apply_to_pyproject ───────────────────────────────────────────────────
@pytest.mark.parametrize(
"entries,actions",
[
# remove `h2` while `python-h2` sits beside it
("python-h2 = false, h2 = \"2026-08-04T00:00:00Z\"", [("h2", "remove", "r", None)]),
# …and with the collider on the right of the target
("h2 = \"2026-08-04T00:00:00Z\", python-h2 = false", [("h2", "remove", "r", None)]),
# replace must not rewrite the longer key's value either
("python-h2 = false, h2 = false", [("h2", "replace", "r", "2026-09-09T00:00:00Z")]),
],
)
def test_key_edit_does_not_eat_a_longer_key_with_the_same_suffix(entries, actions):
"""An unanchored key regex turns `{ python-h2 = false, h2 = ... }` into
`{ python- }` — valid-looking line surgery, unparseable TOML."""
py = _pyproject(entries)
out = mod.apply_to_pyproject(py, actions)
table = tomllib.loads(out)["tool"]["uv"]["exclude-newer-package"]
assert "python-h2" in table, f"collider key was clobbered: {out!r}"
mod.verify_only_age_changed(
py, out, ("tool", "uv", "exclude-newer-package"), "pyproject"
)
def test_section_form_key_edit_does_not_eat_a_longer_key():
py = (
"[project]\nname = \"fixture\"\nversion = \"0.0.0\"\n\n"
"[tool.uv]\nexclude-newer = \"14 days\"\n\n"
"[tool.uv.exclude-newer-package]\n"
"python-h2 = false\nh2 = \"2026-08-04T00:00:00Z\"\n"
)
out = mod.apply_to_pyproject(py, [("h2", "remove", "r", None)])
table = tomllib.loads(out)["tool"]["uv"]["exclude-newer-package"]
assert set(table) == {"python-h2"}
def test_lock_mirror_key_edit_does_not_eat_a_longer_key():
lock = _lock(
[("h2", "4.4.1", OLD)],
mirror='python-h2 = false\nh2 = "2026-08-04T00:00:00Z"',
)
out = mod.apply_to_lock(lock, [("h2", "remove", "r", None)])
assert set(tomllib.loads(out)["options"]["exclude-newer-package"]) == {"python-h2"}
def test_apply_replace_false_in_pyproject():
py = _pyproject("vercel = false, h2 = false")
actions = [("h2", "replace", "r", "2026-08-04T00:00:00Z")]
out = mod.apply_to_pyproject(py, actions)
assert 'h2 = "2026-08-04T00:00:00Z"' in out
assert "vercel = false" in out # untouched
tomllib.loads(out) # stays valid TOML
def test_apply_remove_entry_in_pyproject():
py = _pyproject("vercel = false, h2 = false")
out = mod.apply_to_pyproject(py, [("h2", "remove", "r", None)])
parsed = tomllib.loads(out)["tool"]["uv"]["exclude-newer-package"]
assert parsed == {"vercel": False}
def test_apply_remove_last_entry_drops_table_and_comment():
py = _pyproject("h2 = false")
out = mod.apply_to_pyproject(py, [("h2", "remove", "r", None)])
parsed = tomllib.loads(out)
assert "exclude-newer-package" not in parsed["tool"]["uv"]
assert "temporary exception" not in out # comment went with it
assert parsed["tool"]["uv"]["exclude-newer"] == "14 days" # untouched
# ── apply_to_lock ────────────────────────────────────────────────────────
MIRROR = "vercel = false\nh2 = false"
def test_apply_replace_in_lock():
lock = _lock([("h2", "4.4.1", FRESH)], mirror=MIRROR)
out = mod.apply_to_lock(lock, [("h2", "replace", "r", "2026-08-04T00:00:00Z")])
parsed = tomllib.loads(out)["options"]["exclude-newer-package"]
assert parsed == {"vercel": False, "h2": "2026-08-04T00:00:00Z"}
def test_apply_remove_in_lock_normalizes_names():
lock = _lock([("h2", "4.4.1", FRESH)], mirror="huggingface-hub = false\nh2 = false")
out = mod.apply_to_lock(lock, [("huggingface_hub", "remove", "r", None)])
parsed = tomllib.loads(out)["options"]["exclude-newer-package"]
assert parsed == {"h2": False}
def test_apply_remove_last_entry_drops_lock_section():
lock = _lock([("h2", "4.4.1", FRESH)], mirror="h2 = false")
out = mod.apply_to_lock(lock, [("h2", "remove", "r", None)])
parsed = tomllib.loads(out)
assert "exclude-newer-package" not in parsed.get("options", {})
assert parsed["options"]["exclude-newer"] # rest of options intact
def test_apply_to_lock_without_section_is_noop():
lock = _lock([("h2", "4.4.1", FRESH)])
assert mod.apply_to_lock(lock, [("h2", "remove", "r", None)]) == lock
# ── verifier ─────────────────────────────────────────────────────────────
def test_verifier_accepts_age_only_changes():
py = _pyproject("h2 = false")
out = mod.apply_to_pyproject(py, [("h2", "replace", "r", "2026-08-04T00:00:00Z")])
mod.verify_only_age_changed(py, out, ("tool", "uv", "exclude-newer-package"), "pyproject")
def test_verifier_rejects_out_of_scope_change():
py = _pyproject("h2 = false")
tampered = py.replace('exclude-newer = "14 days"', 'exclude-newer = "1 days"')
with pytest.raises(RuntimeError, match="outside"):
mod.verify_only_age_changed(
py, tampered, ("tool", "uv", "exclude-newer-package"), "pyproject"
)
def test_verifier_rejects_dependency_tampering_in_lock():
lock = _lock([("h2", "4.4.1", FRESH)], mirror="h2 = false")
tampered = lock.replace("https://x/h2.tar.gz", "https://evil/h2.tar.gz")
with pytest.raises(RuntimeError, match="outside"):
mod.verify_only_age_changed(
lock, tampered, ("options", "exclude-newer-package"), "uv.lock"
)
# ── end-to-end fix() roundtrip ───────────────────────────────────────────
def test_fix_roundtrip(tmp_path, monkeypatch):
real_now = datetime.now(timezone.utc)
old_stamp = (real_now - timedelta(days=40)).strftime("%Y-%m-%dT%H:%M:%SZ")
fresh_stamp = (real_now - timedelta(days=3)).strftime("%Y-%m-%dT%H:%M:%SZ")
pyproject = tmp_path / "pyproject.toml"
pyproject.write_text(
_pyproject("stale = false, fresh = false"), encoding="utf-8"
)
lock = tmp_path / "uv.lock"
lock.write_text(
_lock(
[("stale", "1.0.0", old_stamp), ("fresh", "2.0.0", fresh_stamp)],
mirror="stale = false\nfresh = false",
),
encoding="utf-8",
)
monkeypatch.setattr(mod, "_pyproject_path", lambda: pyproject)
monkeypatch.setattr(mod, "_uvlock_path", lambda: lock)
findings = mod.check()
assert {f.path for f in findings} == {"pyproject.toml"}
assert all(f.fixable for f in findings)
changed = mod.fix()
assert changed == ["pyproject.toml", "uv.lock"]
py_parsed = tomllib.loads(pyproject.read_text(encoding="utf-8"))
table = py_parsed["tool"]["uv"]["exclude-newer-package"]
assert "stale" not in table # old package's entry removed
assert isinstance(table["fresh"], str) # false -> timestamp
lock_parsed = tomllib.loads(lock.read_text(encoding="utf-8"))
lock_table = lock_parsed["options"]["exclude-newer-package"]
assert "stale" not in lock_table
assert lock_table["fresh"] == table["fresh"] # mirrored stamp
# Idempotent.
assert mod.check() == []
assert mod.fix() == []
def test_lint_metadata_contract():
lint = mod.LINT
assert lint.network is False # dates come from uv.lock, no registry
assert lint.autofix is True
assert set(lint.fix_touches) == {"pyproject.toml", "uv.lock"}