Skip to content

fix(utils): handles Retry-After header as str value. #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
14 changes: 12 additions & 2 deletions shipengine/util/sdk_assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,18 +200,28 @@ def check_response_for_errors(

# Check if status_code is 429 and raises an error if so.
if status_code == 429:
request_id = response_body.get("request_id") or response_headers.get(
'x-shipengine-requestid'
)
retry_after = response_headers["Retry-After"]
if not isinstance(retry_after, str) or not retry_after.isdigit():
raise ShipEngineError(
message="Unexpected Retry-After header value.",
error_source=ErrorSource.SHIPENGINE.value,
request_id=request_id,
)
retry_after = int(retry_after)
if retry_after > config.timeout:
raise ClientTimeoutError(
retry_after=config.timeout,
error_source=ErrorSource.SHIPENGINE.value,
request_id=response_body["request_id"],
request_id=request_id,
)
else:
raise RateLimitExceededError(
retry_after=retry_after,
error_source=ErrorSource.SHIPENGINE.value,
request_id=response_body["request_id"],
request_id=request_id,
)

# Check if the status code is 500 and raises an error if so.
Expand Down
127 changes: 93 additions & 34 deletions tests/services/test_validate_addresses.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,51 @@
import unittest
import urllib.parse as urlparse

import pytest
import responses

from shipengine.enums import BaseURL, Endpoints
from shipengine.errors import ShipEngineError, RateLimitExceededError

from ..util import stub_shipengine_instance, valid_commercial_address

expected_response_body = [
{
"status": "verified",
"original_address": {
"name": "ShipEngine",
"phone": "1-123-123-1234",
"company_name": "None",
"address_line1": "3800 N Lamar Blvd",
"address_line2": "ste 220",
"address_line3": "None",
"city_locality": "Austin",
"state_province": "TX",
"postal_code": "78756",
"country_code": "US",
"address_residential_indicator": "unknown",
},
"matched_address": {
"name": "SHIPENGINE",
"phone": "1-123-123-1234",
"company_name": "None",
"address_line1": "3800 N LAMAR BLVD STE 220",
"address_line2": "",
"address_line3": "None",
"city_locality": "AUSTIN",
"state_province": "TX",
"postal_code": "78756-0003",
"country_code": "US",
"address_residential_indicator": "no",
},
"messages": [],
}
]

rate_limit_exceeded_response_body = {
"message": "API rate limit exceeded",
}


class TestValidateAddresses(unittest.TestCase):
@responses.activate
Expand All @@ -23,40 +62,7 @@ def test_validate_addresses(self) -> None:
"url": urlparse.urljoin(
BaseURL.SHIPENGINE_RPC_URL.value, Endpoints.ADDRESSES_VALIDATE.value
),
"body": json.dumps(
[
{
"status": "verified",
"original_address": {
"name": "ShipEngine",
"phone": "1-123-123-1234",
"company_name": "None",
"address_line1": "3800 N Lamar Blvd",
"address_line2": "ste 220",
"address_line3": "None",
"city_locality": "Austin",
"state_province": "TX",
"postal_code": "78756",
"country_code": "US",
"address_residential_indicator": "unknown",
},
"matched_address": {
"name": "SHIPENGINE",
"phone": "1-123-123-1234",
"company_name": "None",
"address_line1": "3800 N LAMAR BLVD STE 220",
"address_line2": "",
"address_line3": "None",
"city_locality": "AUSTIN",
"state_province": "TX",
"postal_code": "78756-0003",
"country_code": "US",
"address_residential_indicator": "no",
},
"messages": [],
}
]
),
"body": json.dumps(expected_response_body),
"status": 200,
"content_type": "application/json",
}
Expand All @@ -65,3 +71,56 @@ def test_validate_addresses(self) -> None:
shipengine = stub_shipengine_instance()
result = shipengine.validate_addresses(valid_commercial_address())
self.assertEqual(result[0]["status"], "verified")

@responses.activate
def test_validate_addresses_when_rate_limit_exceeded_and_invalid_retry_value(self) -> None:
"""
Tests that the validate_addresses method handles unexpected Retry-After
header values upon the rate limit being exceeded.
"""
responses.add(
**{
"method": responses.POST,
"url": urlparse.urljoin(
BaseURL.SHIPENGINE_RPC_URL.value, Endpoints.ADDRESSES_VALIDATE.value
),
"body": json.dumps(rate_limit_exceeded_response_body),
"status": 429,
"content_type": "application/json",
"headers": {
"Retry-After": "10.1"
}
}
)

shipengine = stub_shipengine_instance()
with pytest.raises(ShipEngineError) as exc_info:
shipengine.validate_addresses(valid_commercial_address())

self.assertEqual(exc_info.value.message, 'Unexpected Retry-After header value.')

@responses.activate
def test_validate_addresses_when_rate_limit_exceeded(self) -> None:
"""
Tests that the validate_addresses method handles the rate limit being exceeded
with the appropriate error.
"""
responses.add(
**{
"method": responses.POST,
"url": urlparse.urljoin(
BaseURL.SHIPENGINE_RPC_URL.value, Endpoints.ADDRESSES_VALIDATE.value
),
"body": json.dumps(rate_limit_exceeded_response_body),
"status": 429,
"content_type": "application/json",
"headers": {
"Retry-After": "0", # NOTE: we use 0 here so that the test doesn't block
"x-shipengine-requestid": "56b2b0a3-8df5-4cfe-95f0-d7efefdda2ad"
}
}
)

shipengine = stub_shipengine_instance()
with pytest.raises(RateLimitExceededError):
shipengine.validate_addresses(valid_commercial_address())