diff --git a/shipengine_sdk/__init__.py b/shipengine_sdk/__init__.py index ef1b01b..0bbbcc5 100644 --- a/shipengine_sdk/__init__.py +++ b/shipengine_sdk/__init__.py @@ -4,8 +4,6 @@ import logging from logging import NullHandler -from .models import * # noqa - # SDK imports here from .shipengine import ShipEngine from .shipengine_config import ShipEngineConfig diff --git a/shipengine_sdk/models/enums/error_type.py b/shipengine_sdk/models/enums/error_type.py index 64b0608..f496ee1 100644 --- a/shipengine_sdk/models/enums/error_type.py +++ b/shipengine_sdk/models/enums/error_type.py @@ -47,3 +47,6 @@ class ErrorType(Enum): AUTHORIZATION = "authorization" """General authorization error type.""" + + ERROR = "error" + """Generic error.""" diff --git a/shipengine_sdk/services/address_validation.py b/shipengine_sdk/services/address_validation.py index 0b4a435..a7fc851 100644 --- a/shipengine_sdk/services/address_validation.py +++ b/shipengine_sdk/services/address_validation.py @@ -5,6 +5,7 @@ from ..models.address import Address, AddressValidateResult from ..models.enums import RPCMethods from ..shipengine_config import ShipEngineConfig +from ..util import does_normalized_address_have_errors def validate(address: Address, config: ShipEngineConfig) -> AddressValidateResult: @@ -33,5 +34,13 @@ def validate(address: Address, config: ShipEngineConfig) -> AddressValidateResul def normalize(address: Address, config: ShipEngineConfig) -> Address: + """ + Normalize a given address into a standardized format. + + :param Address address: The address to be validate. + :param ShipEngineConfig config: The global ShipEngine configuration object. + :returns: :class:`Address`: The normalized address returned from ShipEngine API. + """ validation_result: AddressValidateResult = validate(address=address, config=config) + does_normalized_address_have_errors(result=validation_result) return validation_result.normalized_address diff --git a/shipengine_sdk/shipengine.py b/shipengine_sdk/shipengine.py index 4e701c2..75b906f 100644 --- a/shipengine_sdk/shipengine.py +++ b/shipengine_sdk/shipengine.py @@ -2,7 +2,7 @@ from typing import Dict, Union from .models.address import Address, AddressValidateResult -from .services.address_validation import validate +from .services.address_validation import normalize, validate from .shipengine_config import ShipEngineConfig @@ -40,3 +40,10 @@ def validate_address( """ config: ShipEngineConfig = self.config.merge(new_config=config) return validate(address, config) + + def normalize_address( + self, address: Address, config: Union[Dict[str, any], ShipEngineConfig] = None + ) -> Address: + """Normalize a given address into a standardized format used by carriers.""" + config: ShipEngineConfig = self.config.merge(new_config=config) + return normalize(address=address, config=config) diff --git a/shipengine_sdk/util/sdk_assertions.py b/shipengine_sdk/util/sdk_assertions.py index ceb5da3..919f7f4 100644 --- a/shipengine_sdk/util/sdk_assertions.py +++ b/shipengine_sdk/util/sdk_assertions.py @@ -226,3 +226,42 @@ def is_response_500(status_code: int, response_body: Dict[str, any]) -> None: error_type=error_data["type"], error_code=error_data["code"], ) + + +def does_normalized_address_have_errors(result) -> None: + """ + Assertions to check if the returned normalized address has any errors. If errors + are present an exception is thrown. + + :param AddressValidateResult result: The address validation response from ShipEngine API. + """ + if len(result.errors) > 1: + error_list = list() + for err in result.errors: + error_list.append(err["message"]) + + str_errors = "\n".join(error_list) + + raise ShipEngineError( + message=f"Invalid address.\n{str_errors}", + request_id=result.request_id, + source=ErrorSource.SHIPENGINE.value, + error_type=ErrorType.ERROR.value, + error_code=ErrorCode.INVALID_ADDRESS.value, + ) + elif len(result.errors) == 1: + raise ShipEngineError( + message=f"Invalid address. {result.errors[0]['message']}", + request_id=result.request_id, + source=ErrorSource.SHIPENGINE.value, + error_type=ErrorType.ERROR.value, + error_code=result.errors[0]["code"], + ) + elif result.is_valid is False: + raise ShipEngineError( + message="Invalid address - The address provided could not be normalized.", + request_id=result.request_id, + source=ErrorSource.SHIPENGINE.value, + error_type=ErrorType.ERROR.value, + error_code=ErrorCode.INVALID_ADDRESS.value, + ) diff --git a/tests/jsonrpc/test_process_request.py b/tests/jsonrpc/test_process_request.py index 25ddfe0..5d8dce8 100644 --- a/tests/jsonrpc/test_process_request.py +++ b/tests/jsonrpc/test_process_request.py @@ -1,7 +1,6 @@ """Testing the process request and response functions.""" import pytest -from shipengine_sdk import ErrorCode, ErrorSource, ErrorType, RPCMethods from shipengine_sdk.errors import ( AccountStatusError, BusinessRuleError, @@ -11,6 +10,7 @@ ValidationError, ) from shipengine_sdk.jsonrpc import handle_response, wrap_request +from shipengine_sdk.models import ErrorCode, ErrorSource, ErrorType, RPCMethods def handle_response_errors(error_source: str, error_code: str, error_type: str): diff --git a/tests/services/__init__.py b/tests/services/__init__.py new file mode 100644 index 0000000..b5bcf10 --- /dev/null +++ b/tests/services/__init__.py @@ -0,0 +1 @@ +"""Initial Docstring""" diff --git a/tests/services/test_address_validation.py b/tests/services/test_address_validation.py index e36b9be..2c4e07f 100644 --- a/tests/services/test_address_validation.py +++ b/tests/services/test_address_validation.py @@ -1,236 +1,47 @@ -"""Initial Docstring""" +"""Test the validate address method of the ShipEngine SDK.""" import re -from typing import Dict -from shipengine_sdk import ShipEngine from shipengine_sdk.errors import ClientSystemError, ValidationError from shipengine_sdk.models import ( Address, AddressValidateResult, - Endpoints, ErrorCode, ErrorSource, ErrorType, ) - -def stub_config() -> Dict[str, any]: - """ - Return a test configuration dictionary to be used - when instantiating the ShipEngine object. - """ - return dict( - api_key="baz", base_uri=Endpoints.TEST_RPC_URL.value, page_size=50, retries=2, timeout=15 - ) - - -def stub_shipengine_instance() -> ShipEngine: - """Return a test instance of the ShipEngine object.""" - return ShipEngine(stub_config()) - - -def valid_residential_address() -> Address: - """ - Return a test Address object with valid residential - address information. - """ - return Address( - street=["4 Jersey St", "Apt. 2b"], - city_locality="Boston", - state_province="MA", - postal_code="02215", - country_code="US", - ) - - -def valid_commercial_address() -> Address: - """ - Return a test Address object with valid commercial - address information. - """ - return Address( - street=["4 Jersey St", "ste 200"], - city_locality="Boston", - state_province="MA", - postal_code="02215", - country_code="US", - ) - - -def address_with_warnings() -> Address: - """Return a test Address object that will cause the server to return warning messages.""" - return Address( - street=["170 Warning Blvd", "Apartment 32-B"], - city_locality="Toronto", - state_province="ON", - postal_code="M6K 3C3", - country_code="CA", - ) - - -def address_with_errors() -> Address: - """Return a test Address object that will cause the server to return an error message.""" - return Address( - street=["4 Invalid St"], - city_locality="Boston", - state_province="MA", - postal_code="02215", - country_code="US", - ) - - -def valid_canadian_address() -> Address: - """Return an Address object with a valid canadian address.""" - return Address( - street=["170 Princes Blvd", "Ste 200"], - city_locality="Toronto", - state_province="ON", - postal_code="M6K 3C3", - country_code="CA", - ) - - -def multi_line_address() -> Address: - """Returns a valid multiline address.""" - return Address( - street=["4 Jersey St", "ste 200", "1st Floor"], - city_locality="Boston", - state_province="MA", - postal_code="02215", - country_code="US", - ) - - -def non_latin_address() -> Address: - """Return an address with non-latin characters.""" - return Address( - street=["上鳥羽角田町68"], - city_locality="南区", - state_province="京都", - postal_code="601-8104", - country_code="JP", - ) - - -def unknown_address() -> Address: - """ - Return an address that will make the server respond with an - address with an unknown residential flag. - """ - return Address( - street=["4 Unknown St"], - city_locality="Toronto", - state_province="ON", - postal_code="M6K 3C3", - country_code="CA", - ) - - -def address_missing_required_fields() -> Address: - """Return an address that is missing a state, city, and postal_code to return a ValidationError..""" - return Address( - street=["4 Jersey St"], - city_locality="", - state_province="", - postal_code="", - country_code="US", - ) - - -def address_missing_country() -> Address: - """Return an address that is only missing the country_code.""" - return Address( - street=["4 Jersey St", "Apt. 2b"], - city_locality="Boston", - state_province="MA", - postal_code="02215", - country_code="", - ) - - -def address_with_invalid_country() -> Address: - """Return an address that has an invalid country_code specified.""" - return Address( - street=["4 Jersey St", "Apt. 2b"], - city_locality="Boston", - state_province="MA", - postal_code="02215", - country_code="RZ", - ) - - -def get_server_side_error() -> Address: - """Return an address that will cause the server to return a 500 server error.""" - return Address( - street=["500 Server Error"], - city_locality="Boston", - state_province="MA", - postal_code="02215", - country_code="US", - ) - - -def validate_an_address(address: Address) -> AddressValidateResult: - """ - Helper function that passes a config dictionary into the ShipEngine object to instantiate - it and calls the `validate_address` method, providing it the `address` that is passed into - this function. - """ - return stub_shipengine_instance().validate_address(address=address) - - -def us_valid_address_assertions( - original_address: Address, - validated_address: AddressValidateResult, - expected_residential_indicator, -) -> None: - """A set of common assertions that are regularly made on the commercial US address used for testing.""" - address = validated_address.normalized_address - assert type(validated_address) is AddressValidateResult - assert validated_address.is_valid is True - assert type(address) is Address - assert len(validated_address.info) == 0 - assert len(validated_address.warnings) == 0 - assert len(validated_address.errors) == 0 - assert address is not None - assert address.city_locality == original_address.city_locality.upper() - assert address.state_province == original_address.state_province.upper() - assert address.postal_code == original_address.postal_code - assert address.country_code == original_address.country_code.upper() - assert address.is_residential is expected_residential_indicator - - -def canada_valid_address_assertions( - original_address: Address, - validated_address: AddressValidateResult, - expected_residential_indicator, -) -> None: - """A set of common assertions that are regularly made on the canadian_address used for testing.""" - address = validated_address.normalized_address - assert type(validated_address) is AddressValidateResult - assert validated_address.is_valid is True - assert type(address) is Address - assert len(validated_address.info) == 0 - assert len(validated_address.warnings) == 0 - assert len(validated_address.errors) == 0 - assert address is not None - assert address.city_locality == original_address.city_locality - assert address.state_province == original_address.state_province.title() - assert address.postal_code == "M6 K 3 C3" - assert address.country_code == original_address.country_code.upper() - assert address.is_residential is expected_residential_indicator +from ..util.test_helpers import ( + address_missing_required_fields, + address_with_errors, + address_with_invalid_country, + address_with_warnings, + canada_valid_avs_assertions, + get_server_side_error, + multi_line_address, + non_latin_address, + unknown_address, + valid_address_assertions, + valid_canadian_address, + valid_commercial_address, + valid_residential_address, + validate_an_address, +) class TestValidateAddress: + TEST_METHOD: str = "validate" + def test_valid_residential_address(self) -> None: """DX-1024 - Valid residential address.""" residential_address = valid_residential_address() validated_address = validate_an_address(residential_address) address = validated_address.normalized_address - us_valid_address_assertions( + valid_address_assertions( + test_method=self.TEST_METHOD, + locale="domestic", original_address=residential_address, - validated_address=validated_address, + returned_address=validated_address, expected_residential_indicator=True, ) assert ( @@ -246,9 +57,11 @@ def test_valid_commercial_address(self) -> None: validated_address = validate_an_address(commercial_address) address = validated_address.normalized_address - us_valid_address_assertions( + valid_address_assertions( + test_method=self.TEST_METHOD, + locale="domestic", original_address=commercial_address, - validated_address=validated_address, + returned_address=validated_address, expected_residential_indicator=False, ) assert ( @@ -264,9 +77,11 @@ def test_multi_line_address(self) -> None: validated_address = validate_an_address(valid_multi_line_address) address = validated_address.normalized_address - us_valid_address_assertions( + valid_address_assertions( + test_method=self.TEST_METHOD, + locale="domestic", original_address=valid_multi_line_address, - validated_address=validated_address, + returned_address=validated_address, expected_residential_indicator=False, ) assert ( @@ -281,34 +96,38 @@ def test_numeric_postal_code(self) -> None: """DX-1028 - Validate numeric postal code.""" residential_address = valid_residential_address() validated_address = validate_an_address(residential_address) - us_valid_address_assertions( + valid_address_assertions( + test_method=self.TEST_METHOD, + locale="domestic", original_address=residential_address, - validated_address=validated_address, + returned_address=validated_address, expected_residential_indicator=True, ) assert re.match(r"\d", validated_address.normalized_address.postal_code) - def test_alpha_postal_code(self): + def test_alpha_postal_code(self) -> None: """DX-1029 - Alpha postal code.""" canadian_address = valid_canadian_address() validated_address = validate_an_address(canadian_address) - canada_valid_address_assertions( + valid_address_assertions( + test_method=self.TEST_METHOD, + locale="international", original_address=canadian_address, - validated_address=validated_address, + returned_address=validated_address, expected_residential_indicator=False, ) - def test_unknown_address(self): + def test_unknown_address(self) -> None: """DX-1026 - Validate address of unknown address.""" address = unknown_address() validated_address = validate_an_address(address) - canada_valid_address_assertions( + canada_valid_avs_assertions( original_address=address, validated_address=validated_address, expected_residential_indicator=None, ) - def test_address_with_non_latin_chars(self): + def test_address_with_non_latin_chars(self) -> None: """DX-1030 - non-latin characters.""" non_latin = non_latin_address() validated_address = validate_an_address(non_latin) @@ -325,7 +144,7 @@ def test_address_with_non_latin_chars(self): assert address.is_residential is False assert len(address.street) == 1 - def test_address_with_warnings(self): + def test_address_with_warnings(self) -> None: """DX-1031 - validate with warnings.""" warnings_address = address_with_warnings() validated_address = validate_an_address(warnings_address) @@ -345,14 +164,13 @@ def test_address_with_warnings(self): == "This address has been verified down to the house/building level (highest possible accuracy with the provided data)" # noqa ) assert len(validated_address.errors) == 0 - assert address is not None - assert address.city_locality == validated_address.normalized_address.city_locality - assert address.state_province == validated_address.normalized_address.state_province.title() + assert address.city_locality == warnings_address.city_locality + assert address.state_province == warnings_address.state_province.title() assert address.postal_code == "M6K 3C3" - assert address.country_code == validated_address.normalized_address.country_code.upper() + assert address.country_code == warnings_address.country_code.upper() assert address.is_residential is True - def test_address_with_errors(self): + def test_address_with_errors(self) -> None: """DX-1032 - Validate with error messages.""" error_address = address_with_errors() validated_address = validate_an_address(error_address) @@ -370,7 +188,7 @@ def test_address_with_errors(self): assert validated_address.errors[1]["code"] == ErrorCode.ADDRESS_NOT_FOUND.value assert validated_address.errors[1]["message"] == "Insufficient or Incorrect Address Data" - def test_missing_city_state_and_postal_code(self): + def test_missing_city_state_and_postal_code(self) -> None: """DX-1035 & DX-1036 - Missing city, state, and postal code.""" try: address_missing_required_fields() @@ -384,7 +202,7 @@ def test_missing_city_state_and_postal_code(self): == "Invalid address. Either the postal code or the city/locality and state/province must be specified." # noqa ) - def test_invalid_country_code(self): + def test_invalid_country_code(self) -> None: """DX-1037 - Invalid country code.""" try: address_with_invalid_country() @@ -395,7 +213,7 @@ def test_invalid_country_code(self): assert err.error_code is ErrorCode.FIELD_VALUE_REQUIRED.value assert err.message == "Invalid address: [RZ] is not a valid country code." - def test_server_side_error(self): + def test_server_side_error(self) -> None: """DX-1038 - Server-side error.""" try: get_server_side_error() diff --git a/tests/services/test_normalize_address.py b/tests/services/test_normalize_address.py new file mode 100644 index 0000000..1ed3f16 --- /dev/null +++ b/tests/services/test_normalize_address.py @@ -0,0 +1,163 @@ +"""Test the normalize address method of the ShipEngine SDK.""" +import re + +from shipengine_sdk.errors import ShipEngineError +from shipengine_sdk.models import Address, ErrorCode, ErrorSource, ErrorType + +from ..util.test_helpers import ( + address_with_errors, + address_with_single_error, + address_with_warnings, + multi_line_address, + non_latin_address, + normalize_an_address, + unknown_address, + valid_address_assertions, + valid_canadian_address, + valid_commercial_address, + valid_residential_address, +) + + +class TestNormalizeAddress: + TEST_METHOD: str = "normalize" + + def test_normalize_valid_residential_address(self) -> None: + """DX-1041 - Normalize valid residential address.""" + residential_address = valid_residential_address() + normalized = normalize_an_address(residential_address) + + valid_address_assertions( + test_method=self.TEST_METHOD, + locale="domestic", + original_address=residential_address, + returned_address=normalized, + expected_residential_indicator=True, + ) + + def test_normalize_valid_commercial_address(self) -> None: + """DX-1042 - Normalize valid commercial address.""" + commercial_address = valid_commercial_address() + normalized = normalize_an_address(commercial_address) + + valid_address_assertions( + test_method=self.TEST_METHOD, + locale="domestic", + original_address=commercial_address, + returned_address=normalized, + expected_residential_indicator=False, + ) + + def test_normalize_unknown_address(self) -> None: + """DX-1043 - Normalize unknown address.""" + address = unknown_address() + normalized = normalize_an_address(address) + + valid_address_assertions( + test_method=self.TEST_METHOD, + locale="international", + original_address=address, + returned_address=normalized, + expected_residential_indicator=None, + ) + + def test_normalize_multi_line_address(self) -> None: + """DX-1044 - Normalize multi-line address.""" + multi_line = multi_line_address() + normalized = normalize_an_address(multi_line) + + valid_address_assertions( + test_method=self.TEST_METHOD, + locale="domestic", + original_address=multi_line, + returned_address=normalized, + expected_residential_indicator=False, + ) + assert ( + normalized.street[0] + == (multi_line.street[0] + " " + multi_line.street[1]).replace(".", "").upper() + ) + assert normalized.street[1] == multi_line.street[2].upper() + + def test_normalize_numeric_postal_code(self) -> None: + """DX-1045 - Normalize address with numeric postal code.""" + address = valid_residential_address() + normalized = normalize_an_address(address) + + valid_address_assertions( + test_method=self.TEST_METHOD, + locale="domestic", + original_address=address, + returned_address=normalized, + expected_residential_indicator=True, + ) + assert re.match(r"\d", normalized.postal_code) + + def test_normalize_alpha_postal_code(self) -> None: + """DX-1046 - Normalize address with alpha-numeric postal code.""" + address = valid_canadian_address() + normalized = normalize_an_address(address) + + valid_address_assertions( + test_method=self.TEST_METHOD, + locale="international", + original_address=address, + returned_address=normalized, + expected_residential_indicator=False, + ) + + def test_normalize_non_latin_chars(self) -> None: + """DX-1047 - Normalize address with non-latin characters.""" + non_latin = non_latin_address() + normalized = normalize_an_address(non_latin) + + assert type(normalized) is Address + assert normalized.street[0] == "68 Kamitobatsunodacho" + assert normalized.city_locality == "Kyoto-Shi Minami-Ku" + assert normalized.state_province == "Kyoto" + assert normalized.postal_code == non_latin.postal_code + assert normalized.country_code == non_latin.country_code + assert normalized.is_residential is False + assert len(normalized.street) == 1 + + def test_normalize_with_warnings(self) -> None: + """DX-1048 - Normalize address with warnings.""" + warning_address = address_with_warnings() + normalized = normalize_an_address(warning_address) + + assert type(normalized) is Address + assert normalized is not None + assert normalized.city_locality == warning_address.city_locality + assert normalized.state_province == warning_address.state_province.title() + assert normalized.postal_code == "M6K 3C3" + assert normalized.country_code == warning_address.country_code.upper() + assert normalized.is_residential is True + + def test_normalize_with_single_error_message(self) -> None: + """DX-1049 - Normalize address with single error message.""" + single_error = address_with_single_error() + try: + normalize_an_address(single_error) + except ShipEngineError as err: + assert err.request_id is not None + assert err.request_id.startswith("req_") is True + assert err.source is ErrorSource.SHIPENGINE.value + assert err.error_type is ErrorType.ERROR.value + assert err.error_code == ErrorCode.MINIMUM_POSTAL_CODE_VERIFICATION_FAILED.value + assert err.message == "Invalid address. Insufficient or inaccurate postal code" + + def test_normalize_with_multiple_errors(self) -> None: + """DX-1050 - Normalize address with multiple error messages.""" + errors_address = address_with_errors() + try: + normalize_an_address(errors_address) + except ShipEngineError as err: + assert err.request_id is not None + assert err.request_id.startswith("req_") is True + assert err.source is ErrorSource.SHIPENGINE.value + assert err.error_type is ErrorType.ERROR.value + assert err.error_code is ErrorCode.INVALID_ADDRESS.value + assert ( + err.message + == "Invalid address.\nInvalid City, State, or Zip\nInsufficient or Incorrect Address Data" + ) diff --git a/tests/util/__init__.py b/tests/util/__init__.py new file mode 100644 index 0000000..b5bcf10 --- /dev/null +++ b/tests/util/__init__.py @@ -0,0 +1 @@ +"""Initial Docstring""" diff --git a/tests/util/test_helpers.py b/tests/util/test_helpers.py new file mode 100644 index 0000000..f74d07e --- /dev/null +++ b/tests/util/test_helpers.py @@ -0,0 +1,305 @@ +"""Test data as functions and common assertion helper functions.""" +from typing import Dict, Union + +from shipengine_sdk import ShipEngine +from shipengine_sdk.models import Address, AddressValidateResult, Endpoints + + +def stub_config() -> Dict[str, any]: + """ + Return a test configuration dictionary to be used + when instantiating the ShipEngine object. + """ + return dict( + api_key="baz", base_uri=Endpoints.TEST_RPC_URL.value, page_size=50, retries=2, timeout=15 + ) + + +def stub_shipengine_instance() -> ShipEngine: + """Return a test instance of the ShipEngine object.""" + return ShipEngine(stub_config()) + + +def valid_residential_address() -> Address: + """ + Return a test Address object with valid residential + address information. + """ + return Address( + street=["4 Jersey St", "Apt. 2b"], + city_locality="Boston", + state_province="MA", + postal_code="02215", + country_code="US", + ) + + +def valid_commercial_address() -> Address: + """ + Return a test Address object with valid commercial + address information. + """ + return Address( + street=["4 Jersey St", "ste 200"], + city_locality="Boston", + state_province="MA", + postal_code="02215", + country_code="US", + ) + + +def address_with_warnings() -> Address: + """Return a test Address object that will cause the server to return warning messages.""" + return Address( + street=["170 Warning Blvd", "Apartment 32-B"], + city_locality="Toronto", + state_province="ON", + postal_code="M6K 3C3", + country_code="CA", + ) + + +def address_with_single_error() -> Address: + """Return a test Address object that will cause the server to return a single error message.""" + return Address( + street=["170 Error Blvd"], + city_locality="Boston", + state_province="MA", + postal_code="02215", + country_code="US", + ) + + +def address_with_errors() -> Address: + """Return a test Address object that will cause the server to return an error message.""" + return Address( + street=["4 Invalid St"], + city_locality="Boston", + state_province="MA", + postal_code="02215", + country_code="US", + ) + + +def valid_canadian_address() -> Address: + """Return an Address object with a valid canadian address.""" + return Address( + street=["170 Princes Blvd", "Ste 200"], + city_locality="Toronto", + state_province="ON", + postal_code="M6K 3C3", + country_code="CA", + ) + + +def multi_line_address() -> Address: + """Returns a valid multiline address.""" + return Address( + street=["4 Jersey St", "ste 200", "1st Floor"], + city_locality="Boston", + state_province="MA", + postal_code="02215", + country_code="US", + ) + + +def non_latin_address() -> Address: + """Return an address with non-latin characters.""" + return Address( + street=["上鳥羽角田町68"], + city_locality="南区", + state_province="京都", + postal_code="601-8104", + country_code="JP", + ) + + +def unknown_address() -> Address: + """ + Return an address that will make the server respond with an + address with an unknown residential flag. + """ + return Address( + street=["4 Unknown St"], + city_locality="Toronto", + state_province="ON", + postal_code="M6K 3C3", + country_code="CA", + ) + + +def address_missing_required_fields() -> Address: + """Return an address that is missing a state, city, and postal_code to return a ValidationError..""" + return Address( + street=["4 Jersey St"], + city_locality="", + state_province="", + postal_code="", + country_code="US", + ) + + +def address_missing_country() -> Address: + """Return an address that is only missing the country_code.""" + return Address( + street=["4 Jersey St", "Apt. 2b"], + city_locality="Boston", + state_province="MA", + postal_code="02215", + country_code="", + ) + + +def address_with_invalid_country() -> Address: + """Return an address that has an invalid country_code specified.""" + return Address( + street=["4 Jersey St", "Apt. 2b"], + city_locality="Boston", + state_province="MA", + postal_code="02215", + country_code="RZ", + ) + + +def get_server_side_error() -> Address: + """Return an address that will cause the server to return a 500 server error.""" + return Address( + street=["500 Server Error"], + city_locality="Boston", + state_province="MA", + postal_code="02215", + country_code="US", + ) + + +def validate_an_address(address: Address) -> AddressValidateResult: + """ + Helper function that passes a config dictionary into the ShipEngine object to instantiate + it and calls the `validate_address` method, providing it the `address` that is passed into + this function. + """ + return stub_shipengine_instance().validate_address(address=address) + + +def normalize_an_address(address: Address) -> Address: + """ + Helper function that passes a config dictionary into the ShipEngine object to instantiate + it and calls the `normalize_address` method, providing it the `address` that is passed into + this function. + """ + return stub_shipengine_instance().normalize_address(address) + + +# Assertion helper functions + + +def valid_address_assertions( + test_method: str, + locale: str, + original_address: Address, + returned_address: Union[Address, AddressValidateResult], + expected_residential_indicator, +) -> None: + """ + A set of common assertions that are regularly made on the commercial US address + used for testing the `validate_address` or `normalize_address` methods, based on + the `test_method` that is passed in. It also makes different sets of assertions + depending on what `locale` is passed in. + """ + address = ( + returned_address.normalized_address + if type(returned_address) is AddressValidateResult + else returned_address + ) # noqa + if locale == "domestic": + if test_method == "validate": + assert type(returned_address) is AddressValidateResult + assert returned_address.is_valid is True + assert type(address) is Address + assert len(returned_address.info) == 0 + assert len(returned_address.warnings) == 0 + assert len(returned_address.errors) == 0 + assert address is not None + assert address.city_locality == original_address.city_locality.upper() + assert address.state_province == original_address.state_province.upper() + assert address.postal_code == original_address.postal_code + assert address.country_code == original_address.country_code.upper() + assert address.is_residential is expected_residential_indicator + elif test_method == "normalize": + assert type(returned_address) is Address + assert returned_address.city_locality == original_address.city_locality.upper() + assert returned_address.state_province == original_address.state_province.upper() + assert returned_address.postal_code == original_address.postal_code + assert returned_address.country_code == original_address.country_code.upper() + assert returned_address.is_residential is expected_residential_indicator + elif locale == "international": + if test_method == "validate": + canada_valid_avs_assertions( + original_address=original_address, + validated_address=returned_address, + expected_residential_indicator=expected_residential_indicator, + ) + if test_method == "normalize": + canada_valid_normalize_assertions( + original_address=original_address, + normalized_address=returned_address, + expected_residential_indicator=expected_residential_indicator, + ) + + +def canada_valid_avs_assertions( + original_address: Address, + validated_address: AddressValidateResult, + expected_residential_indicator, +) -> None: + """ + A set of common assertions that are regularly made on the canadian_address + used for testing `validate_address`. + """ + address = validated_address.normalized_address + assert type(validated_address) is AddressValidateResult + assert validated_address.is_valid is True + assert type(address) is Address + assert len(validated_address.info) == 0 + assert len(validated_address.warnings) == 0 + assert len(validated_address.errors) == 0 + assert address is not None + assert address.city_locality == original_address.city_locality + assert address.state_province == original_address.state_province.title() + assert address.postal_code == "M6 K 3 C3" + assert address.country_code == original_address.country_code.upper() + assert address.is_residential is expected_residential_indicator + + +def us_valid_normalize_assertions( + original_address: Address, + normalized_address: Address, + expected_residential_indicator, +) -> None: + """ + A set of common assertions that are regularly made on the commercial US address + used for `normalized_address` testing. + """ + assert type(normalized_address) is Address + assert normalized_address.city_locality == original_address.city_locality.upper() + assert normalized_address.state_province == original_address.state_province.upper() + assert normalized_address.postal_code == original_address.postal_code + assert normalized_address.country_code == original_address.country_code.upper() + assert normalized_address.is_residential is expected_residential_indicator + + +def canada_valid_normalize_assertions( + original_address: Address, + normalized_address: Address, + expected_residential_indicator, +) -> None: + """ + A set of common assertions that are regularly made on the canadian_address + used for testing `validate_address`. + """ + assert type(normalized_address) is Address + assert normalized_address.city_locality == original_address.city_locality + assert normalized_address.state_province == original_address.state_province.title() + assert normalized_address.postal_code == "M6 K 3 C3" + assert normalized_address.country_code == original_address.country_code.upper() + assert normalized_address.is_residential is expected_residential_indicator