Files
edx-platform/openedx/features/course_duration_limits/tests/test_access.py
Michael Terry 74887aa216 feat: turn on schedule creation by default
This commit removes several waffle toggles that have been enabled
on edx.org for years. It's time to remove the rollout gating for
these features and enable them by default.

This doesn't directly change any behavior. But it does create new
database objects by default now and allows for enabling other
schedule based features more easily.

Specifically, the following toggles were affected.

schedules.create_schedules_for_course
- Waffle flag removed as always-enabled
- We now always create a schedule when an enrollment is created

schedules.send_updates_for_course
- Waffle flag removed as always-enabled
- Course update emails are sent as long as the ScheduleConfig
  allows it.
- This is not a change in default behavior, because ScheduleConfig
  is off by default.

dynamic_pacing.studio_course_update
- Waffle switch removed as always-enabled
- Course teams can now always edit course updates directly in Studio

ScheduleConfig.create_schedules
ScheduleConfig.hold_back_ratio
- Model fields for rolling out the schedules feature
- Schedules are now always created
- This commit only removes references to these fields, they still
  exist in the database. A future commit will remove them entirely

This commit also adds a new has_highlights field to CourseOverview.
This is used to cache whether a course has highlights, used to
decide which course update email behavior they get. Previously every
enrollment had to dig into the modulestore to determine that.
2021-02-23 12:34:02 -05:00

164 lines
6.5 KiB
Python

"""Tests of openedx.features.course_duration_limits.access"""
import itertools
from datetime import datetime, timedelta
import ddt
from crum import set_current_request
from django.test import RequestFactory
from django.utils import timezone
from pytz import UTC
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
from common.djangoapps.student.tests.factories import UserFactory
from lms.djangoapps.courseware.models import DynamicUpgradeDeadlineConfiguration
from openedx.core.djangoapps.schedules.models import Schedule
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
from openedx.features.course_duration_limits.access import (
generate_course_expired_message,
get_access_expiration_data,
get_user_course_duration,
get_user_course_expiration_date
)
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory
from common.djangoapps.util.date_utils import strftime_localized
@ddt.ddt
class TestAccess(CacheIsolationTestCase):
"""Tests of openedx.features.course_duration_limits.access"""
def setUp(self):
super(TestAccess, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=UTC))
DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True)
def assertDateInMessage(self, date, message): # lint-amnesty, pylint: disable=missing-function-docstring
# First, check that the formatted version is in there
assert strftime_localized(date, 'SHORT_DATE') in message
# But also that the machine-readable version is in there
assert 'data-datetime="%s"' % date.isoformat() in message
def test_get_access_expiration_data(self):
enrollment = CourseEnrollmentFactory()
overview = enrollment.course
user = enrollment.user
now = timezone.now()
upgrade_deadline = now + timedelta(days=2)
CourseModeFactory(
course_id=enrollment.course.id,
mode_slug=CourseMode.VERIFIED,
expiration_datetime=upgrade_deadline,
)
CourseModeFactory(
course_id=enrollment.course.id,
mode_slug=CourseMode.AUDIT,
)
expiration_date = get_user_course_expiration_date(user, overview)
assert expiration_date is not None
data = get_access_expiration_data(user, overview)
assert data == \
{
'expiration_date': expiration_date,
'masquerading_expired_course': False,
'upgrade_deadline': upgrade_deadline,
'upgrade_url': '/dashboard'
}
@ddt.data(
*itertools.product(
itertools.product([None, -2, -1, 1, 2], repeat=2),
)
)
@ddt.unpack
def test_generate_course_expired_message(self, offsets):
now = timezone.now()
schedule_offset, course_offset = offsets
# Set a timezone and request, to test that the message looks at the user's setting
request = RequestFactory().get('/')
request.user = UserFactory()
set_current_request(request)
self.addCleanup(set_current_request, None)
set_user_preference(request.user, 'time_zone', 'Asia/Tokyo')
if schedule_offset is not None:
schedule_upgrade_deadline = now + timedelta(days=schedule_offset)
else:
schedule_upgrade_deadline = None
if course_offset is not None:
course_upgrade_deadline = now + timedelta(days=course_offset)
else:
course_upgrade_deadline = None
enrollment = CourseEnrollmentFactory.create(
course__start=datetime(2018, 1, 1, tzinfo=UTC),
course__self_paced=True,
)
CourseModeFactory.create(
course_id=enrollment.course.id,
mode_slug=CourseMode.VERIFIED,
expiration_datetime=course_upgrade_deadline,
)
CourseModeFactory.create(
course_id=enrollment.course.id,
mode_slug=CourseMode.AUDIT,
)
Schedule.objects.update(upgrade_deadline=schedule_upgrade_deadline)
duration_limit_upgrade_deadline = get_user_course_expiration_date(enrollment.user, enrollment.course)
assert duration_limit_upgrade_deadline is not None
message = generate_course_expired_message(enrollment.user, enrollment.course)
self.assertDateInMessage(duration_limit_upgrade_deadline, message)
assert 'data-timezone="Asia/Tokyo"' in message
soft_upgradeable = schedule_upgrade_deadline is not None and now < schedule_upgrade_deadline
upgradeable = course_upgrade_deadline is None or now < course_upgrade_deadline
has_upgrade_deadline = course_upgrade_deadline is not None
if upgradeable and soft_upgradeable:
self.assertDateInMessage(schedule_upgrade_deadline, message)
elif upgradeable and has_upgrade_deadline:
self.assertDateInMessage(course_upgrade_deadline, message)
else:
assert 'Upgrade by' not in message
def test_schedule_start_date_in_past(self):
"""
Test that when schedule start date is before course start or
enrollment date, content_availability_date is set to max of course start
or enrollment date
"""
enrollment = CourseEnrollmentFactory.create(
course__start=datetime(2018, 1, 1, tzinfo=UTC),
course__self_paced=True,
)
CourseModeFactory.create(
course_id=enrollment.course.id,
mode_slug=CourseMode.VERIFIED,
)
CourseModeFactory.create(
course_id=enrollment.course.id,
mode_slug=CourseMode.AUDIT,
)
Schedule.objects.update(start_date=datetime(2017, 1, 1, tzinfo=UTC))
content_availability_date = max(enrollment.created, enrollment.course.start)
access_duration = get_user_course_duration(enrollment.user, enrollment.course)
expected_course_expiration_date = content_availability_date + access_duration
duration_limit_upgrade_deadline = get_user_course_expiration_date(enrollment.user, enrollment.course)
assert duration_limit_upgrade_deadline is not None
assert duration_limit_upgrade_deadline == expected_course_expiration_date