def _is_eligible_for_REV1074_experiment(request, sku): """ For https://openedx.atlassian.net/browse/REV-1074 we are testing a mostly hardcoded version of the checkout page. We are trying to improve performance and measure if there is an effect on revenue. In order to improve performance and simplify the engineering work, many use cases are not being handled. These use cases will all need to be omitted from the experiment and sent to the regular checkout page """ basket = request.basket user_agent = request.META.get('HTTP_USER_AGENT') omit = ( # We applied filters to our list of courses and only some courses will be included in the experiment sku not in SKUS_IN_EXPERIMENT or # The static page doesn't support offers/coupons so baskets with either are omitted from the experiment basket.applied_offers() or basket.voucher_discounts or basket.total_discount or # Optimizely is being used by the mobile app on the checkout page. # We are removing optimizely from the static version of the page, # so we are omitting mobile app traffic from this experiment (user_agent and re.search(r'edX/org.edx.mobile', user_agent)) or # Bundles would add substantial additional complexity to the experiment so we are omitting bundles basket.num_items > 1 or # The static page only supports seat products not basket.lines.first().product.is_seat_product or # The static page is not supporting enterprise use cases so enterprise learners need to be excluded # Excluding all offers and coupons above should handle most enterprise use cases # This check should handle enterprise users getattr(request.basket, 'ENTERPRISE_CATALOG_ATTRIBUTE_TYPE', None) or get_enterprise_id_for_user(basket.site, basket.owner) or # We do not want to include zero dollar purchases request.basket.total_incl_tax == 0 or str(request.user.username).startswith('test_') and str(request.user.email).endswith('example.com') ) return not omit
def test_get_enterprise_id_for_user_enterprise_in_jwt( self, mock_get_jwt_uuid): """ Verify get_enterprise_id_for_user returns ent id if uuid in jwt context """ mock_get_jwt_uuid.return_value = 'my-uuid' assert get_enterprise_id_for_user('some-site', self.learner) == 'my-uuid'
def test_get_enterprise_id_for_user_no_uuid_in_response( self, mock_get_jwt_uuid, mock_fetch): """ Verify if learner data fetch is successful but does not include uuid field, None is returned """ mock_get_jwt_uuid.return_value = None mock_fetch.return_value = {'results': []} assert get_enterprise_id_for_user('some-site', self.learner) is None
def test_get_enterprise_id_for_user_fetch_errors(self, mock_get_jwt_uuid, mock_fetch): """ Verify if that learner data fetch errors, get_enterprise_id_for_user returns None """ mock_get_jwt_uuid.return_value = None mock_fetch.side_effect = [KeyError] assert get_enterprise_id_for_user('some-site', self.learner) is None
def _get_enterprise_offers(self, site, user): """ Return enterprise offers filtered by the user's enterprise, if it exists. """ enterprise_id = get_enterprise_id_for_user(site, user) if enterprise_id: ConditionalOffer = get_model('offer', 'ConditionalOffer') offers = ConditionalOffer.active.filter( offer_type=ConditionalOffer.SITE, condition__enterprise_customer_uuid=enterprise_id) return offers.select_related('condition', 'benefit') return []
def test_get_enterprise_id_for_user_fetch_learner_data_has_uuid( self, mock_get_jwt_uuid, mock_fetch): """ Verify get_enterprise_id_for_user returns enterprise id if jwt does not have enterprise uuid, but is able to fetch it via api call """ mock_get_jwt_uuid.return_value = None mock_fetch.return_value = { 'results': [{ 'enterprise_customer': { 'uuid': 'my-uuid' } }] } assert get_enterprise_id_for_user('some-site', self.learner) == 'my-uuid'
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) enterprise_id = get_enterprise_id_for_user(basket.site, basket.owner) if enterprise_id and enterprise_customer != enterprise_id: # 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', enterprise_id, 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, request=basket.strategy.request) except (ReqConnectionError, 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