Skip to content
Snippets Groups Projects
Commit 708118f9 authored by Calen Pennington's avatar Calen Pennington
Browse files

Enable holdback in first purchase discounts

parent 8b4b5a45
No related branches found
No related tags found
No related merge requests found
......@@ -8,13 +8,18 @@ Keep in mind that the code in this file only applies to discounts controlled in
not other discounts like coupons or enterprise/program offers configured in ecommerce.
"""
from datetime import datetime
import crum
import pytz
from course_modes.models import CourseMode
from entitlements.models import CourseEntitlement
from lms.djangoapps.experiments.stable_bucketing import stable_bucketing_hash_group
from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace
from openedx.features.discounts.models import DiscountRestrictionConfig
from student.models import CourseEnrollment
from track import segment
# .. feature_toggle_name: discounts.enable_discounting
# .. feature_toggle_type: flag
......@@ -33,6 +38,8 @@ DISCOUNT_APPLICABILITY_FLAG = WaffleFlag(
flag_undefined_default=False
)
DISCOUNT_APPLICABILITY_HOLDBACK = 'first_purchase_discount_holdback'
def can_receive_discount(user, course): # pylint: disable=unused-argument
"""
......@@ -68,9 +75,48 @@ def can_receive_discount(user, course): # pylint: disable=unused-argument
if CourseEntitlement.objects.filter(user=user).exists():
return False
# Excute holdback
if _is_in_holdback(user):
return False
return True
def _is_in_holdback(user):
"""
Return whether the specified user is in the first-purchase-discount holdback group.
"""
if datetime(2020, 8, 1, tzinfo=pytz.UTC) <= datetime.now(tz=pytz.UTC):
return False
if not datetime(2019, 8, 1, tzinfo=pytz.UTC) <= user.date_joined <= datetime(2019, 11, 1, tzinfo=pytz.UTC):
return False
# Holdback is 50/50
bucket = stable_bucketing_hash_group(DISCOUNT_APPLICABILITY_HOLDBACK, 2, user.username)
request = crum.get_current_request()
if hasattr(request, 'session') and DISCOUNT_APPLICABILITY_HOLDBACK not in request.session:
properties = {
'site': request.site.domain,
'app_label': 'discounts',
'nonInteraction': 1,
'bucket': bucket,
'experiment': 'REVEM-363',
}
segment.track(
user_id=user.id,
event_name='edx.bi.experiment.user.bucketed',
properties=properties,
)
# Mark that we've recorded this bucketing, so that we don't do it again this session
request.session[DISCOUNT_APPLICABILITY_HOLDBACK] = True
return bucket == 0
def discount_percentage():
"""
Get the configured discount amount.
......
"""Tests of openedx.features.discounts.applicability"""
# -*- coding: utf-8 -*-
from datetime import timedelta
from datetime import timedelta, datetime
import ddt
from django.utils.timezone import now
from mock import patch, Mock
import pytz
from course_modes.models import CourseMode
from course_modes.tests.factories import CourseModeFactory
......@@ -15,7 +17,7 @@ from student.tests.factories import UserFactory, CourseEnrollmentFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from ..applicability import can_receive_discount, DISCOUNT_APPLICABILITY_FLAG
from ..applicability import can_receive_discount, DISCOUNT_APPLICABILITY_FLAG, _is_in_holdback
@ddt.ddt
......@@ -31,6 +33,10 @@ class TestApplicability(ModuleStoreTestCase):
self.course = CourseFactory.create(run='test', display_name='test')
CourseModeFactory.create(course_id=self.course.id, mode_slug='verified')
holdback_patcher = patch('openedx.features.discounts.applicability._is_in_holdback', return_value=False)
self.mock_holdback = holdback_patcher.start()
self.addCleanup(holdback_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)
......@@ -95,3 +101,44 @@ class TestApplicability(ModuleStoreTestCase):
applicability = can_receive_discount(user=self.user, course=self.course)
assert applicability == (entitlement_mode is None)
@override_waffle_flag(DISCOUNT_APPLICABILITY_FLAG, active=True)
def test_holdback_denies_discount(self):
"""
Ensure that users in the holdback do not receive the discount.
"""
self.mock_holdback.return_value = True
applicability = can_receive_discount(user=self.user, course=self.course)
assert not applicability
@ddt.data(
(0, True),
(1, False),
)
@ddt.unpack
def test_holdback_group_ids(self, group_number, in_holdback):
with patch('openedx.features.discounts.applicability.stable_bucketing_hash_group', return_value=group_number):
with patch.object(self.user, 'date_joined', datetime(2019, 8, 1, 0, 1, tzinfo=pytz.UTC)):
assert _is_in_holdback(self.user) == in_holdback
@ddt.data(
(datetime(2019, 7, 31, tzinfo=pytz.UTC), False),
(datetime(2019, 8, 1, 0, 1, tzinfo=pytz.UTC), True),
(datetime(2019, 10, 30, 23, 59, tzinfo=pytz.UTC), True),
(datetime(2019, 11, 1, 0, 1, tzinfo=pytz.UTC), False),
)
@ddt.unpack
def test_holdback_registration_limits(self, registration_date, in_holdback):
with patch('openedx.features.discounts.applicability.stable_bucketing_hash_group', return_value=0):
with patch.object(self.user, 'date_joined', registration_date):
assert _is_in_holdback(self.user) == in_holdback
def test_holdback_expiry(self):
with patch('openedx.features.discounts.applicability.stable_bucketing_hash_group', return_value=0):
with patch.object(self.user, 'date_joined', datetime(2019, 8, 1, 0, 1, tzinfo=pytz.UTC)):
with patch(
'openedx.features.discounts.applicability.datetime',
Mock(now=Mock(return_value=datetime(2020, 8, 1, 0, 1, tzinfo=pytz.UTC)))
):
assert not _is_in_holdback(self.user)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment