From 0ccb71e1153660b869f304d58d19575a8e6f8565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=86=D0=B2=D0=B0=D0=BD=20=D0=9D=D1=94=D0=B4=D1=94=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=96=D1=86=D0=B5=D0=B2?= Date: Tue, 12 Mar 2024 12:08:14 +0200 Subject: [PATCH] test: [AXIMST-584] add tests for CourseSidebarBlocksView --- .../outline/tests/test_view.py | 233 ++++++++++++++++++ 1 file changed, 233 insertions(+) diff --git a/lms/djangoapps/course_home_api/outline/tests/test_view.py b/lms/djangoapps/course_home_api/outline/tests/test_view.py index eebaf19cf65b..4b854c2174f9 100644 --- a/lms/djangoapps/course_home_api/outline/tests/test_view.py +++ b/lms/djangoapps/course_home_api/outline/tests/test_view.py @@ -447,3 +447,236 @@ def test_cannot_enroll_if_full(self): self.update_course_and_overview() CourseEnrollment.enroll(UserFactory(), self.course.id) # grr, some rando took our spot! self.assert_can_enroll(False) + + +@ddt.ddt +class SidebarBlocksTestViews(BaseCourseHomeTests): + """ + Tests for the Course Sidebar Blocks API + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.chapter = '' + self.sequential = '' + self.vertical = '' + self.ungraded_sequential = '' + self.ungraded_vertical = '' + self.url = '' + + def setUp(self): + super().setUp() + self.url = reverse('course-home:course-sidebar-blocks', args=[self.course.id]) + + def update_course_and_overview(self): + """ + Update the course and course overview records. + """ + self.update_course(self.course, self.user.id) + CourseOverview.load_from_module_store(self.course.id) + + def add_blocks_to_course(self): + """ + Add test blocks to the self course. + """ + with self.store.bulk_operations(self.course.id): + self.chapter = BlockFactory.create(category='chapter', parent_location=self.course.location) + self.sequential = BlockFactory.create( + display_name='Test', + category='sequential', + graded=True, + has_score=True, + parent_location=self.chapter.location + ) + self.vertical = BlockFactory.create( + category='problem', + graded=True, + has_score=True, + parent_location=self.sequential.location + ) + self.ungraded_sequential = BlockFactory.create( + display_name='Ungraded', + category='sequential', + parent_location=self.chapter.location + ) + self.ungraded_vertical = BlockFactory.create( + category='problem', + parent_location=self.ungraded_sequential.location + ) + update_outline_from_modulestore(self.course.id) + + @ddt.data(CourseMode.AUDIT, CourseMode.VERIFIED) + def test_get_authenticated_enrolled_user(self, enrollment_mode): + """ + Test that the API returns the correct data for an authenticated, enrolled user. + """ + self.add_blocks_to_course() + CourseEnrollment.enroll(self.user, self.course.id, enrollment_mode) + + response = self.client.get(self.url) + assert response.status_code == 200 + + chapter_data = response.data['blocks'][str(self.chapter.location)] + assert str(self.sequential.location) in chapter_data['children'] + + sequential_data = response.data['blocks'][str(self.sequential.location)] + assert str(self.vertical.location) in sequential_data['children'] + + vertical_data = response.data['blocks'][str(self.vertical.location)] + assert vertical_data['children'] == [] + + @ddt.data(True, False) + def test_get_authenticated_user_not_enrolled(self, has_previously_enrolled): + """ + Test that the API returns an empty response for an authenticated user who is not enrolled in the course. + """ + if has_previously_enrolled: + CourseEnrollment.enroll(self.user, self.course.id) + CourseEnrollment.unenroll(self.user, self.course.id) + + response = self.client.get(self.url) + assert response.status_code == 200 + assert response.data == {} + + def test_get_unauthenticated_user(self): + """ + Test that the API returns an empty response for an unauthenticated user. + """ + self.client.logout() + response = self.client.get(self.url) + + assert response.status_code == 200 + assert response.data.get('blocks') is None + + def test_course_staff_can_see_non_user_specific_content_in_masquerade(self): + """ + Test that course staff can see the outline and other non-user-specific content when masquerading as a learner + """ + instructor = UserFactory(username='instructor', email='instructor@example.com', password='foo', is_staff=False) + CourseInstructorRole(self.course.id).add_users(instructor) + self.client.login(username=instructor, password='foo') + self.update_masquerade(role='student') + response = self.client.get(self.url) + assert response.data['blocks'] is not None + + def test_get_unknown_course(self): + """ + Test that the API returns a 404 when the course is not found. + """ + url = reverse('course-home:course-sidebar-blocks', args=['course-v1:unknown+course+2T2020']) + response = self.client.get(url) + assert response.status_code == 404 + + @patch.dict('django.conf.settings.FEATURES', {'ENABLE_SPECIAL_EXAMS': True}) + def test_proctored_exam(self): + """ + Test that the API returns the correct data for a proctored exam. + """ + course = CourseFactory.create( + org='edX', + course='900', + run='test_run', + enable_proctored_exams=True, + proctoring_provider=settings.PROCTORING_BACKENDS['DEFAULT'], + ) + chapter = BlockFactory.create(parent=course, category='chapter', display_name='Test Section') + sequence = BlockFactory.create( + parent=chapter, + category='sequential', + display_name='Test Proctored Exam', + graded=True, + is_time_limited=True, + default_time_limit_minutes=10, + is_practice_exam=True, + due=datetime.now(), + exam_review_rules='allow_use_of_paper', + hide_after_due=False, + is_onboarding_exam=False, + ) + sequence.is_proctored_exam = True + update_outline_from_modulestore(course.id) + CourseEnrollment.enroll(self.user, course.id) + + url = reverse('course-home:course-sidebar-blocks', args=[course.id]) + response = self.client.get(url) + assert response.status_code == 200 + + exam_data = response.data['blocks'][str(sequence.location)] + assert not exam_data['complete'] + assert exam_data['display_name'] == 'Test Proctored Exam' + assert exam_data['due'] is not None + + def test_assignment(self): + """ + Test that the API returns the correct data for an assignment. + """ + self.add_blocks_to_course() + CourseEnrollment.enroll(self.user, self.course.id) + + response = self.client.get(self.url) + assert response.status_code == 200 + + exam_data = response.data['blocks'][str(self.sequential.location)] + assert exam_data['display_name'] == 'Test (1 Question)' + assert exam_data['icon'] == 'fa-pencil-square-o' + assert str(self.vertical.location) in exam_data['children'] + + ungraded_data = response.data['blocks'][str(self.ungraded_sequential.location)] + assert ungraded_data['display_name'] == 'Ungraded' + assert ungraded_data['icon'] is None + assert str(self.ungraded_vertical.location) in ungraded_data['children'] + + @override_waffle_flag(COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, active=True) + @ddt.data(*itertools.product( + [True, False], [True, False], [None, COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE] + )) + @ddt.unpack + def test_visibility(self, is_enrolled, is_staff, course_visibility): + """ + Test that the API returns the correct data based on the user's enrollment status and the course's visibility. + """ + if is_enrolled: + CourseEnrollment.enroll(self.user, self.course.id) + if is_staff: + self.user.is_staff = True + self.user.save() + if course_visibility: + self.course.course_visibility = course_visibility + self.update_course_and_overview() + + show_enrolled = is_enrolled or is_staff + is_public = course_visibility == COURSE_VISIBILITY_PUBLIC + is_public_outline = course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE + + data = self.client.get(self.url).data + if not (show_enrolled or is_public or is_public_outline): + assert data == {} + else: + assert (data['blocks'] is not None) == (show_enrolled or is_public or is_public_outline) + + def test_hide_learning_sequences(self): + """ + Check that Learning Sequences filters out sequences. + """ + CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED) + response = self.client.get(self.url) + assert response.status_code == 200 + + blocks = response.data['blocks'] + seq_block_id = next(block_id for block_id, block in blocks.items() if block['type'] == 'sequential') + + # With a course outline loaded, the same sequence is removed. + new_learning_seq_outline = CourseOutlineData( + course_key=self.course.id, + title='Test Course Outline!', + published_at=datetime(2021, 6, 14, tzinfo=timezone.utc), + published_version='5ebece4b69dd593d82fe2022', + entrance_exam_id=None, + days_early_for_beta=None, + sections=[], + self_paced=False, + course_visibility=CourseVisibility.PRIVATE + ) + replace_course_outline(new_learning_seq_outline) + blocks = self.client.get(self.url).data['blocks'] + assert seq_block_id not in blocks