Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 20 additions & 26 deletions barte/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def _request(
path: str,
params: Optional[Dict[str, Any]] = None,
json: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
) -> Union[Dict[str, Any], List[Any], None]:
"""
Private method to centralize HTTP requests.

Expand All @@ -86,32 +86,40 @@ def _request(
json: JSON body for POST, PATCH requests.

Returns:
The response JSON as a dictionary.
The response JSON as a dictionary or list.

Raises:
HTTPError: If the HTTP request returned an unsuccessful status code.
BarteError: If the API returns an error response with Barte error codes.
HTTPError: If the HTTP request returned an unsuccessful status code
without a structured error response.
"""
url = f"{self.base_url}{path}"
response = self.session.request(method, url, params=params, json=json)
response.raise_for_status()

if response.status_code == 204:
return None

return response.json()
try:
json_response = response.json()
except ValueError:
response.raise_for_status()
return None

if isinstance(json_response, dict) and "errors" in json_response:
error_response = from_dict(
data_class=ErrorResponse, data=json_response, config=DACITE_CONFIG
)
error_response.raise_exception(response=response)

response.raise_for_status()

return json_response

def create_order(self, data: Union[Dict[str, Any], OrderPayload]) -> Order:
"""Create a new order"""
if isinstance(data, OrderPayload):
data = asdict(data)
json_response = self._request("POST", "/v2/orders", json=data)

if "errors" in json_response:
error_response = from_dict(
data_class=ErrorResponse, data=json_response, config=DACITE_CONFIG
)
error_response.raise_exception(response=json_response)

return from_dict(data_class=Order, data=json_response, config=DACITE_CONFIG)

def get_charge(self, charge_id: str) -> Charge:
Expand Down Expand Up @@ -176,13 +184,6 @@ def refund_charge(self, charge_id: str, as_fraud: Optional[bool] = False) -> Cha
json_response = self._request(
"PATCH", f"/v2/charges/{charge_id}/refund", json={"asFraud": as_fraud}
)

if "errors" in json_response:
error_response = from_dict(
data_class=ErrorResponse, data=json_response, config=DACITE_CONFIG
)
error_response.raise_exception(response=json_response)

return from_dict(data_class=Charge, data=json_response, config=DACITE_CONFIG)

def partial_refund_charge(
Expand All @@ -196,13 +197,6 @@ def partial_refund_charge(
json_response = self._request(
"PATCH", f"/v2/charges/partial-refund/{charge_id}", json={"value": value}
)

if isinstance(json_response, dict) and "errors" in json_response:
error_response = from_dict(
data_class=ErrorResponse, data=json_response, config=DACITE_CONFIG
)
error_response.raise_exception(response=json_response)

return [
from_dict(data_class=PartialRefund, data=item, config=DACITE_CONFIG)
for item in json_response
Expand Down
83 changes: 83 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import pytest
from dacite import from_dict
from requests.exceptions import HTTPError

from barte import BarteClient, CardToken, Charge, PartialRefund, PixCharge
from barte.exceptions import BarteError
Expand Down Expand Up @@ -791,3 +792,85 @@ def test_refund_charge_with_error_and_charge_uuid(
assert exc_info.value.code == "BAR-7010"
assert exc_info.value.message == "Não foi possível realizar o reembolso"
assert exc_info.value.charge_uuid == "abc123-charge-uuid"

@patch("barte.client.requests.Session.request")
def test_request_raises_barte_error_on_http_error_with_error_body(
self, mock_request, barte_client, mock_order_error_response
):
"""Test _request raises BarteError when API returns HTTP error with structured error body"""
mock_response = Mock()
mock_response.status_code = 400
mock_response.ok = False
mock_response.json.return_value = mock_order_error_response
mock_request.return_value = mock_response

with pytest.raises(BarteError) as exc_info:
barte_client._request("POST", "/v2/orders", json={})

assert exc_info.value.code == "BAR-7005"
assert exc_info.value.message == "Erro no Pagamento"
assert (
exc_info.value.action
== "Verifique os detalhes da transação e/ou contate a central do seu cartão"
)
assert exc_info.value.charge_uuid == "c4e5bf04-7dd3-42bd-9904-f46c8ed43b3c"

@patch("barte.client.requests.Session.request")
def test_request_raises_http_error_on_http_error_without_error_body(
self, mock_request, barte_client
):
"""Test _request raises HTTPError when API returns HTTP error without structured error body"""
mock_response = Mock()
mock_response.status_code = 500
mock_response.ok = False
mock_response.json.return_value = {"message": "Internal Server Error"}
mock_response.raise_for_status.side_effect = HTTPError("500 Server Error")
mock_request.return_value = mock_response

with pytest.raises(HTTPError):
barte_client._request("GET", "/v2/orders")

@patch("barte.client.requests.Session.request")
def test_request_raises_http_error_on_invalid_json(
self, mock_request, barte_client
):
"""Test _request raises HTTPError when response is not valid JSON"""
mock_response = Mock()
mock_response.status_code = 500
mock_response.json.side_effect = ValueError("No JSON object could be decoded")
mock_response.raise_for_status.side_effect = HTTPError("500 Server Error")
mock_request.return_value = mock_response

with pytest.raises(HTTPError):
barte_client._request("GET", "/v2/orders")

@patch("barte.client.requests.Session.request")
def test_request_returns_none_on_204(self, mock_request, barte_client):
"""Test _request returns None when API returns 204 No Content"""
mock_response = Mock()
mock_response.status_code = 204
mock_request.return_value = mock_response

result = barte_client._request("DELETE", "/v2/charges/123")

assert result is None
mock_response.json.assert_not_called()

@patch("barte.client.requests.Session.request")
def test_request_handles_list_response(self, mock_request, barte_client):
"""Test _request correctly handles list JSON responses"""
list_response = [
{"uuid": "item1", "value": 100},
{"uuid": "item2", "value": 200},
]
mock_response = Mock()
mock_response.status_code = 200
mock_response.ok = True
mock_response.json.return_value = list_response
mock_request.return_value = mock_response

result = barte_client._request("GET", "/v2/charges/partial-refund/123")

assert result == list_response
assert isinstance(result, list)
assert len(result) == 2