1
0
mirror of https://github.com/home-assistant/supervisor.git synced 2026-07-07 13:55:07 +01:00
Files
supervisor/tests/store/test_repository_git.py
T
Stefan Agner ed91b18c4b tests: enable flake8-pytest-style (PT) ruff rules (#6857)
* tests: enable flake8-pytest-style (PT) ruff rules

Enable the `PT` ruff rule set and fix the resulting violations across the
test suite:

- PT006: pass parametrize argument names as tuples instead of a single
  comma-separated string.
- PT022: switch fixtures that have no teardown from `yield` to `return`
  so the lack of cleanup is obvious at a glance.
- PT011: add `match=` to broad `pytest.raises(ValueError)` blocks so the
  expected error is anchored to a specific message.
- PT012: hoist setup (patches, branching) out of `pytest.raises()`
  blocks so only the call that is expected to raise remains inside.
- PT013: replace `from pytest import X` with `import pytest` and access
  attributes via the module.
- PT015: replace `try/except` + `assert False` patterns with
  `pytest.raises(...)`.
- PT017: replace `assert` on exceptions inside `except` blocks with
  `pytest.raises(...) as exc_info` and assert on `exc_info.value`.

No behavioral changes to the tests; the full suite still passes.

* tests: address review feedback on PT ruff rule enablement

- Fix fixture return-type annotations after switching `yield` to `return`
  in tests/conftest.py: drop the `Generator[...]`/`AsyncGenerator[...]`
  wrapper for `dns_manager_service`, `supervisor_internet`, `websession`,
  and `mock_update_data` so the annotation matches what the fixture
  actually returns.
- Correct the return-type annotation of `fixture_ip6config_service` from
  `IP4ConfigService` to `IP6ConfigService`.
- Fix recurring "excepiton" typo in tests/utils/test_exception_helper.py.

* tests: verify backup cleanup on permission error

After `test_new_backup_permission_error` raises `BackupPermissionError`,
assert that no tarfile was left behind and `tmp_path` is empty. The
previous version only checked that the exception was raised, which
missed any regression where a partial tarfile would survive the failed
create.

* tests: rename DNS_GOOD_V6 to DNS_V6_UNSUPPORTED

The constant was named "good" but its tests assert that the URLs are
rejected by the DNS validator. The IPv6 URLs are well-formed but
currently rejected because IPv6 doesn't work with the Docker network
(see `dns_url` in supervisor/validate.py). Rename the constant and the
related test to make the intent obvious.
2026-05-20 22:17:54 +02:00

154 lines
4.6 KiB
Python

"""Test git repository."""
from __future__ import annotations
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch
from git import GitCommandError, InvalidGitRepositoryError, NoSuchPathError
import pytest
from supervisor.coresys import CoreSys
from supervisor.exceptions import StoreGitCloneError, StoreGitError
from supervisor.resolution.const import ContextType, IssueType, SuggestionType
from supervisor.store.git import GitRepo
REPO_URL = "https://github.com/awesome-developer/awesome-repo"
@pytest.fixture(name="clone_from")
async def fixture_clone_from():
"""Mock git clone_from."""
with patch("git.Repo.clone_from") as clone_from:
yield clone_from
@pytest.mark.parametrize("branch", [None, "dev"])
async def test_git_clone(
coresys: CoreSys, tmp_path: Path, clone_from: AsyncMock, branch: str | None
):
"""Test git clone."""
fragment = f"#{branch}" if branch else ""
repo = GitRepo(coresys, tmp_path, f"{REPO_URL}{fragment}")
await repo.clone.__wrapped__(repo)
kwargs = {"recursive": True, "depth": 1, "shallow-submodules": True}
if branch:
kwargs["branch"] = branch
clone_from.assert_called_once_with(
REPO_URL,
str(tmp_path),
**kwargs,
)
@pytest.mark.parametrize(
"git_error",
[
InvalidGitRepositoryError(),
NoSuchPathError(),
GitCommandError("clone"),
UnicodeDecodeError("decode", b"", 0, 0, ""),
],
)
async def test_git_clone_error(
coresys: CoreSys, tmp_path: Path, clone_from: AsyncMock, git_error: Exception
):
"""Test git clone error."""
repo = GitRepo(coresys, tmp_path, REPO_URL)
clone_from.side_effect = git_error
with pytest.raises(StoreGitCloneError):
await repo.clone.__wrapped__(repo)
assert len(coresys.resolution.suggestions) == 0
async def test_git_load(coresys: CoreSys, tmp_path: Path):
"""Test git load."""
repo_dir = tmp_path / "repo"
repo = GitRepo(coresys, repo_dir, REPO_URL)
repo.clone = AsyncMock()
# Test with non-existing git repo root directory
await repo.load()
assert repo.clone.call_count == 1
repo.clone.reset_mock()
# Test with existing git repo root directory, but empty
repo_dir.mkdir()
await repo.load()
assert repo.clone.call_count == 1
repo.clone.reset_mock()
# Pretend we have a repo
(repo_dir / ".git").mkdir()
with patch("git.Repo") as mock_repo:
await repo.load()
assert repo.clone.call_count == 0
assert mock_repo.call_count == 1
@pytest.mark.parametrize(
"git_errors",
[
InvalidGitRepositoryError(),
NoSuchPathError(),
GitCommandError("init"),
UnicodeDecodeError("decode", b"", 0, 0, ""),
GitCommandError("fsck"),
],
)
async def test_git_load_error(coresys: CoreSys, tmp_path: Path, git_errors: Exception):
"""Test git load error."""
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
repo = GitRepo(coresys, tmp_path, REPO_URL)
# Pretend we have a repo
(tmp_path / ".git").mkdir()
with patch("git.Repo") as mock_repo:
mock_repo.side_effect = git_errors
with pytest.raises(StoreGitError):
await repo.load()
assert len(coresys.resolution.suggestions) == 0
@pytest.mark.usefixtures("supervisor_internet")
async def test_git_pull_missing_origin_remote(coresys: CoreSys, tmp_path: Path):
"""Test git pull with missing origin remote creates reset suggestion.
This tests the scenario where a repository exists but has no 'origin' remote,
which can happen if the remote was renamed or deleted. The pull operation
should create a CORRUPT_REPOSITORY issue with EXECUTE_RESET suggestion.
Fixes: SUPERVISOR-69Z, SUPERVISOR-172C
"""
repo = GitRepo(coresys, tmp_path, REPO_URL)
# Create a mock git repo without an origin remote
mock_repo = MagicMock()
mock_repo.remotes = [] # Empty remotes list - no 'origin'
mock_repo.active_branch.name = "main"
repo.repo = mock_repo
with patch("git.Git") as mock_git:
mock_git.return_value.ls_remote = MagicMock()
with pytest.raises(StoreGitError):
await repo.pull.__wrapped__(repo)
# Verify resolution issue was created
assert len(coresys.resolution.issues) == 1
assert coresys.resolution.issues[0].type == IssueType.CORRUPT_REPOSITORY
assert coresys.resolution.issues[0].context == ContextType.STORE
# Verify reset suggestion was created
assert len(coresys.resolution.suggestions) == 1
assert coresys.resolution.suggestions[0].type == SuggestionType.EXECUTE_RESET