Skip to content

Commit

Permalink
chore: Error handling improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Ugochukwu Onyebuchi committed Jul 14, 2023
1 parent 150dd2b commit 6eb5326
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 24 deletions.
39 changes: 25 additions & 14 deletions decide/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,48 @@
adapter = HTTPAdapter(max_retries=retries)
logger = logging.getLogger(__name__)


def _fetch_auth_code(url) -> Optional[str]:
client_id = os.getenv("INDICINA_CLIENT_ID")
client_secret = os.getenv("INDICINA_CLIENT_SECRET")

print("Fetching authorization token...")
if not client_id or not client_secret:
logger.error("Both INDICINA_CLIENT_ID and INDICINA_CLIENT_SECRET must be set as environment variables.")
raise DecideException(message="Both INDICINA_CLIENT_ID and INDICINA_CLIENT_SECRET must be set as environment variables.", status_code=400)

payload = {
"client_id": client_id,
"client_secret": client_secret
}

logger.info("Fetching authorization token...")
try:
with requests.Session() as session:
session.mount(url, adapter)
response = session.post(url=url, data={
"client_id": client_id,
"client_secret": client_secret
})
response = session.post(url=url, data=payload)

response.raise_for_status()
response = response.json()
response_json = response.json()

return response["data"]["token"]
try:
return response_json["data"]["token"]
except KeyError:
raise DecideException("Token not found in the response.", response.status_code, url, payload, response.headers) from None
except requests.exceptions.HTTPError as errh:
logger.error("HTTP Error: %s", errh)
raise DecideException(response.json()["message"]) from errh
error_message = f"HTTP Error: {errh}"
status_code = errh.response.status_code
endpoint = url
response_headers = response.headers
raise DecideException(error_message, status_code, endpoint, payload, response_headers) from errh
except requests.exceptions.ConnectionError as errc:
logger.error("Connection error: %s", errc)
raise DecideException("Unable to connect to the Decide API.") from errc
logger.error("Connection Error: %s", errc)
raise DecideException(f"Connection Error: {errc}", request_payload=payload) from errc
except requests.exceptions.Timeout as errt:
logger.error("Timeout Error: %s", errt)
raise DecideException("Timeout occurred while fetching auth code.") from errt
raise DecideException(f"Timeout Error: {errt}", request_payload=payload) from errt
except requests.exceptions.RequestException as err:
logger.error("Request Exception: %s", err)
raise DecideException("An error occurred while fetching auth code.") from err

raise DecideException(f"Request Exception: {err}", request_payload=payload) from err

class Auth:
"""
Expand Down
2 changes: 1 addition & 1 deletion decide/models/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def _req(self, method: str, **kwargs) -> Response:
self.auth.refresh() # refresh token
return requests.request(method, full_path, headers=self.headers, **kwargs)
if response.status_code != 200:
raise DecideException(response_code=response.status_code, message=response.text)
raise DecideException(status_code=response.status_code, message=response.text)
return response

@property
Expand Down
18 changes: 9 additions & 9 deletions decide/models/error.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
from typing import Any


class DecideException(Exception):
def __init__(self, message, **kwargs):
def __init__(self, message, status_code=None, endpoint=None, request_payload=None, response_headers=None):
super().__init__(message)
self.message = message
self.additional_info = kwargs

self.status_code = status_code
self.endpoint = endpoint
self.request_payload = request_payload
self.response_headers = response_headers

def __str__(self):
if self.additional_info:
additional_info_str = ', '.join(f"{key}={value}" for key, value in self.additional_info.items())
return f"Decide Exception: {self.message}. Additional info: {additional_info_str}"
else:
return f"Decide Exception: {self.message}"
additional_info_str = ', '.join(f"{key}={value}" for key, value in self.__dict__.items() if value is not None and key != 'message')
return f"Decide Exception: {self.message}. Additional info: {additional_info_str}"


class IllegalAssignmentException(DecideException):
Expand Down
33 changes: 33 additions & 0 deletions decide/test/test_decide.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
from unittest import mock
from unittest.mock import patch, MagicMock

import pytest
import responses

from decide import Customer, JSONStatement, StatementFormat, PDFStatement, Bank, CSVStatement
from decide.auth import _fetch_auth_code
from decide.models.error import DecideException


def auth_code_gen(secret=False):
Expand Down Expand Up @@ -36,6 +41,34 @@ def test_different_auth_codes():
auth2 = auth_code_gen(secret=True)
assert auth1 != auth2


def test_fetch_auth_code_with_missing_env_vars():
os.environ.pop("INDICINA_CLIENT_ID", None) # ensure the environment variable is not set
os.environ.pop("INDICINA_CLIENT_SECRET", None) # ensure the environment variable is not set
with pytest.raises(DecideException) as excinfo:
_fetch_auth_code("http://example.com")
assert "Both INDICINA_CLIENT_ID and INDICINA_CLIENT_SECRET must be set as environment variables." in str(excinfo.value)
assert excinfo.value.status_code == 400

@responses.activate
def test_fetch_auth_code_with_invalid_credentials():
responses.add(responses.POST, 'http://example.com', json={"error": "invalid_credentials"}, status=401)
os.environ["INDICINA_CLIENT_ID"] = "invalid"
os.environ["INDICINA_CLIENT_SECRET"] = "invalid"
with pytest.raises(DecideException) as excinfo:
_fetch_auth_code("http://example.com")
assert "HTTP Error: 401 Client Error: Unauthorized" in str(excinfo.value)
assert excinfo.value.status_code == 401

@responses.activate
def test_fetch_auth_code_with_successful_response():
responses.add(responses.POST, 'http://example.com', json={"data": {"token": "token123"}}, status=200)
os.environ["INDICINA_CLIENT_ID"] = "valid"
os.environ["INDICINA_CLIENT_SECRET"] = "valid"
token = _fetch_auth_code("http://example.com")
assert token == "token123"


@patch.object(JSONStatement, 'analyze')
def test_json_mono_statement(json_statement_mock):
with open("decide/test/data/test1.json", "r") as f:
Expand Down

0 comments on commit 6eb5326

Please sign in to comment.