def test_fulfillment_success(self): """Verify that the task exits without an error when fulfillment succeeds.""" httpretty.register_uri(httpretty.PUT, self.API_URL, status=200, body={}) result = fulfill_order.delay(self.ORDER_NUMBER).get() self.assertIsNone(result) # Validate the value of the HTTP Authorization header. last_request = httpretty.last_request() token = last_request.headers.get('authorization').split()[1] payload = jwt.decode(token, get_configuration('JWT_SECRET_KEY')) self.assertEqual(payload['username'], get_configuration('ECOMMERCE_SERVICE_USERNAME'))
def test_fulfillment_success(self): """Verify that the task exits without an error when fulfillment succeeds.""" responses.add(responses.PUT, self.API_URL, status=200, body="{}") result = fulfill_order.delay(self.ORDER_NUMBER).get() self.assertIsNone(result) # Validate the value of the HTTP Authorization header. last_request = responses.calls[-1].request token = last_request.headers.get('authorization').split()[1] payload = jwt.decode(token, get_configuration('JWT_SECRET_KEY')) self.assertEqual(payload['username'], get_configuration('ECOMMERCE_SERVICE_USERNAME'))
def fulfill_order(self, order_number): """Fulfills an order. Arguments: order_number (str): Order number indicating which order to fulfill. Returns: None """ ecommerce_api_root = get_configuration('ECOMMERCE_API_ROOT') max_fulfillment_retries = get_configuration('MAX_FULFILLMENT_RETRIES') signing_key = get_configuration('JWT_SECRET_KEY') issuer = get_configuration('JWT_ISSUER') service_username = get_configuration('ECOMMERCE_SERVICE_USERNAME') api = EcommerceApiClient(ecommerce_api_root, signing_key=signing_key, issuer=issuer, username=service_username) try: logger.info('Requesting fulfillment of order [%s].', order_number) api.orders(order_number).fulfill.put() except exceptions.HttpClientError as exc: status_code = exc.response.status_code if status_code == 406: # The order is not fulfillable. Therefore, it must be complete. logger.info('Order [%s] has already been fulfilled. Ignoring.', order_number) raise Ignore() else: # Unknown client error. Re-raise the exception. logger.exception('Fulfillment of order [%s] failed.', order_number) raise exc except (exceptions.HttpServerError, exceptions.Timeout) as exc: # Fulfillment failed. Retry with exponential backoff until fulfillment # succeeds or the retry limit is reached. If the retry limit is exceeded, # the exception is re-raised. retries = self.request.retries if retries == max_fulfillment_retries: logger.exception('Fulfillment of order [%s] failed. Giving up.', order_number) else: logger.warning('Fulfillment of order [%s] failed. Retrying.', order_number) countdown = 2**retries raise self.retry(exc=exc, countdown=countdown, max_retries=max_fulfillment_retries)
def test_update_course_upgrade_complete_site(self, mock_sailthru_api_post, mock_sailthru_api_get, mock_sailthru_purchase): """test upgrade complete with site code""" # create mocked Sailthru API responses mock_sailthru_api_post.return_value = MockSailthruResponse({'ok': True}) mock_sailthru_api_get.return_value = MockSailthruResponse({'vars': {'upgrade_deadline_verified': '2020-03-12'}}) mock_sailthru_purchase.return_value = MockSailthruResponse({'ok': True}) # test upgrade complete with site code with patch('ecommerce_worker.configuration.test.SITE_OVERRIDES', get_configuration('TEST_SITE_OVERRIDES')): update_course_enrollment.delay(TEST_EMAIL, self.course_url, False, 'verified', course_id=self.course_id, currency='USD', message_id='cookie_bid', unit_cost=Decimal(99.01), site_code='test_site') mock_sailthru_purchase.assert_called_with(TEST_EMAIL, [{'vars': {'course_run_id': self.course_id, 'mode': 'verified', 'upgrade_deadline_verified': '2020-03-12'}, 'title': 'Course ' + self.course_id + ' mode: verified', 'url': self.course_url, 'price': 9901, 'qty': 1, 'id': self.course_id + '-verified'}], options={'send_template': 'site_upgrade_template'}, incomplete=False, message_id='cookie_bid')
def test_configuration_no_site_code_matched(self, site_code): """ Test various states of SITE_OVERRIDES to ensure all branches (ie, use cases) are covered """ with mock.patch.dict(self.SITE_OVERRIDES_MODULE, self.OVERRIDES_DICT): test_setting = get_configuration(self.TEST_SETTING, site_code=site_code) self.assertEqual(test_setting, ECOMMERCE_API_ROOT)
def test_configuration_valid_site_code_dict_value(self, site_code): """ Confirm that valid SITE_OVERRIDES parameters are correctly returned """ with mock.patch.dict(self.SITE_OVERRIDES_MODULE, self.OVERRIDES_DICT): test_setting = get_configuration(self.TEST_SETTING, site_code=site_code) self.assertEqual(test_setting, self.OVERRIDES_DICT[site_code][self.TEST_SETTING])
def fulfill_order(self, order_number, site_code=None, email_opt_in=False): """Fulfills an order. Arguments: order_number (str): Order number indicating which order to fulfill. Returns: None """ max_fulfillment_retries = get_configuration('MAX_FULFILLMENT_RETRIES', site_code=site_code) api = get_ecommerce_client(site_code=site_code) try: logger.info('Requesting fulfillment of order [%s].', order_number) api.orders(order_number).fulfill.put(email_opt_in=email_opt_in) except exceptions.HttpClientError as exc: status_code = exc.response.status_code # pylint: disable=no-member if status_code == 406: # The order is not fulfillable. Therefore, it must be complete. logger.info('Order [%s] has already been fulfilled. Ignoring.', order_number) raise Ignore() # Unknown client error. Let's retry to resolve it. logger.warning( 'Fulfillment of order [%s] failed because of HttpClientError. Retrying', order_number, exc_info=True ) _retry_order(self, exc, max_fulfillment_retries, order_number) except (exceptions.HttpServerError, exceptions.Timeout, SSLError) as exc: # Fulfillment failed, retry _retry_order(self, exc, max_fulfillment_retries, order_number)
def test_update_course_enroll_skip(self, mock_sailthru_api_post, mock_sailthru_api_get, mock_sailthru_purchase, mock_get_configuration): """test audit enroll with configured cost = 0""" config = get_configuration('SAILTHRU') config['SAILTHRU_MINIMUM_COST'] = 0 mock_get_configuration.return_value = config # create mocked Sailthru API responses mock_sailthru_api_post.return_value = MockSailthruResponse( {'ok': True}) mock_sailthru_api_get.return_value = MockSailthruResponse( {'vars': { 'upgrade_deadline_verified': '2020-03-12' }}) mock_sailthru_purchase.return_value = MockSailthruResponse( {'ok': True}) update_course_enrollment.delay(TEST_EMAIL, self.course_url, False, 'audit', course_id=self.course_id, currency='USD', message_id='cookie_bid', unit_cost=Decimal(0)) mock_sailthru_purchase.assert_not_called()
def fulfill_order(self, order_number, site_code=None): """Fulfills an order. Arguments: order_number (str): Order number indicating which order to fulfill. Returns: None """ ecommerce_api_root = get_configuration('ECOMMERCE_API_ROOT', site_code=site_code) max_fulfillment_retries = get_configuration('MAX_FULFILLMENT_RETRIES', site_code=site_code) signing_key = get_configuration('JWT_SECRET_KEY', site_code=site_code) issuer = get_configuration('JWT_ISSUER', site_code=site_code) service_username = get_configuration('ECOMMERCE_SERVICE_USERNAME', site_code=site_code) api = EdxRestApiClient(ecommerce_api_root, signing_key=signing_key, issuer=issuer, username=service_username) try: logger.info('Requesting fulfillment of order [%s].', order_number) api.orders(order_number).fulfill.put() except exceptions.HttpClientError as exc: status_code = exc.response.status_code # pylint: disable=no-member if status_code == 406: # The order is not fulfillable. Therefore, it must be complete. logger.info('Order [%s] has already been fulfilled. Ignoring.', order_number) raise Ignore() else: # Unknown client error. Let's retry to resolve it. logger.warning( 'Fulfillment of order [%s] failed because of HttpClientError. Retrying', order_number, exc_info=True) _retry_order(self, exc, max_fulfillment_retries, order_number) except (exceptions.HttpServerError, exceptions.Timeout) as exc: # Fulfillment failed, retry _retry_order(self, exc, max_fulfillment_retries, order_number)
def mock_ecommerce_assignmentemail_api(self, body, status=200): """ Mock POST requests to the ecommerce assignmentemail API endpoint. """ httpretty.reset() httpretty.register_uri( httpretty.POST, '{}/assignment-email/status/'.format( get_configuration('ECOMMERCE_API_ROOT').strip('/') ), status=status, body=json.dumps(body), content_type='application/json', )
def mock_ecommerce_api(self, body, course_id, status=200): """ Mock GET requests to the ecommerce course API endpoint. """ httpretty.reset() httpretty.register_uri( httpretty.GET, '{}/courses/{}/'.format( get_configuration('ECOMMERCE_API_ROOT').strip('/'), text_type(course_id) ), status=status, body=json.dumps(body), content_type='application/json', )
def fulfill_order(self, order_number): """Fulfills an order. Arguments: order_number (str): Order number indicating which order to fulfill. Returns: None """ ecommerce_api_root = get_configuration("ECOMMERCE_API_ROOT") max_fulfillment_retries = get_configuration("MAX_FULFILLMENT_RETRIES") signing_key = get_configuration("JWT_SECRET_KEY") issuer = get_configuration("JWT_ISSUER") service_username = get_configuration("ECOMMERCE_SERVICE_USERNAME") api = EcommerceApiClient(ecommerce_api_root, signing_key=signing_key, issuer=issuer, username=service_username) try: logger.info("Requesting fulfillment of order [%s].", order_number) api.orders(order_number).fulfill.put() except exceptions.HttpClientError as exc: status_code = exc.response.status_code if status_code == 406: # The order is not fulfillable. Therefore, it must be complete. logger.info("Order [%s] has already been fulfilled. Ignoring.", order_number) raise Ignore() else: # Unknown client error. Re-raise the exception. logger.exception("Fulfillment of order [%s] failed.", order_number) raise exc except (exceptions.HttpServerError, exceptions.Timeout) as exc: # Fulfillment failed. Retry with exponential backoff until fulfillment # succeeds or the retry limit is reached. If the retry limit is exceeded, # the exception is re-raised. retries = self.request.retries if retries == max_fulfillment_retries: logger.exception("Fulfillment of order [%s] failed. Giving up.", order_number) else: logger.warning("Fulfillment of order [%s] failed. Retrying.", order_number) countdown = 2 ** retries raise self.retry(exc=exc, countdown=countdown, max_retries=max_fulfillment_retries)
def fulfill_order(self, order_number, site_code=None): """Fulfills an order. Arguments: order_number (str): Order number indicating which order to fulfill. Returns: None """ ecommerce_api_root = get_configuration('ECOMMERCE_API_ROOT', site_code=site_code) max_fulfillment_retries = get_configuration('MAX_FULFILLMENT_RETRIES', site_code=site_code) signing_key = get_configuration('JWT_SECRET_KEY', site_code=site_code) issuer = get_configuration('JWT_ISSUER', site_code=site_code) service_username = get_configuration('ECOMMERCE_SERVICE_USERNAME', site_code=site_code) api = EdxRestApiClient(ecommerce_api_root, signing_key=signing_key, issuer=issuer, username=service_username) try: logger.info('Requesting fulfillment of order [%s].', order_number) api.orders(order_number).fulfill.put() except exceptions.HttpClientError as exc: status_code = exc.response.status_code # pylint: disable=no-member if status_code == 406: # The order is not fulfillable. Therefore, it must be complete. logger.info('Order [%s] has already been fulfilled. Ignoring.', order_number) raise Ignore() else: # Unknown client error. Let's retry to resolve it. logger.warning( 'Fulfillment of order [%s] failed because of HttpClientError. Retrying', order_number, exc_info=True ) _retry_order(self, exc, max_fulfillment_retries, order_number) except (exceptions.HttpServerError, exceptions.Timeout) as exc: # Fulfillment failed, retry _retry_order(self, exc, max_fulfillment_retries, order_number)
def test_update_course_upgrade_complete_site(self, mock_sailthru_api_post, mock_sailthru_api_get, mock_sailthru_purchase): """test upgrade complete with site code""" # create mocked Sailthru API responses mock_sailthru_api_post.return_value = MockSailthruResponse( {'ok': True}) mock_sailthru_api_get.return_value = MockSailthruResponse( {'vars': { 'upgrade_deadline_verified': '2020-03-12' }}) mock_sailthru_purchase.return_value = MockSailthruResponse( {'ok': True}) # test upgrade complete with site code with patch('ecommerce_worker.configuration.test.SITE_OVERRIDES', get_configuration('TEST_SITE_OVERRIDES')): update_course_enrollment.delay(TEST_EMAIL, self.course_url, False, 'verified', course_id=self.course_id, currency='USD', message_id='cookie_bid', unit_cost=Decimal(99.01), site_code='test_site') mock_sailthru_purchase.assert_called_with( TEST_EMAIL, [{ 'vars': { 'purchase_sku': None, 'course_run_id': self.course_id, 'mode': 'verified', 'upgrade_deadline_verified': '2020-03-12' }, 'title': 'Course ' + self.course_id + ' mode: verified', 'url': self.course_url, 'price': 9901, 'qty': 1, 'id': self.course_id + '-verified' }], options={'send_template': 'site_upgrade_template'}, incomplete=False, message_id='cookie_bid')
def test_update_course_enroll_skip(self, mock_sailthru_api_post, mock_sailthru_api_get, mock_sailthru_purchase, mock_get_configuration): """test audit enroll with configured cost = 0""" config = get_configuration('SAILTHRU') config['SAILTHRU_MINIMUM_COST'] = 0 mock_get_configuration.return_value = config # create mocked Sailthru API responses mock_sailthru_api_post.return_value = MockSailthruResponse({'ok': True}) mock_sailthru_api_get.return_value = MockSailthruResponse({'vars': {'upgrade_deadline_verified': '2020-03-12'}}) mock_sailthru_purchase.return_value = MockSailthruResponse({'ok': True}) update_course_enrollment.delay(TEST_EMAIL, self.course_url, False, 'audit', course_id=self.course_id, currency='USD', message_id='cookie_bid', unit_cost=Decimal(0)) mock_sailthru_purchase.assert_not_called()
def get_sailthru_configuration(site_code): """ Returns the Sailthru configuration for the specified site. """ config = get_configuration('SAILTHRU', site_code=site_code) return config
def test_configuration_no_site_code_specified(self): with mock.patch.dict(self.SITE_OVERRIDES_MODULE, self.OVERRIDES_DICT): test_setting = get_configuration(self.TEST_SETTING) self.assertEqual(test_setting, ECOMMERCE_API_ROOT)
def test_configuration_no_site_overrides(self): test_setting = get_configuration(self.TEST_SETTING) self.assertEqual(test_setting, ECOMMERCE_API_ROOT)
def update_course_enrollment(self, email, course_url, purchase_incomplete, mode, unit_cost=None, course_id=None, currency=None, message_id=None, site_code=None): # pylint: disable=unused-argument """Adds/updates Sailthru when a user adds to cart/purchases/upgrades a course Args: email(str): The user's email address course_url(str): Course home page url incomplete(boolean): True if adding to cart mode(string): enroll mode (audit, verification, ...) unit_cost(decimal): cost if purchase event course_id(CourseKey): course id currency(str): currency if purchase event - currently ignored since Sailthru only supports USD message_id(str): value from Sailthru marketing campaign cookie site_code(str): site code Returns: None """ # Get configuration config = get_configuration('SAILTHRU', site_code=site_code) # Return if Sailthru integration disabled if not config.get('SAILTHRU_ENABLE'): return # Make sure key and secret configured sailthru_key = config.get('SAILTHRU_KEY') sailthru_secret = config.get('SAILTHRU_SECRET') if not (sailthru_key and sailthru_secret): logger.error("Sailthru key and or secret not specified for site %s", site_code) return sailthru_client = SailthruClient(sailthru_key, sailthru_secret) # Use event type to figure out processing required new_enroll = False send_template = None if not purchase_incomplete: if mode == 'verified': # upgrade complete send_template = config.get('SAILTHRU_UPGRADE_TEMPLATE') elif mode == 'audit' or mode == 'honor': # free enroll new_enroll = True send_template = config.get('SAILTHRU_ENROLL_TEMPLATE') else: # paid course purchase complete new_enroll = True send_template = config.get('SAILTHRU_PURCHASE_TEMPLATE') # calc price in pennies for Sailthru # https://getstarted.sailthru.com/new-for-developers-overview/advanced-features/purchase/ cost_in_cents = int(unit_cost * 100) if not cost_in_cents: cost_in_cents = config.get('SAILTHRU_MINIMUM_COST') # if still zero, ignore purchase since Sailthru can't deal with $0 transactions if not cost_in_cents: return # update the "unenrolled" course array in the user record on Sailthru if new enroll or unenroll if new_enroll: if not _update_unenrolled_list(sailthru_client, email, course_url, False): _schedule_retry(self, config) # Get course data from Sailthru content library or cache course_data = _get_course_content(course_url, sailthru_client, site_code, config) # build item description item = _build_purchase_item(course_id, course_url, cost_in_cents, mode, course_data) # build purchase api options list options = {} if purchase_incomplete and config.get('SAILTHRU_ABANDONED_CART_TEMPLATE'): options['reminder_template'] = config.get('SAILTHRU_ABANDONED_CART_TEMPLATE') # Sailthru reminder time format is '+n time unit' options['reminder_time'] = "+{} minutes".format(config.get('SAILTHRU_ABANDONED_CART_DELAY')) # add appropriate send template if send_template: options['send_template'] = send_template if not _record_purchase(sailthru_client, email, item, purchase_incomplete, message_id, options): _schedule_retry(self, config)
class OrderFulfillmentTaskTests(TestCase): """Tests of the order fulfillment task.""" ORDER_NUMBER = 'FAKE-123456' API_URL = '{root}/orders/{number}/fulfill/'.format( root=get_configuration('ECOMMERCE_API_ROOT').strip('/'), number=ORDER_NUMBER) @ddt.data('ECOMMERCE_API_ROOT', 'MAX_FULFILLMENT_RETRIES', 'JWT_SECRET_KEY', 'JWT_ISSUER', 'ECOMMERCE_SERVICE_USERNAME') def test_requires_configuration(self, setting): """Verify that the task refuses to run without the configuration it requires.""" with mock.patch('ecommerce_worker.configuration.test.' + setting, None): with self.assertRaises(RuntimeError): fulfill_order(self.ORDER_NUMBER) @httpretty.activate def test_fulfillment_success(self): """Verify that the task exits without an error when fulfillment succeeds.""" httpretty.register_uri(httpretty.PUT, self.API_URL, status=200, body={}) result = fulfill_order.delay(self.ORDER_NUMBER).get() self.assertIsNone(result) # Validate the value of the HTTP Authorization header. last_request = httpretty.last_request() token = last_request.headers.get('authorization').split()[1] payload = jwt.decode(token, get_configuration('JWT_SECRET_KEY')) self.assertEqual(payload['username'], get_configuration('ECOMMERCE_SERVICE_USERNAME')) @httpretty.activate def test_fulfillment_not_possible(self): """Verify that the task exits without an error when fulfillment is not possible.""" httpretty.register_uri(httpretty.PUT, self.API_URL, status=406, body={}) with self.assertRaises(Ignore): fulfill_order(self.ORDER_NUMBER) @httpretty.activate def test_fulfillment_unknown_client_error(self): """ Verify that the task raises an exception when fulfillment fails because of an unknown client error. """ httpretty.register_uri(httpretty.PUT, self.API_URL, status=404, body={}) with self.assertRaises(exceptions.HttpClientError): fulfill_order(self.ORDER_NUMBER) @httpretty.activate def test_fulfillment_failure(self): """Verify that the task raises an exception when fulfillment fails.""" httpretty.register_uri(httpretty.PUT, self.API_URL, status=500, body={}) with self.assertRaises(exceptions.HttpServerError): fulfill_order.delay(self.ORDER_NUMBER).get() @httpretty.activate def test_fulfillment_timeout(self): """Verify that the task raises an exception when fulfillment times out.""" httpretty.register_uri(httpretty.PUT, self.API_URL, status=404, body=self._timeout_body) with self.assertRaises(exceptions.Timeout): fulfill_order.delay(self.ORDER_NUMBER).get() @httpretty.activate def test_fulfillment_retry_success(self): """Verify that the task is capable of successfully retrying after fulfillment failure.""" httpretty.register_uri(httpretty.PUT, self.API_URL, responses=[ httpretty.Response(status=500, body={}), httpretty.Response(status=200, body={}), ]) result = fulfill_order.delay(self.ORDER_NUMBER).get() self.assertIsNone(result) def _timeout_body(self, request, uri, headers): # pylint: disable=unused-argument """Helper used to force httpretty to raise Timeout exceptions.""" raise exceptions.Timeout
class OrderFulfillmentTaskTests(TestCase): """Tests of the order fulfillment task.""" ORDER_NUMBER = 'FAKE-123456' API_URL = '{root}/orders/{number}/fulfill/'.format( root=get_configuration('ECOMMERCE_API_ROOT').strip('/'), number=ORDER_NUMBER) @ddt.data('ECOMMERCE_API_ROOT', 'MAX_FULFILLMENT_RETRIES', 'JWT_SECRET_KEY', 'JWT_ISSUER', 'ECOMMERCE_SERVICE_USERNAME') def test_requires_configuration(self, setting): """Verify that the task refuses to run without the configuration it requires.""" with mock.patch('ecommerce_worker.configuration.test.' + setting, None): with self.assertRaises(RuntimeError): fulfill_order(self.ORDER_NUMBER) @responses.activate def test_fulfillment_success(self): """Verify that the task exits without an error when fulfillment succeeds.""" responses.add(responses.PUT, self.API_URL, status=200, body="{}") result = fulfill_order.delay(self.ORDER_NUMBER).get() self.assertIsNone(result) # Validate the value of the HTTP Authorization header. last_request = responses.calls[-1].request token = last_request.headers.get('authorization').split()[1] payload = jwt.decode(token, get_configuration('JWT_SECRET_KEY')) self.assertEqual(payload['username'], get_configuration('ECOMMERCE_SERVICE_USERNAME')) @responses.activate def test_fulfillment_not_possible(self): """Verify that the task exits without an error when fulfillment is not possible.""" responses.add(responses.PUT, self.API_URL, status=406, body="{}") with self.assertRaises(Ignore): fulfill_order(self.ORDER_NUMBER) @responses.activate def test_fulfillment_unknown_client_error(self): """ Verify that the task raises an exception when fulfillment fails because of an unknown client error. """ responses.add(responses.PUT, self.API_URL, status=404, body="{}") with self.assertRaises(exceptions.HttpClientError): fulfill_order(self.ORDER_NUMBER) @responses.activate def test_fulfillment_unknown_client_error_retry_success(self): """Verify that the task is capable of successfully retrying after client error.""" responses.add( responses.Response(responses.PUT, self.API_URL, status=404, body="{}"), ) responses.add( responses.Response(responses.PUT, self.API_URL, status=200, body="{}"), ) result = fulfill_order.delay(self.ORDER_NUMBER).get() self.assertIsNone(result) @responses.activate def test_fulfillment_failure(self): """Verify that the task raises an exception when fulfillment fails.""" responses.add(responses.PUT, self.API_URL, status=500, body="{}") with self.assertRaises(exceptions.HttpServerError): fulfill_order.delay(self.ORDER_NUMBER).get() @responses.activate def test_fulfillment_timeout(self): """Verify that the task raises an exception when fulfillment times out.""" responses.add(responses.PUT, self.API_URL, status=404, body=exceptions.Timeout()) with self.assertRaises(exceptions.Timeout): fulfill_order.delay(self.ORDER_NUMBER).get() @responses.activate def test_fulfillment_retry_success(self): """Verify that the task is capable of successfully retrying after fulfillment failure.""" responses.add( responses.Response(responses.PUT, self.API_URL, status=500, body="{}"), ) responses.add( responses.Response(responses.PUT, self.API_URL, status=200, body="{}"), ) result = fulfill_order.delay(self.ORDER_NUMBER).get() self.assertIsNone(result) @ddt.data([True, 'True'], [False, 'False']) @ddt.unpack @responses.activate def test_email_opt_in_parameter_sent(self, email_opt_in_bool, email_opt_in_str): """Verify that the task correctly adds the email_opt_in parameter to the request.""" email_opt_in_api_url = self.API_URL + '?email_opt_in=' + email_opt_in_str responses.add(responses.PUT, email_opt_in_api_url, status=200, body="{}") result = fulfill_order.delay(self.ORDER_NUMBER, email_opt_in=email_opt_in_bool).get() self.assertIsNone(result) last_request = responses.calls[-1].request self.assertIn('?email_opt_in=' + email_opt_in_str, last_request.path_url)
class OrderFulfillmentTaskTests(TestCase): """Tests of the order fulfillment task.""" ORDER_NUMBER = 'FAKE-123456' API_URL = '{root}/orders/{number}/fulfill/'.format( root=get_configuration('ECOMMERCE_API_ROOT').strip('/'), number=ORDER_NUMBER) @ddt.data('ECOMMERCE_API_ROOT', 'MAX_FULFILLMENT_RETRIES', 'JWT_SECRET_KEY', 'JWT_ISSUER', 'ECOMMERCE_SERVICE_USERNAME') def test_requires_configuration(self, setting): """Verify that the task refuses to run without the configuration it requires.""" with mock.patch('ecommerce_worker.configuration.test.' + setting, None): with self.assertRaises(RuntimeError): fulfill_order(self.ORDER_NUMBER) @httpretty.activate def test_fulfillment_success(self): """Verify that the task exits without an error when fulfillment succeeds.""" httpretty.register_uri(httpretty.PUT, self.API_URL, status=200, body={}) result = fulfill_order.delay(self.ORDER_NUMBER).get() self.assertIsNone(result) # Validate the value of the HTTP Authorization header. last_request = httpretty.last_request() token = last_request.headers.get('authorization').split()[1] payload = jwt.decode(token, get_configuration('JWT_SECRET_KEY')) self.assertEqual(payload['username'], get_configuration('ECOMMERCE_SERVICE_USERNAME')) @httpretty.activate def test_fulfillment_not_possible(self): """Verify that the task exits without an error when fulfillment is not possible.""" httpretty.register_uri(httpretty.PUT, self.API_URL, status=406, body={}) with self.assertRaises(Ignore): fulfill_order(self.ORDER_NUMBER) @httpretty.activate def test_fulfillment_unknown_client_error(self): """ Verify that the task raises an exception when fulfillment fails because of an unknown client error. """ httpretty.register_uri(httpretty.PUT, self.API_URL, status=404, body={}) with self.assertRaises(exceptions.HttpClientError): fulfill_order(self.ORDER_NUMBER) @httpretty.activate def test_fulfillment_unknown_client_error_retry_success(self): """Verify that the task is capable of successfully retrying after client error.""" httpretty.register_uri(httpretty.PUT, self.API_URL, responses=[ httpretty.Response(status=404, body={}), httpretty.Response(status=200, body={}), ]) result = fulfill_order.delay(self.ORDER_NUMBER).get() self.assertIsNone(result) @httpretty.activate def test_fulfillment_failure(self): """Verify that the task raises an exception when fulfillment fails.""" httpretty.register_uri(httpretty.PUT, self.API_URL, status=500, body={}) with self.assertRaises(exceptions.HttpServerError): fulfill_order.delay(self.ORDER_NUMBER).get() @httpretty.activate def test_fulfillment_timeout(self): """Verify that the task raises an exception when fulfillment times out.""" httpretty.register_uri(httpretty.PUT, self.API_URL, status=404, body=self._timeout_body) with self.assertRaises(exceptions.Timeout): fulfill_order.delay(self.ORDER_NUMBER).get() @httpretty.activate def test_fulfillment_retry_success(self): """Verify that the task is capable of successfully retrying after fulfillment failure.""" httpretty.register_uri(httpretty.PUT, self.API_URL, responses=[ httpretty.Response(status=500, body={}), httpretty.Response(status=200, body={}), ]) result = fulfill_order.delay(self.ORDER_NUMBER).get() self.assertIsNone(result) @ddt.data([True, 'True'], [False, 'False']) @ddt.unpack @httpretty.activate def test_email_opt_in_parameter_sent(self, email_opt_in_bool, email_opt_in_str): """Verify that the task correctly adds the email_opt_in parameter to the request.""" email_opt_in_api_url = self.API_URL + '?email_opt_in=' + email_opt_in_str httpretty.register_uri(httpretty.PUT, email_opt_in_api_url, status=200, body={}) result = fulfill_order.delay(self.ORDER_NUMBER, email_opt_in=email_opt_in_bool).get() self.assertIsNone(result) last_request = httpretty.last_request() self.assertIn('?email_opt_in=' + email_opt_in_str, last_request.path) # QueryDicts store their values as lists in case multiple values are passed in. # last_request.querystring is returned as a dict instead of a QueryDict # so we have the grab the first element in the list to actually get the value. self.assertEqual(last_request.querystring['email_opt_in'][0], email_opt_in_str) def _timeout_body(self, request, uri, headers): # pylint: disable=unused-argument """Helper used to force httpretty to raise Timeout exceptions.""" raise exceptions.Timeout