Files
gitea-dashboard/tests/test_collector.py
sylvain 4c66fbe98d feat(v1.2.0): retry API, dernier commit, tri, coloration, export JSON
- client.py: _get_with_retry (max 2 retries, backoff lineaire), get_latest_commit
- collector.py: champ last_commit_date dans RepoData
- display.py: colonne "Dernier commit", _sort_repos (name/issues/release/activity),
  _colorize_milestone_due (rouge/jaune/vert selon echeance)
- cli.py: options --sort/-s et --format/-f (table/json)
- exporter.py: nouveau module, repos_to_dicts + export_json
- 88 tests (35 nouveaux), ruff clean

fixes #8, fixes #7, fixes #10, fixes #9, fixes #6

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 03:58:45 +01:00

280 lines
9.6 KiB
Python

"""Tests for data collector."""
from unittest.mock import MagicMock
from gitea_dashboard.collector import RepoData, collect_all
def _make_repo(
name="my-repo",
full_name="admin/my-repo",
description="A repo",
open_issues_count=5,
open_pr_counter=2,
fork=False,
archived=False,
mirror=False,
owner_login="admin",
):
"""Build a fake repo dict as returned by the Gitea API."""
return {
"name": name,
"full_name": full_name,
"description": description,
"open_issues_count": open_issues_count,
"open_pr_counter": open_pr_counter,
"fork": fork,
"archived": archived,
"mirror": mirror,
"owner": {"login": owner_login},
}
class TestCollectAll:
"""Test collect_all function."""
def test_basic_repo(self):
"""Collects repo data with release and milestones."""
client = MagicMock()
client.get_repos.return_value = [_make_repo()]
client.get_latest_release.return_value = {
"tag_name": "v1.0",
"published_at": "2026-01-01",
}
client.get_milestones.return_value = [
{"title": "v2.0", "open_issues": 3, "closed_issues": 2, "due_on": None},
]
result = collect_all(client)
assert len(result) == 1
repo = result[0]
assert isinstance(repo, RepoData)
assert repo.name == "my-repo"
assert repo.full_name == "admin/my-repo"
assert repo.description == "A repo"
assert repo.open_issues == 3 # 5 - 2
assert repo.is_fork is False
assert repo.is_archived is False
assert repo.is_mirror is False
assert repo.latest_release == {"tag_name": "v1.0", "published_at": "2026-01-01"}
assert len(repo.milestones) == 1
def test_repo_without_release(self):
"""Repo with no release gets None for latest_release."""
client = MagicMock()
client.get_repos.return_value = [_make_repo()]
client.get_latest_release.return_value = None
client.get_milestones.return_value = []
result = collect_all(client)
assert result[0].latest_release is None
def test_repo_without_milestones(self):
"""Repo with no milestones gets empty list."""
client = MagicMock()
client.get_repos.return_value = [_make_repo()]
client.get_latest_release.return_value = None
client.get_milestones.return_value = []
result = collect_all(client)
assert result[0].milestones == []
def test_open_issues_subtracts_prs(self):
"""open_issues = open_issues_count - open_pr_counter."""
client = MagicMock()
client.get_repos.return_value = [
_make_repo(open_issues_count=10, open_pr_counter=4),
]
client.get_latest_release.return_value = None
client.get_milestones.return_value = []
result = collect_all(client)
assert result[0].open_issues == 6
def test_multiple_repos(self):
"""Collects data for multiple repos."""
client = MagicMock()
client.get_repos.return_value = [
_make_repo(name="repo-a", full_name="admin/repo-a"),
_make_repo(name="repo-b", full_name="org/repo-b", owner_login="org"),
]
client.get_latest_release.return_value = None
client.get_milestones.return_value = []
result = collect_all(client)
assert len(result) == 2
assert result[0].name == "repo-a"
assert result[1].name == "repo-b"
# Verify correct owner/repo passed to enrichment calls
client.get_latest_release.assert_any_call("admin", "repo-a")
client.get_latest_release.assert_any_call("org", "repo-b")
def test_fork_and_archived_flags(self):
"""Fork and archived flags are propagated."""
client = MagicMock()
client.get_repos.return_value = [
_make_repo(fork=True, archived=True, mirror=True),
]
client.get_latest_release.return_value = None
client.get_milestones.return_value = []
result = collect_all(client)
assert result[0].is_fork is True
assert result[0].is_archived is True
assert result[0].is_mirror is True
class TestCollectAllLastCommit:
"""Test last_commit_date field in RepoData."""
def test_repo_data_has_last_commit_date(self):
"""RepoData includes last_commit_date field."""
repo = RepoData(
name="test",
full_name="admin/test",
description="",
open_issues=0,
is_fork=False,
is_archived=False,
is_mirror=False,
latest_release=None,
milestones=[],
last_commit_date="2026-03-10T14:30:00Z",
)
assert repo.last_commit_date == "2026-03-10T14:30:00Z"
def test_collect_all_calls_get_latest_commit(self):
"""collect_all calls get_latest_commit and fills last_commit_date."""
client = MagicMock()
client.get_repos.return_value = [_make_repo()]
client.get_latest_release.return_value = None
client.get_milestones.return_value = []
client.get_latest_commit.return_value = {
"sha": "abc123",
"created": "2026-03-10T14:30:00Z",
}
result = collect_all(client)
client.get_latest_commit.assert_called_once_with("admin", "my-repo")
assert result[0].last_commit_date == "2026-03-10T14:30:00Z"
def test_collect_all_no_commits(self):
"""Repo without commits gets last_commit_date=None."""
client = MagicMock()
client.get_repos.return_value = [_make_repo()]
client.get_latest_release.return_value = None
client.get_milestones.return_value = []
client.get_latest_commit.return_value = None
result = collect_all(client)
assert result[0].last_commit_date is None
class TestCollectAllFiltering:
"""Test collect_all filtering (include/exclude)."""
def _setup_client(self, repo_names: list[str]) -> MagicMock:
"""Create a mock client returning repos with the given names."""
client = MagicMock()
client.get_repos.return_value = [
_make_repo(name=n, full_name=f"admin/{n}") for n in repo_names
]
client.get_latest_release.return_value = None
client.get_milestones.return_value = []
return client
def test_no_filter_returns_all(self):
"""Without include/exclude, all repos are returned (backward compat)."""
client = self._setup_client(["alpha", "beta", "gamma"])
result = collect_all(client)
assert [r.name for r in result] == ["alpha", "beta", "gamma"]
def test_include_single(self):
"""Include filters repos by substring match."""
client = self._setup_client(["gitea-dashboard", "infra-core", "notes"])
result = collect_all(client, include=["dashboard"])
assert [r.name for r in result] == ["gitea-dashboard"]
def test_include_multiple(self):
"""Multiple include patterns are OR-combined."""
client = self._setup_client(["gitea-dashboard", "infra-core", "notes"])
result = collect_all(client, include=["dashboard", "infra"])
assert [r.name for r in result] == ["gitea-dashboard", "infra-core"]
def test_exclude_single(self):
"""Exclude removes repos matching the substring."""
client = self._setup_client(["gitea-dashboard", "old-fork", "notes"])
result = collect_all(client, exclude=["fork"])
assert [r.name for r in result] == ["gitea-dashboard", "notes"]
def test_include_and_exclude(self):
"""Include is applied first, then exclude."""
client = self._setup_client(["projet-web", "projet-old", "infra"])
result = collect_all(client, include=["projet"], exclude=["old"])
assert [r.name for r in result] == ["projet-web"]
def test_case_insensitive(self):
"""Filtering is case-insensitive."""
client = self._setup_client(["Gitea-Dashboard", "infra"])
result = collect_all(client, include=["dashboard"])
assert [r.name for r in result] == ["Gitea-Dashboard"]
def test_no_match_returns_empty(self):
"""Returns empty list when no repo matches include filter."""
client = self._setup_client(["alpha", "beta"])
result = collect_all(client, include=["inexistant"])
assert result == []
def test_exclude_all_returns_empty(self):
"""Returns empty list when all repos are excluded."""
client = self._setup_client(["alpha", "beta"])
result = collect_all(client, exclude=["alpha", "beta"])
assert result == []
def test_filtered_repos_have_no_api_calls(self):
"""Repos excluded by include filter must not trigger enrichment API calls."""
client = self._setup_client(["gitea-dashboard", "infra-core", "notes"])
collect_all(client, include=["dashboard"])
# Only gitea-dashboard passed the filter — enrichment calls must target it only
client.get_latest_release.assert_called_once_with("admin", "gitea-dashboard")
client.get_milestones.assert_called_once_with("admin", "gitea-dashboard")
def test_collect_all_include_empty_list(self):
"""include=[] behaves like include=None — all repos are returned.
The contract: an empty list is falsy, so `if include:` is False, meaning
no inclusion filter is applied and every repo is included before exclude.
"""
client = self._setup_client(["alpha", "beta", "gamma"])
result_none = collect_all(client)
result_empty = collect_all(client, include=[])
assert [r.name for r in result_empty] == [r.name for r in result_none]