Files
edx-platform/openedx/features/course_duration_limits/tests/test_models.py
Kyle McCormick 151bd13666 Use full names for common.djangoapps imports; warn when using old style (#25477)
* Generate common/djangoapps import shims for LMS
* Generate common/djangoapps import shims for Studio
* Stop appending project root to sys.path
* Stop appending common/djangoapps to sys.path
* Import from common.djangoapps.course_action_state instead of course_action_state
* Import from common.djangoapps.course_modes instead of course_modes
* Import from common.djangoapps.database_fixups instead of database_fixups
* Import from common.djangoapps.edxmako instead of edxmako
* Import from common.djangoapps.entitlements instead of entitlements
* Import from common.djangoapps.pipline_mako instead of pipeline_mako
* Import from common.djangoapps.static_replace instead of static_replace
* Import from common.djangoapps.student instead of student
* Import from common.djangoapps.terrain instead of terrain
* Import from common.djangoapps.third_party_auth instead of third_party_auth
* Import from common.djangoapps.track instead of track
* Import from common.djangoapps.util instead of util
* Import from common.djangoapps.xblock_django instead of xblock_django
* Add empty common/djangoapps/__init__.py to fix pytest collection
* Fix pylint formatting violations
* Exclude import_shims/ directory tree from linting
2020-11-10 07:02:01 -05:00

416 lines
17 KiB
Python

"""
Tests of CourseDurationLimitConfig.
"""
import itertools
from datetime import datetime, timedelta
import ddt
import pytz
from django.utils import timezone
from edx_django_utils.cache import RequestCache
from mock import Mock
from opaque_keys.edx.locator import CourseLocator
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
from openedx.core.djangoapps.config_model_utils.models import Provenance
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
@ddt.ddt
class TestCourseDurationLimitConfig(CacheIsolationTestCase):
"""
Tests of CourseDurationLimitConfig
"""
ENABLED_CACHES = ['default']
def setUp(self):
self.course_overview = CourseOverviewFactory.create()
CourseModeFactory.create(course_id=self.course_overview.id, mode_slug='audit')
CourseModeFactory.create(course_id=self.course_overview.id, mode_slug='verified')
self.user = UserFactory.create()
super(TestCourseDurationLimitConfig, self).setUp()
@ddt.data(
(True, True),
(True, False),
(False, True),
(False, False),
)
@ddt.unpack
def test_enabled_for_enrollment(
self,
already_enrolled,
enrolled_before_enabled,
):
# Tweak the datetime to enable the config so that it is either before
# or after now (which is when the enrollment will be created)
if enrolled_before_enabled:
enabled_as_of = timezone.now() + timedelta(days=1)
else:
enabled_as_of = timezone.now() - timedelta(days=1)
CourseDurationLimitConfig.objects.create(
enabled=True,
course=self.course_overview,
enabled_as_of=enabled_as_of,
)
if already_enrolled:
existing_enrollment = CourseEnrollmentFactory.create(
user=self.user,
course=self.course_overview,
)
else:
existing_enrollment = None
user = self.user
course_key = self.course_overview.id
query_count = 7
with self.assertNumQueries(query_count):
enabled = CourseDurationLimitConfig.enabled_for_enrollment(user, self.course_overview)
self.assertEqual(not enrolled_before_enabled, enabled)
def test_enabled_for_enrollment_failure(self):
with self.assertRaises(ValueError):
CourseDurationLimitConfig.enabled_for_enrollment(None, None)
with self.assertRaises(ValueError):
CourseDurationLimitConfig.enabled_for_enrollment(
Mock(name='user'),
None
)
with self.assertRaises(ValueError):
CourseDurationLimitConfig.enabled_for_enrollment(
None,
Mock(name='course_key')
)
@ddt.data(True, False)
def test_enabled_for_course(
self,
before_enabled,
):
config = CourseDurationLimitConfig.objects.create(
enabled=True,
course=self.course_overview,
enabled_as_of=timezone.now(),
)
# Tweak the datetime to check for course enablement so it is either
# before or after when the configuration was enabled
if before_enabled:
target_datetime = config.enabled_as_of - timedelta(days=1)
else:
target_datetime = config.enabled_as_of + timedelta(days=1)
course_key = self.course_overview.id
self.assertEqual(
not before_enabled,
CourseDurationLimitConfig.enabled_for_course(
course_key=course_key,
target_datetime=target_datetime,
)
)
@ddt.data(
# Generate all combinations of setting each configuration level to True/False/None
*itertools.product(*([(True, False, None)] * 4 + [(True, False)]))
)
@ddt.unpack
def test_config_overrides(self, global_setting, site_setting, org_setting, course_setting, reverse_order):
"""
Test that the stacked configuration overrides happen in the correct order and priority.
This is tested by exhaustively setting each combination of contexts, and validating that only
the lowest level context that is set to not-None is applied.
"""
# Add a bunch of configuration outside the contexts that are being tested, to make sure
# there are no leaks of configuration across contexts
non_test_course_enabled = CourseOverviewFactory.create(org='non-test-org-enabled')
non_test_course_disabled = CourseOverviewFactory.create(org='non-test-org-disabled')
non_test_site_cfg_enabled = SiteConfigurationFactory.create(
site_values={'course_org_filter': non_test_course_enabled.org}
)
non_test_site_cfg_disabled = SiteConfigurationFactory.create(
site_values={'course_org_filter': non_test_course_disabled.org}
)
CourseDurationLimitConfig.objects.create(course=non_test_course_enabled, enabled=True)
CourseDurationLimitConfig.objects.create(course=non_test_course_disabled, enabled=False)
CourseDurationLimitConfig.objects.create(org=non_test_course_enabled.org, enabled=True)
CourseDurationLimitConfig.objects.create(org=non_test_course_disabled.org, enabled=False)
CourseDurationLimitConfig.objects.create(site=non_test_site_cfg_enabled.site, enabled=True)
CourseDurationLimitConfig.objects.create(site=non_test_site_cfg_disabled.site, enabled=False)
# Set up test objects
test_course = CourseOverviewFactory.create(org='test-org')
test_site_cfg = SiteConfigurationFactory.create(
site_values={'course_org_filter': test_course.org}
)
if reverse_order:
CourseDurationLimitConfig.objects.create(site=test_site_cfg.site, enabled=site_setting)
CourseDurationLimitConfig.objects.create(org=test_course.org, enabled=org_setting)
CourseDurationLimitConfig.objects.create(course=test_course, enabled=course_setting)
CourseDurationLimitConfig.objects.create(enabled=global_setting)
else:
CourseDurationLimitConfig.objects.create(enabled=global_setting)
CourseDurationLimitConfig.objects.create(course=test_course, enabled=course_setting)
CourseDurationLimitConfig.objects.create(org=test_course.org, enabled=org_setting)
CourseDurationLimitConfig.objects.create(site=test_site_cfg.site, enabled=site_setting)
expected_global_setting = self._resolve_settings([global_setting])
expected_site_setting = self._resolve_settings([global_setting, site_setting])
expected_org_setting = self._resolve_settings([global_setting, site_setting, org_setting])
expected_course_setting = self._resolve_settings([global_setting, site_setting, org_setting, course_setting])
self.assertEqual(expected_global_setting, CourseDurationLimitConfig.current().enabled)
self.assertEqual(expected_site_setting, CourseDurationLimitConfig.current(site=test_site_cfg.site).enabled)
self.assertEqual(expected_org_setting, CourseDurationLimitConfig.current(org=test_course.org).enabled)
self.assertEqual(expected_course_setting, CourseDurationLimitConfig.current(course_key=test_course.id).enabled)
def test_all_current_course_configs(self):
# Set up test objects
for global_setting in (True, False, None):
CourseDurationLimitConfig.objects.create(enabled=global_setting, enabled_as_of=datetime(2018, 1, 1))
for site_setting in (True, False, None):
test_site_cfg = SiteConfigurationFactory.create(
site_values={'course_org_filter': []}
)
CourseDurationLimitConfig.objects.create(
site=test_site_cfg.site, enabled=site_setting, enabled_as_of=datetime(2018, 1, 1)
)
for org_setting in (True, False, None):
test_org = "{}-{}".format(test_site_cfg.id, org_setting)
test_site_cfg.site_values['course_org_filter'].append(test_org)
test_site_cfg.save()
CourseDurationLimitConfig.objects.create(
org=test_org, enabled=org_setting, enabled_as_of=datetime(2018, 1, 1)
)
for course_setting in (True, False, None):
test_course = CourseOverviewFactory.create(
org=test_org,
id=CourseLocator(test_org, 'test_course', 'run-{}'.format(course_setting))
)
CourseDurationLimitConfig.objects.create(
course=test_course, enabled=course_setting, enabled_as_of=datetime(2018, 1, 1)
)
with self.assertNumQueries(4):
all_configs = CourseDurationLimitConfig.all_current_course_configs()
# Deliberatly using the last all_configs that was checked after the 3rd pass through the global_settings loop
# We should be creating 3^4 courses (3 global values * 3 site values * 3 org values * 3 course values)
# Plus 1 for the edX/toy/2012_Fall course
self.assertEqual(len(all_configs), 3**4 + 1)
# Point-test some of the final configurations
self.assertEqual(
all_configs[CourseLocator('7-True', 'test_course', 'run-None')],
{
'enabled': (True, Provenance.org),
'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC), Provenance.run),
}
)
self.assertEqual(
all_configs[CourseLocator('7-True', 'test_course', 'run-False')],
{
'enabled': (False, Provenance.run),
'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC), Provenance.run),
}
)
self.assertEqual(
all_configs[CourseLocator('7-None', 'test_course', 'run-None')],
{
'enabled': (True, Provenance.site),
'enabled_as_of': (datetime(2018, 1, 1, 0, tzinfo=pytz.UTC), Provenance.run),
}
)
def test_caching_global(self):
global_config = CourseDurationLimitConfig(enabled=True, enabled_as_of=datetime(2018, 1, 1))
global_config.save()
RequestCache.clear_all_namespaces()
# Check that the global value is not retrieved from cache after save
with self.assertNumQueries(1):
self.assertTrue(CourseDurationLimitConfig.current().enabled)
RequestCache.clear_all_namespaces()
# Check that the global value can be retrieved from cache after read
with self.assertNumQueries(0):
self.assertTrue(CourseDurationLimitConfig.current().enabled)
global_config.enabled = False
global_config.save()
RequestCache.clear_all_namespaces()
# Check that the global value in cache was deleted on save
with self.assertNumQueries(1):
self.assertFalse(CourseDurationLimitConfig.current().enabled)
def test_caching_site(self):
site_cfg = SiteConfigurationFactory()
site_config = CourseDurationLimitConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1))
site_config.save()
RequestCache.clear_all_namespaces()
# Check that the site value is not retrieved from cache after save
with self.assertNumQueries(1):
self.assertTrue(CourseDurationLimitConfig.current(site=site_cfg.site).enabled)
RequestCache.clear_all_namespaces()
# Check that the site value can be retrieved from cache after read
with self.assertNumQueries(0):
self.assertTrue(CourseDurationLimitConfig.current(site=site_cfg.site).enabled)
site_config.enabled = False
site_config.save()
RequestCache.clear_all_namespaces()
# Check that the site value in cache was deleted on save
with self.assertNumQueries(1):
self.assertFalse(CourseDurationLimitConfig.current(site=site_cfg.site).enabled)
global_config = CourseDurationLimitConfig(enabled=True, enabled_as_of=datetime(2018, 1, 1))
global_config.save()
RequestCache.clear_all_namespaces()
# Check that the site value is not updated in cache by changing the global value
with self.assertNumQueries(0):
self.assertFalse(CourseDurationLimitConfig.current(site=site_cfg.site).enabled)
def test_caching_org(self):
course = CourseOverviewFactory.create(org='test-org')
site_cfg = SiteConfigurationFactory.create(
site_values={'course_org_filter': course.org}
)
org_config = CourseDurationLimitConfig(org=course.org, enabled=True, enabled_as_of=datetime(2018, 1, 1))
org_config.save()
RequestCache.clear_all_namespaces()
# Check that the org value is not retrieved from cache after save
with self.assertNumQueries(2):
self.assertTrue(CourseDurationLimitConfig.current(org=course.org).enabled)
RequestCache.clear_all_namespaces()
# Check that the org value can be retrieved from cache after read
with self.assertNumQueries(0):
self.assertTrue(CourseDurationLimitConfig.current(org=course.org).enabled)
org_config.enabled = False
org_config.save()
RequestCache.clear_all_namespaces()
# Check that the org value in cache was deleted on save
with self.assertNumQueries(2):
self.assertFalse(CourseDurationLimitConfig.current(org=course.org).enabled)
global_config = CourseDurationLimitConfig(enabled=True, enabled_as_of=datetime(2018, 1, 1))
global_config.save()
RequestCache.clear_all_namespaces()
# Check that the org value is not updated in cache by changing the global value
with self.assertNumQueries(0):
self.assertFalse(CourseDurationLimitConfig.current(org=course.org).enabled)
site_config = CourseDurationLimitConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1))
site_config.save()
RequestCache.clear_all_namespaces()
# Check that the org value is not updated in cache by changing the site value
with self.assertNumQueries(0):
self.assertFalse(CourseDurationLimitConfig.current(org=course.org).enabled)
def test_caching_course(self):
course = CourseOverviewFactory.create(org='test-org')
site_cfg = SiteConfigurationFactory.create(
site_values={'course_org_filter': course.org}
)
course_config = CourseDurationLimitConfig(course=course, enabled=True, enabled_as_of=datetime(2018, 1, 1))
course_config.save()
RequestCache.clear_all_namespaces()
# Check that the org value is not retrieved from cache after save
with self.assertNumQueries(2):
self.assertTrue(CourseDurationLimitConfig.current(course_key=course.id).enabled)
RequestCache.clear_all_namespaces()
# Check that the org value can be retrieved from cache after read
with self.assertNumQueries(0):
self.assertTrue(CourseDurationLimitConfig.current(course_key=course.id).enabled)
course_config.enabled = False
course_config.save()
RequestCache.clear_all_namespaces()
# Check that the org value in cache was deleted on save
with self.assertNumQueries(2):
self.assertFalse(CourseDurationLimitConfig.current(course_key=course.id).enabled)
global_config = CourseDurationLimitConfig(enabled=True, enabled_as_of=datetime(2018, 1, 1))
global_config.save()
RequestCache.clear_all_namespaces()
# Check that the org value is not updated in cache by changing the global value
with self.assertNumQueries(0):
self.assertFalse(CourseDurationLimitConfig.current(course_key=course.id).enabled)
site_config = CourseDurationLimitConfig(site=site_cfg.site, enabled=True, enabled_as_of=datetime(2018, 1, 1))
site_config.save()
RequestCache.clear_all_namespaces()
# Check that the org value is not updated in cache by changing the site value
with self.assertNumQueries(0):
self.assertFalse(CourseDurationLimitConfig.current(course_key=course.id).enabled)
org_config = CourseDurationLimitConfig(org=course.org, enabled=True, enabled_as_of=datetime(2018, 1, 1))
org_config.save()
RequestCache.clear_all_namespaces()
# Check that the org value is not updated in cache by changing the site value
with self.assertNumQueries(0):
self.assertFalse(CourseDurationLimitConfig.current(course_key=course.id).enabled)
def _resolve_settings(self, settings):
if all(setting is None for setting in settings):
return None
return [
setting
for setting
in settings
if setting is not None
][-1]