diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py index bb6d42025e..5b910565e4 100644 --- a/lms/djangoapps/courseware/tests/test_access.py +++ b/lms/djangoapps/courseware/tests/test_access.py @@ -681,47 +681,6 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes assert bool(access._has_access_course(self.student, 'load_mobile', course)) == student_expected assert bool(access._has_access_course(self.staff, 'load_mobile', course)) == staff_expected - @patch.dict("django.conf.settings.FEATURES", {'ENABLE_PREREQUISITE_COURSES': True, 'MILESTONES_APP': True}) - def test_courseware_page_unfulfilled_prereqs(self): - """ - Test courseware access when a course has pre-requisite course yet to be completed - """ - pre_requisite_course = CourseFactory.create( - org='edX', - course='900', - run='test_run', - ) - - pre_requisite_courses = [str(pre_requisite_course.id)] - course = CourseFactory.create( - org='edX', - course='1000', - run='test_run', - pre_requisite_courses=pre_requisite_courses, - ) - set_prerequisite_courses(course.id, pre_requisite_courses) - - test_password = 't3stp4ss.!' - user = UserFactory.create() - user.set_password(test_password) - user.save() - self.login(user.email, test_password) - CourseEnrollmentFactory(user=user, course_id=course.id) - - url = reverse('courseware', args=[str(course.id)]) - response = self.client.get(url) - self.assertRedirects( - response, - reverse( - 'dashboard' - ) - ) - assert response.status_code == 302 - - fulfill_course_milestone(pre_requisite_course.id, user) - response = self.client.get(url) - assert response.status_code == 200 - class UserRoleTestCase(TestCase): """ diff --git a/lms/djangoapps/courseware/tests/test_course_survey.py b/lms/djangoapps/courseware/tests/test_course_survey.py index 98b5842cd2..25dc37724d 100644 --- a/lms/djangoapps/courseware/tests/test_course_survey.py +++ b/lms/djangoapps/courseware/tests/test_course_survey.py @@ -16,6 +16,7 @@ from common.test.utils import XssTestMixin from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase from lms.djangoapps.survey.models import SurveyAnswer, SurveyForm from openedx.features.course_experience import course_home_url +from openedx.features.course_experience.url_helpers import make_learning_mfe_courseware_url class SurveyViewsTests(LoginEnrollmentTestCase, SharedModuleStoreTestCase, XssTestMixin): @@ -89,26 +90,44 @@ class SurveyViewsTests(LoginEnrollmentTestCase, SharedModuleStoreTestCase, XssTe reverse('course_survey', kwargs={'course_id': str(course.id)}) ) - def _assert_no_redirect(self, course): + def _assert_no_survey_redirect(self, course): """ - Helper method to asswer that all known conditionally redirect points do - not redirect as expected + Helper method to assert that all known conditionally redirecting endpoints do + not redirect to the survey as expected """ - for view_name in ['courseware', 'progress']: - resp = self.client.get( - reverse( - view_name, - kwargs={'course_id': str(course.id)} - ) + + # Make sure we get to the progress page. + resp = self.client.get( + reverse( + 'progress', + kwargs={'course_id': str(course.id)} ) - assert resp.status_code == 200 + ) + assert resp.status_code == 200 + + # Make sure we are redirected to the MFE for courseware + resp = self.client.get( + reverse( + 'courseware', + kwargs={'course_id': str(course.id)} + ) + ) + assert resp.status_code == 302 + expected_redirect_url = make_learning_mfe_courseware_url( + course.id, + None, + None, + params=None, + preview=False + ) + assert resp.url == expected_redirect_url def test_visiting_course_without_survey(self): """ Verifies that going to the courseware which does not have a survey does not redirect to a survey """ - self._assert_no_redirect(self.course_without_survey) + self._assert_no_survey_redirect(self.course_without_survey) def test_visiting_course_with_survey_redirects(self): """ @@ -143,7 +162,7 @@ class SurveyViewsTests(LoginEnrollmentTestCase, SharedModuleStoreTestCase, XssTe ) assert resp.status_code == 200 - self._assert_no_redirect(self.course) + self._assert_no_survey_redirect(self.course) def test_course_id_field(self): """ @@ -180,7 +199,7 @@ class SurveyViewsTests(LoginEnrollmentTestCase, SharedModuleStoreTestCase, XssTe ) assert resp.status_code == 200 - self._assert_no_redirect(self.course) + self._assert_no_survey_redirect(self.course) # however we want to make sure we persist the course_id answer_objs = SurveyAnswer.objects.filter( @@ -195,7 +214,7 @@ class SurveyViewsTests(LoginEnrollmentTestCase, SharedModuleStoreTestCase, XssTe """ Verifies that going to the courseware with a required, but non-existing survey, does not redirect """ - self._assert_no_redirect(self.course_with_bogus_survey) + self._assert_no_survey_redirect(self.course_with_bogus_survey) def test_visiting_survey_with_bogus_survey_name(self): """ diff --git a/lms/djangoapps/courseware/tests/test_masquerade.py b/lms/djangoapps/courseware/tests/test_masquerade.py index 33d100c7a2..0f0eca4dc2 100644 --- a/lms/djangoapps/courseware/tests/test_masquerade.py +++ b/lms/djangoapps/courseware/tests/test_masquerade.py @@ -96,11 +96,11 @@ class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase, Mas Returns the server response for the courseware page. """ url = reverse( - 'courseware_section', + 'courseware_subsection', kwargs={ 'course_id': str(self.course.id), - 'chapter': self.chapter.location.block_id, - 'section': self.sequential.location.block_id, + 'section': self.chapter.location.block_id, + 'subsection': self.sequential.location.block_id, } ) return self.client.get(url) @@ -244,51 +244,6 @@ class TestMasqueradeOptionsNoContentGroups(StaffMasqueradeTestCase): assert is_target_available == expected -# These tests are testing a capability of the old courseware page. We have to not -# force redirect to the new MFE in order to be able to load the old pages which are -# being tested by this page. -# -# This is a temporary change, until we can remove the old courseware pages -# all together. -@patch('lms.djangoapps.courseware.views.index.CoursewareIndex._redirect_to_learning_mfe', return_value=None) -class TestStaffMasqueradeAsStudent(StaffMasqueradeTestCase): - """ - Check for staff being able to masquerade as student. - """ - - @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) - def test_staff_debug_with_masquerade(self, mock_redirect): - """ - Tests that staff debug control is not visible when masquerading as a student. - """ - # Verify staff initially can see staff debug - self.verify_staff_debug_present(True) - - # Toggle masquerade to student - self.update_masquerade(role='student') - self.verify_staff_debug_present(False) - - # Toggle masquerade back to staff - self.update_masquerade(role='staff') - self.verify_staff_debug_present(True) - - @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) - def test_show_answer_for_staff(self, mock_redirect): - """ - Tests that "Show Answer" is not visible when masquerading as a student. - """ - # Verify that staff initially can see "Show Answer". - self.verify_show_answer_present(True) - - # Toggle masquerade to student - self.update_masquerade(role='student') - self.verify_show_answer_present(False) - - # Toggle masquerade back to staff - self.update_masquerade(role='staff') - self.verify_show_answer_present(True) - - @ddt.ddt class TestStaffMasqueradeAsSpecificStudent(StaffMasqueradeTestCase, ProblemSubmissionTestMixin): """ diff --git a/lms/djangoapps/courseware/tests/test_navigation.py b/lms/djangoapps/courseware/tests/test_navigation.py deleted file mode 100644 index 8c2144ee2a..0000000000 --- a/lms/djangoapps/courseware/tests/test_navigation.py +++ /dev/null @@ -1,285 +0,0 @@ -""" -This test file will run through some LMS test scenarios regarding access and navigation of the LMS -""" - - -import time - -from unittest.mock import patch -from django.conf import settings -from django.test.utils import override_settings -from django.urls import reverse -from edx_toggles.toggles.testutils import override_waffle_flag -from xmodule.modulestore.django import modulestore -from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase -from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory - -from common.djangoapps.student.tests.factories import GlobalStaffFactory -from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase -from openedx.features.course_experience import DISABLE_COURSE_OUTLINE_PAGE_FLAG -from openedx.features.course_experience.url_helpers import make_learning_mfe_courseware_url - - -class TestNavigation(SharedModuleStoreTestCase, LoginEnrollmentTestCase): - """ - Check that navigation state is saved properly. - """ - @classmethod - def setUpClass(cls): - # pylint: disable=super-method-not-called - with super().setUpClassAndTestData(): - cls.test_course = CourseFactory.create() - cls.test_course_proctored = CourseFactory.create() - cls.course = CourseFactory.create() - - @classmethod - def setUpTestData(cls): # lint-amnesty, pylint: disable=super-method-not-called - cls.chapter0 = BlockFactory.create(parent=cls.course, - display_name='Overview') - cls.chapter9 = BlockFactory.create(parent=cls.course, - display_name='factory_chapter') - cls.section0 = BlockFactory.create(parent=cls.chapter0, - display_name='Welcome') - cls.section9 = BlockFactory.create(parent=cls.chapter9, - display_name='factory_section') - cls.unit0 = BlockFactory.create(parent=cls.section0, - display_name='New Unit 0') - - cls.chapterchrome = BlockFactory.create(parent=cls.course, - display_name='Chrome') - cls.chromelesssection = BlockFactory.create(parent=cls.chapterchrome, - display_name='chromeless', - chrome='none') - cls.accordionsection = BlockFactory.create(parent=cls.chapterchrome, - display_name='accordion', - chrome='accordion') - cls.tabssection = BlockFactory.create(parent=cls.chapterchrome, - display_name='tabs', - chrome='tabs') - cls.defaultchromesection = BlockFactory.create( - parent=cls.chapterchrome, - display_name='defaultchrome', - ) - cls.fullchromesection = BlockFactory.create(parent=cls.chapterchrome, - display_name='fullchrome', - chrome='accordion,tabs') - cls.tabtest = BlockFactory.create(parent=cls.chapterchrome, - display_name='pdf_textbooks_tab', - default_tab='progress') - - cls.user = GlobalStaffFactory(password='test') - - def setUp(self): - super().setUp() - self.login(self.user.email, 'test') - self.patcher = patch( - 'lms.djangoapps.courseware.views.index.CoursewareIndex._redirect_to_learning_mfe', return_value=None) - self.mock_redirect = self.patcher.start() - - def tearDown(self): - self.patcher.stop() - super().tearDown() - - def assertTabActive(self, tabname, response): - ''' Check if the progress tab is active in the tab set ''' - for line in response.content.decode('utf-8').split('\n'): - if tabname in line and 'active' in line: - return - raise AssertionError(f"assertTabActive failed: {tabname} not active") - - def assertTabInactive(self, tabname, response): # lint-amnesty, pylint: disable=useless-return - ''' Check if the progress tab is active in the tab set ''' - for line in response.content.decode('utf-8').split('\n'): - if tabname in line and 'active' in line: - raise AssertionError("assertTabInactive failed: " + tabname + " active") - return - - # TODO: LEARNER-71: Do we need to adjust or remove this test? - @override_waffle_flag(DISABLE_COURSE_OUTLINE_PAGE_FLAG, active=True) - def test_chrome_settings(self): - ''' - Test settings for disabling and modifying navigation chrome in the courseware: - - Accordion enabled, or disabled - - Navigation tabs enabled, disabled, or redirected - ''' - test_data = ( - ('tabs', False, True), - ('none', False, False), - ('accordion', True, False), - ('fullchrome', True, True), - ) - for (displayname, accordion, tabs) in test_data: - response = self.client.get(reverse('courseware_section', kwargs={ - 'course_id': str(self.course.id), - 'chapter': 'Chrome', - 'section': displayname, - })) - assert ('course-tabs' in response.content.decode('utf-8')) == tabs - assert ('course-navigation' in response.content.decode('utf-8')) == accordion - - self.assertTabInactive('progress', response) - self.assertTabActive(make_learning_mfe_courseware_url(self.course.id), response) - - response = self.client.get(reverse('courseware_section', kwargs={ - 'course_id': str(self.course.id), - 'chapter': 'Chrome', - 'section': 'pdf_textbooks_tab', - })) - - self.assertTabActive('progress', response) - self.assertTabInactive(make_learning_mfe_courseware_url(self.course.id), response) - - @override_settings(SESSION_INACTIVITY_TIMEOUT_IN_SECONDS=1) - def test_inactive_session_timeout(self): - """ - Verify that an inactive session times out and redirects to the - login page - """ - # make sure we can access courseware immediately - resp = self.client.get(reverse('dashboard')) - assert resp.status_code == 200 - - # then wait a bit and see if we get timed out - time.sleep(2) - - resp = self.client.get(reverse('dashboard')) - - # re-request, and we should get a redirect to login page - self.assertRedirects(resp, settings.LOGIN_REDIRECT_URL + '?next=' + reverse('dashboard')) - - def test_redirects_first_time(self): - """ - Verify that the first time we click on the courseware tab we are - redirected to the 'Welcome' section. - """ - resp = self.client.get(reverse('courseware', - kwargs={'course_id': str(self.course.id)})) - self.assertRedirects(resp, reverse( - 'courseware_section', kwargs={'course_id': str(self.course.id), - 'chapter': 'Overview', - 'section': 'Welcome'})) - - def test_redirects_second_time(self): - """ - Verify the accordion remembers we've already visited the Welcome section - and redirects correspondingly. - """ - section_url = reverse( - 'courseware_section', - kwargs={ - 'course_id': str(self.course.id), - 'chapter': 'Overview', - 'section': 'Welcome', - }, - ) - self.client.get(section_url) - resp = self.client.get( - reverse('courseware', kwargs={'course_id': str(self.course.id)}), - ) - self.assertRedirects(resp, section_url) - - def test_accordion_state(self): - """ - Verify the accordion remembers which chapter you were last viewing. - """ - # Now we directly navigate to a section in a chapter other than 'Overview'. - section_url = reverse( - 'courseware_section', - kwargs={ - 'course_id': str(self.course.id), - 'chapter': 'factory_chapter', - 'section': 'factory_section', - } - ) - self.assert_request_status_code(200, section_url) - - # And now hitting the courseware tab should redirect to 'factory_chapter' - url = reverse( - 'courseware', - kwargs={'course_id': str(self.course.id)} - ) - resp = self.client.get(url) - self.assertRedirects(resp, section_url) - - # TODO: LEARNER-71: Do we need to adjust or remove this test? - @override_waffle_flag(DISABLE_COURSE_OUTLINE_PAGE_FLAG, active=True) - def test_incomplete_course(self): - test_course_id = str(self.test_course.id) - - url = reverse( - 'courseware', - kwargs={'course_id': test_course_id} - ) - response = self.assert_request_status_code(200, url) - self.assertContains(response, "No content has been added to this course") - - section = BlockFactory.create( - parent_location=self.test_course.location, - display_name='New Section' - ) - url = reverse( - 'courseware', - kwargs={'course_id': test_course_id} - ) - response = self.assert_request_status_code(200, url) - self.assertNotContains(response, "No content has been added to this course") - self.assertContains(response, "New Section") - - subsection = BlockFactory.create( - parent_location=section.location, - display_name='New Subsection', - ) - url = reverse( - 'courseware', - kwargs={'course_id': test_course_id} - ) - response = self.assert_request_status_code(200, url) - self.assertContains(response, "New Subsection") - self.assertNotContains(response, "sequence-nav") - - BlockFactory.create( - parent_location=subsection.location, - display_name='New Unit', - ) - url = reverse( - 'courseware', - kwargs={'course_id': test_course_id} - ) - self.assert_request_status_code(302, url) - - def test_proctoring_js_includes(self): - """ - Make sure that proctoring JS does not get included on - courseware pages if either the FEATURE flag is turned off - or the course is not proctored enabled - """ - test_course_id = str(self.test_course_proctored.id) - - with patch.dict(settings.FEATURES, {'ENABLE_SPECIAL_EXAMS': False}): - url = reverse( - 'courseware', - kwargs={'course_id': test_course_id} - ) - resp = self.client.get(url) - - self.assertNotContains(resp, '/static/js/lms-proctoring.js') - - with patch.dict(settings.FEATURES, {'ENABLE_SPECIAL_EXAMS': True}): - url = reverse( - 'courseware', - kwargs={'course_id': test_course_id} - ) - resp = self.client.get(url) - - self.assertNotContains(resp, '/static/js/lms-proctoring.js') - - # now set up a course which is proctored enabled - - self.test_course_proctored.enable_proctored_exams = True - self.test_course_proctored.save() - - modulestore().update_item(self.test_course_proctored, self.user.id) - - resp = self.client.get(url) - - self.assertContains(resp, '/static/js/lms-proctoring.js') diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py index 67ada18e85..f9668853b2 100644 --- a/lms/djangoapps/courseware/tests/test_view_authentication.py +++ b/lms/djangoapps/courseware/tests/test_view_authentication.py @@ -63,7 +63,7 @@ class TestViewAuth(EnterpriseTestConsentRequired, ModuleStoreTestCase, LoginEnro Check that non-staff don't have access to dark urls. """ - names = ['courseware', 'progress'] + names = ['progress'] urls = self._reverse_urls(names, course) urls.extend([ reverse('book', kwargs={'course_id': str(course.id), @@ -106,10 +106,6 @@ class TestViewAuth(EnterpriseTestConsentRequired, ModuleStoreTestCase, LoginEnro ) self.assert_request_status_code(302, url) - # The courseware url should redirect, not 200 - url = self._reverse_urls(['courseware'], course)[0] - self.assert_request_status_code(302, url) - def login(self, user): # lint-amnesty, pylint: disable=arguments-differ return super().login(user.email, self.TEST_PASSWORD) @@ -164,60 +160,6 @@ class TestViewAuth(EnterpriseTestConsentRequired, ModuleStoreTestCase, LoginEnro self.org_staff_user = OrgStaffFactory(course_key=self.course.id) self.org_instructor_user = OrgInstructorFactory(course_key=self.course.id) - def test_redirection_unenrolled(self): - """ - Verify unenrolled student is redirected to the 'about' section of the chapter - instead of the 'Welcome' section after clicking on the courseware tab. - """ - self.login(self.unenrolled_user) - response = self.client.get(reverse('courseware', - kwargs={'course_id': str(self.course.id)})) - self.assertRedirects( - response, - reverse( - 'about_course', - args=[str(self.course.id)] - ) - ) - - def test_redirection_enrolled(self): - """ - Verify enrolled student is redirected to the 'Welcome' section of - the chapter after clicking on the courseware tab. - """ - self.login(self.enrolled_user) - - response = self.client.get( - reverse( - 'courseware', - kwargs={'course_id': str(self.course.id)} - ) - ) - - self.assertRedirects( - response, - reverse( - 'courseware_section', - kwargs={'course_id': str(self.course.id), - 'chapter': self.overview_chapter.url_name, - 'section': self.welcome_section.url_name} - ), - fetch_redirect_response=False, # just sends us on to MFE - ) - - def test_redirection_missing_enterprise_consent(self): - """ - Verify that enrolled students are redirected to the Enterprise consent - URL if a linked Enterprise Customer requires data sharing consent - and it has not yet been provided. - """ - self.login(self.enrolled_user) - url = reverse( - 'courseware', - kwargs={'course_id': str(self.course.id)} - ) - self.verify_consent_required(self.client, url, status_code=302) # lint-amnesty, pylint: disable=no-value-for-parameter - def test_instructor_page_access_nonstaff(self): """ Verify non-staff cannot load the instructor diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 2a27a3fc41..4b59bfac24 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -73,7 +73,6 @@ from lms.djangoapps.commerce.utils import EcommerceService from lms.djangoapps.courseware.access_utils import check_course_open_for_learner from lms.djangoapps.courseware.model_data import FieldDataCache, set_score from lms.djangoapps.courseware.block_render import get_block, handle_xblock_callback -from lms.djangoapps.courseware.tests.factories import StudentModuleFactory from lms.djangoapps.courseware.tests.helpers import MasqueradeMixin, get_expiration_banner_text from lms.djangoapps.courseware.testutils import RenderXBlockTestMixin from lms.djangoapps.courseware.toggles import ( @@ -94,7 +93,6 @@ from lms.djangoapps.verify_student.services import IDVerificationService from openedx.core.djangoapps.catalog.tests.factories import CourseFactory as CatalogCourseFactory from openedx.core.djangoapps.catalog.tests.factories import CourseRunFactory, ProgramFactory from openedx.core.djangoapps.content.course_overviews.models import CourseOverview -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.testutils import WAFFLE_TABLES @@ -103,12 +101,8 @@ from openedx.core.djangoapps.video_config.toggles import PUBLIC_VIDEO_SHARE 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 import ( - DISABLE_COURSE_OUTLINE_PAGE_FLAG, -) from openedx.features.course_experience.tests.views.helpers import add_course_mode from openedx.features.course_experience.url_helpers import ( - _get_legacy_courseware_url, get_learning_mfe_home_url, make_learning_mfe_courseware_url ) @@ -232,31 +226,6 @@ class TestJumpTo(ModuleStoreTestCase): assert response.status_code == 302 assert response.url == expected_redirect_url - # The new courseware experience does not support this sort of course structure; - # it assumes a simple course->chapter->sequence->unit->component tree. - @patch.object(views, 'get_courseware_url', new=_get_legacy_courseware_url) - def test_jump_to_legacy_from_nested_block(self): - with self.store.default_store(ModuleStoreEnum.Type.split): - course = CourseFactory.create() - chapter = BlockFactory.create(category='chapter', parent_location=course.location) - sequence = BlockFactory.create(category='sequential', parent_location=chapter.location) - vertical = BlockFactory.create(category='vertical', parent_location=sequence.location) - nested_sequence = BlockFactory.create(category='sequential', parent_location=vertical.location) - nested_vertical1 = BlockFactory.create(category='vertical', parent_location=nested_sequence.location) - # put a block into nested_vertical1 for completeness - BlockFactory.create(category='html', parent_location=nested_vertical1.location) - nested_vertical2 = BlockFactory.create(category='vertical', parent_location=nested_sequence.location) - block2 = BlockFactory.create(category='html', parent_location=nested_vertical2.location) - - # internal position of block2 will be 1_2 (2nd item withing 1st item) - activate_block_id = urlencode({'activate_block_id': str(block2.location)}) - expected_redirect_url = ( - f'/courses/{course.id}/courseware/{chapter.url_name}/{sequence.url_name}/1?{activate_block_id}' - ) - jumpto_url = f'/courses/{course.id}/jump_to/{block2.location}' - response = self.client.get(jumpto_url) - self.assertRedirects(response, expected_redirect_url, status_code=302, target_status_code=302) - @ddt.data( (False, ModuleStoreEnum.Type.split), (True, ModuleStoreEnum.Type.split), @@ -273,80 +242,6 @@ class TestJumpTo(ModuleStoreTestCase): response = self.client.get(jumpto_url) assert response.status_code == 404 - @ddt.data( - (ModuleStoreEnum.Type.split, False, '1'), - (ModuleStoreEnum.Type.split, True, '2'), - ) - @ddt.unpack - def test_jump_to_legacy_for_learner_with_staff_only_content(self, store_type, is_staff_user, position): - """ - Test for checking correct position in redirect_url for learner when a course has staff-only units. - - (When the MFE is active, it handles this logic itself with the help of the - courseware blocks/metadata/outline APIs, so we don't test for it here.) - """ - with self.store.default_store(store_type): - course = CourseFactory.create() - request = RequestFactory().get('/') - request.user = UserFactory(is_staff=is_staff_user, username="staff") - request.session = {} - course_key = CourseKey.from_string(str(course.id)) - chapter = BlockFactory.create(category='chapter', parent_location=course.location) - sequence = BlockFactory.create(category='sequential', parent_location=chapter.location) - __ = BlockFactory.create(category='vertical', parent_location=sequence.location) - staff_only_vertical = BlockFactory.create(category='vertical', parent_location=sequence.location, - metadata=dict(visible_to_staff_only=True)) - __ = BlockFactory.create(category='vertical', parent_location=sequence.location) - - usage_key = UsageKey.from_string(str(staff_only_vertical.location)).replace(course_key=course_key) - expected_url = reverse( - 'courseware_position', - kwargs={ - 'course_id': str(course.id), - 'chapter': chapter.url_name, - 'section': sequence.url_name, - 'position': position, - } - ) - expected_url += "?{}".format(urlencode({'activate_block_id': str(staff_only_vertical.location)})) - assert expected_url == _get_legacy_courseware_url(usage_key, request) - - -class IndexQueryTestCase(ModuleStoreTestCase): - """ - Tests for query count. - """ - NUM_PROBLEMS = 20 - - @patch('lms.djangoapps.courseware.views.index.CoursewareIndex._redirect_to_learning_mfe', return_value=None) - def test_index_query_counts(self, mock_redirect): - # TODO: decrease query count as part of REVO-28 - ContentTypeGatingConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1)) - with self.store.default_store(ModuleStoreEnum.Type.split): - course = CourseFactory.create() - with self.store.bulk_operations(course.id): - chapter = BlockFactory.create(category='chapter', parent_location=course.location) - section = BlockFactory.create(category='sequential', parent_location=chapter.location) - vertical = BlockFactory.create(category='vertical', parent_location=section.location) - for _ in range(self.NUM_PROBLEMS): - BlockFactory.create(category='problem', parent_location=vertical.location) - - self.client.login(username=self.user.username, password=self.user_password) - CourseEnrollment.enroll(self.user, course.id) - - with self.assertNumQueries(152, table_ignorelist=QUERY_COUNT_TABLE_IGNORELIST): - with check_mongo_calls(3): - url = reverse( - 'courseware_section', - kwargs={ - 'course_id': str(course.id), - 'chapter': str(chapter.location.block_id), - 'section': str(section.location.block_id), - } - ) - response = self.client.get(url) - assert response.status_code == 200 - class BaseViewsTestCase(ModuleStoreTestCase, MasqueradeMixin): """Base class for courseware tests""" @@ -430,109 +325,47 @@ class CoursewareIndexTestCase(BaseViewsTestCase): super().setUp() self._create_global_staff_user() # this view needs staff permission - @patch('lms.djangoapps.courseware.views.index.CoursewareIndex._redirect_to_learning_mfe', return_value=None) - def test_index_success(self, mock_redirect): - response = self._verify_index_response() - self.assertContains(response, self.problem2.location.replace(branch=None, version_guid=None)) + def test_course_redirect(self): + lms_url = reverse( + 'courseware', + kwargs={ + 'course_id': str(self.course_key), + } + ) - # re-access to the main course page redirects to last accessed view. - url = reverse('courseware', kwargs={'course_id': str(self.course_key)}) - response = self.client.get(url) - assert response.status_code == 302 - response = self.client.get(response.url) - self.assertNotContains(response, self.problem.location.replace(branch=None, version_guid=None)) - self.assertContains(response, self.problem2.location.replace(branch=None, version_guid=None)) + mfe_url = make_learning_mfe_courseware_url(self.course.id) - def test_index_nonexistent_chapter(self): - self._verify_index_response(expected_response_code=404, chapter_name='non-existent') + response = self.client.get(lms_url) + assert response.url == mfe_url - def test_index_nonexistent_chapter_masquerade(self): - self.update_masquerade(username=self.user.username) - self._verify_index_response(expected_response_code=302, chapter_name='non-existent') - - def test_index_nonexistent_section(self): - self._verify_index_response(expected_response_code=404, section_name='non-existent') - - def test_index_nonexistent_section_masquerade(self): - self.update_masquerade(username=self.user.username) - self._verify_index_response(expected_response_code=302, section_name='non-existent') - - def _verify_index_response(self, expected_response_code=200, chapter_name=None, section_name=None): - """ - Verifies the response when the courseware index page is accessed with - the given chapter and section names. - """ - url = reverse( + def test_section_redirect(self): + lms_url = reverse( 'courseware_section', kwargs={ 'course_id': str(self.course_key), - 'chapter': str(self.chapter.location.block_id) if chapter_name is None else chapter_name, - 'section': str(self.section2.location.block_id) if section_name is None else section_name, + 'section': str(self.chapter.location.block_id), } ) - response = self.client.get(url) - assert response.status_code == expected_response_code - return response - def test_get_redirect_url(self): - # test the course location - assert '/courses/{course_key}/courseware?{activate_block_id}'.format( - course_key=str(self.course_key), - activate_block_id=urlencode({'activate_block_id': str(self.course.location)}) - ) == _get_legacy_courseware_url(self.course.location) - # test a section location - assert '/courses/{course_key}/courseware/Chapter_1/Sequential_1/?{activate_block_id}'.format( - course_key=str(self.course_key), - activate_block_id=urlencode({'activate_block_id': str(self.section.location)}) - ) == _get_legacy_courseware_url(self.section.location) + mfe_url = make_learning_mfe_courseware_url(self.course.id) - def test_index_invalid_position(self): - request_url = '/'.join([ - '/courses', - str(self.course.id), - 'courseware', - self.chapter.location.block_id, - self.section.location.block_id, - 'f' - ]) - response = self.client.get(request_url) - assert response.status_code == 404 + response = self.client.get(lms_url) + assert response.url == mfe_url - def test_unicode_handling_in_url(self): - url_parts = [ - '/courses', - str(self.course.id), - 'courseware', - self.chapter.location.block_id, - self.section.location.block_id, - '1' - ] - for idx, val in enumerate(url_parts): - url_parts_copy = url_parts[:] - url_parts_copy[idx] = val + 'χ' - request_url = '/'.join(url_parts_copy) - response = self.client.get(request_url) - assert response.status_code == 404 - - # TODO: TNL-6387: Remove test - @override_waffle_flag(DISABLE_COURSE_OUTLINE_PAGE_FLAG, active=True) - @patch('lms.djangoapps.courseware.views.index.CoursewareIndex._redirect_to_learning_mfe', return_value=None) - def test_accordion(self, mock_redirect): - """ - This needs a response_context, which is not included in the render_accordion's main method - returning a render_to_string, so we will render via the courseware URL in order to include - the needed context - """ - response = self.client.get( - reverse('courseware', args=[str(self.course.id)]), - follow=True + def test_subsection_redirect(self): + lms_url = reverse( + 'courseware_subsection', + kwargs={ + 'course_id': str(self.course_key), + 'section': str(self.chapter.location.block_id), + 'subsection': str(self.section2.location.block_id), + } ) - test_responses = [ - '
Sequential 1 current section
', - 'Sequential 2
' - ] - for test in test_responses: - self.assertContains(response, test) + + mfe_url = make_learning_mfe_courseware_url(self.course.id, self.section2.location) + + response = self.client.get(lms_url) + assert response.url == mfe_url @ddt.ddt @@ -1122,56 +955,6 @@ class TestProgressDueDate(BaseDueDateTests): return self.client.get(reverse('progress', args=[str(course.id)])) -# TODO: LEARNER-71: Delete entire TestAccordionDueDate class -class TestAccordionDueDate(BaseDueDateTests): - """ - Test that the accordion page displays due dates correctly - """ - __test__ = True - - def setUp(self): - super().setUp() - self.patcher = patch( - 'lms.djangoapps.courseware.views.index.CoursewareIndex._redirect_to_learning_mfe', return_value=None) - self.mock_redirect = self.patcher.start() - - def tearDown(self): - self.patcher.stop() - super().tearDown() - - def get_response(self, course): - """ Returns the HTML for the accordion """ - return self.client.get( - reverse('courseware', args=[str(course.id)]), - follow=True - ) - - # TODO: LEARNER-71: Delete entire TestAccordionDueDate class - @override_waffle_flag(DISABLE_COURSE_OUTLINE_PAGE_FLAG, active=True) - def test_backwards_compatibility(self): - super().test_backwards_compatibility() - - # TODO: LEARNER-71: Delete entire TestAccordionDueDate class - @override_waffle_flag(DISABLE_COURSE_OUTLINE_PAGE_FLAG, active=True) - def test_defaults(self): - super().test_defaults() - - # TODO: LEARNER-71: Delete entire TestAccordionDueDate class - @override_waffle_flag(DISABLE_COURSE_OUTLINE_PAGE_FLAG, active=True) - def test_format_date(self): - super().test_format_date() - - # TODO: LEARNER-71: Delete entire TestAccordionDueDate class - @override_waffle_flag(DISABLE_COURSE_OUTLINE_PAGE_FLAG, active=True) - def test_format_invalid(self): - super().test_format_invalid() - - # TODO: LEARNER-71: Delete entire TestAccordionDueDate class - @override_waffle_flag(DISABLE_COURSE_OUTLINE_PAGE_FLAG, active=True) - def test_format_none(self): - super().test_format_none() - - class StartDateTests(ModuleStoreTestCase): """ Test that start dates are properly localized and displayed on the student @@ -2351,72 +2134,6 @@ class TestIndexView(ModuleStoreTestCase): """ Tests of the courseware.views.index view. """ - @XBlock.register_temp_plugin(ViewCheckerBlock, 'view_checker') - @patch('lms.djangoapps.courseware.views.index.CoursewareIndex._redirect_to_learning_mfe', return_value=None) - def test_student_state(self, mock_redirect): - """ - Verify that saved student state is loaded for xblocks rendered in the index view. - """ - with modulestore().default_store(ModuleStoreEnum.Type.split): - course = CourseFactory.create() - chapter = BlockFactory.create(parent_location=course.location, category='chapter') - section = BlockFactory.create(parent_location=chapter.location, category='view_checker', - display_name="Sequence Checker") - vertical = BlockFactory.create(parent_location=section.location, category='view_checker', - display_name="Vertical Checker") - block = BlockFactory.create(parent_location=vertical.location, category='view_checker', - display_name="Block Checker") - - for item in (section, vertical, block): - StudentModuleFactory.create( - student=self.user, - course_id=course.id, - module_state_key=item.scope_ids.usage_id, - state=json.dumps({'state': str(item.scope_ids.usage_id)}) - ) - - CourseOverview.load_from_module_store(course.id) - CourseEnrollmentFactory(user=self.user, course_id=course.id) - - assert self.client.login(username=self.user.username, password=self.user_password) - response = self.client.get( - reverse( - 'courseware_section', - kwargs={ - 'course_id': str(course.id), - 'chapter': chapter.url_name, - 'section': section.url_name, - } - ) - ) - # Trigger the assertions embedded in the ViewCheckerBlocks - self.assertContains(response, "ViewCheckerPassed", count=3) - - @XBlock.register_temp_plugin(ActivateIDCheckerBlock, 'id_checker') - @patch('lms.djangoapps.courseware.views.index.CoursewareIndex._redirect_to_learning_mfe', return_value=None) - def test_activate_block_id(self, mock_redirect): - course = CourseFactory.create() - with self.store.bulk_operations(course.id): - chapter = BlockFactory.create(parent=course, category='chapter') - section = BlockFactory.create(parent=chapter, category='sequential', display_name="Sequence") - vertical = BlockFactory.create(parent=section, category='vertical', display_name="Vertical") - BlockFactory.create(parent=vertical, category='id_checker', display_name="ID Checker") - - CourseOverview.load_from_module_store(course.id) - CourseEnrollmentFactory(user=self.user, course_id=course.id) - - assert self.client.login(username=self.user.username, password=self.user_password) - response = self.client.get( - reverse( - 'courseware_section', - kwargs={ - 'course_id': str(course.id), - 'chapter': chapter.url_name, - 'section': section.url_name, - } - ) + '?activate_block_id=test_block_id' - ) - self.assertContains(response, "Activate Block ID: test_block_id") @patch('lms.djangoapps.courseware.views.views.CourseTabView.course_open_for_learner_enrollment') @patch('openedx.core.djangoapps.util.user_messages.PageLevelMessages.register_warning_message') @@ -2501,207 +2218,6 @@ class TestIndexView(ModuleStoreTestCase): assert views.CourseTabView.course_open_for_learner_enrollment(course) == expected_should_show_enroll_button -@ddt.ddt -class TestIndexViewCompleteOnView(ModuleStoreTestCase, CompletionWaffleTestMixin): - """ - Tests CompleteOnView is set up correctly in CoursewareIndex. - """ - - def setup_course(self, default_store): - """ - Set up course content for modulestore. - """ - # pylint:disable=attribute-defined-outside-init - - self.request_factory = RequestFactoryNoCsrf() - - with modulestore().default_store(default_store): - self.course = CourseFactory.create() - - with self.store.bulk_operations(self.course.id): - - self.chapter = BlockFactory.create( - parent_location=self.course.location, category='chapter', display_name='Week 1' - ) - self.section_1 = BlockFactory.create( - parent_location=self.chapter.location, category='sequential', display_name='Lesson 1' - ) - self.vertical_1 = BlockFactory.create( - parent_location=self.section_1.location, category='vertical', display_name='Subsection 1' - ) - self.html_1_1 = BlockFactory.create( - parent_location=self.vertical_1.location, category='html', display_name="HTML 1_1" - ) - self.problem_1 = BlockFactory.create( - parent_location=self.vertical_1.location, category='problem', display_name="Problem 1" - ) - self.html_1_2 = BlockFactory.create( - parent_location=self.vertical_1.location, category='html', display_name="HTML 1_2" - ) - - self.section_2 = BlockFactory.create( - parent_location=self.chapter.location, category='sequential', display_name='Lesson 2' - ) - self.vertical_2 = BlockFactory.create( - parent_location=self.section_2.location, category='vertical', display_name='Subsection 2' - ) - self.video_2 = BlockFactory.create( - parent_location=self.vertical_2.location, category='video', display_name="Video 2" - ) - self.problem_2 = BlockFactory.create( - parent_location=self.vertical_2.location, category='problem', display_name="Problem 2" - ) - - self.section_1_url = reverse( - 'courseware_section', - kwargs={ - 'course_id': str(self.course.id), - 'chapter': self.chapter.url_name, - 'section': self.section_1.url_name, - } - ) - - self.section_2_url = reverse( - 'courseware_section', - kwargs={ - 'course_id': str(self.course.id), - 'chapter': self.chapter.url_name, - 'section': self.section_2.url_name, - } - ) - - CourseOverview.load_from_module_store(self.course.id) - CourseEnrollmentFactory(user=self.user, course_id=self.course.id) - assert self.client.login(username=self.user.username, password=self.user_password) - - @patch('lms.djangoapps.courseware.views.index.CoursewareIndex._redirect_to_learning_mfe', return_value=None) - def test_completion_service_disabled(self, mock_redirect): - - self.setup_course(ModuleStoreEnum.Type.split) - - response = self.client.get(self.section_1_url) - self.assertNotContains(response, 'data-mark-completed-on-view-after-delay') - - response = self.client.get(self.section_2_url) - self.assertNotContains(response, 'data-mark-completed-on-view-after-delay') - - @patch('lms.djangoapps.courseware.views.index.CoursewareIndex._redirect_to_learning_mfe', return_value=None) - def test_completion_service_enabled(self, mock_redirect): - - self.override_waffle_switch(True) - - self.setup_course(ModuleStoreEnum.Type.split) - - response = self.client.get(self.section_1_url) - self.assertContains(response, 'data-mark-completed-on-view-after-delay') - self.assertContains(response, 'data-mark-completed-on-view-after-delay', count=2) - - request = self.request_factory.post( - '/', - data=json.dumps({"completion": 1}), - content_type='application/json', - ) - request.user = self.user - request.session = {} - response = handle_xblock_callback( - request, - str(self.course.id), - quote_slashes(str(self.html_1_1.scope_ids.usage_id)), - 'publish_completion', - ) - assert json.loads(response.content.decode('utf-8')) == {'result': 'ok'} - - response = self.client.get(self.section_1_url) - self.assertContains(response, 'data-mark-completed-on-view-after-delay') - self.assertContains(response, 'data-mark-completed-on-view-after-delay', count=1) - - request = self.request_factory.post( - '/', - data=json.dumps({"completion": 1}), - content_type='application/json', - ) - request.user = self.user - request.session = {} - response = handle_xblock_callback( - request, - str(self.course.id), - quote_slashes(str(self.html_1_2.scope_ids.usage_id)), - 'publish_completion', - ) - assert json.loads(response.content.decode('utf-8')) == {'result': 'ok'} - - response = self.client.get(self.section_1_url) - self.assertNotContains(response, 'data-mark-completed-on-view-after-delay') - - response = self.client.get(self.section_2_url) - self.assertNotContains(response, 'data-mark-completed-on-view-after-delay') - - -@ddt.ddt -class TestIndexViewWithVerticalPositions(ModuleStoreTestCase): - """ - Test the index view to handle vertical positions. Confirms that first position is loaded - if input position is non-positive or greater than number of positions available. - """ - - def setUp(self): - """ - Set up initial test data - """ - super().setUp() - - # create course with 3 positions - self.course = CourseFactory.create() - with self.store.bulk_operations(self.course.id): - self.chapter = BlockFactory.create(parent_location=self.course.location, category='chapter') - self.section = BlockFactory.create(parent_location=self.chapter.location, category='sequential', - display_name="Sequence") - BlockFactory.create(parent_location=self.section.location, category='vertical', display_name="Vertical1") - BlockFactory.create(parent_location=self.section.location, category='vertical', display_name="Vertical2") - BlockFactory.create(parent_location=self.section.location, category='vertical', display_name="Vertical3") - - CourseOverview.load_from_module_store(self.course.id) - - self.client.login(username=self.user, password=self.user_password) - CourseEnrollmentFactory(user=self.user, course_id=self.course.id) - - def _get_course_vertical_by_position(self, input_position): - """ - Returns client response to input position. - """ - return self.client.get( - reverse( - 'courseware_position', - kwargs={ - 'course_id': str(self.course.id), - 'chapter': self.chapter.url_name, - 'section': self.section.url_name, - 'position': input_position, - } - ) - ) - - def _assert_correct_position(self, response, expected_position): - """ - Asserts that the expected position and the position in the response are the same - """ - self.assertContains(response, f'data-position="{expected_position}"') - - @ddt.data(("-1", 1), ("0", 1), ("-0", 1), ("2", 2), ("5", 1)) - @ddt.unpack - @patch('lms.djangoapps.courseware.views.index.CoursewareIndex._redirect_to_learning_mfe', return_value=None) - def test_vertical_positions(self, input_position, expected_position, mock_redirect): - """ - Tests the following cases: - * Load first position when negative position inputted. - * Load first position when 0/-0 position inputted. - * Load given position when 0 < input_position <= num_positions_available. - * Load first position when positive position > num_positions_available. - """ - resp = self._get_course_vertical_by_position(input_position) - self._assert_correct_position(resp, expected_position) - - @ddt.ddt class TestRenderXBlock(RenderXBlockTestMixin, ModuleStoreTestCase, CompletionWaffleTestMixin): """ @@ -3083,89 +2599,6 @@ class TestRenderXBlockSelfPaced(TestRenderXBlock): # lint-amnesty, pylint: disa return options -class TestIndexViewCrawlerStudentStateWrites(SharedModuleStoreTestCase): - """ - Ensure that courseware index requests do not trigger student state writes. - This is to prevent locking issues that have caused latency spikes in the - courseware_studentmodule table when concurrent requests each try to update - the same rows for sequence, section, and course positions. - """ - @classmethod - def setUpClass(cls): - """Set up the simplest course possible.""" - # setUpClassAndTestData() already calls setUpClass on SharedModuleStoreTestCase - # pylint: disable=super-method-not-called - with super().setUpClassAndTestData(): - cls.course = CourseFactory.create() - with cls.store.bulk_operations(cls.course.id): - cls.chapter = BlockFactory.create(category='chapter', parent_location=cls.course.location) - cls.section = BlockFactory.create(category='sequential', parent_location=cls.chapter.location) - cls.vertical = BlockFactory.create(category='vertical', parent_location=cls.section.location) - - @classmethod - def setUpTestData(cls): # lint-amnesty, pylint: disable=super-method-not-called - """Set up and enroll our fake user in the course.""" - cls.user = UserFactory(is_staff=True) - CourseEnrollment.enroll(cls.user, cls.course.id) - - def setUp(self): - """Do the client login.""" - super().setUp() - self.client.login(username=self.user.username, password=TEST_PASSWORD) - - @patch('lms.djangoapps.courseware.views.index.CoursewareIndex._redirect_to_learning_mfe', return_value=None) - def test_write_by_default(self, mock_redirect): - """By default, always write student state, regardless of user agent.""" - with patch('lms.djangoapps.courseware.model_data.UserStateCache.set_many') as patched_state_client_set_many: - # Simulate someone using Chrome - self._load_courseware('Mozilla/5.0 AppleWebKit/537.36') - assert patched_state_client_set_many.called - patched_state_client_set_many.reset_mock() - - # Common crawler user agent - self._load_courseware('edX-downloader/0.1') - assert patched_state_client_set_many.called - - @patch('lms.djangoapps.courseware.views.index.CoursewareIndex._redirect_to_learning_mfe', return_value=None) - def test_writes_with_config(self, mock_redirect): - """Test state writes (or lack thereof) based on config values.""" - CrawlersConfig.objects.create(known_user_agents='edX-downloader,crawler_foo', enabled=True) - with patch('lms.djangoapps.courseware.model_data.UserStateCache.set_many') as patched_state_client_set_many: - # Exact matching of crawler user agent - self._load_courseware('crawler_foo') - assert not patched_state_client_set_many.called - - # Partial matching of crawler user agent - self._load_courseware('edX-downloader/0.1') - assert not patched_state_client_set_many.called - - # Simulate an actual browser hitting it (we should write) - self._load_courseware('Mozilla/5.0 AppleWebKit/537.36') - assert patched_state_client_set_many.called - - # Disabling the crawlers config should revert us to default behavior - CrawlersConfig.objects.create(enabled=False) - - # Disabling the violation because pylint just can't see that we'll get the mock_redirect param passed in via the - # patch. - self.test_write_by_default() # pylint: disable=no-value-for-parameter - - def _load_courseware(self, user_agent): - """Helper to load the actual courseware page.""" - url = reverse( - 'courseware_section', - kwargs={ - 'course_id': str(self.course.id), - 'chapter': str(self.chapter.location.block_id), - 'section': str(self.section.location.block_id), - } - ) - response = self.client.get(url, HTTP_USER_AGENT=user_agent) - # Make sure we get back an actual 200, and aren't redirected because we - # messed up the setup somehow (e.g. didn't enroll properly) - assert response.status_code == 200 - - class EnterpriseConsentTestCase(EnterpriseTestConsentRequired, ModuleStoreTestCase): """ Ensure that the Enterprise Data Consent redirects are in place only when consent is required. @@ -3188,7 +2621,6 @@ class EnterpriseConsentTestCase(EnterpriseTestConsentRequired, ModuleStoreTestCa course_id = str(self.course.id) for url in ( - reverse("courseware", kwargs=dict(course_id=course_id)), reverse("progress", kwargs=dict(course_id=course_id)), reverse("student_progress", kwargs=dict(course_id=course_id, student_id=str(self.user.id))), ): @@ -3419,7 +2851,6 @@ class TestCourseWideResources(ModuleStoreTestCase): """ @ddt.data( - ('courseware', 'course_id', False, True), ('progress', 'course_id', False, False), ('instructor_dashboard', 'course_id', True, False), ('forum_form_discussion', 'course_id', False, False), diff --git a/lms/djangoapps/courseware/testutils.py b/lms/djangoapps/courseware/testutils.py index b0f444f288..5f47b258ae 100644 --- a/lms/djangoapps/courseware/testutils.py +++ b/lms/djangoapps/courseware/testutils.py @@ -12,12 +12,10 @@ import ddt from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from lms.djangoapps.courseware.utils import is_mode_upsellable -from openedx.features.course_experience.url_helpers import _get_legacy_courseware_url from common.djangoapps.student.tests.factories import AdminFactory, CourseEnrollmentFactory, UserFactory from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.course_modes.tests.factories import CourseModeFactory -from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory, check_mongo_calls # lint-amnesty, pylint: disable=wrong-import-order @@ -165,32 +163,6 @@ class RenderXBlockTestMixin(MasqueradeMixin, metaclass=ABCMeta): self.assertNotContains(response, self.html_block.data, status_code=expected_response_code) return response - @ddt.data( - ('vertical_block', 4), - ('html_block', 4), - ) - @ddt.unpack - @patch('lms.djangoapps.courseware.views.index.CoursewareIndex._redirect_to_learning_mfe', return_value=None) - def test_courseware_html(self, block_name, mongo_calls, mock_redirect): - """ - To verify that the removal of courseware chrome elements is working, - we include this test here to make sure the chrome elements that should - be removed actually exist in the full courseware page. - If this test fails, it's probably because the HTML template for courseware - has changed and COURSEWARE_CHROME_HTML_ELEMENTS needs to be updated. - """ - with self.store.default_store(ModuleStoreEnum.Type.split): - self.block_name_to_be_tested = block_name - self.setup_course(ModuleStoreEnum.Type.split) - self.setup_user(admin=True, enroll=True, login=True) - - with check_mongo_calls(mongo_calls): - url = _get_legacy_courseware_url(self.block_to_be_tested.location) - response = self.client.get(url) - expected_elements = self.block_specific_chrome_html_elements + self.COURSEWARE_CHROME_HTML_ELEMENTS - for chrome_element in expected_elements: - self.assertContains(response, chrome_element) - def test_success_enrolled_staff(self): self.setup_course() self.setup_user(admin=True, enroll=True, login=True) diff --git a/lms/djangoapps/courseware/views/index.py b/lms/djangoapps/courseware/views/index.py index 02f0ef1f7f..1034674b7b 100644 --- a/lms/djangoapps/courseware/views/index.py +++ b/lms/djangoapps/courseware/views/index.py @@ -6,72 +6,32 @@ View for Courseware Index import logging -import urllib -from django.conf import settings from django.contrib.auth.views import redirect_to_login -from django.db import transaction -from django.http import Http404 -from django.template.context_processors import csrf -from django.urls import reverse from django.utils.decorators import method_decorator -from django.utils.functional import cached_property -from django.utils.translation import gettext as _ from django.views.decorators.cache import cache_control from django.views.decorators.csrf import ensure_csrf_cookie +from django.utils.functional import cached_property from django.views.generic import View -from edx_django_utils.monitoring import set_custom_attributes_for_course_key + from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey, UsageKey -from web_fragments.fragment import Fragment -from xmodule.course_block import COURSE_VISIBILITY_PUBLIC from xmodule.modulestore.django import modulestore -from xmodule.x_module import PUBLIC_VIEW, STUDENT_VIEW -from common.djangoapps.edxmako.shortcuts import render_to_response, render_to_string -from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.util.views import ensure_valid_course_key -from lms.djangoapps.courseware.exceptions import CourseAccessRedirect, Redirect -from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context -from lms.djangoapps.gating.api import get_entrance_exam_score, get_entrance_exam_usage_key -from lms.djangoapps.grades.api import CourseGradeFactory -from openedx.core.djangoapps.content.course_overviews.models import CourseOverview -from openedx.core.djangoapps.crawlers.models import CrawlersConfig -from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY -from openedx.core.djangoapps.user_api.preferences.api import get_user_preference -from openedx.core.djangoapps.util.user_messages import PageLevelMessages -from openedx.core.djangolib.markup import HTML, Text -from openedx.features.course_experience import ( - COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, - DISABLE_COURSE_OUTLINE_PAGE_FLAG, - default_course_url -) +from lms.djangoapps.courseware.exceptions import Redirect +from lms.djangoapps.courseware.masquerade import setup_masquerade from openedx.features.course_experience.url_helpers import make_learning_mfe_courseware_url +from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG from openedx.features.enterprise_support.api import data_sharing_consent_required -from ..access import has_access -from ..access_utils import check_public_access -from ..courses import get_course_with_access, get_current_child, get_studio_url -from ..entrance_exams import ( - course_has_entrance_exam, - get_entrance_exam_content, - user_can_skip_entrance_exam, - user_has_passed_entrance_exam -) -from ..masquerade import check_content_start_date_for_masquerade_user, setup_masquerade -from ..model_data import FieldDataCache -from ..block_render import get_block_for_descriptor, toc_for_course +from ..block_render import get_block_for_descriptor +from ..courses import get_course_with_access from ..permissions import MASQUERADE_AS_STUDENT -from ..toggles import ENABLE_OPTIMIZELY_IN_COURSEWARE -from .views import CourseTabView log = logging.getLogger("edx.courseware.views.index") -TEMPLATE_IMPORTS = {'urllib': urllib} -CONTENT_DEPTH = 2 - -@method_decorator(transaction.non_atomic_requests, name='dispatch') class CoursewareIndex(View): """ View class for the Courseware page. @@ -85,477 +45,74 @@ class CoursewareIndex(View): @method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True)) @method_decorator(ensure_valid_course_key) @method_decorator(data_sharing_consent_required) - def get(self, request, course_id, chapter=None, section=None, position=None): + def get(self, request, course_id, section=None, subsection=None, position=None): """ - Displays courseware accordion and associated content. If course, chapter, - and section are all specified, renders the page, or returns an error if they - are invalid. - - If section is not specified, displays the accordion opened to the right - chapter. - - If neither chapter or section are specified, displays the user's most - recent chapter, or the first chapter if this is the user's first visit. + Instead of loading the legacy courseware sequences pages, load the equivalent URL + in the learning MFE. This view does not do any auth checks since they are done by + the MFE when attempting to load content. Arguments: request: HTTP request course_id (unicode): course id - chapter (unicode): chapter url_name section (unicode): section url_name + subsection (unicode): subsection url_name position (unicode): position in block, eg of