"""JitPack commit pins: "newest" is the source repo's default-branch HEAD. The fail-green case here is a hash the version parser cannot read: `behind` returns False for unparseable input, so without its own ecosystem a moved default branch would pass silently. Every path below is pinned down. """ from __future__ import annotations import subprocess from pathlib import Path import pytest from dep_freshness.cache import ttl_for from dep_freshness.evaluate import judge from dep_freshness.models import Dep, Severity from dep_freshness.parsers import gradle from dep_freshness.registries import _git, gitcommit from dep_freshness.registries.http import Offline from dep_freshness.resolve import _LOOKUP, Answer from dep_freshness.tests.conftest import write HEAD = "844a07002c3d2f2b68a6b26a25e8e401777c73e7" SIDE = "c80135339b6f4c2d8e1a7b3f9d0e5c6a2b4d8f10" @pytest.fixture def ls_remote(monkeypatch): monkeypatch.setattr(_git, "host_reachable", lambda _url: True) calls: list[list[str]] = [] def install(stdout="", returncode=0, error=None): def fake_run(args, **_kwargs): calls.append(args) if error: raise error return subprocess.CompletedProcess(args, returncode, stdout, "") monkeypatch.setattr(subprocess, "run", fake_run) return calls return install @pytest.mark.parametrize( "name,expected", [ ("com.github.mihonapp:image-decoder", "mihonapp/image-decoder"), ("com.github.arkon.FlexibleAdapter:flexible-adapter", "arkon/FlexibleAdapter"), ("com.github.zhanghai.quickjs-java:quickjs-android", "zhanghai/quickjs-java"), ("com.github.owner.repo.deeper:module", "owner/repo"), ("com.github.owner.:module", "owner/module"), ("io.coil-kt:coil", None), ("com.github.:module", None), ("com.github.owner", None), ("com.github.owner:", None), ], ) def test_the_github_repo_behind_a_jitpack_coordinate(name, expected): assert gitcommit.source_repo(name) == expected def test_latest_lists_every_branch_head_default_first(ls_remote): calls = ls_remote( f"{SIDE}\trefs/heads/fix-build2\n{HEAD}\tHEAD\n{HEAD}\trefs/heads/master\n" f"{SIDE}\trefs/pull/1/head\n" ) assert ( gitcommit.latest("com.github.arkon.FlexibleAdapter:flexible-adapter") == f"{HEAD} {SIDE}" ) assert calls[-1] == ["git", "ls-remote", "https://github.com/arkon/FlexibleAdapter"] def test_a_repo_with_no_default_head_still_lists_its_branches(ls_remote): ls_remote(f"{SIDE}\trefs/heads/dev\n") assert gitcommit.latest("com.github.mihonapp:image-decoder") == SIDE def test_no_head_line_means_no_answer(ls_remote): ls_remote("\n \nabc\trefs/tags/v1\n") assert gitcommit.latest("com.github.mihonapp:image-decoder") is None def test_a_non_jitpack_coordinate_never_touches_git(ls_remote): calls = ls_remote(f"{HEAD}\tHEAD\n") assert gitcommit.latest("io.coil-kt:coil") is None assert calls == [] def test_ls_remote_failures_degrade_to_offline(ls_remote, monkeypatch): ls_remote(returncode=128) with pytest.raises(Offline): gitcommit.latest("com.github.mihon:unifile") ls_remote(error=OSError("no git")) with pytest.raises(Offline): gitcommit.latest("com.github.mihon:unifile") monkeypatch.setattr(_git, "host_reachable", lambda _url: False) with pytest.raises(Offline): gitcommit.latest("com.github.mihon:unifile") CATALOG = """\ [versions] unifile = "08f224c8f9" photoView = "2.3.0" tagged = "abc1234" [libraries] unifile = { module = "com.github.mihon:unifile", version.ref = "unifile" } photoView = { module = "com.github.chrisbanes:PhotoView", version.ref = "photoView" } inline = "com.github.mihonapp:image-decoder:e03b81e18a" central = { module = "io.coil-kt:coil", version = "deadbeef" } short = { module = "com.github.o:r", version = "abc12" } tagged = { module = "org.example:lib", version.ref = "tagged" } """ def test_the_catalog_parser_tells_commit_pins_from_versions(tmp_path): deps = { d.name: d for d in gradle.parse_catalog(write(tmp_path, "libs.versions.toml", CATALOG)) } assert ( deps["com.github.mihon:unifile"].ecosystem, deps["com.github.mihon:unifile"].pinned, ) == ( "gitcommit", "08f224c8f9", ) assert deps["com.github.mihonapp:image-decoder"].ecosystem == "gitcommit" assert deps["com.github.chrisbanes:PhotoView"].ecosystem == "maven" assert deps["com.github.chrisbanes:PhotoView"].pinned == "2.3.0" # A hash-shaped version outside com.github.* is still a Maven version # (an unpinned one, since it is not exact), and six hex digits is too few. assert deps["io.coil-kt:coil"].ecosystem == "maven" assert deps["io.coil-kt:coil"].pinned is None assert deps["com.github.o:r"].ecosystem == "maven" assert deps["org.example:lib"].ecosystem == "maven" def dep(pinned: str | None = "844a07002c") -> Dep: return Dep( ecosystem="gitcommit", name="com.github.arkon.FlexibleAdapter:flexible-adapter", constraint=pinned or "", path=Path("libs.versions.toml"), line=1, pinned=pinned, ) def test_a_pin_at_any_branch_head_passes_whatever_its_length_or_case(): assert judge(dep("844a07002c"), Answer(HEAD)) is None assert judge(dep("844A070"), Answer(HEAD)) is None assert judge(dep(HEAD), Answer(HEAD)) is None # fix-build2's head, not master's: still current. assert judge(dep("c8013533"), Answer(f"{HEAD} {SIDE}")) is None def test_a_pin_on_no_branch_head_is_stale_and_proposes_the_default_head(): finding = judge(dep("0123abc"), Answer(f"{HEAD} {SIDE}")) assert finding.severity is Severity.STALE assert finding.latest == "844a07002c" assert "no branch" in finding.detail def test_a_missing_pin_is_reported_unpinned_with_head_to_use(): finding = judge(dep(None), Answer(HEAD)) assert finding.severity is Severity.UNPINNED assert "844a07002c" in finding.detail def test_no_head_at_all_is_unknown_not_a_pass(): assert judge(dep(), Answer(None)).severity is Severity.UNKNOWN def test_commit_answers_cache_as_long_as_tags(): assert ttl_for("gitcommit") == ttl_for("gittag") > ttl_for("maven") def test_the_resolver_routes_gitcommit_to_the_git_registry(): assert _LOOKUP["gitcommit"] is gitcommit.latest