fix(audit): correct findings from review round 1

- client: add raise_for_status() in get_latest_release() for non-404 errors (FINDING-001)
- client: add timeout parameter (default 30s) passed to all session.get() calls (FINDING-004/SEC-002)
- cli: replace return with sys.exit(1) in all except blocks (FINDING-002)
- test_cli: remove duplicate test_exits_when_token_missing, assert GITEA_TOKEN in stderr (FINDING-006)
- test_cli: update connection error tests to expect SystemExit(1) after exit code fix
- test_cli: rework token masking test to inject token into exception message (FINDING-007)
- test_client: add test_raises_on_server_error for HTTP 500 path (FINDING-001)
- test_client: add tests for default and custom timeout values (FINDING-004)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sylvain
2026-03-10 19:00:57 +01:00
parent e05578676f
commit 01f88a0eca
4 changed files with 74 additions and 27 deletions

View File

@@ -46,17 +46,15 @@ class TestMainNominal:
class TestMainMissingToken:
"""Test main() when GITEA_TOKEN is not set."""
def test_exits_when_token_missing(self):
"""main() exits with SystemExit when GITEA_TOKEN is absent."""
def test_error_message_when_token_missing(self, capsys):
"""main() exits with code 1 and prints message mentioning GITEA_TOKEN."""
with patch.dict("os.environ", {}, clear=True):
with pytest.raises(SystemExit):
with pytest.raises(SystemExit) as exc_info:
main()
def test_error_message_when_token_missing(self, capsys):
"""main() prints a clear error message about missing token."""
with patch.dict("os.environ", {}, clear=True):
with pytest.raises(SystemExit):
main()
assert exc_info.value.code == 1
captured = capsys.readouterr()
assert "GITEA_TOKEN" in captured.err
class TestMainConnectionErrors:
@@ -65,48 +63,65 @@ class TestMainConnectionErrors:
@patch("gitea_dashboard.cli.collect_all")
@patch("gitea_dashboard.cli.GiteaClient")
def test_connection_error_handled(self, mock_client_cls, mock_collect):
"""ConnectionError is caught and does not crash."""
"""ConnectionError is caught and exits with code 1."""
env = {"GITEA_TOKEN": "test-token"}
mock_client_cls.return_value = MagicMock()
mock_collect.side_effect = requests.ConnectionError("Connection refused")
with patch.dict("os.environ", env, clear=True):
# Should not raise — error is handled gracefully
main()
with pytest.raises(SystemExit) as exc_info:
main()
assert exc_info.value.code == 1
@patch("gitea_dashboard.cli.collect_all")
@patch("gitea_dashboard.cli.GiteaClient")
def test_timeout_error_handled(self, mock_client_cls, mock_collect):
"""Timeout is caught and does not crash."""
"""Timeout is caught and exits with code 1."""
env = {"GITEA_TOKEN": "test-token"}
mock_client_cls.return_value = MagicMock()
mock_collect.side_effect = requests.Timeout("Request timed out")
with patch.dict("os.environ", env, clear=True):
main()
with pytest.raises(SystemExit) as exc_info:
main()
assert exc_info.value.code == 1
@patch("gitea_dashboard.cli.collect_all")
@patch("gitea_dashboard.cli.GiteaClient")
def test_request_exception_handled(self, mock_client_cls, mock_collect):
"""Generic RequestException is caught and does not crash."""
"""Generic RequestException is caught and exits with code 1."""
env = {"GITEA_TOKEN": "test-token"}
mock_client_cls.return_value = MagicMock()
mock_collect.side_effect = requests.RequestException("Something went wrong")
with patch.dict("os.environ", env, clear=True):
main()
with pytest.raises(SystemExit) as exc_info:
main()
assert exc_info.value.code == 1
@patch("gitea_dashboard.cli.collect_all")
@patch("gitea_dashboard.cli.GiteaClient")
def test_token_not_in_error_output(self, mock_client_cls, mock_collect, capsys):
"""Token must never appear in error messages."""
"""Token must never appear in error messages, even when present in the exception."""
env = {"GITEA_TOKEN": "super-secret-token-xyz"}
mock_client_cls.return_value = MagicMock()
mock_collect.side_effect = requests.ConnectionError("Connection refused")
# Build exception message that embeds the token value from env
# to simulate a real-world leak scenario
def make_exc(environ):
leaked = environ["GITEA_TOKEN"]
return requests.RequestException(f"HTTP Error: Authorization token {leaked} rejected")
import os as _os
with patch.dict("os.environ", env, clear=True):
main()
mock_collect.side_effect = make_exc(_os.environ)
with pytest.raises(SystemExit):
main()
captured = capsys.readouterr()
assert "super-secret-token-xyz" not in captured.out
assert "super-secret-token-xyz" not in captured.err
assert env["GITEA_TOKEN"] not in captured.out
assert env["GITEA_TOKEN"] not in captured.err