Exemplo n.º 1
0
 def test_already_expired_entitlement_order(self):
     """
     Test the case that user has a non refunded order for the course entitlement
     """
     self.mock_access_token_response()
     body = {
         "user": "******",
         "uuid": "adfca7da-e593-428b-b12d-f728e2dd220d",
         "course_uuid": "b084097a-7596-4fe6-b6a2-d335bffeb3f1",
         "expired_at": None,
         "created": "2017-12-16T21:35:59.402622Z",
         "modified": "2017-12-16T21:36:19.280197Z",
         "mode": "verified",
         "order_number": "EDX-100014"
     }
     httpretty.register_uri(httpretty.GET,
                            get_lms_entitlement_api_url() +
                            'entitlements/' + self.course_entitlement_uuid +
                            '/',
                            status=200,
                            body=json.dumps(body),
                            content_type='application/json')
     self.assertTrue(
         UserAlreadyPlacedOrder.user_already_placed_order(
             user=self.user,
             product=self.course_entitlement,
             site=self.site))
Exemplo n.º 2
0
    def test_entitlement_module_fulfill_connection_error(self):
        """Test Course Entitlement Fulfillment with exception when posting to LMS."""

        self.mock_access_token_response()
        httpretty.register_uri(httpretty.POST,
                               get_lms_entitlement_api_url() + 'entitlements/',
                               status=408,
                               body={},
                               content_type='application/json')
        logger_name = 'ecommerce.extensions.fulfillment.modules'

        line = self.order.lines.first()

        with LogCapture(logger_name) as l:
            CourseEntitlementFulfillmentModule().fulfill_product(
                self.order, list(self.order.lines.all()))
            self.assertEqual(LINE.FULFILLMENT_SERVER_ERROR,
                             self.order.lines.all()[0].status)
            l.check((
                logger_name, 'INFO',
                'Attempting to fulfill "Course Entitlement" product types for order [{}]'
                .format(self.order.number)
            ), (
                logger_name, 'ERROR',
                'Unable to fulfill line [{}] of order [{}]'.format(
                    line.id, self.order.number)
            ), (logger_name, 'INFO',
                'Finished fulfilling "Course Entitlement" product types for order [{}]'
                .format(self.order.number)))
Exemplo n.º 3
0
    def is_entitlement_expired(entitlement_uuid, site):
        """
        Checks to see if a given entitlement is expired.

        Args:
            entitlement_uuid: UUID
            site: (Site)

        Returns:
            bool: True if the entitlement is expired

        """
        entitlement_api_client = EdxRestApiClient(
            get_lms_entitlement_api_url(),
            jwt=site.siteconfiguration.access_token)
        partner_short_code = site.siteconfiguration.partner.short_code
        key = 'course_entitlement_detail_{}{}'.format(entitlement_uuid,
                                                      partner_short_code)
        entitlement_cached_response = TieredCache.get_cached_response(key)
        if entitlement_cached_response.is_found:
            entitlement = entitlement_cached_response.value
        else:
            logger.debug('Trying to get entitlement {%s}', entitlement_uuid)
            entitlement = entitlement_api_client.entitlements(
                entitlement_uuid).get()
            TieredCache.set_all_tiers(key, entitlement,
                                      settings.COURSES_API_CACHE_TIMEOUT)

        expired = entitlement.get('expired_at')
        logger.debug('Entitlement {%s} expired = {%s}', entitlement_uuid,
                     expired)

        return expired
Exemplo n.º 4
0
    def test_entitlement_module_fulfill(self):
        """ Test to ensure we can properly fulfill course entitlements. """

        self.mock_access_token_response()
        httpretty.register_uri(httpretty.POST,
                               get_lms_entitlement_api_url() + 'entitlements/',
                               status=200,
                               body=json.dumps(self.return_data),
                               content_type='application/json')

        # Attempt to fulfill entitlement.
        with LogCapture(LOGGER_NAME) as l:
            CourseEntitlementFulfillmentModule().fulfill_product(
                self.order, list(self.order.lines.all()))

            line = self.order.lines.get()
            l.check(
                (LOGGER_NAME, 'INFO',
                 'line_fulfilled: UUID="{}", mode="{}", order_line_id="{}", '
                 'order_number="{}", product_class="{}", user_id="{}"'.format(
                     line.product.attr.UUID,
                     mode_for_product(line.product),
                     line.id,
                     line.order.number,
                     line.product.get_product_class().name,
                     line.order.user.id,
                 )))

            course_entitlement_uuid = line.attributes.get(
                option=self.entitlement_option).value
            self.assertEqual(course_entitlement_uuid, '111-222-333')
            self.assertEqual(LINE.COMPLETE, line.status)
Exemplo n.º 5
0
    def revoke_line(self, line):
        try:
            logger.info('Attempting to revoke fulfillment of Line [%d]...', line.id)

            UUID = line.product.attr.UUID
            entitlement_option = Option.objects.get(code='course_entitlement')
            course_entitlement_uuid = line.attributes.get(option=entitlement_option).value

            entitlement_api_client = EdxRestApiClient(
                get_lms_entitlement_api_url(),
                jwt=line.order.site.siteconfiguration.access_token
            )

            # DELETE to the Entitlement API.
            entitlement_api_client.entitlements(course_entitlement_uuid).delete()

            audit_log(
                'line_revoked',
                order_line_id=line.id,
                order_number=line.order.number,
                product_class=line.product.get_product_class().name,
                UUID=UUID,
                certificate_type=getattr(line.product.attr, 'certificate_type', ''),
                user_id=line.order.user.id
            )

            return True
        except Exception:  # pylint: disable=broad-except
            logger.exception('Failed to revoke fulfillment of Line [%d].', line.id)

        return False
Exemplo n.º 6
0
    def test_is_entitlement_expired_cached(self):
        """
        Test that entitlement's expired status gets cached

        We expect 2 calls to set_all_tiers in the is_entitlement_expired
        method due to:
            - the site_configuration api setup
            - the result being cached
        """
        self.mock_access_token_response()

        self.course_entitlement.expires = EXPIRED_DATE
        httpretty.register_uri(httpretty.GET,
                               get_lms_entitlement_api_url() +
                               'entitlements/' + self.course_entitlement_uuid +
                               '/',
                               status=200,
                               body=json.dumps({}),
                               content_type='application/json')

        with mock.patch.object(
                TieredCache, 'set_all_tiers',
                wraps=TieredCache.set_all_tiers) as mocked_set_all_tiers:
            mocked_set_all_tiers.assert_not_called()

            _ = UserAlreadyPlacedOrder.is_entitlement_expired(
                self.course_entitlement_uuid, site=self.site)
            self.assertEqual(mocked_set_all_tiers.call_count, 2)

            _ = UserAlreadyPlacedOrder.is_entitlement_expired(
                self.course_entitlement_uuid, site=self.site)
            self.assertEqual(mocked_set_all_tiers.call_count, 2)
Exemplo n.º 7
0
    def test_entitlement_module_revoke_error(self):
        """ Test to handle an error when revoking a Course Entitlement. """
        self.mock_access_token_response()

        httpretty.register_uri(httpretty.DELETE, get_lms_entitlement_api_url() +
                               'entitlements/111-222-333/', status=500, body={}, content_type='application/json')

        line = self.order.lines.first()

        self.assertFalse(CourseEntitlementFulfillmentModule().revoke_line(line))
Exemplo n.º 8
0
 def mock_user_data(self, username, mocked_api='enrollments', owned_products=None, response_code=200):
     """ Mocks user ownership data retrieval from LMS
     Returns:
         list: Mocked entitlement or enrollment data
     """
     self.mock_access_token_response()
     if mocked_api == 'enrollments':
         api_url = get_lms_enrollment_api_url()
     else:
         api_url = get_lms_entitlement_api_url() + 'entitlements/'
     httpretty.register_uri(
         method=httpretty.GET,
         uri='{}?user={}'.format(api_url, username),
         body=json.dumps([] if owned_products is None else owned_products),
         status=response_code,
         content_type='application/json'
     )
     return owned_products
Exemplo n.º 9
0
    def test_refunded_entitlement_order_connection_timeout(self):
        """
        Test the case that we get an error trying to get the entitlement from LMS
        """
        httpretty.register_uri(httpretty.GET,
                               get_lms_entitlement_api_url() +
                               'entitlements/' + self.course_entitlement_uuid +
                               '/',
                               status=200,
                               body={},
                               content_type='application/json',
                               side_effect=Timeout)

        self.assertFalse(
            UserAlreadyPlacedOrder.user_already_placed_order(
                user=self.user,
                product=self.course_entitlement,
                site=self.site))
Exemplo n.º 10
0
    def fulfill_product(self, order, lines):
        """ Fulfills the purchase of a 'Course Entitlement'.
        Uses the order and the lines to determine which courses to grant an entitlement for, and with certain
        certificate types. May result in an error if the Entitlement API cannot be reached, or if there is
        additional business logic errors when trying grant the entitlement.
        Args:
            order (Order): The Order associated with the lines to be fulfilled. The user associated with the order
                is presumed to be the student to grant an entitlement.
            lines (List of Lines): Order Lines, associated with purchased products in an Order. These should only
                be "Course Entitlement" products.
        Returns:
            The original set of lines, with new statuses set based on the success or failure of fulfillment.
        """
        logger.info('Attempting to fulfill "Course Entitlement" product types for order [%s]', order.number)

        for line in lines:
            try:
                mode = mode_for_product(line.product)
                UUID = line.product.attr.UUID
            except AttributeError:
                logger.error('Entitlement Product does not have required attributes, [certificate_type, UUID]')
                line.set_status(LINE.FULFILLMENT_CONFIGURATION_ERROR)
                continue

            data = {
                'user': order.user.username,
                'course_uuid': UUID,
                'mode': mode,
                'order_number': order.number,
            }

            try:
                entitlement_option = Option.objects.get(code='course_entitlement')

                entitlement_api_client = EdxRestApiClient(
                    get_lms_entitlement_api_url(),
                    jwt=order.site.siteconfiguration.access_token
                )

                # POST to the Entitlement API.
                response = entitlement_api_client.entitlements.post(data)
                line.attributes.create(option=entitlement_option, value=response['uuid'])
                line.set_status(LINE.COMPLETE)

                audit_log(
                    'line_fulfilled',
                    order_line_id=line.id,
                    order_number=order.number,
                    product_class=line.product.get_product_class().name,
                    UUID=UUID,
                    mode=mode,
                    user_id=order.user.id,
                )
            except (Timeout, ConnectionError):
                logger.exception(
                    'Unable to fulfill line [%d] of order [%s] due to a network problem', line.id, order.number
                )
                line.set_status(LINE.FULFILLMENT_NETWORK_ERROR)
            except Exception:  # pylint: disable=broad-except
                logger.exception(
                    'Unable to fulfill line [%d] of order [%s]', line.id, order.number
                )
                line.set_status(LINE.FULFILLMENT_SERVER_ERROR)

        logger.info('Finished fulfilling "Course Entitlement" product types for order [%s]', order.number)
        return order, lines
Exemplo n.º 11
0
    def fulfill_product(self, order, lines, email_opt_in=False):
        """ Fulfills the purchase of a 'Course Entitlement'.
        Uses the order and the lines to determine which courses to grant an entitlement for, and with certain
        certificate types. May result in an error if the Entitlement API cannot be reached, or if there is
        additional business logic errors when trying grant the entitlement.

        Updates the user's email preferences based on email_opt_in as a side effect.

        Args:
            order (Order): The Order associated with the lines to be fulfilled. The user associated with the order
                is presumed to be the student to grant an entitlement.
            lines (List of Lines): Order Lines, associated with purchased products in an Order. These should only
                be "Course Entitlement" products.
            email_opt_in (bool): Whether the user should be opted in to emails
                as part of the fulfillment. Defaults to False.

        Returns:
            The original set of lines, with new statuses set based on the success or failure of fulfillment.
        """
        logger.info(
            'Attempting to fulfill "Course Entitlement" product types for order [%s]',
            order.number)

        for line in lines:
            try:
                mode = mode_for_product(line.product)
                UUID = line.product.attr.UUID
            except AttributeError:
                logger.error(
                    'Entitlement Product does not have required attributes, [certificate_type, UUID]'
                )
                line.set_status(LINE.FULFILLMENT_CONFIGURATION_ERROR)
                continue

            data = {
                'user': order.user.username,
                'course_uuid': UUID,
                'mode': mode,
                'order_number': order.number,
                'email_opt_in': email_opt_in,
            }

            try:
                self._create_enterprise_customer_user(order)
                self.update_orderline_with_enterprise_discount_metadata(
                    order, line)
                entitlement_option = Option.objects.get(
                    code='course_entitlement')

                api_client = line.order.site.siteconfiguration.oauth_api_client
                entitlement_url = urljoin(get_lms_entitlement_api_url(),
                                          'entitlements/')

                # POST to the Entitlement API.
                response = api_client.post(entitlement_url, json=data)
                response.raise_for_status()
                response = response.json()
                line.attributes.create(option=entitlement_option,
                                       value=response['uuid'])
                line.set_status(LINE.COMPLETE)

                audit_log(
                    'line_fulfilled',
                    order_line_id=line.id,
                    order_number=order.number,
                    product_class=line.product.get_product_class().name,
                    UUID=UUID,
                    mode=mode,
                    user_id=order.user.id,
                )
            except (Timeout, ReqConnectionError):
                logger.exception(
                    'Unable to fulfill line [%d] of order [%s] due to a network problem',
                    line.id, order.number)
                order.notes.create(
                    message=
                    'Fulfillment of order failed due to a network problem.',
                    note_type='Error')
                line.set_status(LINE.FULFILLMENT_NETWORK_ERROR)
            except Exception:  # pylint: disable=broad-except
                logger.exception('Unable to fulfill line [%d] of order [%s]',
                                 line.id, order.number)
                order.notes.create(
                    message='Fulfillment of order failed due to an Exception.',
                    note_type='Error')
                line.set_status(LINE.FULFILLMENT_SERVER_ERROR)

        logger.info(
            'Finished fulfilling "Course Entitlement" product types for order [%s]',
            order.number)
        return order, lines