Exemple #1
0
    def test_fetch_enterprise_learner_entitlements(self):
        """
        Verify that method "fetch_enterprise_learner_data" returns a proper
        response for the enterprise learner.
        """
        # API should be hit only twice in this test case,
        # once by `fetch_enterprise_learner_data` and once by `fetch_enterprise_learner_entitlements`.
        expected_number_of_requests = 3

        self.mock_access_token_response()
        self.mock_enterprise_learner_api()
        enterprise_learners = enterprise_api.fetch_enterprise_learner_data(
            self.request.site, self.learner)

        enterprise_learner_id = enterprise_learners['results'][0]['id']
        self.mock_enterprise_learner_entitlements_api(enterprise_learner_id)
        enterprise_api.fetch_enterprise_learner_entitlements(
            self.request.site, enterprise_learner_id)

        # Verify the API was hit just two times, once by `fetch_enterprise_learner_data`
        # and once by `fetch_enterprise_learner_entitlements`
        self._assert_num_requests(expected_number_of_requests)

        # Now fetch the enterprise learner entitlements again and verify that there was
        # no actual call to Enterprise API, as the data will be taken from
        # the cache
        enterprise_api.fetch_enterprise_learner_entitlements(
            self.request.site, enterprise_learner_id)
        self._assert_num_requests(expected_number_of_requests)
Exemple #2
0
    def add_message_if_enterprise_user(self, request):
        enterprise_customer = None
        learner_data = None
        try:
            # If enterprise feature is enabled return all the enterprise_customer associated with user.
            learner_data = fetch_enterprise_learner_data(request.site, request.user)
        except (ReqConnectionError, KeyError, SlumberHttpBaseException, Timeout) as exc:
            logging.exception('[enterprise learner message] Exception while retrieving enterprise learner data for'
                              'User: %s, Exception: %s', request.user, exc)
        if learner_data:
            try:
                enterprise_customer = learner_data['results'][0]['enterprise_customer']
            except IndexError:
                # If enterprise feature is enable and user is not associated to any enterprise
                pass

        if enterprise_customer:
            enable_learner_portal = enterprise_customer['enable_learner_portal']
            learner_portal_hostname = enterprise_customer['learner_portal_hostname']
            if enable_learner_portal and learner_portal_hostname:
                message = (
                    'Your company, {enterprise_customer_name}, has a dedicated page where '
                    'you can see all of your sponsored courses. '
                    'Go to <a href="{scheme}://{hostname}">your learner portal</a>.'
                ).format(
                    enterprise_customer_name=enterprise_customer['name'],
                    hostname=learner_portal_hostname,
                    scheme=request.scheme
                )
                messages.add_message(request, messages.INFO, message, extra_tags='safe')
Exemple #3
0
def get_course_entitlements_for_learner(site, user, course_id):
    """
    Get entitlements for the provided learner against the provided course id
    if the provided learner is affiliated with an enterprise.

    Arguments:
        course_id (str): The course ID.
        site: (django.contrib.sites.Site) site instance
        user: (django.contrib.auth.User) django auth user

    Returns:
        (list): List of entitlement ids, where entitlement id is actually a voucher id.
    """
    try:
        enterprise_learner_data = enterprise_api.fetch_enterprise_learner_data(site, user)['results']
    except (ConnectionError, SlumberBaseException, Timeout, KeyError, TypeError):
        logger.exception(
            'Failed to retrieve enterprise info for the learner [%s]',
            user.username
        )
        return None

    if not enterprise_learner_data:
        logger.info('Learner with username [%s] in not affiliated with any enterprise', user.username)
        return None

    try:
        enterprise_catalog_id = enterprise_learner_data[0]['enterprise_customer']['catalog']
        learner_id = enterprise_learner_data[0]['id']
    except KeyError:
        logger.exception('Invalid structure for enterprise learner API response for the learner [%s]', user.username)
        return None

    # Before returning entitlements verify that the provided course exists in
    # the enterprise course catalog
    if not is_course_in_enterprise_catalog(site, course_id, enterprise_catalog_id):
        return None

    try:
        entitlements = enterprise_api.fetch_enterprise_learner_entitlements(site, learner_id)
    except (ConnectionError, SlumberBaseException, Timeout):
        logger.exception(
            'Failed to retrieve entitlements for enterprise learner [%s].',
            learner_id
        )
        return None

    try:
        # Currently, we are returning only those entitlements that
        # do not require any further action on part of enterprise learner
        entitlements = [item['entitlement_id'] for item in entitlements['entitlements'] if not item['requires_consent']]
    except KeyError:
        logger.exception(
            'Invalid structure for enterprise learner entitlements API response for enterprise learner [%s].',
            learner_id,
        )
        return None

    return entitlements
Exemple #4
0
    def test_fetch_enterprise_learner_data(self):
        """
        Verify that method "fetch_enterprise_learner_data" returns a proper
        response for the enterprise learner.
        """
        self.mock_access_token_response()
        self.mock_enterprise_learner_api()
        self._assert_fetch_enterprise_learner_data()

        # API should be hit only once in this test case
        expected_number_of_requests = 2

        # Verify the API was hit once
        self._assert_num_requests(expected_number_of_requests)

        # Now fetch the enterprise learner data again and verify that there was
        # no actual call to Enterprise API, as the data will be fetched from
        # the cache
        enterprise_api.fetch_enterprise_learner_data(self.request.site, self.learner)
        self._assert_num_requests(expected_number_of_requests)
Exemple #5
0
    def is_satisfied(self, offer, basket):  # pylint: disable=unused-argument
        """
        Determines if a user is eligible for an enterprise customer offer
        based on their association with the enterprise customer.

        Args:
            basket (Basket): Contains information about order line items, the current site,
                             and the user attempting to make the purchase.
        Returns:
            bool
        """
        if not basket.owner:
            # An anonymous user is never linked to any EnterpriseCustomer.
            return False

        try:
            learner_data = fetch_enterprise_learner_data(
                basket.site, basket.owner)['results'][0]
        except (ConnectionError, KeyError, SlumberHttpBaseException, Timeout):
            logger.exception(
                'Failed to retrieve enterprise learner data for site [%s] and user [%s].',
                basket.site.domain,
                basket.owner.username,
            )
            return False
        except IndexError:
            return False

        enterprise_customer = learner_data['enterprise_customer']
        if str(self.enterprise_customer_uuid) != enterprise_customer['uuid']:
            # Learner is not linked to the EnterpriseCustomer associated with this condition.
            return False

        course_run_ids = []
        for line in basket.all_lines():
            course = line.product.course
            if not course:
                # Basket contains products not related to a course_run.
                return False

            course_run_ids.append(course.id)

        if not catalog_contains_course_runs(
                basket.site,
                course_run_ids,
                self.enterprise_customer_uuid,
                enterprise_customer_catalog_uuid=self.
                enterprise_customer_catalog_uuid):
            # Basket contains course runs that do not exist in the EnterpriseCustomerCatalogs
            # associated with the EnterpriseCustomer.
            return False

        return True
Exemple #6
0
    def _assert_fetch_enterprise_learner_data(self):
        """
        Helper method to validate the response from the method
        "fetch_enterprise_learner_data".
        """
        cache_key = get_cache_key(
            site_domain=self.request.site.domain,
            partner_code=self.request.site.siteconfiguration.partner.short_code,
            resource='enterprise-learner',
            username=self.learner.username
        )

        enterprise_learner_cached_response = TieredCache.get_cached_response(cache_key)
        self.assertFalse(enterprise_learner_cached_response.is_found)

        response = enterprise_api.fetch_enterprise_learner_data(self.request.site, self.learner)
        self.assertEqual(len(response['results']), 1)

        course_cached_response = TieredCache.get_cached_response(cache_key)
        self.assertEqual(course_cached_response.value, response)
Exemple #7
0
def get_enterprise_id_for_user(site, user):
    enterprise_from_jwt = get_enterprise_id_for_current_request_user_from_jwt()
    if enterprise_from_jwt:
        return enterprise_from_jwt

    try:
        enterprise_learner_response = fetch_enterprise_learner_data(site, user)
    except (ConnectionError, KeyError, SlumberHttpBaseException,
            Timeout) as exc:
        logging.exception(
            'Unable to retrieve enterprise learner data for the user!'
            'User: %s, Exception: %s', user, exc)
        return None

    try:
        return enterprise_learner_response['results'][0][
            'enterprise_customer']['uuid']
    except IndexError:
        pass

    return None
Exemple #8
0
    def add_message_if_enterprise_user(self, request):
        try:
            # If enterprise feature is enabled return all the enterprise_customer associated with user.
            learner_data = fetch_enterprise_learner_data(
                request.site, request.user)
        except (ReqConnectionError, KeyError, SlumberHttpBaseException,
                Timeout) as exc:
            logging.exception(
                '[enterprise learner message] Exception while retrieving enterprise learner data for'
                'User: %s, Exception: %s', request.user, exc)
            return None

        try:
            enterprise_customer = learner_data['results'][0][
                'enterprise_customer']
        except (IndexError, KeyError):
            # If enterprise feature is enabled and user is not associated to any enterprise
            return None

        enable_learner_portal = enterprise_customer.get(
            'enable_learner_portal')
        enterprise_learner_portal_slug = enterprise_customer.get('slug')
        if enable_learner_portal and enterprise_learner_portal_slug:
            learner_portal_url = '{scheme}://{hostname}/{slug}'.format(
                scheme=request.scheme,
                hostname=settings.ENTERPRISE_LEARNER_PORTAL_HOSTNAME,
                slug=enterprise_learner_portal_slug,
            )
            message = (
                'Your company, {enterprise_customer_name}, has a dedicated page where '
                'you can see all of your sponsored courses. '
                'Go to <a href="{url}">your learner portal</a>.').format(
                    enterprise_customer_name=enterprise_customer['name'],
                    url=learner_portal_url)
            messages.add_message(request,
                                 messages.INFO,
                                 message,
                                 extra_tags='safe')
            return learner_portal_url
        return None
Exemple #9
0
 def add_message_if_enterprise_user(self, request):
     learner_data = fetch_enterprise_learner_data(request.site,
                                                  request.user)
     learner_data_results = learner_data.get('results')
     if learner_data_results:
         enterprise_customer = learner_data_results[0][
             'enterprise_customer']
         enable_learner_portal = enterprise_customer[
             'enable_learner_portal']
         learner_portal_hostname = enterprise_customer[
             'learner_portal_hostname']
         if enable_learner_portal and learner_portal_hostname:
             message = (
                 'Your company, {enterprise_customer_name}, has a dedicated page where '
                 'you can see all of your sponsored courses. '
                 'Go to <a href="{scheme}://{hostname}">your learner portal</a>.'
             ).format(enterprise_customer_name=enterprise_customer['name'],
                      hostname=learner_portal_hostname,
                      scheme=request.scheme)
             messages.add_message(request,
                                  messages.INFO,
                                  message,
                                  extra_tags='safe')
Exemple #10
0
    def is_satisfied(self, offer, basket):  # pylint: disable=unused-argument
        """
        Determines if a user is eligible for an enterprise customer offer
        based on their association with the enterprise customer.

        It also filter out the offer if the `enterprise_customer_catalog_uuid`
        value set on the offer condition does not match with the basket catalog
        value when explicitly provided by the enterprise learner.

        Note: Currently there is no mechanism to prioritize or apply multiple
        offers that may apply as opposed to disqualifying offers if the
        catalog doesn't explicitly match.

        Arguments:
            basket (Basket): Contains information about order line items, the current site,
                             and the user attempting to make the purchase.
        Returns:
            bool
        """
        if not basket.owner:
            # An anonymous user is never linked to any EnterpriseCustomer.
            return False

        enterprise_customer = str(self.enterprise_customer_uuid)
        enterprise_catalog = str(self.enterprise_customer_catalog_uuid)
        username = basket.owner.username
        course_run_ids = []
        for line in basket.all_lines():
            course = line.product.course
            if not course:
                # Basket contains products not related to a course_run.
                # Only log for non-site offers to avoid noise.
                if offer.offer_type != ConditionalOffer.SITE:
                    logger.warning(
                        '[Code Redemption Failure] Unable to apply enterprise offer because '
                        'the Basket contains a product not related to a course_run. '
                        'User: %s, Offer: %s, Product: %s, Enterprise: %s, Catalog: %s',
                        username, offer.id, line.product.id,
                        enterprise_customer, enterprise_catalog)
                return False

            course_run_ids.append(course.id)

        courses_in_basket = ','.join(course_run_ids)
        learner_data = {}
        try:
            learner_data = fetch_enterprise_learner_data(
                basket.site, basket.owner)['results'][0]
        except (ConnectionError, KeyError, SlumberHttpBaseException,
                Timeout) as exc:
            logger.exception(
                '[Code Redemption Failure] Unable to apply enterprise offer because '
                'we failed to retrieve enterprise learner data for the user. '
                'User: %s, Offer: %s, Message: %s, Enterprise: %s, Catalog: %s, Courses: %s',
                username, offer.id, exc, enterprise_customer,
                enterprise_catalog, courses_in_basket)
            return False
        except IndexError:
            if offer.offer_type == ConditionalOffer.SITE:
                logger.debug(
                    'Unable to apply enterprise site offer %s because no learner data was returned for user %s',
                    offer.id, basket.owner)
                return False

        if (learner_data and 'enterprise_customer' in learner_data
                and enterprise_customer !=
                learner_data['enterprise_customer']['uuid']):
            # Learner is not linked to the EnterpriseCustomer associated with this condition.
            if offer.offer_type == ConditionalOffer.VOUCHER:
                logger.warning(
                    '[Code Redemption Failure] Unable to apply enterprise offer because Learner\'s '
                    'enterprise (%s) does not match this conditions\'s enterprise (%s). '
                    'User: %s, Offer: %s, Enterprise: %s, Catalog: %s, Courses: %s',
                    learner_data['enterprise_customer']['uuid'],
                    enterprise_customer, username, offer.id,
                    enterprise_customer, enterprise_catalog, courses_in_basket)
            return False

        # Verify that the current conditional offer is related to the provided
        # enterprise catalog, this will also filter out offers which don't
        # have `enterprise_customer_catalog_uuid` value set on the condition.
        catalog = self._get_enterprise_catalog_uuid_from_basket(basket)
        if catalog:
            if offer.condition.enterprise_customer_catalog_uuid != catalog:
                logger.warning(
                    'Unable to apply enterprise offer %s because '
                    'Enterprise catalog id on the basket (%s) '
                    'does not match the catalog for this condition (%s).',
                    offer.id, catalog,
                    offer.condition.enterprise_customer_catalog_uuid)
                return False

        try:
            catalog_contains_course = catalog_contains_course_runs(
                basket.site,
                course_run_ids,
                enterprise_customer,
                enterprise_customer_catalog_uuid=enterprise_catalog)
        except (ConnectionError, KeyError, SlumberHttpBaseException,
                Timeout) as exc:
            logger.exception(
                '[Code Redemption Failure] Unable to apply enterprise offer because '
                'we failed to check if course_runs exist in the catalog. '
                'User: %s, Offer: %s, Message: %s, Enterprise: %s, Catalog: %s, Courses: %s',
                username, offer.id, exc, enterprise_customer,
                enterprise_catalog, courses_in_basket)
            return False

        if not catalog_contains_course:
            # Basket contains course runs that do not exist in the EnterpriseCustomerCatalogs
            # associated with the EnterpriseCustomer.
            logger.warning(
                '[Code Redemption Failure] Unable to apply enterprise offer because '
                'Enterprise catalog does not contain the course(s) in this basket. '
                'User: %s, Offer: %s, Enterprise: %s, Catalog: %s, Courses: %s',
                username, offer.id, enterprise_customer, enterprise_catalog,
                courses_in_basket)
            return False

        return True
Exemple #11
0
    def is_satisfied(self, offer, basket):  # pylint: disable=unused-argument
        """
        Determines if a user is eligible for an enterprise customer offer
        based on their association with the enterprise customer.

        It also filter out the offer if the `enterprise_customer_catalog_uuid`
        value set on the offer condition does not match with the basket catalog
        value when explicitly provided by the enterprise learner.

        Note: Currently there is no mechanism to prioritize or apply multiple
        offers that may apply as opposed to disqualifying offers if the
        catalog doesn't explicitly match.

        Arguments:
            basket (Basket): Contains information about order line items, the current site,
                             and the user attempting to make the purchase.
        Returns:
            bool
        """
        if not basket.owner:
            # An anonymous user is never linked to any EnterpriseCustomer.
            return False

        if offer.offer_type == ConditionalOffer.VOUCHER:
            logger.info(
                'Skipping Voucher type enterprise conditional offer until we are ready to support it.'
            )
            return False

        try:
            learner_data = fetch_enterprise_learner_data(
                basket.site, basket.owner)['results'][0]
        except (ConnectionError, KeyError, SlumberHttpBaseException, Timeout):
            logger.exception(
                'Failed to retrieve enterprise learner data for site [%s] and user [%s].',
                basket.site.domain,
                basket.owner.username,
            )
            return False
        except IndexError:
            return False

        enterprise_customer = learner_data['enterprise_customer']
        if str(self.enterprise_customer_uuid) != enterprise_customer['uuid']:
            # Learner is not linked to the EnterpriseCustomer associated with this condition.
            return False

        course_run_ids = []
        for line in basket.all_lines():
            course = line.product.course
            if not course:
                # Basket contains products not related to a course_run.
                return False

            course_run_ids.append(course.id)

        # Verify that the current conditional offer is related to the provided
        # enterprise catalog, this will also filter out offers which don't
        # have `enterprise_customer_catalog_uuid` value set on the condition.
        catalog = self._get_enterprise_catalog_uuid_from_basket(basket)
        if catalog:
            if offer.condition.enterprise_customer_catalog_uuid != catalog:
                return False

        if not catalog_contains_course_runs(
                basket.site,
                course_run_ids,
                self.enterprise_customer_uuid,
                enterprise_customer_catalog_uuid=self.
                enterprise_customer_catalog_uuid):
            # Basket contains course runs that do not exist in the EnterpriseCustomerCatalogs
            # associated with the EnterpriseCustomer.
            return False

        return True
Exemple #12
0
    def is_satisfied(self, offer, basket):  # pylint: disable=unused-argument
        """
        Determines if a user is eligible for an enterprise customer offer
        based on their association with the enterprise customer.

        It also filter out the offer if the `enterprise_customer_catalog_uuid`
        value set on the offer condition does not match with the basket catalog
        value when explicitly provided by the enterprise learner.

        Note: Currently there is no mechanism to prioritize or apply multiple
        offers that may apply as opposed to disqualifying offers if the
        catalog doesn't explicitly match.

        Arguments:
            basket (Basket): Contains information about order line items, the current site,
                             and the user attempting to make the purchase.
        Returns:
            bool
        """
        if not basket.owner:
            # An anonymous user is never linked to any EnterpriseCustomer.
            return False

        if (offer.offer_type == ConditionalOffer.VOUCHER
                and not waffle.switch_is_active(
                    ENTERPRISE_OFFERS_FOR_COUPONS_SWITCH)):
            logger.info(
                'Skipping Voucher type enterprise conditional offer until we are ready to support it.'
            )
            return False

        learner_data = {}
        try:
            learner_data = fetch_enterprise_learner_data(
                basket.site, basket.owner)['results'][0]
        except (ConnectionError, KeyError, SlumberHttpBaseException, Timeout):
            logger.exception(
                'Failed to retrieve enterprise learner data for site [%s] and user [%s].',
                basket.site.domain,
                basket.owner.username,
            )
            return False
        except IndexError:
            if offer.offer_type == ConditionalOffer.SITE:
                logger.debug(
                    'Unable to apply enterprise site offer %s because no learner data was returned for user %s',
                    offer.id, basket.owner)
                return False

        if (learner_data and 'enterprise_customer' in learner_data
                and str(self.enterprise_customer_uuid) !=
                learner_data['enterprise_customer']['uuid']):
            # Learner is not linked to the EnterpriseCustomer associated with this condition.
            logger.debug(
                'Unable to apply enterprise offer %s because Learner\'s enterprise (%s)'
                'does not match this conditions\'s enterprise (%s).', offer.id,
                learner_data['enterprise_customer']['uuid'],
                str(self.enterprise_customer_uuid))
            return False

        course_run_ids = []
        for line in basket.all_lines():
            course = line.product.course
            if not course:
                # Basket contains products not related to a course_run.
                logger.warning(
                    'Unable to apply enterprise offer %s because '
                    'the Basket (#%s) contains a product (#%s) not related to a course_run.',
                    offer.id, basket.id, line.product.id)
                return False

            course_run_ids.append(course.id)

        # Verify that the current conditional offer is related to the provided
        # enterprise catalog, this will also filter out offers which don't
        # have `enterprise_customer_catalog_uuid` value set on the condition.
        catalog = self._get_enterprise_catalog_uuid_from_basket(basket)
        if catalog:
            if offer.condition.enterprise_customer_catalog_uuid != catalog:
                logger.warning(
                    'Unable to apply enterprise offer %s because '
                    'Enterprise catalog id on the basket (%s) '
                    'does not match the catalog for this condition (%s).',
                    offer.id, catalog,
                    offer.condition.enterprise_customer_catalog_uuid)
                return False

        if not catalog_contains_course_runs(
                basket.site,
                course_run_ids,
                self.enterprise_customer_uuid,
                enterprise_customer_catalog_uuid=self.
                enterprise_customer_catalog_uuid):
            # Basket contains course runs that do not exist in the EnterpriseCustomerCatalogs
            # associated with the EnterpriseCustomer.
            logger.warning(
                'Unable to apply enterprise offer %s because '
                'Enterprise catalog (%s) does not contain the course(s) (%s) in this basket.',
                offer.id, self.enterprise_customer_catalog_uuid,
                ','.join(course_run_ids))
            return False

        return True