diff --git a/reviewtally/queries/get_prs.py b/reviewtally/queries/get_prs.py index 5ffeb5d..4e9692b 100644 --- a/reviewtally/queries/get_prs.py +++ b/reviewtally/queries/get_prs.py @@ -33,6 +33,8 @@ GITHUB_SEARCH_LIMIT = 1000 RATE_LIMIT_REMAINING_THRESHOLD = 10 # arbitrary threshold RATE_LIMIT_SLEEP_SECONDS = 60 # seconds to sleep if rate limit is hit +HTTP_BAD_REQUEST = 400 +HTTP_INTERNAL_SERVER_ERROR = 500 def backoff_if_ratelimited(headers: Mapping[str, str]) -> None: @@ -70,6 +72,16 @@ def _backoff_delay(attempt: int) -> None: time.sleep(delay + jitter) +def _print_4xx_error_message(response: requests.Response) -> None: + if HTTP_BAD_REQUEST <= response.status_code < HTTP_INTERNAL_SERVER_ERROR: + try: + data = response.json() + if isinstance(data, dict) and "message" in data: + print(data["message"]) # noqa: T201 + except ValueError: + pass + + def _make_pr_request_with_retry( url: str, headers: dict[str, str], @@ -99,6 +111,7 @@ def _make_pr_request_with_retry( # Handle rate limiting (existing logic) backoff_if_ratelimited(response.headers) + _print_4xx_error_message(response) try: response.raise_for_status() except requests.exceptions.HTTPError as e: diff --git a/tests/test_get_prs.py b/tests/test_get_prs.py index dcec1e0..630a681 100644 --- a/tests/test_get_prs.py +++ b/tests/test_get_prs.py @@ -3,6 +3,8 @@ from datetime import datetime, timezone from unittest.mock import Mock, patch +import requests + from reviewtally.exceptions.local_exceptions import ( PaginationError, SearchLimitReachedError, @@ -215,6 +217,102 @@ def test_fetch_uses_configured_host(self, mock_get) -> None: # noqa: ANN001 called_url = mock_get.call_args.args[0] self.assertEqual(called_url, expected_url) + @patch("requests.get") + @patch.dict(os.environ, {"GITHUB_TOKEN": "test-token"}) + @patch("builtins.print") + def test_422_response_prints_message( + self, + mock_print: Mock, + mock_get: Mock, + ) -> None: + mock_response = Mock() + mock_response.status_code = 422 + mock_response.headers = {} + mock_response.json.return_value = {"message": "Validation Failed"} + mock_response.raise_for_status.side_effect = ( + requests.exceptions.HTTPError(response=mock_response) + ) + mock_get.return_value = mock_response + + owner = "test_owner" + repo = "test_repo" + start_date = datetime(2023, 1, 1, tzinfo=timezone.utc) + end_date = datetime(2023, 1, 2, tzinfo=timezone.utc) + + with self.assertRaises(requests.exceptions.HTTPError): + get_pull_requests_between_dates( + owner, + repo, + start_date, + end_date, + ) + + mock_print.assert_called_once_with("Validation Failed") + + @patch("requests.get") + @patch.dict(os.environ, {"GITHUB_TOKEN": "test-token"}) + @patch("builtins.print") + def test_404_response_no_message( + self, + mock_print: Mock, + mock_get: Mock, + ) -> None: + mock_response = Mock() + mock_response.status_code = 404 + mock_response.headers = {} + mock_response.json.return_value = {} + mock_response.raise_for_status.side_effect = ( + requests.exceptions.HTTPError(response=mock_response) + ) + mock_get.return_value = mock_response + + owner = "test_owner" + repo = "test_repo" + start_date = datetime(2023, 1, 1, tzinfo=timezone.utc) + end_date = datetime(2023, 1, 2, tzinfo=timezone.utc) + + with self.assertRaises(requests.exceptions.HTTPError): + get_pull_requests_between_dates( + owner, + repo, + start_date, + end_date, + ) + + mock_print.assert_not_called() + + @patch("requests.get") + @patch.dict(os.environ, {"GITHUB_TOKEN": "test-token"}) + @patch("builtins.print") + def test_400_response_invalid_json( + self, + mock_print: Mock, + mock_get: Mock, + ) -> None: + mock_response = Mock() + mock_response.status_code = 400 + mock_response.headers = {} + mock_response.json.side_effect = ValueError("Invalid JSON") + mock_response.raise_for_status.side_effect = ( + requests.exceptions.HTTPError(response=mock_response) + ) + mock_get.return_value = mock_response + + owner = "test_owner" + repo = "test_repo" + start_date = datetime(2023, 1, 1, tzinfo=timezone.utc) + end_date = datetime(2023, 1, 2, tzinfo=timezone.utc) + + with self.assertRaises(requests.exceptions.HTTPError): + get_pull_requests_between_dates( + owner, + repo, + start_date, + end_date, + ) + + mock_print.assert_not_called() + if __name__ == "__main__": unittest.main()