diff --git a/collector/pyproject.toml b/collector/pyproject.toml index a88a165..490ffe1 100644 --- a/collector/pyproject.toml +++ b/collector/pyproject.toml @@ -33,6 +33,10 @@ addopts = "-q --doctest-modules" # Coverage column reports against, and it is deliberately narrower than # jq_local_code_lines, which counts every tracked source file in the repo. source = ["jq_collector"] +# Both arms of every branch, not just every line: an `if` whose false side +# never runs is 100% line-covered and still untested. fail_under below then +# holds lines and branches to the same floor. +branch = true [tool.coverage.report] # The package is fully covered; this keeps it that way. pytest-cov reads it, so diff --git a/collector/tests/test_coverage.py b/collector/tests/test_coverage.py index c56437f..8dcbbd7 100644 --- a/collector/tests/test_coverage.py +++ b/collector/tests/test_coverage.py @@ -58,6 +58,21 @@ def test_the_newest_default_branch_artifact_wins_not_the_newest_overall(make_cli assert client.coverage_artifact("o/r", "main") == 2 +def test_an_older_artifact_listed_after_a_newer_one_does_not_win(make_client): + """Newest wins by created_at, not by where it sits in the listing.""" + client = make_client( + { + ARTIFACTS: { + "artifacts": [ + artifact(2, "main", "2026-08-27T04:07:41Z"), # newest on main + artifact(1, "main", "2026-08-26T04:07:41Z"), # older, listed after + ] + } + } + ) + assert client.coverage_artifact("o/r", "main") == 2 + + def test_expired_and_unrelated_artifacts_are_ignored(make_client): client = make_client( { diff --git a/collector/tests/test_main.py b/collector/tests/test_main.py index 5b1ea7b..b120970 100644 --- a/collector/tests/test_main.py +++ b/collector/tests/test_main.py @@ -312,6 +312,23 @@ def test_main_warns_when_there_is_no_token(monkeypatch, caplog): assert "no GITHUB_TOKEN" in caplog.text +def test_main_does_not_warn_when_a_token_is_set(monkeypatch, caplog): + """The warning is for the deployment that needs it; a set token is silent.""" + monkeypatch.setattr(entry, "_refresh_remote", lambda *_: None) + monkeypatch.setattr(entry, "_tick_local", lambda *_: None) + monkeypatch.setattr(entry, "start_http_server", lambda _port: None) + monkeypatch.setattr(entry.REGISTRY, "register", lambda _c: None) + monkeypatch.setattr(entry.signal, "signal", lambda _sig, handler: handler(_sig, None)) + + cfg = Config() + object.__setattr__(cfg, "token", "ghp_test") + monkeypatch.setattr(entry, "Config", lambda: cfg) + + entry.main() + + assert "no GITHUB_TOKEN" not in caplog.text + + def test_main_starts_a_loop_per_source_and_shuts_them_down(monkeypatch): started: list[str] = [] real_thread = threading.Thread diff --git a/collector/tests/test_workflows.py b/collector/tests/test_workflows.py index c1542cd..6d8c442 100644 --- a/collector/tests/test_workflows.py +++ b/collector/tests/test_workflows.py @@ -83,6 +83,23 @@ def test_latest_is_by_completion_not_feed_order(make_client): assert c.latest_runs("o/r", "main")[0]["conclusion"] == "failure" +def test_an_older_run_later_in_the_feed_does_not_replace_a_newer_one(make_client): + """The mirror of the case above: when the feed does happen to put the newer + run first, the older one after it must not win just by coming last.""" + c = make_client( + { + WFS: {"workflows": [workflow(1, "CI")]}, + RUNS: { + "workflow_runs": [ + run(1, "CI", "failure", "2026-08-01T05:39:00Z"), + run(1, "CI", "success", "2026-08-01T04:09:00Z"), + ] + }, + } + ) + assert c.latest_runs("o/r", "main")[0]["conclusion"] == "failure" + + def test_workflow_missing_from_the_feed_is_fetched_directly(make_client): """The feed's first page is dominated by frequent workflows. cvxgrp/simulator has 2406 runs on main and only 6 of its 23 workflows appear in the first 100; @@ -145,6 +162,27 @@ def test_two_active_workflows_sharing_a_name_get_distinct_labels(make_client): assert len(names) == len(set(names)), f"labels collide: {names}" +def test_a_shared_name_with_no_path_to_fall_back_on_keeps_its_name(make_client): + """Only a workflow that has a path can be relabelled by it. One without + keeps its display name rather than being blanked, and the other still + moves to its path, so the labels stay distinct.""" + pathless = workflow(1, "CI") + del pathless["path"] + c = make_client( + { + WFS: {"workflows": [pathless, workflow(2, "CI", path=".github/workflows/b.yml")]}, + RUNS: { + "workflow_runs": [ + run(1, "CI", "success", "2026-08-01T00:00:00Z"), + run(2, "CI", "failure", "2026-08-01T00:00:00Z"), + ] + }, + } + ) + got = {r["_name"]: r["conclusion"] for r in c.latest_runs("o/r", "main")} + assert got == {"CI": "success", ".github/workflows/b.yml": "failure"} + + def test_cancelled_run_falls_back_to_the_last_real_verdict(make_client): """Stopping a job by hand, or a concurrency group killing it when the next push lands, is not a failure. The workflow keeps its last conclusive run."""