Merge pull request #19293 from edx/bfiller/REVE-96
Don't allow audit-only courses to expire
This commit is contained in:
@@ -32,6 +32,7 @@ from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory
|
||||
from openedx.core.djangoapps.user_authn.cookies import _get_user_info_cookie_data
|
||||
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
|
||||
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
|
||||
from openedx.features.course_experience.tests.views.helpers import add_course_mode
|
||||
from student.helpers import DISABLE_UNENROLL_CERT_STATES
|
||||
from student.models import CourseEnrollment, UserProfile
|
||||
from student.signals import REFUND_ORDER
|
||||
@@ -733,6 +734,7 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin,
|
||||
self.override_waffle_switch(True)
|
||||
|
||||
course = CourseFactory.create(start=self.THREE_YEARS_AGO)
|
||||
add_course_mode(course, upgrade_deadline_expired=False)
|
||||
enrollment = CourseEnrollmentFactory.create(
|
||||
user=self.user,
|
||||
course_id=course.id
|
||||
|
||||
@@ -851,6 +851,11 @@ class CourseOverviewAccessTestCase(ModuleStoreTestCase):
|
||||
else:
|
||||
# checks staff role and enrollment data
|
||||
num_queries = 2
|
||||
elif user_attr_name == 'user_anonymous' and action == 'see_exists':
|
||||
if course_attr_name == 'course_started':
|
||||
num_queries = 3
|
||||
else:
|
||||
num_queries = 0
|
||||
else:
|
||||
# if the course has started, check the duration configuration
|
||||
if action == 'see_exists' and course_attr_name == 'course_started':
|
||||
|
||||
@@ -60,6 +60,7 @@ from openedx.core.djangoapps.crawlers.models import CrawlersConfig
|
||||
from openedx.core.djangoapps.credit.api import set_credit_requirements
|
||||
from openedx.core.djangoapps.credit.models import CreditCourse, CreditProvider
|
||||
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlagNamespace
|
||||
|
||||
from openedx.core.djangoapps.waffle_utils.testutils import WAFFLE_TABLES, override_waffle_flag
|
||||
from openedx.core.djangolib.testing.utils import get_mock_request
|
||||
from openedx.core.lib.gating import api as gating_api
|
||||
@@ -67,6 +68,7 @@ from openedx.core.lib.tests import attr
|
||||
from openedx.core.lib.url_utils import quote_slashes
|
||||
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
|
||||
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
|
||||
from openedx.features.course_experience.tests.views.helpers import add_course_mode
|
||||
from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired
|
||||
from openedx.features.course_experience import (
|
||||
COURSE_OUTLINE_PAGE_FLAG,
|
||||
@@ -1669,6 +1671,7 @@ class ProgressPageTests(ProgressPageBaseTests):
|
||||
CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=date(2018, 1, 1))
|
||||
user = UserFactory.create()
|
||||
self.assertTrue(self.client.login(username=user.username, password='test'))
|
||||
add_course_mode(self.course, upgrade_deadline_expired=False)
|
||||
CourseEnrollmentFactory(user=user, course_id=self.course.id, mode=course_mode)
|
||||
|
||||
response = self._get_progress_page()
|
||||
@@ -2714,6 +2717,7 @@ class TestIndexViewWithCourseDurationLimits(ModuleStoreTestCase):
|
||||
"""
|
||||
CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=date(2018, 1, 1))
|
||||
self.assertTrue(self.client.login(username=self.user.username, password='test'))
|
||||
add_course_mode(self.course, upgrade_deadline_expired=False)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
'courseware_section',
|
||||
@@ -2734,6 +2738,7 @@ class TestIndexViewWithCourseDurationLimits(ModuleStoreTestCase):
|
||||
"""
|
||||
CourseDurationLimitConfig.objects.create(enabled=False)
|
||||
self.assertTrue(self.client.login(username=self.user.username, password='test'))
|
||||
add_course_mode(self.course, upgrade_deadline_expired=False)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
'courseware_section',
|
||||
|
||||
@@ -30,6 +30,7 @@ from openedx.core.lib.courses import course_image_url
|
||||
from openedx.core.lib.tests import attr
|
||||
from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory
|
||||
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
|
||||
from openedx.features.course_experience.tests.views.helpers import add_course_mode
|
||||
from student.models import CourseEnrollment
|
||||
from student.tests.factories import CourseEnrollmentFactory
|
||||
from util.milestones_helpers import set_prerequisite_courses
|
||||
@@ -255,6 +256,9 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest
|
||||
self.assertEqual(entry['course']['org'], 'edX')
|
||||
|
||||
def create_enrollment(self, expired):
|
||||
"""
|
||||
Create an enrollment
|
||||
"""
|
||||
if expired:
|
||||
course = CourseFactory.create(start=self.THREE_YEARS_AGO, mobile_available=True)
|
||||
enrollment = CourseEnrollmentFactory.create(
|
||||
@@ -266,6 +270,8 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest
|
||||
course = CourseFactory.create(start=self.LAST_WEEK, mobile_available=True)
|
||||
self.enroll(course.id)
|
||||
|
||||
add_course_mode(course, upgrade_deadline_expired=False)
|
||||
|
||||
def _get_enrollment_data(self, api_version, expired):
|
||||
self.login()
|
||||
self.create_enrollment(expired)
|
||||
@@ -586,7 +592,6 @@ class TestCourseEnrollmentSerializer(MobileAPITestCase, MilestonesTestCaseMixin)
|
||||
'''
|
||||
if api_version != API_V05:
|
||||
self.assertIn('audit_access_expires', response)
|
||||
self.assertIsNotNone(response.get('audit_access_expires'))
|
||||
else:
|
||||
self.assertNotIn('audit_access_expires', response)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from django.apps import apps
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from util.date_utils import DEFAULT_SHORT_DATE_FORMAT, strftime_localized
|
||||
from lms.djangoapps.courseware.access_response import AccessError
|
||||
from lms.djangoapps.courseware.access_utils import ACCESS_GRANTED
|
||||
@@ -60,6 +61,9 @@ def get_user_course_expiration_date(user, course):
|
||||
|
||||
access_duration = MIN_DURATION
|
||||
|
||||
if not CourseMode.verified_mode_for_course(course.id):
|
||||
return None
|
||||
|
||||
CourseEnrollment = apps.get_model('student.CourseEnrollment')
|
||||
enrollment = CourseEnrollment.get_enrollment(user, course.id)
|
||||
if enrollment is None or enrollment.mode != 'audit':
|
||||
|
||||
@@ -9,6 +9,7 @@ import mock
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from openedx.features.course_duration_limits.access import get_user_course_expiration_date, MIN_DURATION, MAX_DURATION
|
||||
from openedx.features.course_experience.tests.views.helpers import add_course_mode
|
||||
from student.models import CourseEnrollment
|
||||
from student.tests.factories import UserFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
@@ -25,6 +26,9 @@ class CourseExpirationTestCase(ModuleStoreTestCase):
|
||||
)
|
||||
self.user = UserFactory()
|
||||
|
||||
# Make this a verified course so we can test expiration date
|
||||
add_course_mode(self.course, upgrade_deadline_expired=False)
|
||||
|
||||
def tearDown(self):
|
||||
CourseEnrollment.unenroll(self.user, self.course.id)
|
||||
super(CourseExpirationTestCase, self).tearDown()
|
||||
@@ -79,6 +83,10 @@ class CourseExpirationTestCase(ModuleStoreTestCase):
|
||||
past_course = CourseFactory(start=start_date)
|
||||
enrollment = CourseEnrollment.enroll(self.user, past_course.id, CourseMode.AUDIT)
|
||||
result = get_user_course_expiration_date(self.user, past_course)
|
||||
self.assertEqual(result, None)
|
||||
|
||||
add_course_mode(past_course, upgrade_deadline_expired=False)
|
||||
result = get_user_course_expiration_date(self.user, past_course)
|
||||
content_availability_date = enrollment.created
|
||||
self.assertEqual(result, content_availability_date + access_duration)
|
||||
|
||||
@@ -87,5 +95,9 @@ class CourseExpirationTestCase(ModuleStoreTestCase):
|
||||
future_course = CourseFactory(start=start_date)
|
||||
enrollment = CourseEnrollment.enroll(self.user, future_course.id, CourseMode.AUDIT)
|
||||
result = get_user_course_expiration_date(self.user, future_course)
|
||||
self.assertEqual(result, None)
|
||||
|
||||
add_course_mode(future_course, upgrade_deadline_expired=False)
|
||||
result = get_user_course_expiration_date(self.user, future_course)
|
||||
content_availability_date = start_date
|
||||
self.assertEqual(result, content_availability_date + access_duration)
|
||||
|
||||
@@ -45,6 +45,9 @@ from .test_course_updates import create_course_update, remove_course_updates
|
||||
|
||||
TEST_PASSWORD = 'test'
|
||||
TEST_CHAPTER_NAME = 'Test Chapter'
|
||||
TEST_COURSE_TOOLS = 'Course Tools'
|
||||
TEST_COURSE_TODAY = 'Today is'
|
||||
TEST_BANNER_CLASS = '<div class="page-banner">'
|
||||
TEST_WELCOME_MESSAGE = '<h2>Welcome!</h2>'
|
||||
TEST_UPDATE_MESSAGE = '<h2>Test Update!</h2>'
|
||||
TEST_COURSE_UPDATES_TOOL = '/course/updates">'
|
||||
@@ -183,7 +186,7 @@ class TestCourseHomePage(CourseHomePageTestCase):
|
||||
course_home_url(self.course)
|
||||
|
||||
# Fetch the view and verify the query counts
|
||||
with self.assertNumQueries(84, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
|
||||
with self.assertNumQueries(68, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
|
||||
with check_mongo_calls(4):
|
||||
url = course_home_url(self.course)
|
||||
self.client.get(url)
|
||||
@@ -241,8 +244,8 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
|
||||
response = self.client.get(url)
|
||||
|
||||
# Verify that the course tools and dates are always shown
|
||||
self.assertContains(response, 'Course Tools')
|
||||
self.assertContains(response, 'Today is')
|
||||
self.assertContains(response, TEST_COURSE_TOOLS)
|
||||
self.assertContains(response, TEST_COURSE_TODAY)
|
||||
|
||||
# Verify that the outline, start button, course sock, and welcome message
|
||||
# are only shown to enrolled users.
|
||||
@@ -279,8 +282,8 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
|
||||
response = self.client.get(url)
|
||||
|
||||
# Verify that the course tools and dates are always shown
|
||||
self.assertContains(response, 'Course Tools')
|
||||
self.assertContains(response, 'Today is')
|
||||
self.assertContains(response, TEST_COURSE_TOOLS)
|
||||
self.assertContains(response, TEST_COURSE_TODAY)
|
||||
|
||||
# Verify that welcome messages are never shown
|
||||
self.assertNotContains(response, TEST_WELCOME_MESSAGE)
|
||||
@@ -393,7 +396,6 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
|
||||
the student dashboard, not a 404.
|
||||
"""
|
||||
CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=date(2010, 1, 1))
|
||||
|
||||
course = CourseFactory.create(start=THREE_YEARS_AGO)
|
||||
url = course_home_url(course)
|
||||
|
||||
@@ -421,6 +423,20 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
|
||||
)
|
||||
self.assertRedirects(response, expected_url)
|
||||
|
||||
def test_audit_only_not_expired(self):
|
||||
"""
|
||||
Verify that enrolled users are NOT shown the course expiration banner and can
|
||||
access the course home page if course audit only
|
||||
"""
|
||||
CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=date(2010, 1, 1))
|
||||
audit_only_course = CourseFactory.create()
|
||||
self.create_user_for_course(audit_only_course, CourseUserType.ENROLLED)
|
||||
response = self.client.get(course_home_url(audit_only_course))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, TEST_COURSE_TOOLS)
|
||||
self.assertContains(response, TEST_COURSE_TODAY)
|
||||
self.assertNotContains(response, TEST_BANNER_CLASS)
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {'DISABLE_START_DATES': False})
|
||||
@mock.patch("util.date_utils.strftime_localized")
|
||||
def test_non_live_course_other_language(self, mock_strftime_localized):
|
||||
@@ -505,6 +521,7 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
|
||||
response = self.client.get(url)
|
||||
bannerText = get_expiration_banner_text(user, self.course)
|
||||
self.assertContains(response, bannerText, html=True)
|
||||
self.assertContains(response, TEST_BANNER_CLASS)
|
||||
|
||||
# Verify that enrolled users are not shown the course expiration banner if content gating is disabled
|
||||
config.enabled = False
|
||||
|
||||
Reference in New Issue
Block a user