Skip to content

Commit

Permalink
additional error handling for trusted activities (#371)
Browse files Browse the repository at this point in the history
* additional error handling for trusted activities

* documentation

* running build

* re-running build

* re-running build
  • Loading branch information
tora-kozic committed Sep 20, 2021
1 parent 760adf8 commit 9925d24
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 18 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta
- `Py42TrustedActivityInvalidChangeError`
- `Py42TrustedActivityConflictError`
- `Py42TrustedActivityInvalidCharacterError`
- `Py42TrustedActivityIdNotFound`

- New custom `HTTP 409 error` wrapper class `Py42ConflictError`

### Fixed

Expand Down
18 changes: 16 additions & 2 deletions src/py42/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ class Py42NotFoundError(Py42HTTPError):
"""A wrapper to represent an HTTP 404 error."""


class Py42ConflictError(Py42HTTPError):
"""A wrapper to represent an HTTP 409 error."""


class Py42InternalServerError(Py42HTTPError):
"""A wrapper to represent an HTTP 500 error."""

Expand Down Expand Up @@ -345,7 +349,7 @@ def __init__(self, exception):
super().__init__(exception, msg)


class Py42TrustedActivityConflictError(Py42HTTPError):
class Py42TrustedActivityConflictError(Py42ConflictError):
"""An error raised when theres a conflict with a trusted activity domain URL."""

def __init__(self, exception, value):
Expand All @@ -357,13 +361,21 @@ def __init__(self, exception, value):


class Py42TrustedActivityInvalidCharacterError(Py42BadRequestError):
"""An error raised when an invalid change is being made to a trusted activity."""
"""An error raised when an invalid character is in a trusted activity value."""

def __init__(self, exception):
msg = "Invalid character in domain or slack workspace name"
super().__init__(exception, msg)


class Py42TrustedActivityIdNotFound(Py42NotFoundError):
"""An exception raised when the trusted activity ID does not exist."""

def __init__(self, exception, resource_id):
message = f"Resource ID '{resource_id}' not found."
super(Py42NotFoundError, self).__init__(exception, message)


def raise_py42_error(raised_error):
"""Raises the appropriate :class:`py42.exceptions.Py42HttpError` based on the given
HTTPError's response status code.
Expand All @@ -376,6 +388,8 @@ def raise_py42_error(raised_error):
raise Py42ForbiddenError(raised_error)
elif raised_error.response.status_code == 404:
raise Py42NotFoundError(raised_error)
elif raised_error.response.status_code == 409:
raise Py42ConflictError(raised_error)
elif raised_error.response.status_code == 429:
raise Py42TooManyRequestsError(raised_error)
elif 500 <= raised_error.response.status_code < 600:
Expand Down
30 changes: 17 additions & 13 deletions src/py42/services/trustedactivities.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from py42 import settings
from py42.exceptions import Py42BadRequestError
from py42.exceptions import Py42ConflictError
from py42.exceptions import Py42DescriptionLimitExceededError
from py42.exceptions import Py42HTTPError
from py42.exceptions import Py42NotFoundError
from py42.exceptions import Py42TrustedActivityConflictError
from py42.exceptions import Py42TrustedActivityIdNotFound
from py42.exceptions import Py42TrustedActivityInvalidCharacterError
from py42.services import BaseService
from py42.services.util import get_all_pages
Expand Down Expand Up @@ -41,12 +43,15 @@ def create(self, type, value, description=None):
return self._connection.post(self._uri_prefix, json=data)
except Py42BadRequestError as err:
_handle_common_invalid_case_parameters_errors(err)
except Py42HTTPError as err:
_handle_common_client_errors(err, value)
except Py42ConflictError as err:
raise Py42TrustedActivityConflictError(err, value)

def get(self, id):
uri = f"{self._uri_prefix}/{id}"
return self._connection.get(uri)
try:
return self._connection.get(uri)
except Py42NotFoundError as err:
raise Py42TrustedActivityIdNotFound(err, id)

def update(self, id, value=None, description=None):
uri = f"{self._uri_prefix}/{id}"
Expand All @@ -64,12 +69,17 @@ def update(self, id, value=None, description=None):
return self._connection.put(uri, json=data)
except Py42BadRequestError as err:
_handle_common_invalid_case_parameters_errors(err)
except Py42HTTPError as err:
_handle_common_client_errors(err, value)
except Py42NotFoundError as err:
raise Py42TrustedActivityIdNotFound(err, id)
except Py42ConflictError as err:
raise Py42TrustedActivityConflictError(err, value)

def delete(self, id):
uri = f"{self._uri_prefix}/{id}"
return self._connection.delete(uri)
try:
return self._connection.delete(uri)
except Py42NotFoundError as err:
raise Py42TrustedActivityIdNotFound(err, id)


def _handle_common_invalid_case_parameters_errors(base_err):
Expand All @@ -78,9 +88,3 @@ def _handle_common_invalid_case_parameters_errors(base_err):
elif "INVALID_CHARACTERS_IN_VALUE" in base_err.response.text:
raise Py42TrustedActivityInvalidCharacterError(base_err)
raise


def _handle_common_client_errors(base_err, value):
if "CONFLICT" in base_err.response.text:
raise Py42TrustedActivityConflictError(base_err, value)
raise
47 changes: 45 additions & 2 deletions tests/services/test_trustedactivities.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

import py42.settings
from py42.exceptions import Py42BadRequestError
from py42.exceptions import Py42ConflictError
from py42.exceptions import Py42DescriptionLimitExceededError
from py42.exceptions import Py42HTTPError
from py42.exceptions import Py42NotFoundError
from py42.exceptions import Py42TrustedActivityConflictError
from py42.exceptions import Py42TrustedActivityIdNotFound
from py42.exceptions import Py42TrustedActivityInvalidCharacterError
from py42.services.trustedactivities import TrustedActivitiesService

Expand All @@ -24,6 +26,7 @@
"value": "domain.com",
}

_INVALID_RESOURCE_ID = 0
_TEST_TRUSTED_ACTIVITY_RESOURCE_ID = 123
_BASE_URI = "/api/v1/trusted-activities"
_DESCRIPTION_TOO_LONG_ERROR_MSG = (
Expand All @@ -34,6 +37,7 @@
_INVALID_CHARACTER_ERROR_MSG = (
'{"problem":"INVALID_CHARACTERS_IN_VALUE","description":null}'
)
_RESOURCE_ID_NOT_FOUND_ERROR_MSG = ""


@pytest.fixture
Expand Down Expand Up @@ -62,7 +66,7 @@ def mock_long_description_error(mocker):

@pytest.fixture
def mock_conflict_error(mocker):
return create_mock_error(Py42HTTPError, mocker, _CONFLICT_ERROR_MSG)
return create_mock_error(Py42ConflictError, mocker, _CONFLICT_ERROR_MSG)


@pytest.fixture
Expand All @@ -75,6 +79,13 @@ def mock_invalid_character_error(mocker):
return create_mock_error(Py42BadRequestError, mocker, _INVALID_CHARACTER_ERROR_MSG)


@pytest.fixture
def mock_resource_id_not_found_error(mocker):
return create_mock_error(
Py42NotFoundError, mocker, _RESOURCE_ID_NOT_FOUND_ERROR_MSG
)


class TestTrustedActivitiesService:
def test_create_called_with_expected_url_and_params(self, mock_connection):
trusted_activities_service = TrustedActivitiesService(mock_connection)
Expand Down Expand Up @@ -197,6 +208,16 @@ def test_get_called_with_expected_url_and_params(self, mock_connection):
assert mock_connection.get.call_args[0][0] == expected_url
mock_connection.get.assert_called_once_with(expected_url)

def test_get_when_fails_with_resource_id_not_found_error_raises_custom_exception(
self, mock_connection, mock_resource_id_not_found_error
):
trusted_activities_service = TrustedActivitiesService(mock_connection)
mock_connection.get.side_effect = mock_resource_id_not_found_error
with pytest.raises(Py42TrustedActivityIdNotFound) as err:
trusted_activities_service.get(_INVALID_RESOURCE_ID)

assert err.value.args[0] == f"Resource ID '{_INVALID_RESOURCE_ID}' not found."

def test_update_called_with_expected_url_and_params(
self, mock_connection, mock_get_response
):
Expand Down Expand Up @@ -276,9 +297,31 @@ def test_update_when_fails_with_invalid_character_error_raises_custom_exception(
err.value.args[0] == "Invalid character in domain or slack workspace name"
)

def test_update_when_fails_with_resource_id_not_found_error_raises_custom_exception(
self, mock_connection, mock_resource_id_not_found_error
):
trusted_activities_service = TrustedActivitiesService(mock_connection)
mock_connection.put.side_effect = mock_resource_id_not_found_error
with pytest.raises(Py42TrustedActivityIdNotFound) as err:
trusted_activities_service.update(
_INVALID_RESOURCE_ID, description="This id should not be found."
)

assert err.value.args[0] == f"Resource ID '{_INVALID_RESOURCE_ID}' not found."

def test_delete_called_with_expected_url_and_params(self, mock_connection):
trusted_activities_service = TrustedActivitiesService(mock_connection)
trusted_activities_service.delete(_TEST_TRUSTED_ACTIVITY_RESOURCE_ID)
expected_url = f"{_BASE_URI}/{_TEST_TRUSTED_ACTIVITY_RESOURCE_ID}"
assert mock_connection.delete.call_args[0][0] == expected_url
mock_connection.delete.assert_called_once_with(expected_url)

def test_delete_when_fails_with_resource_id_not_found_error_raises_custom_exception(
self, mock_connection, mock_resource_id_not_found_error
):
trusted_activities_service = TrustedActivitiesService(mock_connection)
mock_connection.delete.side_effect = mock_resource_id_not_found_error
with pytest.raises(Py42TrustedActivityIdNotFound) as err:
trusted_activities_service.delete(_INVALID_RESOURCE_ID)

assert err.value.args[0] == f"Resource ID '{_INVALID_RESOURCE_ID}' not found."
8 changes: 7 additions & 1 deletion tests/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from tests.conftest import REQUEST_EXCEPTION_MESSAGE

from py42.exceptions import Py42BadRequestError
from py42.exceptions import Py42ConflictError
from py42.exceptions import Py42ForbiddenError
from py42.exceptions import Py42HTTPError
from py42.exceptions import Py42InternalServerError
Expand Down Expand Up @@ -33,6 +34,11 @@ def test_raise_py42_error_raises_not_found_error(self, error_response):
with pytest.raises(Py42NotFoundError):
raise_py42_error(error_response)

def test_raise_py42_error_raises_conflict_error(self, error_response):
error_response.response.status_code = 409
with pytest.raises(Py42ConflictError):
raise_py42_error(error_response)

def test_raise_py42_error_raises_internal_server_error(self, error_response):
error_response.response.status_code = 500
with pytest.raises(Py42InternalServerError):
Expand All @@ -48,7 +54,7 @@ def test_raise_py42_error_raises_too_many_requests_error(self, error_response):
with pytest.raises(Py42TooManyRequestsError):
raise_py42_error(error_response)

@pytest.mark.parametrize("status_code", [400, 401, 403, 404, 429, 500, 600])
@pytest.mark.parametrize("status_code", [400, 401, 403, 404, 409, 429, 500, 600])
def test_raise_py42_http_error_has_correct_response_type(
self, error_response, status_code
):
Expand Down

0 comments on commit 9925d24

Please sign in to comment.