Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3172,3 +3172,15 @@ def _should_send_certificate_events(settings):
SSL_AUTH_DN_FORMAT_STRING = (
"/C=US/ST=Massachusetts/O=Massachusetts Institute of Technology/OU=Client CA v1/CN={0}/emailAddress={1}"
)

# .. setting_name: OPEN_EDX_FILTERS_CONFIG
# .. setting_default: {}
# .. setting_description: Configuration dict for openedx-filters pipeline steps.
# Keys are filter type strings; values are dicts with 'fail_silently' (bool) and
# 'pipeline' (list of dotted-path strings to PipelineStep subclasses).
OPEN_EDX_FILTERS_CONFIG = {
"org.openedx.learning.discount.eligibility.check.requested.v1": {
"fail_silently": True,
"pipeline": ["enterprise.filters.discounts.DiscountEligibilityStep"],
},
}
28 changes: 15 additions & 13 deletions openedx/features/discounts/applicability.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,24 @@


from datetime import datetime, timedelta

from zoneinfo import ZoneInfo

from crum import get_current_request, impersonate
from django.conf import settings
from django.utils import timezone
from django.utils.dateparse import parse_datetime
from edx_toggles.toggles import WaffleFlag
from openedx_filters.learning.filters import DiscountEligibilityCheckRequested

from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.entitlements.models import CourseEntitlement
from lms.djangoapps.courseware.utils import is_mode_upsellable
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.track import segment
from lms.djangoapps.courseware.toggles import COURSEWARE_MFE_MILESTONES_STREAK_DISCOUNT
from lms.djangoapps.courseware.utils import is_mode_upsellable
from lms.djangoapps.experiments.models import ExperimentData
from lms.djangoapps.experiments.stable_bucketing import stable_bucketing_hash_group
from openedx.features.discounts.models import DiscountPercentageConfig, DiscountRestrictionConfig
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.track import segment


# .. toggle_name: discounts.enable_first_purchase_discount_override
# .. toggle_implementation: WaffleFlag
Expand Down Expand Up @@ -125,10 +125,11 @@ def can_show_streak_discount_coupon(user, course):
if not is_mode_upsellable(user, enrollment):
return False

# We can't import this at Django load time within the openedx tests settings context
from openedx.features.enterprise_support.utils import is_enterprise_learner
# Don't give discount to enterprise users
if is_enterprise_learner(user):
# Allow plugins to mark this user as ineligible for the discount.
_user, _course_key, is_eligible = DiscountEligibilityCheckRequested.run_filter(
user=user, course_key=course.id, is_eligible=True
)
if not is_eligible:
return False

return True
Expand Down Expand Up @@ -179,10 +180,11 @@ def can_receive_discount(user, course, discount_expiration_date=None):
if CourseEntitlement.objects.filter(user=user).exists():
return False

# We can't import this at Django load time within the openedx tests settings context
from openedx.features.enterprise_support.utils import is_enterprise_learner
# Don't give discount to enterprise users
if is_enterprise_learner(user):
# Allow plugins to mark this user as ineligible for the discount.
_user, _course_key, is_eligible = DiscountEligibilityCheckRequested.run_filter(
user=user, course_key=course.id, is_eligible=True
)
if not is_eligible:
return False

# Turn holdback on
Expand Down
28 changes: 16 additions & 12 deletions openedx/features/discounts/tests/test_applicability.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@

from datetime import datetime, timedelta
from unittest.mock import Mock, patch
from zoneinfo import ZoneInfo

import ddt
import pytest
from zoneinfo import ZoneInfo
from django.contrib.sites.models import Site
from django.utils.timezone import now
from edx_toggles.toggles.testutils import override_waffle_flag
from enterprise.models import EnterpriseCustomer, EnterpriseCustomerUser

from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
Expand All @@ -20,7 +19,8 @@
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.features.discounts.models import DiscountRestrictionConfig
from openedx.features.discounts.utils import REV1008_EXPERIMENT_ID
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.django_utils import \
ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order

from ..applicability import DISCOUNT_APPLICABILITY_FLAG, _is_in_holdback_and_bucket, can_receive_discount
Expand Down Expand Up @@ -50,6 +50,14 @@ def setUp(self):
self.mock_holdback = holdback_patcher.start()
self.addCleanup(holdback_patcher.stop)

# By default, the filter passes eligibility through unchanged.
discount_filter_patcher = patch(
'openedx.features.discounts.applicability.DiscountEligibilityCheckRequested.run_filter',
side_effect=lambda user, course_key, is_eligible: (user, course_key, is_eligible),
)
self.mock_discount_filter = discount_filter_patcher.start()
self.addCleanup(discount_filter_patcher.stop)

def test_can_receive_discount(self):
# Right now, no one should be able to receive the discount
applicability = can_receive_discount(user=self.user, course=self.course)
Expand Down Expand Up @@ -134,17 +142,13 @@ def test_can_receive_discount_entitlement(self, entitlement_mode):
assert applicability == (entitlement_mode is None)

@override_waffle_flag(DISCOUNT_APPLICABILITY_FLAG, active=True)
def test_can_receive_discount_false_enterprise(self):
def test_can_receive_discount_false_when_filter_marks_ineligible(self):
"""
Ensure that enterprise users do not receive the discount.
Ensure that when the eligibility filter marks the user as ineligible,
no discount is received.
"""
enterprise_customer = EnterpriseCustomer.objects.create(
name='Test EnterpriseCustomer',
site=self.site
)
EnterpriseCustomerUser.objects.create(
user_id=self.user.id,
enterprise_customer=enterprise_customer
self.mock_discount_filter.side_effect = lambda user, course_key, is_eligible: (
user, course_key, False
)

applicability = can_receive_discount(user=self.user, course=self.course)
Expand Down
Loading