diff --git a/.gitignore b/.gitignore index ee44e23a..0f58a6d5 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ develop-eggs pip-log.txt # Unit test / coverage reports +.pytest_cache/ .coverage .tox diff --git a/mws/apis/inbound_shipments.py b/mws/apis/inbound_shipments.py index 1792cc2e..b4b395cf 100644 --- a/mws/apis/inbound_shipments.py +++ b/mws/apis/inbound_shipments.py @@ -570,9 +570,6 @@ def list_inbound_shipments(self, shipment_ids=None, shipment_statuses=None, Docs: http://docs.developer.amazonservices.com/en_US/fba_inbound/FBAInbound_ListInboundShipments.html """ - last_updated_after = utils.dt_iso_or_none(last_updated_after) - last_updated_before = utils.dt_iso_or_none(last_updated_before) - data = { 'Action': 'ListInboundShipments', 'LastUpdatedAfter': last_updated_after, @@ -605,9 +602,6 @@ def list_inbound_shipment_items(self, shipment_id=None, last_updated_after=None, Docs: http://docs.developer.amazonservices.com/en_US/fba_inbound/FBAInbound_ListInboundShipmentItems.html """ - last_updated_after = utils.dt_iso_or_none(last_updated_after) - last_updated_before = utils.dt_iso_or_none(last_updated_before) - data = { 'Action': 'ListInboundShipmentItems', 'ShipmentId': shipment_id, diff --git a/mws/apis/reports.py b/mws/apis/reports.py index dc5f4141..adfa5862 100644 --- a/mws/apis/reports.py +++ b/mws/apis/reports.py @@ -3,7 +3,7 @@ """ from __future__ import absolute_import -import mws +from ..mws import MWS from .. import utils from ..decorators import next_token_action @@ -11,7 +11,7 @@ # TODO Add Schedule enumerations as constants -class Reports(mws.MWS): +class Reports(MWS): """ Amazon MWS Reports API diff --git a/mws/mws.py b/mws/mws.py index a1e833f3..e0e3651d 100644 --- a/mws/mws.py +++ b/mws/mws.py @@ -76,16 +76,31 @@ def calc_request_description(params): """ description_items = [] for item in sorted(params.keys()): - encoded_val = quote(str(params[item]), safe='-_.~') + encoded_val = params[item] description_items.append('{}={}'.format(item, encoded_val)) return '&'.join(description_items) -def remove_empty(dict_): - """ - Returns dict_ with all empty values removed. - """ - return {k: v for k, v in dict_.items() if v} +def clean_params(params): + """Input cleanup and prevent a lot of common input mistakes.""" + # silently remove parameter where values are empty + params = {k: v for k, v in params.items() if v} + + params_enc = dict() + for key, value in params.items(): + if isinstance(value, (dict, list, set, tuple)): + message = 'expected string or datetime datatype, got {},'\ + 'for key {} and value {}'.format( + type(value), key, str(value)) + raise MWSError(message) + if isinstance(value, (datetime.datetime, datetime.date)): + value = value.isoformat() + if isinstance(value, bool): + value = str(value).lower() + value = str(value) + + params_enc[key] = quote(value, safe='-_.~') + return params_enc def remove_namespace(xml): @@ -121,6 +136,7 @@ class DictWrapper(object): # TODO create a base class for DictWrapper and DataWrapper with all the keys we expect in responses. # This will make it easier to use either class in place of each other. # Either this, or pile everything into DataWrapper and make it able to handle all cases. + def __init__(self, xml, rootkey=None): self.original = xml self.response = None @@ -142,6 +158,7 @@ class DataWrapper(object): """ Text wrapper in charge of validating the hash sent by Amazon. """ + def __init__(self, data, header): self.original = data self.response = None @@ -246,18 +263,11 @@ def make_request(self, extra_data, method="GET", **kwargs): """ Make request to Amazon MWS API with these parameters """ - # Remove all keys with an empty value because - # Amazon's MWS does not allow such a thing. - extra_data = remove_empty(extra_data) - - # convert all Python date/time objects to isoformat - for key, value in extra_data.items(): - if isinstance(value, (datetime.datetime, datetime.date)): - extra_data[key] = value.isoformat() - params = self.get_default_params() proxies = self.get_proxies() params.update(extra_data) + params = clean_params(params) + if self._test_request_params: # Testing method: return the params from this request before the request is made. return params @@ -279,7 +289,8 @@ def make_request(self, extra_data, method="GET", **kwargs): # My answer is, here i have to get the url parsed string of params in order to sign it, so # if i pass the params dict as params to request, request will repeat that step because it will need # to convert the dict to a url parsed string, so why do it twice if i can just pass the full url :). - response = request(method, url, data=kwargs.get('body', ''), headers=headers, proxies=proxies) + response = request(method, url, data=kwargs.get( + 'body', ''), headers=headers, proxies=proxies) response.raise_for_status() # When retrieving data from the response object, # be aware that response.content returns the content in bytes while response.text calls diff --git a/mws/utils.py b/mws/utils.py index 25a71e8a..57872627 100644 --- a/mws/utils.py +++ b/mws/utils.py @@ -27,6 +27,7 @@ class ObjectDict(dict): >>> a.water 'water' """ + def __init__(self, initd=None): if initd is None: initd = {} @@ -271,22 +272,6 @@ def unique_list_order_preserved(seq): return [x for x in seq if not (x in seen or seen_add(x))] -def dt_iso_or_none(dt_obj): - """ - If dt_obj is a datetime, return isoformat() - TODO: if dt_obj is a string in iso8601 already, return it back - Otherwise, return None - """ - # If d is a datetime object, format it to iso and return - if isinstance(dt_obj, datetime.datetime): - return dt_obj.isoformat() - - # TODO: if dt_obj is a string in iso8601 already, return it - - # none of the above: return None - return None - - def get_utc_timestamp(): """ Returns the current UTC timestamp in ISO-8601 format. diff --git a/tests/request_methods/test_feeds.py b/tests/request_methods/test_feeds.py index 736f128b..c3354813 100644 --- a/tests/request_methods/test_feeds.py +++ b/tests/request_methods/test_feeds.py @@ -4,7 +4,7 @@ import unittest import datetime import mws -from .utils import CommonRequestTestTools +from .utils import CommonRequestTestTools, transform_date class FeedsTestCase(unittest.TestCase, CommonRequestTestTools): @@ -12,6 +12,7 @@ class FeedsTestCase(unittest.TestCase, CommonRequestTestTools): Test cases for Feeds. """ # TODO: Add remaining methods for Feeds + def setUp(self): self.api = mws.Feeds( self.CREDENTIAL_ACCESS, @@ -33,9 +34,9 @@ def test_get_feed_submission_list(self): GetFeedSubmissionList operation """ from_date = datetime.datetime.utcnow() - from_date_stamp = from_date.isoformat() + from_date_stamp = transform_date(from_date) to_date = datetime.datetime.utcnow() - to_date_stamp = to_date.isoformat() + to_date_stamp = transform_date(to_date) feed_ids = [ '1058369303', '1228369302', @@ -61,7 +62,7 @@ def test_get_feed_submission_list(self): self.assertEqual(params['Action'], 'GetFeedSubmissionList') self.assertEqual(params['SubmittedFromDate'], from_date_stamp) self.assertEqual(params['SubmittedToDate'], to_date_stamp) - self.assertEqual(params['MaxCount'], max_count) + self.assertEqual(params['MaxCount'], str(max_count)) self.assertEqual(params['FeedSubmissionIdList.Id.1'], feed_ids[0]) self.assertEqual(params['FeedSubmissionIdList.Id.2'], feed_ids[1]) self.assertEqual(params['FeedTypeList.Type.1'], feed_types[0]) @@ -94,9 +95,9 @@ def test_get_feed_submission_count(self): GetFeedSubmissionCount operation """ from_date = datetime.datetime.utcnow() - from_date_stamp = from_date.isoformat() + from_date_stamp = transform_date(from_date) to_date = datetime.datetime.utcnow() - to_date_stamp = to_date.isoformat() + to_date_stamp = transform_date(to_date) feed_types = [ '_POST_PRODUCT_OVERRIDES_DATA_', '_POST_FLAT_FILE_FULFILLMENT_ORDER_CANCELLATION_REQUEST_DATA_', @@ -125,9 +126,9 @@ def test_cancel_feed_submissions(self): CancelFeedSubmissions operation """ from_date = datetime.datetime.utcnow() - from_date_stamp = from_date.isoformat() + from_date_stamp = transform_date(from_date) to_date = datetime.datetime.utcnow() - to_date_stamp = to_date.isoformat() + to_date_stamp = transform_date(to_date) feed_ids = [ 'SUB63kvutS', 'l8dM04jxGD', diff --git a/tests/request_methods/test_finances.py b/tests/request_methods/test_finances.py index 1724e8ba..d3c7acaf 100644 --- a/tests/request_methods/test_finances.py +++ b/tests/request_methods/test_finances.py @@ -4,7 +4,7 @@ import unittest import datetime import mws -from .utils import CommonRequestTestTools +from .utils import CommonRequestTestTools, transform_date class FinancesTestCase(unittest.TestCase, CommonRequestTestTools): @@ -12,6 +12,7 @@ class FinancesTestCase(unittest.TestCase, CommonRequestTestTools): Test cases for Finances. """ # TODO: Add remaining methods for Finances + def setUp(self): self.api = mws.Finances( self.CREDENTIAL_ACCESS, @@ -26,9 +27,7 @@ def test_list_financial_event_groups(self): ListFinancialEventGroups operation. """ created_after = datetime.datetime.utcnow() - created_after_stamp = created_after.isoformat() created_before = datetime.datetime.utcnow() - created_before_stamp = created_before.isoformat() max_results = 659 params = self.api.list_financial_event_groups( created_after=created_after, @@ -37,9 +36,11 @@ def test_list_financial_event_groups(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'ListFinancialEventGroups') - self.assertEqual(params['FinancialEventGroupStartedAfter'], created_after_stamp) - self.assertEqual(params['FinancialEventGroupStartedBefore'], created_before_stamp) - self.assertEqual(params['MaxResultsPerPage'], max_results) + self.assertEqual(params['FinancialEventGroupStartedAfter'], + transform_date(created_after)) + self.assertEqual(params['FinancialEventGroupStartedBefore'], + transform_date(created_before)) + self.assertEqual(params['MaxResultsPerPage'], str(max_results)) def test_list_financial_event_groups_by_next_token(self): """ @@ -66,9 +67,7 @@ def test_list_financial_events(self): ListFinancialEvents operation. """ posted_after = datetime.datetime.utcnow() - posted_after_stamp = posted_after.isoformat() posted_before = datetime.datetime.utcnow() - posted_before_stamp = posted_before.isoformat() amazon_order_id = '123-4567890-1234567' financial_event_group_id = '22YgYW55IGNhcm5hbCBwbGVhEXAMPLE' max_results = 156 @@ -83,9 +82,9 @@ def test_list_financial_events(self): self.assertEqual(params['Action'], 'ListFinancialEvents') self.assertEqual(params['FinancialEventGroupId'], financial_event_group_id) self.assertEqual(params['AmazonOrderId'], amazon_order_id) - self.assertEqual(params['PostedAfter'], posted_after_stamp) - self.assertEqual(params['PostedBefore'], posted_before_stamp) - self.assertEqual(params['MaxResultsPerPage'], max_results) + self.assertEqual(params['PostedAfter'], transform_date(posted_after)) + self.assertEqual(params['PostedBefore'], transform_date(posted_before)) + self.assertEqual(params['MaxResultsPerPage'], str(max_results)) def test_list_financial_events_by_next_token(self): """ diff --git a/tests/request_methods/test_inboundshipments.py b/tests/request_methods/test_inboundshipments.py index eb78d766..8042468f 100644 --- a/tests/request_methods/test_inboundshipments.py +++ b/tests/request_methods/test_inboundshipments.py @@ -6,13 +6,15 @@ import mws from mws.apis.inbound_shipments import parse_item_args from mws.mws import MWSError -from .utils import CommonRequestTestTools +from .utils import CommonRequestTestTools, transform_date, transform_bool +from .utils import transform_string class ParseItemArgsTestCase(unittest.TestCase): """ Test cases that ensure `parse_item_args` raises exceptions where appropriate. """ + def test_empty_args_list(self): """ Should raise `MWSError` for an empty set of arguments. @@ -154,6 +156,7 @@ class SetShipFromAddressTestCase(unittest.TestCase): """ Test case covering msw.InboundShipments.set_ship_from_address """ + def setUp(self): self.inbound = mws.InboundShipments('', '', '') @@ -279,6 +282,7 @@ class FBAShipmentHandlingTestCase(unittest.TestCase, CommonRequestTestTools): Test cases for InboundShipments involving FBA shipment handling. These cases require `from_address` to be set, while others do not. """ + def setUp(self): self.addr = { 'name': 'Roland Deschain', @@ -331,18 +335,24 @@ def test_create_inbound_shipment_plan(self): self.assert_common_params(params) self.assertEqual(params['Action'], 'CreateInboundShipmentPlan') self.assertEqual(params['ShipToCountryCode'], country_code) - self.assertEqual(params['ShipToCountrySubdivisionCode'], subdivision_code) + self.assertEqual(params['ShipToCountrySubdivisionCode'], transform_string(subdivision_code)) self.assertEqual(params['LabelPrepPreference'], label_preference) # from_address expanded - self.assertEqual(params['ShipFromAddress.Name'], self.addr['name']) - self.assertEqual(params['ShipFromAddress.AddressLine1'], self.addr['address_1']) - self.assertEqual(params['ShipFromAddress.City'], self.addr['city']) - self.assertEqual(params['ShipFromAddress.CountryCode'], self.addr['country']) + self.assertEqual(params['ShipFromAddress.Name'], transform_string(self.addr['name'])) + self.assertEqual(params['ShipFromAddress.AddressLine1'], + transform_string(self.addr['address_1'])) + self.assertEqual(params['ShipFromAddress.City'], transform_string(self.addr['city'])) + self.assertEqual(params['ShipFromAddress.CountryCode'], + transform_string(self.addr['country'])) # item data - self.assertEqual(params['InboundShipmentPlanRequestItems.member.1.SellerSKU'], items[0]['sku']) - self.assertEqual(params['InboundShipmentPlanRequestItems.member.1.Quantity'], items[0]['quantity']) - self.assertEqual(params['InboundShipmentPlanRequestItems.member.2.SellerSKU'], items[1]['sku']) - self.assertEqual(params['InboundShipmentPlanRequestItems.member.2.Quantity'], items[1]['quantity']) + self.assertEqual( + params['InboundShipmentPlanRequestItems.member.1.SellerSKU'], items[0]['sku']) + self.assertEqual( + params['InboundShipmentPlanRequestItems.member.1.Quantity'], str(items[0]['quantity'])) + self.assertEqual( + params['InboundShipmentPlanRequestItems.member.2.SellerSKU'], items[1]['sku']) + self.assertEqual( + params['InboundShipmentPlanRequestItems.member.2.Quantity'], str(items[1]['quantity'])) def test_create_inbound_shipment_exceptions(self): """ @@ -411,22 +421,35 @@ def test_create_inbound_shipment(self): self.assert_common_params(params) self.assertEqual(params['Action'], 'CreateInboundShipment') self.assertEqual(params['ShipmentId'], shipment_id) - self.assertEqual(params['InboundShipmentHeader.ShipmentName'], shipment_name) - self.assertEqual(params['InboundShipmentHeader.DestinationFulfillmentCenterId'], destination) + self.assertEqual(params['InboundShipmentHeader.ShipmentName'], + transform_string(shipment_name)) + self.assertEqual( + params['InboundShipmentHeader.DestinationFulfillmentCenterId'], destination) self.assertEqual(params['InboundShipmentHeader.LabelPrepPreference'], label_preference) - self.assertEqual(params['InboundShipmentHeader.AreCasesRequired'], case_required) + self.assertEqual(params['InboundShipmentHeader.AreCasesRequired'], + transform_bool(case_required)) self.assertEqual(params['InboundShipmentHeader.ShipmentStatus'], shipment_status) - self.assertEqual(params['InboundShipmentHeader.IntendedBoxContentsSource'], box_contents_source) + self.assertEqual( + params['InboundShipmentHeader.IntendedBoxContentsSource'], box_contents_source) # from_address - self.assertEqual(params['InboundShipmentHeader.ShipFromAddress.Name'], self.addr['name']) - self.assertEqual(params['InboundShipmentHeader.ShipFromAddress.AddressLine1'], self.addr['address_1']) - self.assertEqual(params['InboundShipmentHeader.ShipFromAddress.City'], self.addr['city']) - self.assertEqual(params['InboundShipmentHeader.ShipFromAddress.CountryCode'], self.addr['country']) + self.assertEqual(params['InboundShipmentHeader.ShipFromAddress.Name'], + transform_string(self.addr['name'])) + self.assertEqual( + params['InboundShipmentHeader.ShipFromAddress.AddressLine1'], + transform_string(self.addr['address_1'])) + self.assertEqual( + params['InboundShipmentHeader.ShipFromAddress.City'], + transform_string(self.addr['city'])) + self.assertEqual( + params['InboundShipmentHeader.ShipFromAddress.CountryCode'], + transform_string(self.addr['country'])) # item data self.assertEqual(params['InboundShipmentItems.member.1.SellerSKU'], items[0]['sku']) - self.assertEqual(params['InboundShipmentItems.member.1.QuantityShipped'], items[0]['quantity']) + self.assertEqual( + params['InboundShipmentItems.member.1.QuantityShipped'], str(items[0]['quantity'])) self.assertEqual(params['InboundShipmentItems.member.2.SellerSKU'], items[1]['sku']) - self.assertEqual(params['InboundShipmentItems.member.2.QuantityShipped'], items[1]['quantity']) + self.assertEqual( + params['InboundShipmentItems.member.2.QuantityShipped'], str(items[1]['quantity'])) def test_update_inbound_shipment_exceptions(self): """ @@ -488,22 +511,35 @@ def test_update_inbound_shipment(self): self.assert_common_params(params_1) self.assertEqual(params_1['Action'], 'UpdateInboundShipment') self.assertEqual(params_1['ShipmentId'], shipment_id) - self.assertEqual(params_1['InboundShipmentHeader.ShipmentName'], shipment_name) - self.assertEqual(params_1['InboundShipmentHeader.DestinationFulfillmentCenterId'], destination) + self.assertEqual(params_1['InboundShipmentHeader.ShipmentName'], + transform_string(shipment_name)) + self.assertEqual( + params_1['InboundShipmentHeader.DestinationFulfillmentCenterId'], destination) self.assertEqual(params_1['InboundShipmentHeader.LabelPrepPreference'], label_preference) - self.assertEqual(params_1['InboundShipmentHeader.AreCasesRequired'], case_required) + self.assertEqual(params_1['InboundShipmentHeader.AreCasesRequired'], + transform_bool(case_required)) self.assertEqual(params_1['InboundShipmentHeader.ShipmentStatus'], shipment_status) - self.assertEqual(params_1['InboundShipmentHeader.IntendedBoxContentsSource'], box_contents_source) + self.assertEqual( + params_1['InboundShipmentHeader.IntendedBoxContentsSource'], box_contents_source) # from_address - self.assertEqual(params_1['InboundShipmentHeader.ShipFromAddress.Name'], self.addr['name']) - self.assertEqual(params_1['InboundShipmentHeader.ShipFromAddress.AddressLine1'], self.addr['address_1']) - self.assertEqual(params_1['InboundShipmentHeader.ShipFromAddress.City'], self.addr['city']) - self.assertEqual(params_1['InboundShipmentHeader.ShipFromAddress.CountryCode'], self.addr['country']) + self.assertEqual( + params_1['InboundShipmentHeader.ShipFromAddress.Name'], + transform_string(self.addr['name'])) + self.assertEqual( + params_1['InboundShipmentHeader.ShipFromAddress.AddressLine1'], + transform_string(self.addr['address_1'])) + self.assertEqual( + params_1['InboundShipmentHeader.ShipFromAddress.City'], + transform_string(self.addr['city'])) + self.assertEqual( + params_1['InboundShipmentHeader.ShipFromAddress.CountryCode'], self.addr['country']) # item data self.assertEqual(params_1['InboundShipmentItems.member.1.SellerSKU'], items[0]['sku']) - self.assertEqual(params_1['InboundShipmentItems.member.1.QuantityShipped'], items[0]['quantity']) + self.assertEqual( + params_1['InboundShipmentItems.member.1.QuantityShipped'], str(items[0]['quantity'])) self.assertEqual(params_1['InboundShipmentItems.member.2.SellerSKU'], items[1]['sku']) - self.assertEqual(params_1['InboundShipmentItems.member.2.QuantityShipped'], items[1]['quantity']) + self.assertEqual( + params_1['InboundShipmentItems.member.2.QuantityShipped'], str(items[1]['quantity'])) # Additional case: no items required. Params should have no Items keys if not provided params_2 = self.api.update_inbound_shipment( shipment_id=shipment_id, @@ -517,17 +553,26 @@ def test_update_inbound_shipment(self): self.assert_common_params(params_1) self.assertEqual(params_2['Action'], 'UpdateInboundShipment') self.assertEqual(params_2['ShipmentId'], shipment_id) - self.assertEqual(params_2['InboundShipmentHeader.ShipmentName'], shipment_name) - self.assertEqual(params_2['InboundShipmentHeader.DestinationFulfillmentCenterId'], destination) + self.assertEqual(params_2['InboundShipmentHeader.ShipmentName'], + transform_string(shipment_name)) + self.assertEqual( + params_2['InboundShipmentHeader.DestinationFulfillmentCenterId'], destination) self.assertEqual(params_2['InboundShipmentHeader.LabelPrepPreference'], label_preference) - self.assertEqual(params_2['InboundShipmentHeader.AreCasesRequired'], case_required) + self.assertEqual(params_2['InboundShipmentHeader.AreCasesRequired'], + transform_bool(case_required)) self.assertEqual(params_2['InboundShipmentHeader.ShipmentStatus'], shipment_status) - self.assertEqual(params_2['InboundShipmentHeader.IntendedBoxContentsSource'], box_contents_source) + self.assertEqual( + params_2['InboundShipmentHeader.IntendedBoxContentsSource'], box_contents_source) # from_address - self.assertEqual(params_2['InboundShipmentHeader.ShipFromAddress.Name'], self.addr['name']) - self.assertEqual(params_2['InboundShipmentHeader.ShipFromAddress.AddressLine1'], self.addr['address_1']) - self.assertEqual(params_2['InboundShipmentHeader.ShipFromAddress.City'], self.addr['city']) - self.assertEqual(params_2['InboundShipmentHeader.ShipFromAddress.CountryCode'], self.addr['country']) + self.assertEqual(params_2['InboundShipmentHeader.ShipFromAddress.Name'], + transform_string(self.addr['name'])) + self.assertEqual( + params_2['InboundShipmentHeader.ShipFromAddress.AddressLine1'], + transform_string(self.addr['address_1'])) + self.assertEqual(params_2['InboundShipmentHeader.ShipFromAddress.City'], + transform_string(self.addr['city'])) + self.assertEqual( + params_2['InboundShipmentHeader.ShipFromAddress.CountryCode'], self.addr['country']) # items keys should not be present param_item_keys = [x for x in params_2.keys() if x.startswith('InboundShipmentItems')] # list should be empty, because no keys should be present @@ -539,6 +584,7 @@ class InboundShipmentsRequestsTestCase(unittest.TestCase, CommonRequestTestTools Test cases for InboundShipments requests that do not involve FBA shipment handling and do not require `from_address` to be set. """ + def setUp(self): self.api = mws.InboundShipments( self.CREDENTIAL_ACCESS, @@ -631,7 +677,7 @@ def test_confirm_preorder(self): self.assert_common_params(params) self.assertEqual(params['Action'], 'ConfirmPreorder') self.assertEqual(params['ShipmentId'], shipment_id) - self.assertEqual(params['NeedByDate'], need_by_date.isoformat()) + self.assertEqual(params['NeedByDate'], transform_date(need_by_date)) def test_get_prep_instructions_for_sku(self): """ @@ -777,7 +823,7 @@ def test_get_package_labels(self): self.assertEqual(params['Action'], 'GetPackageLabels') self.assertEqual(params['ShipmentId'], shipment_id) self.assertEqual(params['PageType'], page_type) - self.assertEqual(params['NumberOfPackages'], num_labels) + self.assertEqual(params['NumberOfPackages'], str(num_labels)) def test_get_unique_package_labels(self): """ @@ -830,7 +876,7 @@ def test_get_pallet_labels(self): self.assertEqual(params['Action'], 'GetPalletLabels') self.assertEqual(params['ShipmentId'], shipment_id) self.assertEqual(params['PageType'], page_type) - self.assertEqual(params['NumberOfPallets'], num_labels) + self.assertEqual(params['NumberOfPallets'], str(num_labels)) def test_get_bill_of_lading(self): """ @@ -866,8 +912,8 @@ def test_list_inbound_shipments(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'ListInboundShipments') - self.assertEqual(params['LastUpdatedBefore'], last_updated_before.isoformat()) - self.assertEqual(params['LastUpdatedAfter'], last_updated_after.isoformat()) + self.assertEqual(params['LastUpdatedBefore'], transform_date(last_updated_before)) + self.assertEqual(params['LastUpdatedAfter'], transform_date(last_updated_after)) self.assertEqual(params['ShipmentStatusList.member.1'], shipment_statuses[0]) self.assertEqual(params['ShipmentStatusList.member.2'], shipment_statuses[1]) self.assertEqual(params['ShipmentIdList.member.1'], shipment_ids[0]) @@ -908,8 +954,8 @@ def test_list_inbound_shipment_items(self): self.assert_common_params(params) self.assertEqual(params['Action'], 'ListInboundShipmentItems') self.assertEqual(params['ShipmentId'], shipment_id) - self.assertEqual(params['LastUpdatedBefore'], last_updated_before.isoformat()) - self.assertEqual(params['LastUpdatedAfter'], last_updated_after.isoformat()) + self.assertEqual(params['LastUpdatedBefore'], transform_date(last_updated_before)) + self.assertEqual(params['LastUpdatedAfter'], transform_date(last_updated_after)) def test_list_inbound_shipment_items_by_next_token(self): """ diff --git a/tests/request_methods/test_inventory.py b/tests/request_methods/test_inventory.py index 59a9d9a1..83572c1f 100644 --- a/tests/request_methods/test_inventory.py +++ b/tests/request_methods/test_inventory.py @@ -4,13 +4,14 @@ import unittest import datetime import mws -from .utils import CommonRequestTestTools +from .utils import CommonRequestTestTools, transform_date class InventoryTestCase(unittest.TestCase, CommonRequestTestTools): """ Test cases for Inventory. """ + def setUp(self): self.api = mws.Inventory( self.CREDENTIAL_ACCESS, @@ -25,13 +26,12 @@ def test_list_inventory_supply(self): ListInventorySupply operation """ now = datetime.datetime.utcnow() - now_timestamp = now.isoformat() skus = ['thing1', 'thing2'] response_group = 'Detailed' params = self.api.list_inventory_supply(skus, now, response_group=response_group) self.assert_common_params(params) self.assertEqual(params['Action'], 'ListInventorySupply') - self.assertEqual(params['QueryStartDateTime'], now_timestamp) + self.assertEqual(params['QueryStartDateTime'], transform_date(now)) self.assertEqual(params['ResponseGroup'], 'Detailed') self.assertEqual(params['SellerSkus.member.1'], 'thing1') self.assertEqual(params['SellerSkus.member.2'], 'thing2') diff --git a/tests/request_methods/test_orders.py b/tests/request_methods/test_orders.py index 1b2a72b0..e3707f50 100644 --- a/tests/request_methods/test_orders.py +++ b/tests/request_methods/test_orders.py @@ -4,7 +4,7 @@ import datetime import unittest import mws -from .utils import CommonRequestTestTools +from .utils import CommonRequestTestTools, transform_date class OrdersTestCase(unittest.TestCase, CommonRequestTestTools): @@ -12,6 +12,7 @@ class OrdersTestCase(unittest.TestCase, CommonRequestTestTools): Test cases for Orders. """ # TODO: Add remaining methods for Orders + def setUp(self): self.api = mws.Orders( self.CREDENTIAL_ACCESS, @@ -26,13 +27,9 @@ def test_list_orders(self): ListOrders operation. """ created_after = datetime.datetime.utcnow() - created_after_stamp = created_after.isoformat() created_before = datetime.datetime.utcnow() + datetime.timedelta(hours=1) - created_before_stamp = created_before.isoformat() last_updated_after = datetime.datetime.utcnow() + datetime.timedelta(hours=2) - last_updated_after_stamp = last_updated_after.isoformat() last_updated_before = datetime.datetime.utcnow() + datetime.timedelta(hours=3) - last_updated_before_stamp = last_updated_before.isoformat() max_results = 83 marketplace_ids = [ 'DV1t7ZOrjM', @@ -72,13 +69,13 @@ def test_list_orders(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'ListOrders') - self.assertEqual(params['CreatedAfter'], created_after_stamp) - self.assertEqual(params['CreatedBefore'], created_before_stamp) - self.assertEqual(params['LastUpdatedAfter'], last_updated_after_stamp) - self.assertEqual(params['LastUpdatedBefore'], last_updated_before_stamp) - self.assertEqual(params['BuyerEmail'], buyer_email) + self.assertEqual(params['CreatedAfter'], transform_date(created_after)) + self.assertEqual(params['CreatedBefore'], transform_date(created_before)) + self.assertEqual(params['LastUpdatedAfter'], transform_date(last_updated_after)) + self.assertEqual(params['LastUpdatedBefore'], transform_date(last_updated_before)) + self.assertEqual(params['BuyerEmail'], 'dudley.do.right%40example.com') self.assertEqual(params['SellerOrderId'], seller_order_id) - self.assertEqual(params['MaxResultsPerPage'], max_results) + self.assertEqual(params['MaxResultsPerPage'], str(max_results)) self.assertEqual(params['OrderStatus.Status.1'], order_statuses[0]) self.assertEqual(params['OrderStatus.Status.2'], order_statuses[1]) self.assertEqual(params['MarketplaceId.Id.1'], marketplace_ids[0]) diff --git a/tests/request_methods/test_outboundshipments.py b/tests/request_methods/test_outboundshipments.py index d7b4757b..9859fc50 100644 --- a/tests/request_methods/test_outboundshipments.py +++ b/tests/request_methods/test_outboundshipments.py @@ -11,6 +11,7 @@ class OutboundShipmentsTestCase(unittest.TestCase, CommonRequestTestTools): Test cases for OutboundShipments. """ # TODO: Add remaining methods for OutboundShipments + def setUp(self): self.api = mws.OutboundShipments( self.CREDENTIAL_ACCESS, diff --git a/tests/request_methods/test_products.py b/tests/request_methods/test_products.py index 57d55730..1d924adb 100644 --- a/tests/request_methods/test_products.py +++ b/tests/request_methods/test_products.py @@ -3,7 +3,7 @@ """ import unittest import mws -from .utils import CommonRequestTestTools +from .utils import CommonRequestTestTools, transform_bool, transform_string class ProductsTestCase(unittest.TestCase, CommonRequestTestTools): @@ -11,6 +11,7 @@ class ProductsTestCase(unittest.TestCase, CommonRequestTestTools): Test cases for Products. """ # TODO: Add remaining methods for Products + def setUp(self): self.api = mws.Products( self.CREDENTIAL_ACCESS, @@ -34,8 +35,8 @@ def test_list_matching_products(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'ListMatchingProducts') - self.assertEqual(params['MarketplaceId'], marketplace_id) - self.assertEqual(params['Query'], query) + self.assertEqual(params['MarketplaceId'], transform_string(marketplace_id)) + self.assertEqual(params['Query'], transform_string(query)) self.assertEqual(params['QueryContextId'], context_id) def test_get_matching_product(self): @@ -53,7 +54,7 @@ def test_get_matching_product(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'GetMatchingProduct') - self.assertEqual(params['MarketplaceId'], marketplace_id) + self.assertEqual(params['MarketplaceId'], transform_string(marketplace_id)) self.assertEqual(params['ASINList.ASIN.1'], asins[0]) self.assertEqual(params['ASINList.ASIN.2'], asins[1]) @@ -74,7 +75,7 @@ def test_get_matching_product_for_id(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'GetMatchingProductForId') - self.assertEqual(params['MarketplaceId'], marketplace_id) + self.assertEqual(params['MarketplaceId'], transform_string(marketplace_id)) self.assertEqual(params['IdType'], type_) self.assertEqual(params['IdList.Id.1'], ids[0]) self.assertEqual(params['IdList.Id.2'], ids[1]) @@ -94,7 +95,7 @@ def test_get_competitive_pricing_for_sku(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'GetCompetitivePricingForSKU') - self.assertEqual(params['MarketplaceId'], marketplace_id) + self.assertEqual(params['MarketplaceId'], transform_string(marketplace_id)) self.assertEqual(params['SellerSKUList.SellerSKU.1'], skus[0]) self.assertEqual(params['SellerSKUList.SellerSKU.2'], skus[1]) @@ -113,7 +114,7 @@ def test_get_competitive_pricing_for_asin(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'GetCompetitivePricingForASIN') - self.assertEqual(params['MarketplaceId'], marketplace_id) + self.assertEqual(params['MarketplaceId'], transform_string(marketplace_id)) self.assertEqual(params['ASINList.ASIN.1'], asins[0]) self.assertEqual(params['ASINList.ASIN.2'], asins[1]) @@ -137,11 +138,11 @@ def test_get_lowest_offer_listings_for_sku(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'GetLowestOfferListingsForSKU') - self.assertEqual(params['MarketplaceId'], marketplace_id) - self.assertEqual(params['ItemCondition'], condition) + self.assertEqual(params['MarketplaceId'], transform_string(marketplace_id)) + self.assertEqual(params['ItemCondition'], transform_string(condition)) # TODO when this fails later after "clean" implemented, test against str conversion instead # (use commented `exclude_me_str` above) - self.assertEqual(params['ExcludeMe'], exclude_me) + self.assertEqual(params['ExcludeMe'], 'true') self.assertEqual(params['SellerSKUList.SellerSKU.1'], skus[0]) self.assertEqual(params['SellerSKUList.SellerSKU.2'], skus[1]) @@ -165,11 +166,11 @@ def test_get_lowest_offer_listings_for_asin(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'GetLowestOfferListingsForASIN') - self.assertEqual(params['MarketplaceId'], marketplace_id) - self.assertEqual(params['ItemCondition'], condition) + self.assertEqual(params['MarketplaceId'], transform_string(marketplace_id)) + self.assertEqual(params['ItemCondition'], transform_string(condition)) # TODO when this fails later after "clean" implemented, test against str conversion instead # (use commented `exclude_me_str` above) - self.assertEqual(params['ExcludeMe'], exclude_me) + self.assertEqual(params['ExcludeMe'], transform_bool(exclude_me)) self.assertEqual(params['ASINList.ASIN.1'], asins[0]) self.assertEqual(params['ASINList.ASIN.2'], asins[1]) @@ -190,11 +191,11 @@ def test_get_lowest_priced_offers_for_sku(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'GetLowestPricedOffersForSKU') - self.assertEqual(params['MarketplaceId'], marketplace_id) + self.assertEqual(params['MarketplaceId'], transform_string(marketplace_id)) self.assertEqual(params['ItemCondition'], condition) # TODO when this fails later after "clean" implemented, test against str conversion instead # (use commented `exclude_me_str` above) - self.assertEqual(params['ExcludeMe'], exclude_me) + self.assertEqual(params['ExcludeMe'], transform_bool(exclude_me)) self.assertEqual(params['SellerSKU'], sku) def test_get_lowest_priced_offers_for_asin(self): @@ -214,11 +215,11 @@ def test_get_lowest_priced_offers_for_asin(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'GetLowestPricedOffersForASIN') - self.assertEqual(params['MarketplaceId'], marketplace_id) - self.assertEqual(params['ItemCondition'], condition) + self.assertEqual(params['MarketplaceId'], transform_string(marketplace_id)) + self.assertEqual(params['ItemCondition'], transform_string(condition)) # TODO when this fails later after "clean" implemented, test against str conversion instead # (use commented `exclude_me_str` above) - self.assertEqual(params['ExcludeMe'], exclude_me) + self.assertEqual(params['ExcludeMe'], 'true') self.assertEqual(params['ASIN'], asin) # def test_get_my_fees_estimate(self): @@ -244,8 +245,8 @@ def test_get_my_price_for_sku(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'GetMyPriceForSKU') - self.assertEqual(params['MarketplaceId'], marketplace_id) - self.assertEqual(params['ItemCondition'], condition) + self.assertEqual(params['MarketplaceId'], transform_string(marketplace_id)) + self.assertEqual(params['ItemCondition'], transform_string(condition)) self.assertEqual(params['SellerSKUList.SellerSKU.1'], skus[0]) self.assertEqual(params['SellerSKUList.SellerSKU.2'], skus[1]) @@ -266,8 +267,8 @@ def test_get_my_price_for_asin(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'GetMyPriceForASIN') - self.assertEqual(params['MarketplaceId'], marketplace_id) - self.assertEqual(params['ItemCondition'], condition) + self.assertEqual(params['MarketplaceId'], transform_string(marketplace_id)) + self.assertEqual(params['ItemCondition'], transform_string(condition)) self.assertEqual(params['ASINList.ASIN.1'], asins[0]) self.assertEqual(params['ASINList.ASIN.2'], asins[1]) @@ -283,7 +284,7 @@ def test_get_product_categories_for_sku(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'GetProductCategoriesForSKU') - self.assertEqual(params['MarketplaceId'], marketplace_id) + self.assertEqual(params['MarketplaceId'], transform_string(marketplace_id)) self.assertEqual(params['SellerSKU'], sku) def test_get_product_categories_for_asin(self): @@ -298,5 +299,5 @@ def test_get_product_categories_for_asin(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'GetProductCategoriesForASIN') - self.assertEqual(params['MarketplaceId'], marketplace_id) + self.assertEqual(params['MarketplaceId'], transform_string(marketplace_id)) self.assertEqual(params['ASIN'], asin) diff --git a/tests/request_methods/test_recommendations.py b/tests/request_methods/test_recommendations.py index 3a049fc1..cfbb6acd 100644 --- a/tests/request_methods/test_recommendations.py +++ b/tests/request_methods/test_recommendations.py @@ -3,7 +3,7 @@ """ import unittest import mws -from .utils import CommonRequestTestTools +from .utils import CommonRequestTestTools, transform_string class RecommendationsTestCase(unittest.TestCase, CommonRequestTestTools): @@ -11,6 +11,7 @@ class RecommendationsTestCase(unittest.TestCase, CommonRequestTestTools): Test cases for Recommendations. """ # TODO: Add remaining methods for Recommendations + def setUp(self): self.api = mws.Recommendations( self.CREDENTIAL_ACCESS, @@ -43,7 +44,8 @@ def test_list_recommendations(self): self.assert_common_params(params) self.assertEqual(params['Action'], 'ListRecommendations') self.assertEqual(params['MarketplaceId'], marketplace_id) - self.assertEqual(params['RecommendationCategory'], recommendation_category) + self.assertEqual(params['RecommendationCategory'], + transform_string(recommendation_category)) def test_list_recommendations_by_next_token(self): """ diff --git a/tests/request_methods/test_reports.py b/tests/request_methods/test_reports.py index f64e68b2..1c556548 100644 --- a/tests/request_methods/test_reports.py +++ b/tests/request_methods/test_reports.py @@ -4,7 +4,7 @@ import datetime import unittest import mws -from .utils import CommonRequestTestTools +from .utils import CommonRequestTestTools, transform_date, transform_bool class ReportsTestCase(unittest.TestCase, CommonRequestTestTools): @@ -12,6 +12,7 @@ class ReportsTestCase(unittest.TestCase, CommonRequestTestTools): Test cases for Reports. """ # TODO: Add remaining methods for Reports + def setUp(self): self.api = mws.Reports( self.CREDENTIAL_ACCESS, @@ -26,8 +27,8 @@ def test_request_report(self): RequestReport operation. """ report_type = '_GET_FLAT_FILE_OPEN_LISTINGS_DATA_' - start_date = datetime.datetime.utcnow() - end_date = datetime.datetime.utcnow() + datetime.timedelta(hours=1) + start_date = datetime.datetime(2018, 4, 30, 22, 59, 59) + end_date = datetime.datetime(2018, 4, 30, 23, 59, 59) marketplace_ids = [ 'iQzBCmf1y3', 'wH9q0CiEMp', @@ -38,14 +39,35 @@ def test_request_report(self): end_date=end_date, marketplace_ids=marketplace_ids, ) + self.assert_common_params(params) self.assertEqual(params['Action'], 'RequestReport') self.assertEqual(params['ReportType'], report_type) - self.assertEqual(params['StartDate'], start_date.isoformat()) - self.assertEqual(params['EndDate'], end_date.isoformat()) + self.assertEqual(params['StartDate'], '2018-04-30T22%3A59%3A59') + self.assertEqual(params['EndDate'], '2018-04-30T23%3A59%3A59') self.assertEqual(params['MarketplaceIdList.Id.1'], marketplace_ids[0]) self.assertEqual(params['MarketplaceIdList.Id.2'], marketplace_ids[1]) + def test_parameter_error(self): + """ + RequestReport wrong parameter + """ + # list will throw error + report_type = ['_GET_FLAT_FILE_OPEN_LISTINGS_DATA_'] + start_date = datetime.datetime(2018, 4, 30, 22, 59, 59) + end_date = datetime.datetime(2018, 4, 30, 23, 59, 59) + marketplace_ids = [ + 'iQzBCmf1y3', + 'wH9q0CiEMp', + ] + with self.assertRaises(mws.MWSError): + self.api.request_report( + report_type=report_type, + start_date=start_date, + end_date=end_date, + marketplace_ids=marketplace_ids, + ) + def test_get_report_request_list(self): """ GetReportRequestList operation. @@ -75,9 +97,9 @@ def test_get_report_request_list(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'GetReportRequestList') - self.assertEqual(params['MaxCount'], max_count) - self.assertEqual(params['RequestedFromDate'], from_date.isoformat()) - self.assertEqual(params['RequestedToDate'], to_date.isoformat()) + self.assertEqual(params['MaxCount'], str(max_count)) + self.assertEqual(params['RequestedFromDate'], transform_date(from_date)) + self.assertEqual(params['RequestedToDate'], transform_date(to_date)) self.assertEqual(params['ReportRequestIdList.Id.1'], request_ids[0]) self.assertEqual(params['ReportRequestIdList.Id.2'], request_ids[1]) self.assertEqual(params['ReportTypeList.Type.1'], report_types[0]) @@ -127,8 +149,8 @@ def test_get_report_request_count(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'GetReportRequestCount') - self.assertEqual(params['RequestedFromDate'], from_date.isoformat()) - self.assertEqual(params['RequestedToDate'], to_date.isoformat()) + self.assertEqual(params['RequestedFromDate'], transform_date(from_date)) + self.assertEqual(params['RequestedToDate'], transform_date(to_date)) self.assertEqual(params['ReportTypeList.Type.1'], report_types[0]) self.assertEqual(params['ReportTypeList.Type.2'], report_types[1]) self.assertEqual(params['ReportProcessingStatusList.Status.1'], processing_statuses[0]) @@ -160,10 +182,10 @@ def test_get_report_list(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'GetReportList') - self.assertEqual(params['Acknowledged'], acknowledged) - self.assertEqual(params['AvailableFromDate'], from_date.isoformat()) - self.assertEqual(params['AvailableToDate'], to_date.isoformat()) - self.assertEqual(params['MaxCount'], max_count) + self.assertEqual(params['Acknowledged'], transform_bool(acknowledged)) + self.assertEqual(params['AvailableFromDate'], transform_date(from_date)) + self.assertEqual(params['AvailableToDate'], transform_date(to_date)) + self.assertEqual(params['MaxCount'], str(max_count)) self.assertEqual(params['ReportRequestIdList.Id.1'], request_ids[0]) self.assertEqual(params['ReportRequestIdList.Id.2'], request_ids[1]) self.assertEqual(params['ReportTypeList.Type.1'], report_types[0]) @@ -208,9 +230,9 @@ def test_get_report_count(self): ) self.assert_common_params(params) self.assertEqual(params['Action'], 'GetReportCount') - self.assertEqual(params['Acknowledged'], acknowledged) - self.assertEqual(params['AvailableFromDate'], from_date.isoformat()) - self.assertEqual(params['AvailableToDate'], to_date.isoformat()) + self.assertEqual(params['Acknowledged'], transform_bool(acknowledged)) + self.assertEqual(params['AvailableFromDate'], transform_date(from_date)) + self.assertEqual(params['AvailableToDate'], transform_date(to_date)) self.assertEqual(params['ReportTypeList.Type.1'], report_types[0]) self.assertEqual(params['ReportTypeList.Type.2'], report_types[1]) diff --git a/tests/request_methods/utils.py b/tests/request_methods/utils.py index 16139db1..74e45583 100644 --- a/tests/request_methods/utils.py +++ b/tests/request_methods/utils.py @@ -3,6 +3,11 @@ """ import datetime +try: + from urllib.parse import quote +except ImportError: + from urllib import quote + class CommonRequestTestTools(object): CREDENTIAL_ACCESS = 'cred_access' @@ -22,13 +27,27 @@ def assert_common_params(self, params): # If test fails here, check that method. self.assertEqual(params['SignatureMethod'], 'HmacSHA256') self.assertEqual(params['SignatureVersion'], '2') - isoformat_str = "%Y-%m-%dT%H:%M:%S" + isoformat_str = "%Y-%m-%dT%H%%3A%M%%3A%S" try: datetime.datetime.strptime(params['Timestamp'], isoformat_str) except ValueError: - self.fail("Timestamp expected an ISO-8601 datetime string with format [YYYY-MM-DDTHH:MM:SS].") + self.fail( + "Timestamp expected an ISO-8601 datetime string url encoded" + " with format [YYYY-MM-DDTHH%3AMM%3ASS].") def test_service_status(self): response = self.api.get_service_status() # Only key we care about here is GetServiceStatus self.assertEqual(response['Action'], 'GetServiceStatus') + + +def transform_string(s): + return quote(s, safe='-_.~') + + +def transform_bool(b): + return str(b).lower() + + +def transform_date(date): + return quote(date.isoformat(), safe='-_.~') diff --git a/tests/test_utils.py b/tests/test_utils.py index 6b716da3..0f181dc3 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -11,7 +11,7 @@ def test_calc_request_description(access_key, account_id): 'AWSAccessKeyId': access_key, 'Markets': account_id, 'SignatureVersion': '2', - 'Timestamp': '2017-08-12T19:40:35Z', + 'Timestamp': '2017-08-12T19%3A40%3A35Z', 'Version': '2017-01-01', 'SignatureMethod': 'HmacSHA256', })