From 0d3434522e523a86a1241c8029be4ff62becfe3f Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Tue, 8 Jun 2021 14:03:34 -0500 Subject: [PATCH 01/12] DX-1051 - Empty address lines - This test is already written from DX-1033. This error is only thrown when instantiating an Address object with a missing street. https://auctane.atlassian.net/browse/DX-1051 --- tests/models/address/test_address.py | 26 ++------------------------ tests/util/test_helpers.py | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/models/address/test_address.py b/tests/models/address/test_address.py index 3d88df4..cd4e09f 100644 --- a/tests/models/address/test_address.py +++ b/tests/models/address/test_address.py @@ -3,29 +3,7 @@ from shipengine_sdk.errors import ValidationError from shipengine_sdk.models import ErrorCode, ErrorSource, ErrorType -from shipengine_sdk.models.address import Address - - -def empty_address_lines() -> Address: - """Returns an invalid address with empty street list.""" - return Address( - street=list(), - city_locality="Boston", - state_province="MA", - postal_code="02215", - country_code="US", - ) - - -def address_with_too_many_lines() -> Address: - """Return an address with too many address lines in the street list.""" - return Address( - street=["4 Jersey St", "ste 200", "1st Floor", "Room B"], - city_locality="Boston", - state_province="MA", - postal_code="02215", - country_code="US", - ) +from tests.util.test_helpers import address_with_too_many_lines, empty_address_lines def address_line_assertions(err: ValidationError, variant: str) -> None: @@ -45,7 +23,7 @@ def address_line_assertions(err: ValidationError, variant: str) -> None: class TestAddress: def test_no_address_lines(self): - """DX-1033 - Too many address lines in the street list.""" + """DX-1033/DX-1051 - No address lines in the street list.""" try: empty_address_lines() except ValidationError as err: diff --git a/tests/util/test_helpers.py b/tests/util/test_helpers.py index f74d07e..79866cf 100644 --- a/tests/util/test_helpers.py +++ b/tests/util/test_helpers.py @@ -92,6 +92,28 @@ def valid_canadian_address() -> Address: ) +def empty_address_lines() -> Address: + """Returns an invalid address with empty street list.""" + return Address( + street=list(), + city_locality="Boston", + state_province="MA", + postal_code="02215", + country_code="US", + ) + + +def address_with_too_many_lines() -> Address: + """Return an address with too many address lines in the street list.""" + return Address( + street=["4 Jersey St", "ste 200", "1st Floor", "Room B"], + city_locality="Boston", + state_province="MA", + postal_code="02215", + country_code="US", + ) + + def multi_line_address() -> Address: """Returns a valid multiline address.""" return Address( From 29b3a6b3675f4b8b2d11a89de4c3b1cbe41c660f Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Tue, 8 Jun 2021 14:11:08 -0500 Subject: [PATCH 02/12] DX-1052 - Empty address lines - This test is already written from DX-1033. This error is only thrown when instantiating an Address object with a missing street. https://auctane.atlassian.net/browse/DX-1052 --- tests/models/address/test_address.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/models/address/test_address.py b/tests/models/address/test_address.py index cd4e09f..2c0856b 100644 --- a/tests/models/address/test_address.py +++ b/tests/models/address/test_address.py @@ -32,7 +32,7 @@ def test_no_address_lines(self): empty_address_lines() def test_address_with_too_many_lines(self): - """DX-1034 - Too many address lines.""" + """DX-1034/DX-1052 - Too many address lines.""" try: address_with_too_many_lines() except ValidationError as err: From b676f65ae36cb7bbae749a2e829c05b1d9e3420d Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Tue, 8 Jun 2021 14:16:27 -0500 Subject: [PATCH 03/12] DX-1053 & DX-1054 - Covers acceptance criteria for both by making assertions for both tickets in one test. https://auctane.atlassian.net/browse/DX-1053 & https://auctane.atlassian.net/browse/DX-1053 --- tests/services/test_normalize_address.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/services/test_normalize_address.py b/tests/services/test_normalize_address.py index 1ed3f16..77c4d6d 100644 --- a/tests/services/test_normalize_address.py +++ b/tests/services/test_normalize_address.py @@ -1,10 +1,11 @@ """Test the normalize address method of the ShipEngine SDK.""" import re -from shipengine_sdk.errors import ShipEngineError +from shipengine_sdk.errors import ShipEngineError, ValidationError from shipengine_sdk.models import Address, ErrorCode, ErrorSource, ErrorType from ..util.test_helpers import ( + address_missing_required_fields, address_with_errors, address_with_single_error, address_with_warnings, @@ -161,3 +162,17 @@ def test_normalize_with_multiple_errors(self) -> None: err.message == "Invalid address.\nInvalid City, State, or Zip\nInsufficient or Incorrect Address Data" ) + + def test_normalize_missing_city_state_and_postal_code(self) -> None: + """DX-1053 & DX-1054 - Missing city, state, and postal code.""" + try: + address_missing_required_fields() + except ValidationError as err: + assert err.request_id is None + assert err.source is ErrorSource.SHIPENGINE.value + assert err.error_type is ErrorType.VALIDATION.value + assert err.error_code is ErrorCode.FIELD_VALUE_REQUIRED.value + assert ( + err.message + == "Invalid address. Either the postal code or the city/locality and state/province must be specified." # noqa + ) From c6ea5df1178431002012f46f5098004759a93e49 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Tue, 8 Jun 2021 14:19:15 -0500 Subject: [PATCH 04/12] DX-1056 - server side error. https://auctane.atlassian.net/browse/DX-1056 --- tests/services/test_normalize_address.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/services/test_normalize_address.py b/tests/services/test_normalize_address.py index 77c4d6d..b9738e8 100644 --- a/tests/services/test_normalize_address.py +++ b/tests/services/test_normalize_address.py @@ -1,7 +1,7 @@ """Test the normalize address method of the ShipEngine SDK.""" import re -from shipengine_sdk.errors import ShipEngineError, ValidationError +from shipengine_sdk.errors import ClientSystemError, ShipEngineError, ValidationError from shipengine_sdk.models import Address, ErrorCode, ErrorSource, ErrorType from ..util.test_helpers import ( @@ -9,6 +9,7 @@ address_with_errors, address_with_single_error, address_with_warnings, + get_server_side_error, multi_line_address, non_latin_address, normalize_an_address, @@ -176,3 +177,14 @@ def test_normalize_missing_city_state_and_postal_code(self) -> None: err.message == "Invalid address. Either the postal code or the city/locality and state/province must be specified." # noqa ) + + def test_normalize_server_side_error(self) -> None: + """DX-1056 - Server-side error.""" + try: + get_server_side_error() + except ClientSystemError 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.SYSTEM.value + assert err.error_code is ErrorCode.UNSPECIFIED.value From 99b2953f123048d638eb5f149db5023529a26784 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Wed, 9 Jun 2021 16:05:07 -0500 Subject: [PATCH 05/12] DX-1074 - Multiple accounts of the same carrier. https://auctane.atlassian.net/browse/DX-1074 --- shipengine_sdk/errors/__init__.py | 6 +- shipengine_sdk/models/__init__.py | 12 ++- shipengine_sdk/models/address/__init__.py | 4 +- shipengine_sdk/models/carriers/__init__.py | 77 +++++++++++++++++++ shipengine_sdk/models/enums/__init__.py | 17 ++++ .../models/enums/carriers/__init__.py | 3 + .../models/enums/carriers/carrier_names.py | 63 +++++++++++++++ .../models/enums/carriers/carriers.py | 63 +++++++++++++++ .../services/get_carrier_accounts.py | 56 ++++++++++++++ tests/services/test_get_carrier_accounts.py | 0 10 files changed, 295 insertions(+), 6 deletions(-) create mode 100644 shipengine_sdk/models/carriers/__init__.py create mode 100644 shipengine_sdk/models/enums/carriers/__init__.py create mode 100644 shipengine_sdk/models/enums/carriers/carrier_names.py create mode 100644 shipengine_sdk/models/enums/carriers/carriers.py create mode 100644 shipengine_sdk/services/get_carrier_accounts.py create mode 100644 tests/services/test_get_carrier_accounts.py diff --git a/shipengine_sdk/errors/__init__.py b/shipengine_sdk/errors/__init__.py index 074d070..6f70892 100644 --- a/shipengine_sdk/errors/__init__.py +++ b/shipengine_sdk/errors/__init__.py @@ -2,7 +2,7 @@ import json from typing import Optional -from ..models.enums import ErrorCode, ErrorSource, ErrorType +from ..models.enums import ErrorCode, ErrorSource, ErrorType, does_member_value_exist class ShipEngineError(Exception): @@ -27,14 +27,14 @@ def __init__( def _are_enums_valid(self): if self.source is None: return self - elif self.source not in (member.value for member in ErrorSource): + elif not does_member_value_exist(self.source, ErrorSource): raise ValueError( f"Error source must be a member of ErrorSource enum - [{self.source}] provided." ) if self.error_type is None: return self - elif self.error_type not in (member.value for member in ErrorType): + elif not does_member_value_exist(self.error_type, ErrorType): raise ValueError( f"Error type must be a member of ErrorType enum - [{self.error_type}] provided." ) diff --git a/shipengine_sdk/models/__init__.py b/shipengine_sdk/models/__init__.py index 92a5b3c..9dd9710 100644 --- a/shipengine_sdk/models/__init__.py +++ b/shipengine_sdk/models/__init__.py @@ -1,3 +1,13 @@ """ShipEngine SDK Models & Enumerations""" from .address import Address, AddressValidateResult -from .enums import Country, Endpoints, ErrorCode, ErrorSource, ErrorType, RPCMethods +from .carriers import Carrier, CarrierAccount +from .enums import ( + CarrierNames, + Carriers, + Country, + Endpoints, + ErrorCode, + ErrorSource, + ErrorType, + RPCMethods, +) diff --git a/shipengine_sdk/models/address/__init__.py b/shipengine_sdk/models/address/__init__.py index 687175a..1917d59 100644 --- a/shipengine_sdk/models/address/__init__.py +++ b/shipengine_sdk/models/address/__init__.py @@ -27,7 +27,7 @@ class Address: phone: str = "" company: str = "" - def __post_init__(self): + def __post_init__(self) -> None: is_street_valid(self.street) is_city_valid(self.city_locality) is_state_valid(self.state_province) @@ -52,7 +52,7 @@ def __init__( info: Optional[List] = None, warnings: Optional[List] = None, errors: Optional[List] = None, - ): + ) -> None: self.is_valid = is_valid self.request_id = request_id self.normalized_address = normalized_address diff --git a/shipengine_sdk/models/carriers/__init__.py b/shipengine_sdk/models/carriers/__init__.py new file mode 100644 index 0000000..36887c4 --- /dev/null +++ b/shipengine_sdk/models/carriers/__init__.py @@ -0,0 +1,77 @@ +"""CarrierAccount class object and immutable carrier object.""" +import json +from typing import Dict + +from ...errors import InvalidFieldValueError, ShipEngineError +from ..enums import Carriers, does_member_value_exist, get_carrier_name_value + +# @dataclass_json(letter_case=LetterCase.CAMEL) +# @dataclass(frozen=True) +# class Carrier: +# """This immutable class object represents a given Carrier provider e.g. `FedEx`, `UPS`, `USPS`.""" +# name: str +# code: str +# +# def __post_init__(self) -> None: +# """An immutable Carrier object. e.g. `FedEx`, `UPS`, `USPS`.""" +# if not does_member_value_exist(self.name, CarrierNames): +# raise ShipEngineError(f"Carrier [{self.name}] not currently supported.") +# +# if not does_member_value_exist(self.code, Carriers): +# raise ShipEngineError(f"Carrier [{self.code}] not currently supported.") +# +# +# @dataclass_json(letter_case=LetterCase.CAMEL) +# @dataclass(frozen=True) +# class CarrierAccount: +# """This class represents a given account with a Carrier provider e.g. `FedEx`, `UPS`, `USPS`.""" +# carrier: Union[str, Carrier] +# account_id: str +# account_number: str +# name: str +# +# def __post_init__(self) -> None: +# if not does_member_value_exist() + + +class Carrier: + def __init__(self, code: str) -> None: + """This class represents a given account with a Carrier provider e.g. `FedEx`, `UPS`, `USPS`.""" + if not does_member_value_exist(code, Carriers): + raise ShipEngineError(f"Carrier [{code}] not currently supported.") + else: + self.name = get_carrier_name_value(code.upper()) + self.code = code + + def to_dict(self): + return (lambda o: o.__dict__)(self) + + def to_json(self): + return json.dumps(self, default=lambda o: o.__dict__, indent=2) + + +class CarrierAccount: + carrier: Carrier + + def __init__(self, account_information: Dict[str, any]) -> None: + """This class represents a given account with a Carrier provider e.g. `FedEx`, `UPS`, `USPS`.""" + self._set_carrier(account_information["carrierCode"]) + self.account_id = account_information["accountID"] + self.account_number = account_information["accountNumber"] + + def _set_carrier(self, carrier: str) -> None: + if does_member_value_exist(carrier, Carriers): + self.carrier = Carrier(code=carrier) + self.name = self.carrier.name + else: + InvalidFieldValueError( + field_name="carrier", + reason=f"Carrier [{carrier}] is currently not supported.", + field_value=carrier, + ) + + def to_dict(self): + return (lambda o: o.__dict__)(self) + + def to_json(self): + return json.dumps(self, default=lambda o: o.__dict__, indent=2) diff --git a/shipengine_sdk/models/enums/__init__.py b/shipengine_sdk/models/enums/__init__.py index c354e8f..efdec97 100644 --- a/shipengine_sdk/models/enums/__init__.py +++ b/shipengine_sdk/models/enums/__init__.py @@ -1,6 +1,7 @@ """ShipEngine SDK Enumerations""" from enum import Enum +from .carriers import CarrierNames, Carriers from .country import Country from .error_code import ErrorCode from .error_source import ErrorSource @@ -21,3 +22,19 @@ class RPCMethods(Enum): CREATE_TAG = "create.tag.v1" LIST_CARRIERS = "carrier.listAccounts.v1" TRACK_PACKAGE = "package.track.v1" + + +def does_member_value_exist(m: str, enum_to_search) -> bool: + """ + Checks if a member value exists on an Enum. + + :param str m: The member value to validate. + :param enum_to_search: The enumeration to check the member value against. + """ + return False if m not in (member.value for member in enum_to_search) else True + + +def get_carrier_name_value(upper_carrier_code: str): + for k in CarrierNames: + if upper_carrier_code == k.name: + return k.value diff --git a/shipengine_sdk/models/enums/carriers/__init__.py b/shipengine_sdk/models/enums/carriers/__init__.py new file mode 100644 index 0000000..ec6f94d --- /dev/null +++ b/shipengine_sdk/models/enums/carriers/__init__.py @@ -0,0 +1,3 @@ +"""Carrier enumerations used throughout the ShipEngine SDK.""" +from .carrier_names import CarrierNames +from .carriers import Carriers diff --git a/shipengine_sdk/models/enums/carriers/carrier_names.py b/shipengine_sdk/models/enums/carriers/carrier_names.py new file mode 100644 index 0000000..fd8c8fd --- /dev/null +++ b/shipengine_sdk/models/enums/carriers/carrier_names.py @@ -0,0 +1,63 @@ +"""Enumeration of valid carrier names.""" +from enum import Enum + + +class CarrierNames(Enum): + """An enumeration valid carrier names.""" + + FEDEX = "FedEx" + """FedEx - Federal Express""" + + UPS = "United Parcel Service" + """UPS - United Parcel Service""" + + USPS = "U.S. Postal Service" + """USPS - United State Postal Service""" + + STAMPS_COM = "Stamps.com" + """USPS services via Stamps.com""" + + DHL_EXPRESS = "DHL Express" + """DHL Express""" + + DHL_GLOBAL_MAIL = "DHL ECommerce" + """DHL ECommerce""" + + CANADA_POST = "Canada Post" + """Canada Post""" + + AUSTRALIA_POST = "Australia Post" + """Australia Post""" + + FIRSTMILE = "First Mile" + """First Mile""" + + ASENDIA = "Asendia" + """Asendia""" + + ONTRAC = "OnTrac" + """OnTrac""" + + APC = "APC" + """APC""" + + NEWGISTICS = "Newgistics" + """Newgistics""" + + GLOBEGISTICS = "Globegistics" + """Globegistics""" + + RR_DONNELLEY = "RR Donnelley" + """RR Donnell""" + + IMEX = "IMEX" + """IMEX""" + + ACCESS_WORLDWIDE = "Access Worldwide" + """Access Worldwide""" + + PUROLATOR_CA = "Purolator Canada" + """Purolator Canada""" + + SENDLE = "Sendle" + """Sendle""" diff --git a/shipengine_sdk/models/enums/carriers/carriers.py b/shipengine_sdk/models/enums/carriers/carriers.py new file mode 100644 index 0000000..a79342c --- /dev/null +++ b/shipengine_sdk/models/enums/carriers/carriers.py @@ -0,0 +1,63 @@ +"""Enumeration of valid carrier providers.""" +from enum import Enum + + +class Carriers(Enum): + """An enumeration of valid carrier providers.""" + + FEDEX = "fedex" + """FedEx - Federal Express""" + + UPS = "ups" + """UPS - United Parcel Service""" + + USPS = "usps" + """USPS - United State Postal Service""" + + STAMPS_COM = "stamps_com" + """USPS services via Stamps.com""" + + DHL_EXPRESS = "dhl_express" + """DHL Express""" + + DHL_GLOBAL_MAIL = "dhl_global_mail" + """DHL ECommerce""" + + CANADA_POST = "canada_post" + """Canada Post""" + + AUSTRALIA_POST = "australia_post" + """Australia Post""" + + FIRSTMILE = "firstmile" + """First Mile""" + + ASENDIA = "asendia" + """Asendia""" + + ONTRAC = "ontrac" + """OnTrac""" + + APC = "apc" + """APC""" + + NEWGISTICS = "newgistics" + """Newgistics""" + + GLOBEGISTICS = "globegistics" + """Globegistics""" + + RR_DONNELLEY = "rr_donnelley" + """RR Donnell""" + + IMEX = "imex" + """IMEX""" + + ACCESS_WORLDWIDE = "access_worldwide" + """Access Worldwide""" + + PUROLATOR_CA = "purolator_ca" + """Purolator Canada""" + + SENDLE = "sendle" + """Sendle""" diff --git a/shipengine_sdk/services/get_carrier_accounts.py b/shipengine_sdk/services/get_carrier_accounts.py new file mode 100644 index 0000000..d0fa1ef --- /dev/null +++ b/shipengine_sdk/services/get_carrier_accounts.py @@ -0,0 +1,56 @@ +""" +Fetch the carrier account connected to a given ShipEngine Account +based on the API Key passed into the ShipEngine SDK. +""" +from typing import List, Optional + +from .. import ShipEngineConfig +from ..jsonrpc import rpc_request +from ..models import CarrierAccount, RPCMethods + + +class GetCarrierAccounts: + cached_accounts: List = list() + + def fetch_carrier_accounts( + self, config: ShipEngineConfig, carrier_code: Optional[str] = None + ) -> List[CarrierAccount]: + if carrier_code is not None: + api_response = rpc_request( + method=RPCMethods.LIST_CARRIERS.value, + config=config, + params={"carrierCode": carrier_code}, + ) + else: + api_response = rpc_request( + method=RPCMethods.LIST_CARRIERS.value, + config=config, + ) + + accounts = api_response["result"]["carrierAccounts"] + self.cached_accounts = list() + for account in accounts: + carrier_account = CarrierAccount(account) + self.cached_accounts.append(carrier_account) + + return self.cached_accounts + + def fetch_cached_carrier_accounts( + self, config: ShipEngineConfig, carrier_code: Optional[str] + ) -> List[CarrierAccount]: + accounts = self.cached_accounts + return ( + accounts + if len(self.cached_accounts) > 0 + else self.fetch_carrier_accounts(config=config, carrier_code=carrier_code) + ) + + def get_cached_accounts_by_carrier_code(self, carrier_code: Optional[str]): + accounts = list() + if carrier_code is not None: + return self.cached_accounts + else: + for account in self.cached_accounts: + if account.carrier.code == carrier_code: + accounts.append(account) + return accounts diff --git a/tests/services/test_get_carrier_accounts.py b/tests/services/test_get_carrier_accounts.py new file mode 100644 index 0000000..e69de29 From e4db609af2f88a4d5085d4e757b099f59b25ee35 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Wed, 9 Jun 2021 17:35:53 -0500 Subject: [PATCH 06/12] DX-1074 - GetCarrierAccounts implementation. https://auctane.atlassian.net/browse/DX-1074 --- shipengine_sdk/models/carriers/__init__.py | 4 ++-- shipengine_sdk/services/get_carrier_accounts.py | 10 +++++----- shipengine_sdk/shipengine.py | 12 +++++++++++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/shipengine_sdk/models/carriers/__init__.py b/shipengine_sdk/models/carriers/__init__.py index 36887c4..cd9ed06 100644 --- a/shipengine_sdk/models/carriers/__init__.py +++ b/shipengine_sdk/models/carriers/__init__.py @@ -61,8 +61,8 @@ def __init__(self, account_information: Dict[str, any]) -> None: def _set_carrier(self, carrier: str) -> None: if does_member_value_exist(carrier, Carriers): - self.carrier = Carrier(code=carrier) - self.name = self.carrier.name + self.carrier = Carrier(code=carrier).to_dict() + self.name = self.carrier["name"] else: InvalidFieldValueError( field_name="carrier", diff --git a/shipengine_sdk/services/get_carrier_accounts.py b/shipengine_sdk/services/get_carrier_accounts.py index d0fa1ef..5bff042 100644 --- a/shipengine_sdk/services/get_carrier_accounts.py +++ b/shipengine_sdk/services/get_carrier_accounts.py @@ -2,11 +2,11 @@ Fetch the carrier account connected to a given ShipEngine Account based on the API Key passed into the ShipEngine SDK. """ -from typing import List, Optional +from typing import Dict, List, Optional -from .. import ShipEngineConfig from ..jsonrpc import rpc_request from ..models import CarrierAccount, RPCMethods +from ..shipengine_config import ShipEngineConfig class GetCarrierAccounts: @@ -14,7 +14,7 @@ class GetCarrierAccounts: def fetch_carrier_accounts( self, config: ShipEngineConfig, carrier_code: Optional[str] = None - ) -> List[CarrierAccount]: + ) -> List[Dict[str, any]]: if carrier_code is not None: api_response = rpc_request( method=RPCMethods.LIST_CARRIERS.value, @@ -30,14 +30,14 @@ def fetch_carrier_accounts( accounts = api_response["result"]["carrierAccounts"] self.cached_accounts = list() for account in accounts: - carrier_account = CarrierAccount(account) + carrier_account = CarrierAccount(account).to_dict() self.cached_accounts.append(carrier_account) return self.cached_accounts def fetch_cached_carrier_accounts( self, config: ShipEngineConfig, carrier_code: Optional[str] - ) -> List[CarrierAccount]: + ) -> List[Dict[str, any]]: accounts = self.cached_accounts return ( accounts diff --git a/shipengine_sdk/shipengine.py b/shipengine_sdk/shipengine.py index 75b906f..f74abd3 100644 --- a/shipengine_sdk/shipengine.py +++ b/shipengine_sdk/shipengine.py @@ -1,8 +1,10 @@ """The entrypoint to the ShipEngine API SDK.""" -from typing import Dict, Union +from typing import Dict, List, Optional, Union +from .models import CarrierAccount from .models.address import Address, AddressValidateResult from .services.address_validation import normalize, validate +from .services.get_carrier_accounts import GetCarrierAccounts from .shipengine_config import ShipEngineConfig @@ -47,3 +49,11 @@ def normalize_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) + + def get_carrier_accounts( + self, carrier_code: Optional[str] = None, config: Optional[Dict[str, any]] = None + ) -> List[CarrierAccount]: + """Fetch a list of the carrier accounts connected to your ShipEngine Account.""" + config: ShipEngineConfig = self.config.merge(new_config=config) + get_accounts = GetCarrierAccounts() + return get_accounts.fetch_carrier_accounts(config=config, carrier_code=carrier_code) From 8596cd17ab74a62d19e01e8367c33b4b6541b12a Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Wed, 9 Jun 2021 17:37:24 -0500 Subject: [PATCH 07/12] DX-DX-1077 - Multiple accounts of the same carrier. https://auctane.atlassian.net/browse/DX-1077 --- tests/services/test_get_carrier_accounts.py | 22 +++++++++++++++++++++ tests/util/test_helpers.py | 7 ++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/tests/services/test_get_carrier_accounts.py b/tests/services/test_get_carrier_accounts.py index e69de29..6feddea 100644 --- a/tests/services/test_get_carrier_accounts.py +++ b/tests/services/test_get_carrier_accounts.py @@ -0,0 +1,22 @@ +"""Tests for the GetCarrierAccounts service in the ShipEngine SDK.""" +from shipengine_sdk.models import Carriers + +from ..util.test_helpers import stub_get_carrier_accounts + + +class TestGetCarrierAccounts: + def test_multiple_accounts_of_same_carrier(self): + """DX-1077 - Multiple accounts of the same carrier.""" + accounts = stub_get_carrier_accounts() + + assert len(accounts) == 5 + assert accounts[0]["carrier"]["code"] == Carriers.UPS.value + assert accounts[0]["account_id"] != accounts[1]["account_id"] + assert accounts[1]["carrier"]["code"] == Carriers.FEDEX.value + assert accounts[2]["carrier"]["code"] == Carriers.FEDEX.value + assert accounts[3]["carrier"]["code"] == Carriers.USPS.value + assert accounts[4]["carrier"]["code"] == Carriers.STAMPS_COM.value + + for account in accounts: + assert account["account_id"].startswith("car_") + assert account["name"] is not None diff --git a/tests/util/test_helpers.py b/tests/util/test_helpers.py index 79866cf..7c401b7 100644 --- a/tests/util/test_helpers.py +++ b/tests/util/test_helpers.py @@ -1,5 +1,5 @@ """Test data as functions and common assertion helper functions.""" -from typing import Dict, Union +from typing import Dict, Optional, Union from shipengine_sdk import ShipEngine from shipengine_sdk.models import Address, AddressValidateResult, Endpoints @@ -212,6 +212,11 @@ def normalize_an_address(address: Address) -> Address: return stub_shipengine_instance().normalize_address(address) +def stub_get_carrier_accounts(carrier_code: Optional[str] = None): + """Helper function that passes the `get_carrier_accounts` method a given carrier_code or None.""" + return stub_shipengine_instance().get_carrier_accounts(carrier_code=carrier_code) + + # Assertion helper functions From 848942a6ccb6bd6f87728da3f57abec1316be8de Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Wed, 9 Jun 2021 17:44:32 -0500 Subject: [PATCH 08/12] DX-1055 - fixed typo reference to server side error test in normalize address. --- tests/services/test_normalize_address.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/services/test_normalize_address.py b/tests/services/test_normalize_address.py index b9738e8..7c2454a 100644 --- a/tests/services/test_normalize_address.py +++ b/tests/services/test_normalize_address.py @@ -179,7 +179,7 @@ def test_normalize_missing_city_state_and_postal_code(self) -> None: ) def test_normalize_server_side_error(self) -> None: - """DX-1056 - Server-side error.""" + """DX-1055 - Server-side error.""" try: get_server_side_error() except ClientSystemError as err: From 54306a90c35e45200b30e235d4947b26272818c6 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Wed, 9 Jun 2021 17:59:11 -0500 Subject: [PATCH 09/12] DX-1075 - No accounts setup yet. https://auctane.atlassian.net/browse/DX-1075 --- tests/services/test_get_carrier_accounts.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/services/test_get_carrier_accounts.py b/tests/services/test_get_carrier_accounts.py index 6feddea..bc106bc 100644 --- a/tests/services/test_get_carrier_accounts.py +++ b/tests/services/test_get_carrier_accounts.py @@ -1,4 +1,5 @@ """Tests for the GetCarrierAccounts service in the ShipEngine SDK.""" + from shipengine_sdk.models import Carriers from ..util.test_helpers import stub_get_carrier_accounts @@ -20,3 +21,10 @@ def test_multiple_accounts_of_same_carrier(self): for account in accounts: assert account["account_id"].startswith("car_") assert account["name"] is not None + + def test_no_accounts_setup(self) -> None: + """DX-1075 - No accounts setup yet.""" + accounts = stub_get_carrier_accounts(carrier_code="sendle") + + assert type(accounts) is list + assert len(accounts) == 0 From a999c9b3fb4767546d70032af62bdd0f873311c9 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Thu, 10 Jun 2021 09:39:12 -0500 Subject: [PATCH 10/12] DX-1076 - Multiple carrier accounts. https://auctane.atlassian.net/browse/DX-1076 --- tests/services/test_get_carrier_accounts.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/services/test_get_carrier_accounts.py b/tests/services/test_get_carrier_accounts.py index bc106bc..3e24ad0 100644 --- a/tests/services/test_get_carrier_accounts.py +++ b/tests/services/test_get_carrier_accounts.py @@ -28,3 +28,24 @@ def test_no_accounts_setup(self) -> None: assert type(accounts) is list assert len(accounts) == 0 + + def test_multiple_accounts(self) -> None: + """DX-1076 - Multiple carrier accounts.""" + accounts = stub_get_carrier_accounts() + + assert len(accounts) == 5 + assert accounts[0]["carrier"]["code"] == Carriers.UPS.value + assert accounts[1]["carrier"]["code"] == Carriers.FEDEX.value + assert accounts[2]["carrier"]["code"] == Carriers.FEDEX.value + assert accounts[3]["carrier"]["code"] == Carriers.USPS.value + assert accounts[4]["carrier"]["code"] == Carriers.STAMPS_COM.value + + for account in accounts: + assert account["account_id"].startswith("car_") + assert account["name"] is not None + assert account["account_number"] is not None + assert account["carrier"] is not None + assert account["carrier"]["code"] is not None + assert type(account["carrier"]["code"]) == str + assert account["carrier"]["name"] is not None + assert type(account["carrier"]["name"]) == str From 60b52184ee2067ce0553d67610f3024b7f101d36 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Thu, 10 Jun 2021 09:41:13 -0500 Subject: [PATCH 11/12] Fixed order of tests to match ticket number order. --- tests/services/test_get_carrier_accounts.py | 32 ++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/services/test_get_carrier_accounts.py b/tests/services/test_get_carrier_accounts.py index 3e24ad0..20be7b7 100644 --- a/tests/services/test_get_carrier_accounts.py +++ b/tests/services/test_get_carrier_accounts.py @@ -6,22 +6,6 @@ class TestGetCarrierAccounts: - def test_multiple_accounts_of_same_carrier(self): - """DX-1077 - Multiple accounts of the same carrier.""" - accounts = stub_get_carrier_accounts() - - assert len(accounts) == 5 - assert accounts[0]["carrier"]["code"] == Carriers.UPS.value - assert accounts[0]["account_id"] != accounts[1]["account_id"] - assert accounts[1]["carrier"]["code"] == Carriers.FEDEX.value - assert accounts[2]["carrier"]["code"] == Carriers.FEDEX.value - assert accounts[3]["carrier"]["code"] == Carriers.USPS.value - assert accounts[4]["carrier"]["code"] == Carriers.STAMPS_COM.value - - for account in accounts: - assert account["account_id"].startswith("car_") - assert account["name"] is not None - def test_no_accounts_setup(self) -> None: """DX-1075 - No accounts setup yet.""" accounts = stub_get_carrier_accounts(carrier_code="sendle") @@ -49,3 +33,19 @@ def test_multiple_accounts(self) -> None: assert type(account["carrier"]["code"]) == str assert account["carrier"]["name"] is not None assert type(account["carrier"]["name"]) == str + + def test_multiple_accounts_of_same_carrier(self): + """DX-1077 - Multiple accounts of the same carrier.""" + accounts = stub_get_carrier_accounts() + + assert len(accounts) == 5 + assert accounts[0]["carrier"]["code"] == Carriers.UPS.value + assert accounts[0]["account_id"] != accounts[1]["account_id"] + assert accounts[1]["carrier"]["code"] == Carriers.FEDEX.value + assert accounts[2]["carrier"]["code"] == Carriers.FEDEX.value + assert accounts[3]["carrier"]["code"] == Carriers.USPS.value + assert accounts[4]["carrier"]["code"] == Carriers.STAMPS_COM.value + + for account in accounts: + assert account["account_id"].startswith("car_") + assert account["name"] is not None From cd69d4651c1b4ef5a8c9f201280bd87e194ce170 Mon Sep 17 00:00:00 2001 From: KaseyCantu Date: Thu, 10 Jun 2021 09:53:46 -0500 Subject: [PATCH 12/12] DX-1078 - Get carrier accounts server-side error https://auctane.atlassian.net/browse/DX-1078 --- tests/services/test_get_carrier_accounts.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/services/test_get_carrier_accounts.py b/tests/services/test_get_carrier_accounts.py index 20be7b7..ef251e6 100644 --- a/tests/services/test_get_carrier_accounts.py +++ b/tests/services/test_get_carrier_accounts.py @@ -1,6 +1,6 @@ """Tests for the GetCarrierAccounts service in the ShipEngine SDK.""" - -from shipengine_sdk.models import Carriers +from shipengine_sdk.errors import ClientSystemError +from shipengine_sdk.models import Carriers, ErrorCode, ErrorSource, ErrorType from ..util.test_helpers import stub_get_carrier_accounts @@ -49,3 +49,14 @@ def test_multiple_accounts_of_same_carrier(self): for account in accounts: assert account["account_id"].startswith("car_") assert account["name"] is not None + + def test_server_side_error(self) -> None: + """DX-1078 - Get carrier accounts server-side error.""" + try: + stub_get_carrier_accounts("access_worldwide") + except ClientSystemError as err: + assert err.request_id is not None + assert err.request_id.startswith("req_") is True + assert err.source == ErrorSource.SHIPENGINE.value + assert err.error_type == ErrorType.SYSTEM.value + assert err.error_code == ErrorCode.UNSPECIFIED.value