Files
edx-platform/lms/djangoapps/experiments/tests/test_flags.py
Michael Terry 2e5f2f2be3 Fix error in experiment tracking
If the user is not logged in, the ExperimentWaffleFlag code was
raising an exception when trying to send an event to segment.

This is a quick fix to ignore anonymous users.
2020-03-16 17:40:55 -04:00

127 lines
5.2 KiB
Python

"""
Tests for experimentation feature flags
"""
import pytz
import ddt
from crum import set_current_request
from dateutil import parser
from django.test.client import RequestFactory
from edx_django_utils.cache import RequestCache
from mock import patch
from opaque_keys.edx.keys import CourseKey
from experiments.factories import ExperimentKeyValueFactory
from experiments.flags import ExperimentWaffleFlag
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
from student.tests.factories import CourseEnrollmentFactory, UserFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
@ddt.ddt
class ExperimentWaffleFlagTests(SharedModuleStoreTestCase):
""" Tests for the ExperimentWaffleFlag class """
def setUp(self):
super().setUp()
self.user = UserFactory()
self.request = RequestFactory().request()
self.request.session = {}
self.request.site = SiteFactory()
self.request.user = self.user
self.addCleanup(set_current_request, None)
set_current_request(self.request)
self.flag = ExperimentWaffleFlag('experiments', 'test', num_buckets=2, experiment_id=0)
self.key = CourseKey.from_string('a/b/c')
bucket_patch = patch('experiments.flags.stable_bucketing_hash_group', return_value=1)
self.addCleanup(bucket_patch.stop)
bucket_patch.start()
self.addCleanup(RequestCache.clear_all_namespaces)
def get_bucket(self, track=False, active=True):
# Does not use ExperimentWaffleFlag.override, since that shortcuts get_bucket and we want to test internals
with override_waffle_flag(self.flag, active):
with override_waffle_flag(self.flag.bucket_flags[1], True):
return self.flag.get_bucket(course_key=self.key, track=track)
def test_basic_happy_path(self):
self.assertEqual(self.get_bucket(), 1)
def test_no_request(self):
set_current_request(None)
self.assertEqual(self.get_bucket(), 0)
def test_not_enabled(self):
self.assertEqual(self.get_bucket(active=False), 0)
@ddt.data(
('2012-01-06', None, 1), # no enrollment (we allow normal bucketing in this case)
('2012-01-06', '2012-01-05', 0), # enrolled before experiment
('2012-01-06', '2012-01-07', 1), # enrolled after experiment
(None, '2012-01-07', 1), # no experiment date
('not-a-date', '2012-01-07', 0), # bad experiment date
)
@ddt.unpack
def test_enrollment_start(self, experiment_start, enrollment_created, expected_bucket):
if enrollment_created:
enrollment = CourseEnrollmentFactory(user=self.user, course_id='a/b/c')
enrollment.created = parser.parse(enrollment_created).replace(tzinfo=pytz.UTC)
enrollment.save()
if experiment_start:
ExperimentKeyValueFactory(experiment_id=0, key='enrollment_start', value=experiment_start)
self.assertEqual(self.get_bucket(), expected_bucket)
@ddt.data(
(True, 0),
(False, 1),
)
@ddt.unpack
def test_bucket_override(self, active, expected_bucket):
bucket_flag = CourseWaffleFlag('experiments', 'test.0')
with bucket_flag.override(active=active):
self.assertEqual(self.get_bucket(), expected_bucket)
def test_tracking(self):
# Run twice, with same request
with patch('experiments.flags.segment') as segment_mock:
self.assertEqual(self.get_bucket(track=True), 1)
RequestCache.clear_all_namespaces() # we want to force get_bucket to check session, not early exit
self.assertEqual(self.get_bucket(track=True), 1)
# Now test that we only sent the signal once, and with the correct properties
self.assertEqual(segment_mock.track.call_count, 1)
self.assertEqual(segment_mock.track.call_args, ((), {
'user_id': self.user.id,
'event_name': 'edx.bi.experiment.user.bucketed',
'properties': {
'site': self.request.site.domain,
'app_label': 'experiments',
'experiment': 'test',
'bucket': 1,
'course_id': 'a/b/c',
'is_staff': self.user.is_staff,
'nonInteraction': 1,
},
}))
def test_caching(self):
self.assertEqual(self.get_bucket(active=True), 1)
self.assertEqual(self.get_bucket(active=False), 1) # still returns 1!
def test_is_enabled(self):
with patch('experiments.flags.ExperimentWaffleFlag.get_bucket', return_value=1):
self.assertEqual(self.flag.is_enabled_without_course_context(), True)
self.assertEqual(self.flag.is_enabled(self.key), True)
self.assertEqual(self.flag.is_enabled(), True)
with patch('experiments.flags.ExperimentWaffleFlag.get_bucket', return_value=0):
self.assertEqual(self.flag.is_enabled_without_course_context(), False)
self.assertEqual(self.flag.is_enabled(self.key), False)
self.assertEqual(self.flag.is_enabled(), False)