feat(cli,display): add --health check and repo description column
Add --health option to verify Gitea connectivity and display version. Add Description column (truncated at 40 chars) with --no-desc to hide it. Add get_version() method to GiteaClient. fixes #14 fixes #15 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,9 @@ class TestMainNominal:
|
||||
|
||||
mock_client_cls.assert_called_once_with("http://localhost:3000", "test-token-123")
|
||||
mock_collect.assert_called_once_with(mock_client, include=None, exclude=None)
|
||||
mock_render.assert_called_once_with(mock_collect.return_value, sort_key="name")
|
||||
mock_render.assert_called_once_with(
|
||||
mock_collect.return_value, sort_key="name", show_description=True
|
||||
)
|
||||
|
||||
@patch("gitea_dashboard.cli.render_dashboard")
|
||||
@patch("gitea_dashboard.cli.collect_all")
|
||||
@@ -259,6 +261,96 @@ class TestParseArgsFormat:
|
||||
parse_args(["--format", "invalid"])
|
||||
|
||||
|
||||
class TestParseArgsHealth:
|
||||
"""Test --health argument parsing."""
|
||||
|
||||
def test_parse_args_health(self):
|
||||
"""--health sets health=True."""
|
||||
from gitea_dashboard.cli import parse_args
|
||||
|
||||
args = parse_args(["--health"])
|
||||
assert args.health is True
|
||||
|
||||
def test_parse_args_no_health_default(self):
|
||||
"""Without --health, health is False."""
|
||||
from gitea_dashboard.cli import parse_args
|
||||
|
||||
args = parse_args([])
|
||||
assert args.health is False
|
||||
|
||||
|
||||
class TestParseArgsNoDesc:
|
||||
"""Test --no-desc argument parsing."""
|
||||
|
||||
def test_parse_args_no_desc(self):
|
||||
"""--no-desc sets no_desc=True."""
|
||||
from gitea_dashboard.cli import parse_args
|
||||
|
||||
args = parse_args(["--no-desc"])
|
||||
assert args.no_desc is True
|
||||
|
||||
def test_parse_args_no_desc_default(self):
|
||||
"""Without --no-desc, no_desc is False."""
|
||||
from gitea_dashboard.cli import parse_args
|
||||
|
||||
args = parse_args([])
|
||||
assert args.no_desc is False
|
||||
|
||||
|
||||
class TestMainHealth:
|
||||
"""Test main() with --health."""
|
||||
|
||||
@patch("gitea_dashboard.cli.GiteaClient")
|
||||
def test_main_health_success(self, mock_client_cls, capsys):
|
||||
"""--health displays version and repo count, exits normally."""
|
||||
env = {"GITEA_TOKEN": "test-token"}
|
||||
mock_client = MagicMock()
|
||||
mock_client_cls.return_value = mock_client
|
||||
mock_client.get_version.return_value = {"version": "1.21.0"}
|
||||
mock_client.get_repos.return_value = [{"id": 1}, {"id": 2}, {"id": 3}]
|
||||
|
||||
with patch.dict("os.environ", env, clear=True):
|
||||
main(["--health"])
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "Gitea v1.21.0" in captured.err
|
||||
assert "3 repos accessibles" in captured.err
|
||||
|
||||
@patch("gitea_dashboard.cli.GiteaClient")
|
||||
def test_main_health_connection_error(self, mock_client_cls):
|
||||
"""--health with connection error exits with code 1."""
|
||||
env = {"GITEA_TOKEN": "test-token"}
|
||||
mock_client = MagicMock()
|
||||
mock_client_cls.return_value = mock_client
|
||||
mock_client.get_version.side_effect = requests.ConnectionError("refused")
|
||||
|
||||
with patch.dict("os.environ", env, clear=True):
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
main(["--health"])
|
||||
|
||||
assert exc_info.value.code == 1
|
||||
|
||||
|
||||
class TestMainNoDesc:
|
||||
"""Test main() with --no-desc."""
|
||||
|
||||
@patch("gitea_dashboard.cli.render_dashboard")
|
||||
@patch("gitea_dashboard.cli.collect_all")
|
||||
@patch("gitea_dashboard.cli.GiteaClient")
|
||||
def test_main_passes_no_desc_to_render(self, mock_client_cls, mock_collect, mock_render):
|
||||
"""--no-desc passes show_description=False to render_dashboard."""
|
||||
env = {"GITEA_TOKEN": "test-token"}
|
||||
mock_client_cls.return_value = MagicMock()
|
||||
mock_collect.return_value = []
|
||||
|
||||
with patch.dict("os.environ", env, clear=True):
|
||||
main(["--no-desc"])
|
||||
|
||||
mock_render.assert_called_once_with(
|
||||
mock_collect.return_value, sort_key="name", show_description=False
|
||||
)
|
||||
|
||||
|
||||
class TestMainFormatJson:
|
||||
"""Test main() with --format json."""
|
||||
|
||||
|
||||
@@ -287,6 +287,30 @@ class TestGetWithRetry429:
|
||||
assert mock_sleep.call_count == 2
|
||||
|
||||
|
||||
class TestGetVersion:
|
||||
"""Test get_version method."""
|
||||
|
||||
def test_get_version_success(self):
|
||||
"""Returns version dict on success."""
|
||||
client = GiteaClient("http://gitea.local:3000", "tok")
|
||||
mock_resp = MagicMock()
|
||||
mock_resp.status_code = 200
|
||||
mock_resp.json.return_value = {"version": "1.21.0"}
|
||||
|
||||
with patch.object(client.session, "get", return_value=mock_resp):
|
||||
result = client.get_version()
|
||||
|
||||
assert result == {"version": "1.21.0"}
|
||||
|
||||
def test_get_version_connection_error(self):
|
||||
"""ConnectionError propagates to caller."""
|
||||
client = GiteaClient("http://gitea.local:3000", "tok")
|
||||
|
||||
with patch.object(client.session, "get", side_effect=requests.ConnectionError("refused")):
|
||||
with pytest.raises(requests.ConnectionError):
|
||||
client.get_version()
|
||||
|
||||
|
||||
class TestGetPaginatedEdgeCases:
|
||||
"""Test edge cases for API responses."""
|
||||
|
||||
|
||||
@@ -230,6 +230,66 @@ class TestRenderDashboardEmpty:
|
||||
assert "Aucun repo" in output
|
||||
|
||||
|
||||
class TestDescriptionColumn:
|
||||
"""Test Description column in dashboard table."""
|
||||
|
||||
def test_description_column_displayed(self):
|
||||
"""Table contains a Description column by default."""
|
||||
console, buf = _make_console()
|
||||
repos = [_make_repo(name="test", description="My project")]
|
||||
|
||||
render_dashboard(repos, console=console)
|
||||
output = buf.getvalue()
|
||||
|
||||
assert "Description" in output
|
||||
assert "My project" in output
|
||||
|
||||
def test_description_truncated_at_40(self):
|
||||
"""Description longer than 40 chars is truncated with '...'."""
|
||||
console, buf = _make_console()
|
||||
long_desc = "A" * 60
|
||||
repos = [_make_repo(name="test", description=long_desc)]
|
||||
|
||||
render_dashboard(repos, console=console)
|
||||
output = buf.getvalue()
|
||||
|
||||
# Should contain first 40 chars + "..."
|
||||
assert "A" * 40 + "..." in output
|
||||
# Should NOT contain the full 60-char string
|
||||
assert "A" * 60 not in output
|
||||
|
||||
def test_description_short_not_truncated(self):
|
||||
"""Description of 20 chars is displayed as-is."""
|
||||
console, buf = _make_console()
|
||||
repos = [_make_repo(name="test", description="Short description")]
|
||||
|
||||
render_dashboard(repos, console=console)
|
||||
output = buf.getvalue()
|
||||
|
||||
assert "Short description" in output
|
||||
|
||||
def test_description_empty(self):
|
||||
"""Empty description renders without crash."""
|
||||
console, buf = _make_console()
|
||||
repos = [_make_repo(name="test", description="")]
|
||||
|
||||
render_dashboard(repos, console=console)
|
||||
output = buf.getvalue()
|
||||
|
||||
assert "test" in output
|
||||
|
||||
def test_no_description_flag(self):
|
||||
"""show_description=False hides the Description column."""
|
||||
console, buf = _make_console()
|
||||
repos = [_make_repo(name="test", description="My project")]
|
||||
|
||||
render_dashboard(repos, console=console, show_description=False)
|
||||
output = buf.getvalue()
|
||||
|
||||
assert "Description" not in output
|
||||
assert "test" in output
|
||||
|
||||
|
||||
class TestRenderDashboardEdgeCases:
|
||||
"""Test edge cases for dashboard rendering."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user