Skip to content

Commit

Permalink
Include more details in the ApiConnectionException message (#316)
Browse files Browse the repository at this point in the history
* Include more details in the ApiConnectionException message

* Content might be empty

* Make changes non-breaking

* Use response object

* Check exception repr

* Run black

* Fix integration test

* Test initial response handler
  • Loading branch information
Andy-Grigg committed Feb 20, 2023
1 parent 4ec4e5b commit 8423aa4
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 41 deletions.
22 changes: 9 additions & 13 deletions src/ansys/openapi/common/_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,19 @@ class ApiConnectionException(Exception):
Attributes
----------
status_code : int
HTTP status code associated with the response.
reason_phrase : str
Description of the response provided by the server.
message : str
Content of the response provided by the server.
response : requests.Response
Response from the server.
"""

def __init__(self, status_code: int, reason_phrase: str, message: str):
self.status_code = status_code
self.reason_phrase = reason_phrase
self.message = message
super().__init__(message)
def __init__(self, response: "requests.Response"):
exception_message = f"Request url '{response.url}' failed with reason {response.status_code}: {response.reason}."
if response.text:
exception_message += f"\n{response.text}"
super().__init__(exception_message)
self.response = response

def __repr__(self) -> str:
return f"ApiConnectionException({self.status_code}, '{self.reason_phrase}','{self.message}')"
return f"ApiConnectionException({repr(self.response)})"


class AuthenticationWarning(Warning):
Expand Down
8 changes: 2 additions & 6 deletions src/ansys/openapi/common/_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ def __test_connection(self) -> bool:
if 200 <= resp.status_code < 300:
return True
else:
raise ApiConnectionException(resp.status_code, resp.reason, resp.text)
raise ApiConnectionException(resp)

def __handle_initial_response(
self, initial_response: requests.Response
Expand Down Expand Up @@ -349,11 +349,7 @@ def __handle_initial_response(
self._configured = True
return self
elif initial_response.status_code != 401:
raise ApiConnectionException(
initial_response.status_code,
initial_response.reason,
initial_response.text,
)
raise ApiConnectionException(initial_response)
else:
return None

Expand Down
32 changes: 18 additions & 14 deletions tests/test_exceptions.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
import uuid

import pytest
import requests
from requests_mock import Mocker
from requests.utils import CaseInsensitiveDict

from ansys.openapi.common import ApiException, ApiConnectionException
from ansys.openapi.common._exceptions import AuthenticationWarning


def test_api_connection_exception_repr():
status_code = 403
reason_phrase = "Forbidden"
message = "You do not have permission to access this resource"

api_connection_exception = ApiConnectionException(
status_code, reason_phrase, message
)
exception_repr = api_connection_exception.__repr__()

exception_from_repr = eval(exception_repr)
assert type(exception_from_repr) == type(api_connection_exception)
assert exception_from_repr.status_code == api_connection_exception.status_code
assert exception_from_repr.reason_phrase == api_connection_exception.reason_phrase
assert exception_from_repr.message == api_connection_exception.message
args = {
"url": "http://protected.url/path/to/resource",
"status_code": 403,
"reason": "Forbidden",
"text": "You do not have permission to access this resource",
}

with Mocker() as m:
m.get(**args)
response = requests.get(args["url"])

assert response.status_code == args["status_code"]
api_connection_exception = ApiConnectionException(response)
assert all([str(v) in str(api_connection_exception) for v in args.values()])

assert repr(response) in repr(api_connection_exception)


def test_api_exception_repr():
Expand Down
4 changes: 2 additions & 2 deletions tests/test_integration_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ def test_invalid_user_return_401(self):
client_factory = ApiClientFactory(TEST_URL, SessionConfiguration())
with pytest.raises(ApiConnectionException) as exception_info:
_ = client_factory.with_credentials("eve", "password").connect()
assert exception_info.value.status_code == 401
assert "Unauthorized" in exception_info.value.reason_phrase
assert exception_info.value.response.status_code == 401
assert "Unauthorized" in exception_info.value.response.reason

def test_get_health_returns_200_ok(self):
client_factory = ApiClientFactory(TEST_URL, SessionConfiguration())
Expand Down
4 changes: 2 additions & 2 deletions tests/test_integration_negotiate.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,5 @@ def test_bad_principal_returns_403(self):
client_factory = ApiClientFactory(TEST_URL, SessionConfiguration())
with pytest.raises(ApiConnectionException) as excinfo:
_ = client_factory.with_autologon().connect()
assert excinfo.value.status_code == 403
assert excinfo.value.reason_phrase == "Forbidden"
assert excinfo.value.response.status_code == 403
assert excinfo.value.response.reason == "Forbidden"
21 changes: 17 additions & 4 deletions tests/test_session_creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from urllib.parse import parse_qs

import pytest
import requests
import requests_mock
import requests_ntlm

Expand Down Expand Up @@ -40,8 +41,8 @@ def test_other_status_codes_throw(status_code, reason_phrase):
m.get(SERVICELAYER_URL, status_code=status_code, reason=reason_phrase)
with pytest.raises(ApiConnectionException) as excinfo:
_ = ApiClientFactory(SERVICELAYER_URL).with_anonymous()
assert excinfo.value.status_code == status_code
assert excinfo.value.reason_phrase == reason_phrase
assert excinfo.value.response.status_code == status_code
assert excinfo.value.response.reason == reason_phrase


def test_missing_www_authenticate_throws():
Expand Down Expand Up @@ -123,8 +124,8 @@ def test_throws_with_invalid_credentials():
_ = ApiClientFactory(SERVICELAYER_URL).with_credentials(
username="NOT_A_TEST_USER", password="PASSWORD"
)
assert exception_info.value.status_code == 401
assert exception_info.value.reason_phrase == UNAUTHORIZED
assert exception_info.value.response.status_code == 401
assert exception_info.value.response.reason == UNAUTHORIZED


def wrap_with_workstation(func):
Expand Down Expand Up @@ -387,3 +388,15 @@ def test_no_oidc_throws():

def test_self_signed_throws():
pass


def test_invalid_initial_response_raises_exception():
factory = ApiClientFactory(SERVICELAYER_URL)
with requests_mock.Mocker() as m:
m.get(
SERVICELAYER_URL,
status_code=404,
)
resp = requests.get(SERVICELAYER_URL)
with pytest.raises(ApiConnectionException, match=rf".*{SERVICELAYER_URL}.*404.*"):
factory._ApiClientFactory__handle_initial_response(resp)

0 comments on commit 8423aa4

Please sign in to comment.