feat(errors): provide readable Redmine API error messages
- Introduce RedmineAPIError with human-friendly messages. - Distinguish AuthError, ForbiddenError, HTTP status codes, timeouts and connection errors in client.py. - Update CLI to print the readable message instead of generic "Redmine API error: ...". - Log original exception with traceback when --debug is enabled. - Add tests for all error paths and CLI output. Closes #39
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from redmine_reporter.client import fetch_issues_with_spent_time
|
||||
from redmine_reporter.config import DEFAULT_REDMINE_VERIFY
|
||||
|
||||
@@ -168,6 +170,85 @@ def test_fetch_uses_custom_verify_path(mock_redmine_class):
|
||||
assert kwargs["requests"]["verify"] == "/tmp/redmine-ca.pem"
|
||||
|
||||
|
||||
@mock.patch.dict(os.environ, PASSWORD_ENV, clear=True)
|
||||
@mock.patch("redmine_reporter.client.Redmine")
|
||||
def test_fetch_raises_redmine_api_error_on_auth(mock_redmine_class):
|
||||
"""AuthError превращается в RedmineAPIError с понятным сообщением."""
|
||||
from redminelib.exceptions import AuthError
|
||||
|
||||
mock_redmine = mock_redmine_class.return_value
|
||||
mock_redmine.user.get.side_effect = AuthError()
|
||||
|
||||
from redmine_reporter.client import RedmineAPIError
|
||||
|
||||
with pytest.raises(RedmineAPIError, match="Authentication failed"):
|
||||
fetch_issues_with_spent_time("2026-01-01", "2026-01-31")
|
||||
|
||||
|
||||
@mock.patch.dict(os.environ, PASSWORD_ENV, clear=True)
|
||||
@mock.patch("redmine_reporter.client.Redmine")
|
||||
def test_fetch_raises_redmine_api_error_on_forbidden(mock_redmine_class):
|
||||
"""ForbiddenError превращается в RedmineAPIError с понятным сообщением."""
|
||||
from redminelib.exceptions import ForbiddenError
|
||||
|
||||
mock_redmine = mock_redmine_class.return_value
|
||||
mock_redmine.user.get.side_effect = ForbiddenError()
|
||||
|
||||
from redmine_reporter.client import RedmineAPIError
|
||||
|
||||
with pytest.raises(RedmineAPIError, match="Access denied"):
|
||||
fetch_issues_with_spent_time("2026-01-01", "2026-01-31")
|
||||
|
||||
|
||||
@mock.patch.dict(os.environ, PASSWORD_ENV, clear=True)
|
||||
@mock.patch("redmine_reporter.client.Redmine")
|
||||
def test_fetch_raises_redmine_api_error_on_timeout(mock_redmine_class):
|
||||
"""requests Timeout превращается в RedmineAPIError с понятным сообщением."""
|
||||
import requests
|
||||
|
||||
mock_redmine = mock_redmine_class.return_value
|
||||
mock_redmine.user.get.side_effect = requests.exceptions.Timeout("timeout")
|
||||
|
||||
from redmine_reporter.client import RedmineAPIError
|
||||
|
||||
with pytest.raises(RedmineAPIError, match="timed out"):
|
||||
fetch_issues_with_spent_time("2026-01-01", "2026-01-31")
|
||||
|
||||
|
||||
@mock.patch.dict(os.environ, PASSWORD_ENV, clear=True)
|
||||
@mock.patch("redmine_reporter.client.Redmine")
|
||||
def test_fetch_raises_redmine_api_error_on_connection_error(mock_redmine_class):
|
||||
"""ConnectionError превращается в RedmineAPIError с понятным сообщением."""
|
||||
import requests
|
||||
|
||||
mock_redmine = mock_redmine_class.return_value
|
||||
mock_redmine.user.get.side_effect = requests.exceptions.ConnectionError("no route")
|
||||
|
||||
from redmine_reporter.client import RedmineAPIError
|
||||
|
||||
with pytest.raises(RedmineAPIError, match="Cannot connect"):
|
||||
fetch_issues_with_spent_time("2026-01-01", "2026-01-31")
|
||||
|
||||
|
||||
@mock.patch.dict(os.environ, PASSWORD_ENV, clear=True)
|
||||
@mock.patch("redmine_reporter.client.Redmine")
|
||||
def test_fetch_raises_redmine_api_error_on_http_500(mock_redmine_class):
|
||||
"""HTTP 500 превращается в RedmineAPIError с понятным сообщением."""
|
||||
import requests
|
||||
|
||||
mock_redmine = mock_redmine_class.return_value
|
||||
response = requests.Response()
|
||||
response.status_code = 500
|
||||
mock_redmine.user.get.side_effect = requests.exceptions.HTTPError(
|
||||
"server error", response=response
|
||||
)
|
||||
|
||||
from redmine_reporter.client import RedmineAPIError
|
||||
|
||||
with pytest.raises(RedmineAPIError, match="server error"):
|
||||
fetch_issues_with_spent_time("2026-01-01", "2026-01-31")
|
||||
|
||||
|
||||
# -- #24: Таймаут и retry --
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user