diff --git a/tests/test_errors.py b/tests/test_errors.py index b491825..efa6789 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -1,8 +1,11 @@ # -*- coding: utf-8 -*- """unit tests to define behavior of custom exception types""" +from unittest.mock import MagicMock + from trakt.errors import (BadRequestException, ConflictException, ForbiddenException, NotFoundException, - OAuthException, ProcessException, RateLimitException, + OAuthException, OAuthRefreshException, + ProcessException, RateLimitException, TraktException, TraktInternalException, TraktUnavailable) @@ -74,3 +77,51 @@ def test_503_exception(): assert texc.http_code == 503 assert texc.message == 'Trakt Unavailable - server overloaded' assert str(texc) == texc.message + + +def test_oauth_refresh_exception_default_str(): + texc = OAuthRefreshException() + assert str(texc) == 'Unauthorized - OAuth token refresh failed' + + +def test_oauth_refresh_exception_from_response_json(): + response = MagicMock() + response.json.return_value = {'error': 'invalid_grant', 'error_description': 'Token has expired'} + texc = OAuthRefreshException(response=response) + assert texc.error == 'invalid_grant' + assert texc.error_description == 'Token has expired' + assert str(texc) == 'Unauthorized - OAuth token refresh failed: invalid_grant - Token has expired' + + +def test_oauth_refresh_exception_explicit_args_override_response(): + response = MagicMock() + response.json.return_value = {'error': 'from_response', 'error_description': 'from_response_desc'} + texc = OAuthRefreshException(response=response, error='explicit_error', error_description='explicit_desc') + assert texc.error == 'explicit_error' + assert texc.error_description == 'explicit_desc' + + +def test_oauth_refresh_exception_no_response(): + texc = OAuthRefreshException(response=None) + assert texc.error is None + assert texc.error_description is None + assert str(texc) == 'Unauthorized - OAuth token refresh failed' + + +def test_oauth_refresh_exception_json_raises(): + response = MagicMock() + response.json.side_effect = ValueError('not json') + texc = OAuthRefreshException(response=response) + assert texc.error is None + assert texc.error_description is None + + +def test_oauth_refresh_exception_error_only_str(): + texc = OAuthRefreshException(error='invalid_client') + assert str(texc) == 'Unauthorized - OAuth token refresh failed: invalid_client' + + +def test_oauth_refresh_exception_cause(): + cause = RuntimeError('original') + texc = OAuthRefreshException(cause=cause) + assert texc.cause is cause diff --git a/trakt/errors.py b/trakt/errors.py index 190271c..956ce9f 100644 --- a/trakt/errors.py +++ b/trakt/errors.py @@ -69,17 +69,43 @@ class OAuthException(TraktException): class OAuthRefreshException(OAuthException): - def __init__(self, response=None): + """Raised when an OAuth access token could not be refreshed.""" + + message = 'Unauthorized - OAuth token refresh failed' + + def __init__(self, response=None, error=None, error_description=None, cause=None): super().__init__(response) - self.data = self.response.json() + self.cause = cause + data = self._load_data() + self._error = error if error is not None else data.get("error") + self._error_description = error_description if error_description is not None else data.get("error_description") + + def _load_data(self): + from json import JSONDecodeError + if self.response is None: + return {} + try: + data = self.response.json() + except (ValueError, JSONDecodeError, AttributeError): + return {} + if not isinstance(data, dict): + return {} + return data @property def error(self): - return self.data["error"] + return self._error @property def error_description(self): - return self.data["error_description"] + return self._error_description + + def __str__(self): + if self._error and self._error_description: + return f'{self.message}: {self._error} - {self._error_description}' + if self._error: + return f'{self.message}: {self._error}' + return self.message class ForbiddenException(TraktException):