diff --git a/openedx/features/content_type_gating/tests/test_access.py b/openedx/features/content_type_gating/tests/test_access.py index 3fb1a8ea0f..93fa0c0702 100644 --- a/openedx/features/content_type_gating/tests/test_access.py +++ b/openedx/features/content_type_gating/tests/test_access.py @@ -15,7 +15,14 @@ from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag from openedx.core.lib.url_utils import quote_slashes from openedx.features.content_type_gating.partitions import CONTENT_GATING_PARTITION_ID from openedx.features.course_duration_limits.config import CONTENT_TYPE_GATING_FLAG -from student.tests.factories import TEST_PASSWORD, AdminFactory, CourseEnrollmentFactory, UserFactory +from student.roles import CourseInstructorRole, CourseStaffRole +from student.tests.factories import ( + AdminFactory, + CourseAccessRoleFactory, + CourseEnrollmentFactory, + UserFactory, + TEST_PASSWORD +) from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -240,8 +247,9 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase): Arguments: block: some sort of xblock descriptor, must implement .scope_ids.usage_id is_gated (bool): if True, this user is expected to be gated from this block - user_id (int): id of user, if not set will be set to self.audit_user.id - course_id (CourseLocator): id of course, if not set will be set to self.course.id + user_id (int): id of user + course_id (CourseLocator): id of course + view_name (str): type of view for the block, if not set will default to 'student_view' """ fake_request = self.factory.get('') mock_get_current_request.return_value = fake_request @@ -254,7 +262,6 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase): usage_key_string=unicode(self.blocks_dict['vertical'].scope_ids.usage_id), course=None ) - runtime = vertical_xblock.runtime # This method of fetching the block from the descriptor bypassess access checks @@ -368,3 +375,24 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase): self.client.login(username=self.users[user].username, password=TEST_PASSWORD) response = self.client.post(url) self.assertEqual(response.status_code, status_code) + + def test_access_course_team_users(self): + """ + Test that members of the course team do not lose access to graded content + """ + # There are two types of course team members: instructor and staff + # they have different privileges, but for the purpose of this test the important thing is that they should both + # have access to all graded content + instructor = UserFactory.create() + CourseInstructorRole(self.course.id).add_users(instructor) + staff = UserFactory.create() + CourseStaffRole(self.course.id).add_users(staff) + + # assert that all course team members have access to graded content + for course_team_member in [instructor, staff]: + self._assert_block_is_gated( + block=self.blocks_dict['problem'], + user_id=course_team_member.id, + course_id=self.course.id, + is_gated=False + ) 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 7e91e458eb..6b27b676d7 100644 --- a/openedx/features/course_experience/tests/views/test_course_home.py +++ b/openedx/features/course_experience/tests/views/test_course_home.py @@ -16,6 +16,7 @@ from waffle.models import Flag from waffle.testutils import override_flag from course_modes.models import CourseMode +from course_modes.tests.factories import CourseModeFactory from courseware.tests.factories import StaffFactory from courseware.tests.helpers import get_expiration_banner_text from lms.djangoapps.commerce.models import CommerceConfiguration @@ -31,6 +32,7 @@ from openedx.features.course_experience import ( UNIFIED_COURSE_TAB_FLAG ) from student.models import CourseEnrollment +from student.roles import CourseInstructorRole, CourseStaffRole from student.tests.factories import UserFactory from util.date_utils import strftime_localized from xmodule.modulestore import ModuleStoreEnum @@ -54,6 +56,7 @@ TEST_COURSE_GOAL_OPTIONS = 'goal-options-container' TEST_COURSE_GOAL_UPDATE_FIELD = 'section-goals' TEST_COURSE_GOAL_UPDATE_FIELD_HIDDEN = 'section-goals hidden' COURSE_GOAL_DISMISS_OPTION = 'unsure' +THREE_YEARS_AGO = now() - timedelta(days=(365 * 3)) QUERY_COUNT_TABLE_BLACKLIST = WAFFLE_TABLES @@ -94,7 +97,7 @@ class CourseHomePageTestCase(SharedModuleStoreTestCase): Set up a course to be used for testing. """ # pylint: disable=super-method-not-called - with super(CourseHomePageTestCase, cls).setUpClassAndTestData(): + with cls.setUpClassAndTestData(): with cls.store.default_store(ModuleStoreEnum.Type.split): cls.course = CourseFactory.create( org='edX', @@ -116,6 +119,7 @@ class CourseHomePageTestCase(SharedModuleStoreTestCase): @classmethod def setUpTestData(cls): """Set up and enroll our fake user in the course.""" + super(CourseHomePageTestCase, cls).setUpTestData() cls.staff_user = StaffFactory(course_key=cls.course.id, password=TEST_PASSWORD) cls.user = UserFactory(password=TEST_PASSWORD) CourseEnrollment.enroll(cls.user, cls.course.id) @@ -323,20 +327,70 @@ class TestCourseHomePageAccess(CourseHomePageTestCase): ) self.assertRedirects(response, expected_url) + @override_waffle_flag(CONTENT_TYPE_GATING_FLAG, True) + @mock.patch.dict(settings.FEATURES, {'DISABLE_START_DATES': False}) + def test_course_does_not_expire_for_different_roles(self): + """ + There are a number of different roles/users that should not lose access after the expiration date. + Ensure that users who should not lose access get a 200 (ok) response + when attempting to visit the course after their would be expiration date. + """ + course = CourseFactory.create(start=THREE_YEARS_AGO) + url = course_home_url(course) + + # create a list of those users who should not lose their access, + # then assert that their access persists past the 'expiration date' + users_no_expired_access = [] + + verified_user = UserFactory(password=self.TEST_PASSWORD) + verified_enrollment = CourseEnrollment.enroll(verified_user, course.id, mode=CourseMode.VERIFIED) + ScheduleFactory(start=THREE_YEARS_AGO, enrollment=verified_enrollment) + users_no_expired_access.append((verified_user, 'Verified Learner')) + + # There are two types of course team members: instructor and staff + # they have different privileges, but for the purpose of this test the important thing is that they should + # retain their access to the course after the access would expire for a normal audit learner + instructor = UserFactory.create(password=self.TEST_PASSWORD) + enrollment = CourseEnrollment.enroll(instructor, course.id, mode=CourseMode.AUDIT) + CourseInstructorRole(course.id).add_users(instructor) + ScheduleFactory(start=THREE_YEARS_AGO, enrollment=enrollment) + users_no_expired_access.append((instructor, 'Course Instructor')) + + staff = UserFactory.create(password=self.TEST_PASSWORD) + enrollment = CourseEnrollment.enroll(staff, course.id, mode=CourseMode.AUDIT) + CourseStaffRole(course.id).add_users(staff) + ScheduleFactory(start=THREE_YEARS_AGO, enrollment=enrollment) + users_no_expired_access.append((staff, 'Course Staff')) + + for user, user_description in users_no_expired_access: + self.client.login(username=user.username, password=self.TEST_PASSWORD) + response = self.client.get(url) + self.assertEqual( + response.status_code, + 200, + "Should not expire access for user [{}]".format(user_description) + ) + @override_waffle_flag(CONTENT_TYPE_GATING_FLAG, True) @mock.patch.dict(settings.FEATURES, {'DISABLE_START_DATES': False}) def test_expired_course(self): """ Ensure that a user accessing an expired course sees a redirect to the student dashboard, not a 404. - """ - three_years_ago = now() - timedelta(days=(365 * 3)) - course = CourseFactory.create(start=three_years_ago) - user = self.create_user_for_course(course, CourseUserType.ENROLLED) - enrollment = CourseEnrollment.get_enrollment(user, course.id) - ScheduleFactory(start=three_years_ago, enrollment=enrollment) + """ + course = CourseFactory.create(start=THREE_YEARS_AGO) url = course_home_url(course) + + for mode in [CourseMode.AUDIT, CourseMode.VERIFIED]: + CourseModeFactory.create(course_id=course.id, mode_slug=mode) + + # assert that an if an expired audit user tries to access the course they are redirected to the dashboard + audit_user = UserFactory(password=self.TEST_PASSWORD) + self.client.login(username=audit_user.username, password=self.TEST_PASSWORD) + audit_enrollment = CourseEnrollment.enroll(audit_user, course.id, mode=CourseMode.AUDIT) + ScheduleFactory(start=THREE_YEARS_AGO, enrollment=audit_enrollment) + response = self.client.get(url) expiration_date = strftime_localized(course.start + timedelta(weeks=4), 'SHORT_DATE') @@ -525,6 +579,9 @@ class TestCourseHomePageAccess(CourseHomePageTestCase): class CourseHomeFragmentViewTests(ModuleStoreTestCase): + """ + Test Messages Displayed on the Course Home + """ CREATE_USER = False def setUp(self):