mirror of
https://github.com/home-assistant/core.git
synced 2026-07-04 05:05:38 +01:00
224 lines
7.9 KiB
Python
224 lines
7.9 KiB
Python
"""Tests for script.check_requirements.gate."""
|
|
|
|
from collections.abc import Callable
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock
|
|
|
|
from github import GithubException
|
|
import pytest
|
|
|
|
from script.check_requirements import gate
|
|
from script.check_requirements.gate import (
|
|
decide_skip,
|
|
extract_prior_sha,
|
|
fetch_marker_comment_bodies,
|
|
)
|
|
from script.check_requirements.models import CheckRunResult
|
|
from script.check_requirements.render import render_comment
|
|
|
|
_REPO = "home-assistant/core"
|
|
_TOKEN = "test-token"
|
|
_PRIOR = "1234567890abcdef1234567890abcdef12345678"
|
|
_HEAD = "fedcba0987654321fedcba0987654321fedcba09"
|
|
|
|
InstallGithub = Callable[..., MagicMock]
|
|
|
|
|
|
def _body(sha: str | None) -> str:
|
|
body = "<!-- requirements-check -->\n## Check requirements\n"
|
|
if sha is not None:
|
|
body += (
|
|
f"\nChecked at commit "
|
|
f"[`{sha[:7]}`](https://github.com/home-assistant/core/commit/{sha})."
|
|
)
|
|
return body
|
|
|
|
|
|
def _comment(
|
|
sha: str | None, *, author: str = "github-actions[bot]"
|
|
) -> SimpleNamespace:
|
|
"""A PyGithub-like IssueComment with a body and an author login."""
|
|
return SimpleNamespace(body=_body(sha), user=SimpleNamespace(login=author))
|
|
|
|
|
|
def _file(filename: str) -> SimpleNamespace:
|
|
"""A PyGithub-like File entry from a commit comparison."""
|
|
return SimpleNamespace(filename=filename)
|
|
|
|
|
|
@pytest.fixture
|
|
def install_github(monkeypatch: pytest.MonkeyPatch) -> InstallGithub:
|
|
"""Install a fake PyGithub client and return the installer for assertions."""
|
|
|
|
def _install(
|
|
*,
|
|
comments: list[SimpleNamespace] | None = None,
|
|
files: list[SimpleNamespace] | None = None,
|
|
comments_exc: Exception | None = None,
|
|
compare_exc: Exception | None = None,
|
|
) -> MagicMock:
|
|
issue = MagicMock()
|
|
if comments_exc is not None:
|
|
issue.get_comments.side_effect = comments_exc
|
|
else:
|
|
issue.get_comments.return_value = comments or []
|
|
repo = MagicMock()
|
|
repo.get_issue.return_value = issue
|
|
if compare_exc is not None:
|
|
repo.compare.side_effect = compare_exc
|
|
else:
|
|
repo.compare.return_value = SimpleNamespace(files=files or [])
|
|
client = MagicMock()
|
|
client.get_repo.return_value = repo
|
|
monkeypatch.setattr(gate, "Github", lambda **_: client)
|
|
return client
|
|
|
|
return _install
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("bodies", "expected"),
|
|
[
|
|
pytest.param([], None, id="no-comments"),
|
|
pytest.param(
|
|
["<!-- requirements-check -->\nNo commit link here."],
|
|
None,
|
|
id="marker-without-link",
|
|
),
|
|
pytest.param([_body(_PRIOR)], _PRIOR, id="single-comment"),
|
|
pytest.param([_body(_PRIOR), _body(_HEAD)], _HEAD, id="most-recent-wins"),
|
|
],
|
|
)
|
|
def test_extract_prior_sha(bodies: list[str], expected: str | None) -> None:
|
|
"""The last recorded commit SHA across marker comments is returned."""
|
|
assert extract_prior_sha(bodies) == expected
|
|
|
|
|
|
def test_extract_prior_sha_normalizes_case() -> None:
|
|
"""An upper-case SHA in a link is returned lower-cased."""
|
|
assert extract_prior_sha([_body(_PRIOR.upper())]) == _PRIOR
|
|
|
|
|
|
def test_extract_prior_sha_round_trips_rendered_comment() -> None:
|
|
"""The gate reads back exactly the SHA the renderer wrote, keeping them in sync."""
|
|
body = render_comment(CheckRunResult(pr_number=1, head_sha=_HEAD))
|
|
assert extract_prior_sha([body]) == _HEAD
|
|
|
|
|
|
def test_fetch_marker_comment_bodies_returns_all_bot_comments(
|
|
install_github: InstallGithub,
|
|
) -> None:
|
|
"""Every bot comment body is returned in API order; the marker is not filtered on."""
|
|
install_github(
|
|
comments=[
|
|
_comment(None), # no SHA recorded yet
|
|
_comment(_PRIOR),
|
|
SimpleNamespace(
|
|
body="chatter", user=SimpleNamespace(login="github-actions[bot]")
|
|
),
|
|
]
|
|
)
|
|
bodies = fetch_marker_comment_bodies(7, _REPO, _TOKEN)
|
|
assert bodies == [_body(None), _body(_PRIOR), "chatter"]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"author",
|
|
[
|
|
pytest.param("attacker", id="drive-by-commenter"),
|
|
pytest.param("dependabot[bot]", id="other-bot"),
|
|
pytest.param("maintainer", id="maintainer-account"),
|
|
],
|
|
)
|
|
def test_fetch_marker_comment_bodies_ignores_non_actions_author(
|
|
install_github: InstallGithub,
|
|
author: str,
|
|
) -> None:
|
|
"""A forged marker comment from anyone but github-actions is ignored."""
|
|
install_github(comments=[_comment(_HEAD, author=author)])
|
|
assert fetch_marker_comment_bodies(7, _REPO, _TOKEN) == []
|
|
|
|
|
|
def test_fetch_marker_comment_bodies_handles_api_error(
|
|
install_github: InstallGithub,
|
|
) -> None:
|
|
"""A GitHub API error yields no bodies (fails open) instead of raising."""
|
|
install_github(comments_exc=GithubException(500, {}, {}))
|
|
assert fetch_marker_comment_bodies(7, _REPO, _TOKEN) == []
|
|
|
|
|
|
def test_decide_skip_no_head_sha(install_github: InstallGithub) -> None:
|
|
"""An empty head SHA never skips and makes no API calls."""
|
|
client = install_github()
|
|
assert decide_skip(7, "", _REPO, _TOKEN).skip is False
|
|
client.get_repo.assert_not_called()
|
|
|
|
|
|
def test_decide_skip_no_prior_comment(install_github: InstallGithub) -> None:
|
|
"""The first run (no prior comment) runs the checks."""
|
|
install_github(
|
|
comments=[SimpleNamespace(body="hi", user=SimpleNamespace(login="x"))]
|
|
)
|
|
assert decide_skip(7, _HEAD, _REPO, _TOKEN).skip is False
|
|
|
|
|
|
def test_decide_skip_head_unchanged(install_github: InstallGithub) -> None:
|
|
"""When head matches the last comment's SHA, skip without comparing."""
|
|
client = install_github(comments=[_comment(_HEAD)])
|
|
assert decide_skip(7, _HEAD, _REPO, _TOKEN).skip is True
|
|
client.get_repo.return_value.compare.assert_not_called()
|
|
|
|
|
|
def test_decide_skip_tracked_files_changed(install_github: InstallGithub) -> None:
|
|
"""A requirement file changed since the comment runs the checks."""
|
|
install_github(
|
|
comments=[_comment(_PRIOR)],
|
|
files=[_file("homeassistant/foo.py"), _file("requirements_all.txt")],
|
|
)
|
|
decision = decide_skip(7, _HEAD, _REPO, _TOKEN)
|
|
assert decision.skip is False
|
|
# The untracked file must not be reported as the reason to run.
|
|
assert "requirements_all.txt" in decision.reason
|
|
assert "homeassistant/foo.py" not in decision.reason
|
|
|
|
|
|
def test_decide_skip_no_tracked_files_changed(install_github: InstallGithub) -> None:
|
|
"""Only non-requirement files changed since the comment, so skip."""
|
|
install_github(
|
|
comments=[_comment(_PRIOR)],
|
|
files=[_file("homeassistant/components/demo/light.py")],
|
|
)
|
|
assert decide_skip(7, _HEAD, _REPO, _TOKEN).skip is True
|
|
|
|
|
|
def test_decide_skip_compare_unavailable_runs(install_github: InstallGithub) -> None:
|
|
"""A failed compare falls back to running the checks."""
|
|
install_github(
|
|
comments=[_comment(_PRIOR)], compare_exc=GithubException(404, {}, {})
|
|
)
|
|
assert decide_skip(7, _HEAD, _REPO, _TOKEN).skip is False
|
|
|
|
|
|
def test_decide_skip_comments_error_runs(install_github: InstallGithub) -> None:
|
|
"""A failed comments fetch fails open (runs the checks), never skips."""
|
|
install_github(comments_exc=GithubException(500, {}, {}))
|
|
assert decide_skip(7, _HEAD, _REPO, _TOKEN).skip is False
|
|
|
|
|
|
def test_client_uses_github_api_url(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""GHES is supported by passing GITHUB_API_URL (trailing slash stripped)."""
|
|
captured: dict[str, object] = {}
|
|
monkeypatch.setenv("GITHUB_API_URL", "https://ghe.example.com/api/v3/")
|
|
|
|
def _fake_github(**kwargs: object) -> MagicMock:
|
|
captured.update(kwargs)
|
|
client = MagicMock()
|
|
client.get_repo.return_value.get_issue.return_value.get_comments.return_value = [
|
|
_comment(_HEAD)
|
|
]
|
|
return client
|
|
|
|
monkeypatch.setattr(gate, "Github", _fake_github)
|
|
assert decide_skip(7, _HEAD, _REPO, _TOKEN).skip is True
|
|
assert captured["base_url"] == "https://ghe.example.com/api/v3"
|