From 3520b6b8e1a01db6343647c6e68352f1faa2e518 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Thu, 22 May 2025 11:09:48 -0400 Subject: [PATCH] test: Drop testing for legacy courseware UI This has all been replaced by the learning MFE and will be removed from the platform in subsequent commits. For masquerade testing, the page no longer renders content and so shouldn't be a part of this test. The render_xblock url is what is used by the MFE so we're still testing that the course-wide content is being loaded correctly for content served by the learning MFE. --- .../courseware/tests/test_masquerade.py | 45 -- .../courseware/tests/test_navigation.py | 285 --------- .../tests/test_view_authentication.py | 60 +- lms/djangoapps/courseware/tests/test_views.py | 541 +----------------- 4 files changed, 2 insertions(+), 929 deletions(-) delete mode 100644 lms/djangoapps/courseware/tests/test_navigation.py diff --git a/lms/djangoapps/courseware/tests/test_masquerade.py b/lms/djangoapps/courseware/tests/test_masquerade.py index 33d100c7a2..ed5947ffc6 100644 --- a/lms/djangoapps/courseware/tests/test_masquerade.py +++ b/lms/djangoapps/courseware/tests/test_masquerade.py @@ -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 8cf7737f1b..8575b192a8 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -109,6 +109,7 @@ from openedx.features.course_experience import ( from openedx.features.course_experience.tests.views.helpers import add_course_mode from openedx.features.course_experience.url_helpers import ( get_learning_mfe_home_url, + get_courseware_url, make_learning_mfe_courseware_url ) from openedx.features.enterprise_support.tests.factories import ( @@ -248,42 +249,6 @@ class TestJumpTo(ModuleStoreTestCase): assert response.status_code == 404 -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""" CREATE_USER = False @@ -357,108 +322,6 @@ class BaseViewsTestCase(ModuleStoreTestCase, MasqueradeMixin): assert self.client.login(username=self.global_staff.username, password=TEST_PASSWORD) -@ddt.ddt -class CoursewareIndexTestCase(BaseViewsTestCase): - """ - Tests for the courseware index view, used for instructor previews. - """ - def setUp(self): - 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)) - - # 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)) - - def test_index_nonexistent_chapter(self): - self._verify_index_response(expected_response_code=404, chapter_name='non-existent') - - 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( - '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, - } - ) - response = self.client.get(url) - assert response.status_code == expected_response_code - return response - - 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 - - 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 - ) - test_responses = [ - '

Sequential 1 current section

', - '

Sequential 2

' - ] - for test in test_responses: - self.assertContains(response, test) - - @ddt.ddt class ViewsTestCase(BaseViewsTestCase): """ @@ -1046,56 +909,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 @@ -2275,72 +2088,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') @@ -2425,207 +2172,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): """ @@ -3007,89 +2553,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. @@ -3112,7 +2575,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))), ): @@ -3343,7 +2805,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),