diff --git a/common/djangoapps/student/tests/test_views.py b/common/djangoapps/student/tests/test_views.py index 8c64d0cdc6..aa69ec3bec 100644 --- a/common/djangoapps/student/tests/test_views.py +++ b/common/djangoapps/student/tests/test_views.py @@ -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 diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py index 127c76be62..3547eff232 100644 --- a/lms/djangoapps/courseware/tests/test_access.py +++ b/lms/djangoapps/courseware/tests/test_access.py @@ -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': diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 66165e3bef..b73419dae8 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -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', diff --git a/lms/djangoapps/mobile_api/users/tests.py b/lms/djangoapps/mobile_api/users/tests.py index 1f4989861f..588cda7ffd 100644 --- a/lms/djangoapps/mobile_api/users/tests.py +++ b/lms/djangoapps/mobile_api/users/tests.py @@ -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) diff --git a/openedx/features/course_duration_limits/access.py b/openedx/features/course_duration_limits/access.py index 9c56d166a3..379c9c6884 100644 --- a/openedx/features/course_duration_limits/access.py +++ b/openedx/features/course_duration_limits/access.py @@ -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': diff --git a/openedx/features/course_duration_limits/tests/test_course_expiration.py b/openedx/features/course_duration_limits/tests/test_course_expiration.py index aec144afd0..799311abad 100644 --- a/openedx/features/course_duration_limits/tests/test_course_expiration.py +++ b/openedx/features/course_duration_limits/tests/test_course_expiration.py @@ -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) diff --git a/openedx/features/course_experience/tests/views/test_course_home.py b/openedx/features/course_experience/tests/views/test_course_home.py index 921a916f3e..ceafe936e5 100644 --- a/openedx/features/course_experience/tests/views/test_course_home.py +++ b/openedx/features/course_experience/tests/views/test_course_home.py @@ -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 = '
' TEST_WELCOME_MESSAGE = '

Welcome!

' TEST_UPDATE_MESSAGE = '

Test Update!

' 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