Skip to content
Closed
12 changes: 12 additions & 0 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3293,3 +3293,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.DiscountEligibilityEnterpriseStep"],
},
}
28 changes: 18 additions & 10 deletions openedx/features/discounts/applicability.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"""


import logging
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

Expand All @@ -17,6 +18,7 @@
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
Expand All @@ -28,6 +30,8 @@
from lms.djangoapps.experiments.stable_bucketing import stable_bucketing_hash_group
from openedx.features.discounts.models import DiscountPercentageConfig, DiscountRestrictionConfig

log = logging.getLogger(__name__)

# .. toggle_name: discounts.enable_first_purchase_discount_override
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
Expand Down Expand Up @@ -124,11 +128,13 @@ 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.
try:
DiscountEligibilityCheckRequested.run_filter(
user=user, course_key=course.id,
)
except DiscountEligibilityCheckRequested.DiscountIneligible as exc:
log.info("User is ineligible for streak discount: %s", exc.message)
return False

return True
Expand Down Expand Up @@ -179,11 +185,13 @@ 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.
try:
DiscountEligibilityCheckRequested.run_filter(
user=user, course_key=course.id,
)
except DiscountEligibilityCheckRequested.DiscountIneligible as exc:
log.info("User is ineligible for discount: %s", exc.message)
return False

# Turn holdback on
Expand Down
49 changes: 36 additions & 13 deletions openedx/features/discounts/tests/test_applicability.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,28 @@
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 openedx_filters.learning.filters import DiscountEligibilityCheckRequested

from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
from common.djangoapps.entitlements.tests.factories import CourseEntitlementFactory
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from lms.djangoapps.courseware.toggles import COURSEWARE_MFE_MILESTONES_STREAK_DISCOUNT
from lms.djangoapps.experiments.models import ExperimentData
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, # pylint: disable=wrong-import-order
ModuleStoreTestCase, # lint-amnesty, pylint: disable=wrong-import-order
)
from xmodule.modulestore.tests.factories import CourseFactory # 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
from ..applicability import (
DISCOUNT_APPLICABILITY_FLAG,
_is_in_holdback_and_bucket,
can_receive_discount,
can_show_streak_discount_coupon,
)


@ddt.ddt
Expand All @@ -52,6 +58,13 @@ def setUp(self):
self.mock_holdback = holdback_patcher.start()
self.addCleanup(holdback_patcher.stop)

# By default, the filter does not raise, meaning the user is eligible.
discount_filter_patcher = patch(
'openedx.features.discounts.applicability.DiscountEligibilityCheckRequested.run_filter',
)
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 @@ -136,17 +149,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 raises DiscountIneligible,
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 = DiscountEligibilityCheckRequested.DiscountIneligible(
message="test: user is ineligible for discount"
)

applicability = can_receive_discount(user=self.user, course=self.course)
Expand Down Expand Up @@ -180,3 +189,17 @@ def test_holdback_expiry(self):
Mock(now=Mock(return_value=datetime(2020, 8, 1, 0, 1, tzinfo=ZoneInfo("UTC"))), wraps=datetime),
):
assert not _is_in_holdback_and_bucket(self.user)

@override_waffle_flag(COURSEWARE_MFE_MILESTONES_STREAK_DISCOUNT, active=True)
def test_can_show_streak_discount_coupon_false_when_filter_marks_ineligible(self):
"""
Ensure that when the eligibility filter raises DiscountIneligible,
the streak discount coupon is not shown.
"""
CourseEnrollmentFactory(is_active=True, course_id=self.course.id, user=self.user)
self.mock_discount_filter.side_effect = DiscountEligibilityCheckRequested.DiscountIneligible(
message="test: user is ineligible for streak discount"
)

applicability = can_show_streak_discount_coupon(user=self.user, course=self.course)
assert applicability is False
2 changes: 1 addition & 1 deletion requirements/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ django-stubs<6
# The team that owns this package will manually bump this package rather than having it pulled in automatically.
# This is to allow them to better control its deployment and to do it in a process that works better
# for them.
edx-enterprise==8.1.2
edx-enterprise==8.2.0

# Date: 2023-07-26
# Our legacy Sass code is incompatible with anything except this ancient libsass version.
Expand Down
4 changes: 2 additions & 2 deletions requirements/edx/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ edx-drf-extensions==10.6.0
# enterprise-integrated-channels
# openedx-authz
# openedx-core
edx-enterprise==8.1.2
edx-enterprise==8.2.0
# via
# -c requirements/constraints.txt
# -r requirements/edx/kernel.in
Expand Down Expand Up @@ -862,7 +862,7 @@ openedx-events==11.2.0
# openedx-authz
# openedx-core
# ora2
openedx-filters==3.5.0
openedx-filters==3.7.0
# via
# -r requirements/edx/kernel.in
# edx-enterprise
Expand Down
4 changes: 2 additions & 2 deletions requirements/edx/development.txt
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,7 @@ edx-drf-extensions==10.6.0
# enterprise-integrated-channels
# openedx-authz
# openedx-core
edx-enterprise==8.1.2
edx-enterprise==8.2.0
# via
# -c requirements/constraints.txt
# -r requirements/edx/doc.txt
Expand Down Expand Up @@ -1414,7 +1414,7 @@ openedx-events==11.2.0
# openedx-authz
# openedx-core
# ora2
openedx-filters==3.5.0
openedx-filters==3.7.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
Expand Down
4 changes: 2 additions & 2 deletions requirements/edx/doc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ edx-drf-extensions==10.6.0
# enterprise-integrated-channels
# openedx-authz
# openedx-core
edx-enterprise==8.1.2
edx-enterprise==8.2.0
# via
# -c requirements/constraints.txt
# -r requirements/edx/base.txt
Expand Down Expand Up @@ -1042,7 +1042,7 @@ openedx-events==11.2.0
# openedx-authz
# openedx-core
# ora2
openedx-filters==3.5.0
openedx-filters==3.7.0
# via
# -r requirements/edx/base.txt
# edx-enterprise
Expand Down
4 changes: 2 additions & 2 deletions requirements/edx/testing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ edx-drf-extensions==10.6.0
# enterprise-integrated-channels
# openedx-authz
# openedx-core
edx-enterprise==8.1.2
edx-enterprise==8.2.0
# via
# -c requirements/constraints.txt
# -r requirements/edx/base.txt
Expand Down Expand Up @@ -1082,7 +1082,7 @@ openedx-events==11.2.0
# openedx-authz
# openedx-core
# ora2
openedx-filters==3.5.0
openedx-filters==3.7.0
# via
# -r requirements/edx/base.txt
# edx-enterprise
Expand Down
Loading