test(v1.4.0-p2): add failing tests for milestones and columns

RED phase: 5 tests in test_collector.py (collect_milestones),
10 tests in test_display.py (render_milestones, parse_columns),
2 tests in test_exporter.py (milestones JSON), 7 tests in
test_cli.py (--milestones, --columns).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
sylvain
2026-03-13 03:46:09 +01:00
parent fdd806abcd
commit ebf72c9a56
4 changed files with 405 additions and 1 deletions

View File

@@ -389,3 +389,94 @@ class TestMainFormatJson:
captured = capsys.readouterr()
parsed = json.loads(captured.out)
assert isinstance(parsed, list)
class TestParseArgsMilestones:
"""Test --milestones argument parsing."""
def test_parse_args_milestones(self):
"""--milestones sets milestones=True."""
from gitea_dashboard.cli import parse_args
args = parse_args(["--milestones"])
assert args.milestones is True
def test_parse_args_milestones_default(self):
"""Without --milestones, milestones is False."""
from gitea_dashboard.cli import parse_args
args = parse_args([])
assert args.milestones is False
class TestParseArgsColumns:
"""Test --columns argument parsing."""
def test_parse_args_columns(self):
"""--columns name,issues sets columns='name,issues'."""
from gitea_dashboard.cli import parse_args
args = parse_args(["--columns", "name,issues"])
assert args.columns == "name,issues"
def test_parse_args_columns_default(self):
"""Without --columns, columns is None."""
from gitea_dashboard.cli import parse_args
args = parse_args([])
assert args.columns is None
class TestMainMilestonesMode:
"""Test main() with --milestones."""
@patch("gitea_dashboard.cli.render_milestones")
@patch("gitea_dashboard.cli.collect_milestones")
@patch("gitea_dashboard.cli.GiteaClient")
def test_main_milestones_mode(self, mock_client_cls, mock_collect_ms, mock_render_ms):
"""--milestones routes to collect_milestones + render_milestones."""
env = {"GITEA_TOKEN": "test-tok"}
mock_client_cls.return_value = MagicMock()
mock_collect_ms.return_value = []
with patch.dict("os.environ", env, clear=True):
main(["--milestones"])
mock_collect_ms.assert_called_once()
mock_render_ms.assert_called_once()
class TestMainColumnsHelp:
"""Test main() with --columns help."""
@patch("gitea_dashboard.cli.GiteaClient")
def test_main_columns_help(self, mock_client_cls, capsys):
"""--columns help displays available columns and exits."""
env = {"GITEA_TOKEN": "test-tok"}
mock_client_cls.return_value = MagicMock()
with patch.dict("os.environ", env, clear=True):
main(["--columns", "help"])
captured = capsys.readouterr()
# Should list column names
assert "name" in captured.out or "name" in captured.err
@patch("gitea_dashboard.cli.render_dashboard")
@patch("gitea_dashboard.cli.collect_all")
@patch("gitea_dashboard.cli.GiteaClient")
def test_main_no_desc_and_columns_compat(self, mock_client_cls, mock_collect, mock_render):
"""--no-desc and --columns -commit both apply cumulatively."""
env = {"GITEA_TOKEN": "test-tok"}
mock_client_cls.return_value = MagicMock()
mock_collect.return_value = []
with patch.dict("os.environ", env, clear=True):
main(["--no-desc", "--columns", "-commit"])
# render_dashboard should be called with columns excluding both description and commit
call_kwargs = mock_render.call_args
columns = call_kwargs[1].get("columns") if call_kwargs[1] else None
if columns is not None:
assert "description" not in columns
assert "commit" not in columns

View File

@@ -2,7 +2,10 @@
from unittest.mock import MagicMock
from gitea_dashboard.collector import RepoData, collect_all
from gitea_dashboard.collector import (
RepoData,
collect_all,
)
def _make_repo(
@@ -311,3 +314,158 @@ class TestCollectAllFiltering:
result_empty = collect_all(client, include=[])
assert [r.name for r in result_empty] == [r.name for r in result_none]
class TestCollectMilestones:
"""Test collect_milestones function."""
def _setup_client(self, repo_names, milestones_by_repo=None):
"""Create a mock client with repos and milestones."""
client = MagicMock()
client.get_repos.return_value = [
_make_repo(name=n, full_name=f"admin/{n}") for n in repo_names
]
if milestones_by_repo is None:
milestones_by_repo = {}
def get_milestones_side_effect(owner, repo, state="all"):
return milestones_by_repo.get(repo, [])
client.get_milestones.side_effect = get_milestones_side_effect
return client
def test_collect_milestones_basic(self):
"""2 repos with milestones returns flat list of MilestoneData."""
from gitea_dashboard.collector import MilestoneData, collect_milestones
client = self._setup_client(
["repo-a", "repo-b"],
{
"repo-a": [
{
"title": "v1.0",
"open_issues": 2,
"closed_issues": 3,
"due_on": None,
"state": "open",
},
],
"repo-b": [
{
"title": "v2.0",
"open_issues": 0,
"closed_issues": 5,
"due_on": "2026-04-01T00:00:00Z",
"state": "closed",
},
],
},
)
result = collect_milestones(client)
assert len(result) == 2
assert all(isinstance(m, MilestoneData) for m in result)
assert result[0].repo_name == "repo-a"
assert result[0].title == "v1.0"
assert result[1].repo_name == "repo-b"
def test_collect_milestones_empty_repo(self):
"""Repo without milestones produces no entries."""
from gitea_dashboard.collector import collect_milestones
client = self._setup_client(["empty-repo"], {"empty-repo": []})
result = collect_milestones(client)
assert result == []
def test_collect_milestones_progress_calculation(self):
"""3 open + 7 closed = progress_pct 70."""
from gitea_dashboard.collector import collect_milestones
client = self._setup_client(
["repo"],
{
"repo": [
{
"title": "v1.0",
"open_issues": 3,
"closed_issues": 7,
"due_on": None,
"state": "open",
},
],
},
)
result = collect_milestones(client)
assert result[0].progress_pct == 70
def test_collect_milestones_with_include_filter(self):
"""Include filter is respected."""
from gitea_dashboard.collector import collect_milestones
client = self._setup_client(
["gitea-dashboard", "infra"],
{
"gitea-dashboard": [
{
"title": "v1.0",
"open_issues": 1,
"closed_issues": 1,
"due_on": None,
"state": "open",
},
],
"infra": [
{
"title": "v2.0",
"open_issues": 0,
"closed_issues": 5,
"due_on": None,
"state": "closed",
},
],
},
)
result = collect_milestones(client, include=["dashboard"])
assert len(result) == 1
assert result[0].repo_name == "gitea-dashboard"
def test_collect_milestones_with_exclude_filter(self):
"""Exclude filter is respected."""
from gitea_dashboard.collector import collect_milestones
client = self._setup_client(
["gitea-dashboard", "old-fork"],
{
"gitea-dashboard": [
{
"title": "v1.0",
"open_issues": 1,
"closed_issues": 1,
"due_on": None,
"state": "open",
},
],
"old-fork": [
{
"title": "v2.0",
"open_issues": 0,
"closed_issues": 5,
"due_on": None,
"state": "closed",
},
],
},
)
result = collect_milestones(client, exclude=["fork"])
assert len(result) == 1
assert result[0].repo_name == "gitea-dashboard"

View File

@@ -401,3 +401,130 @@ class TestSortRepos:
]
result = sort_repos(repos, "activity")
assert [r.name for r in result] == ["recent", "old-commit", "inactive"]
class TestRenderMilestones:
"""Test the dedicated milestones table rendering."""
def test_render_milestones_basic(self):
"""Milestones table displays expected columns."""
from gitea_dashboard.collector import MilestoneData
from gitea_dashboard.display import render_milestones
console, buf = _make_console()
milestones = [
MilestoneData(
repo_name="my-repo",
title="v1.0",
open_issues=2,
closed_issues=8,
progress_pct=80,
due_on="2026-04-01T00:00:00Z",
state="open",
),
]
render_milestones(milestones, console=console)
output = buf.getvalue()
assert "my-repo" in output
assert "v1.0" in output
assert "80" in output
def test_render_milestones_empty(self):
"""Empty list shows informative message."""
from gitea_dashboard.display import render_milestones
console, buf = _make_console()
render_milestones([], console=console)
output = buf.getvalue()
assert "Aucune milestone" in output
def test_render_milestones_progress_colors(self):
"""Progress coloring: green > 80%, yellow 50-80%, red < 50%."""
from gitea_dashboard.collector import MilestoneData
from gitea_dashboard.display import render_milestones
console, buf = _make_console()
milestones = [
MilestoneData("repo", "high", 1, 9, 90, None, "open"),
MilestoneData("repo", "mid", 3, 3, 50, None, "open"),
MilestoneData("repo", "low", 8, 2, 20, None, "open"),
]
render_milestones(milestones, console=console)
output = buf.getvalue()
# All three should appear without crash
assert "high" in output
assert "mid" in output
assert "low" in output
class TestParseColumns:
"""Test parse_columns function."""
def test_parse_columns_all_default(self):
"""None returns all columns."""
from gitea_dashboard.display import AVAILABLE_COLUMNS, parse_columns
result = parse_columns(None)
assert result == list(AVAILABLE_COLUMNS.keys())
def test_parse_columns_inclusion(self):
"""'name,issues' returns only those columns."""
from gitea_dashboard.display import parse_columns
result = parse_columns("name,issues")
assert result == ["name", "issues"]
def test_parse_columns_exclusion(self):
"""'-description,-commit' returns all except those."""
from gitea_dashboard.display import AVAILABLE_COLUMNS, parse_columns
result = parse_columns("-description,-commit")
assert "description" not in result
assert "commit" not in result
assert len(result) == len(AVAILABLE_COLUMNS) - 2
def test_parse_columns_unknown_raises(self):
"""Unknown column raises ValueError."""
from gitea_dashboard.display import parse_columns
with pytest.raises(ValueError, match="unknown"):
parse_columns("unknown")
def test_parse_columns_help(self):
"""'help' returns sentinel list."""
from gitea_dashboard.display import parse_columns
result = parse_columns("help")
assert result == ["__help__"]
def test_parse_columns_no_desc_compat(self):
"""no_desc=True excludes description column."""
from gitea_dashboard.display import parse_columns
result = parse_columns(None, no_desc=True)
assert "description" not in result
def test_render_dashboard_with_columns(self):
"""Only specified columns appear in the output."""
from gitea_dashboard.display import render_dashboard
console, buf = _make_console()
repos = [_make_repo(name="test", open_issues=5)]
render_dashboard(repos, console=console, columns=["name", "issues"])
output = buf.getvalue()
assert "test" in output
assert "Description" not in output
assert "Release" not in output

View File

@@ -138,3 +138,31 @@ class TestExportJson:
"""Empty repo list produces '[]'."""
output = export_json([])
assert json.loads(output) == []
class TestExportMilestonesJson:
"""Test milestones JSON export."""
def test_export_milestones_json_basic(self):
"""MilestoneData list produces valid JSON."""
from gitea_dashboard.collector import MilestoneData
from gitea_dashboard.exporter import export_milestones_json
milestones = [
MilestoneData("repo", "v1.0", 2, 8, 80, "2026-04-01T00:00:00Z", "open"),
]
output = export_milestones_json(milestones)
parsed = json.loads(output)
assert len(parsed) == 1
assert parsed[0]["repo_name"] == "repo"
assert parsed[0]["title"] == "v1.0"
assert parsed[0]["progress_pct"] == 80
def test_export_milestones_json_empty(self):
"""Empty milestone list produces '[]'."""
from gitea_dashboard.exporter import export_milestones_json
output = export_milestones_json([])
assert json.loads(output) == []