Beispiel #1
0
    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'))
Beispiel #2
0
    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'))
Beispiel #3
0
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)
Beispiel #4
0
    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])
Beispiel #7
0
 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)
Beispiel #8
0
 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()
Beispiel #11
0
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)
Beispiel #12
0
 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',
     )
Beispiel #13
0
 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',
     )
Beispiel #14
0
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)
Beispiel #15
0
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)
Beispiel #16
0
    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')
Beispiel #17
0
    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()
Beispiel #18
0
def get_sailthru_configuration(site_code):
    """ Returns the Sailthru configuration for the specified site. """
    config = get_configuration('SAILTHRU', site_code=site_code)
    return config
Beispiel #19
0
 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 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)
Beispiel #22
0
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)
Beispiel #23
0
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
Beispiel #24
0
 def test_configuration_no_site_overrides(self):
     test_setting = get_configuration(self.TEST_SETTING)
     self.assertEqual(test_setting, ECOMMERCE_API_ROOT)
Beispiel #25
0
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)
Beispiel #26
0
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