def test_course_messaging(self): """ Ensure that the following four use cases work as expected 1) Anonymous users are shown a course message linking them to the login page 2) Unenrolled users are shown a course message allowing them to enroll 3) Enrolled users who show up on the course page after the course has begun are not shown a course message. 4) Enrolled users who show up on the course page after the course has begun will see the course expiration banner if course duration limits are on for the course. 5) Enrolled users who show up on the course page before the course begins are shown a message explaining when the course starts as well as a call to action button that allows them to add a calendar event. """ # Verify that anonymous users are shown a login link in the course message url = course_home_url(self.course) response = self.client.get(url) self.assertContains(response, TEST_COURSE_HOME_MESSAGE) self.assertContains(response, TEST_COURSE_HOME_MESSAGE_ANONYMOUS) # Verify that unenrolled users are shown an enroll call to action message user = self.create_user_for_course(self.course, CourseUserType.UNENROLLED) url = course_home_url(self.course) response = self.client.get(url) self.assertContains(response, TEST_COURSE_HOME_MESSAGE) self.assertContains(response, TEST_COURSE_HOME_MESSAGE_UNENROLLED) # Verify that enrolled users are not shown any state warning message when enrolled and course has begun. CourseEnrollment.enroll(user, self.course.id) url = course_home_url(self.course) response = self.client.get(url) self.assertNotContains(response, TEST_COURSE_HOME_MESSAGE_ANONYMOUS) self.assertNotContains(response, TEST_COURSE_HOME_MESSAGE_UNENROLLED) self.assertNotContains(response, TEST_COURSE_HOME_MESSAGE_PRE_START) # Verify that enrolled users are shown the course expiration banner if content gating is enabled with override_waffle_flag(CONTENT_TYPE_GATING_FLAG, True): url = course_home_url(self.course) response = self.client.get(url) bannerText = get_expiration_banner_text(user, self.course) self.assertContains(response, bannerText, html=True) # Verify that enrolled users are not shown the course expiration banner if content gating is disabled with override_waffle_flag(CONTENT_TYPE_GATING_FLAG, False): url = course_home_url(self.course) response = self.client.get(url) bannerText = get_expiration_banner_text(user, self.course) self.assertNotContains(response, bannerText, html=True) # Verify that enrolled users are shown 'days until start' message before start date future_course = self.create_future_course() CourseEnrollment.enroll(user, future_course.id) url = course_home_url(future_course) response = self.client.get(url) self.assertContains(response, TEST_COURSE_HOME_MESSAGE) self.assertContains(response, TEST_COURSE_HOME_MESSAGE_PRE_START)
def test_course_no_enrollments(self): with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() resp = self.client.get( self.get_url(course_key=self.empty_course.id) ) self._assert_empty_response(resp)
def test_user_not_enrolled(self): with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() resp = self.client.get( self.get_url(course_key=self.empty_course.id, username=self.student.username) ) self.assertEqual(status.HTTP_404_NOT_FOUND, resp.status_code)
def test_user_does_not_exist(self): with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() resp = self.client.get( self.get_url(course_key=self.course.id, username='******') ) self.assertEqual(status.HTTP_404_NOT_FOUND, resp.status_code)
def test_gradebook_data_filter_username_contains(self): with patch('lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read') as mock_grade: mock_grade.return_value = self.mock_course_grade( self.other_student, passed=True, letter_grade='A', percent=0.85 ) with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() resp = self.client.get( self.get_url(course_key=self.course.id, username_contains='other') ) expected_results = [ OrderedDict([ ('course_id', text_type(self.course.id)), ('email', self.other_student.email), ('user_id', self.other_student.id), ('username', self.other_student.username), ('full_name', self.other_student.get_full_name()), ('passed', True), ('percent', 0.85), ('letter_grade', 'A'), ('progress_page_url', reverse( 'student_progress', kwargs=dict(course_id=text_type(self.course.id), student_id=self.other_student.id) )), ('section_breakdown', self.expected_subsection_grades(letter_grade='A')), ]), ] self.assertEqual(status.HTTP_200_OK, resp.status_code) actual_data = dict(resp.data) self.assertIsNone(actual_data['next']) self.assertIsNone(actual_data['previous']) self.assertEqual(expected_results, actual_data['results'])
def test_user_does_not_exist(self): with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() resp = self.client.get( self.get_url(course_key=self.course.id, username='******')) self.assertEqual(status.HTTP_404_NOT_FOUND, resp.status_code)
def test_invalid_usage_key(self): with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() post_data = [ { 'user_id': self.student.id, 'usage_id': 'not-a-valid-usage-key', 'grade': {}, # doesn't matter what we put here. } ] resp = self.client.post( self.get_url(), data=json.dumps(post_data), content_type='application/json', ) expected_data = [ { 'user_id': self.student.id, 'usage_id': 'not-a-valid-usage-key', 'success': False, 'reason': "<class 'opaque_keys.edx.locator.BlockUsageLocator'>: not-a-valid-usage-key", }, ] self.assertEqual(status.HTTP_422_UNPROCESSABLE_ENTITY, resp.status_code) self.assertEqual(expected_data, resp.data)
def test_feature_not_enabled(self): self.client.login(username=self.global_staff.username, password=self.password) with override_waffle_flag(self.waffle_flag, active=False): resp = self.client.get( self.get_url(course_key=self.empty_course.id)) self.assertEqual(status.HTTP_403_FORBIDDEN, resp.status_code)
def test_user_does_not_exist(self): with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() post_data = [{ 'user_id': -123, 'usage_id': text_type( self.subsections[self.chapter_1.location][0].location), 'grade': {}, # doesn't matter what we put here. }] resp = self.client.post( self.get_url(), data=json.dumps(post_data), content_type='application/json', ) expected_data = [ { 'user_id': -123, 'usage_id': text_type( self.subsections[self.chapter_1.location][0].location), 'success': False, 'reason': 'User matching query does not exist.', }, ] self.assertEqual(status.HTTP_422_UNPROCESSABLE_ENTITY, resp.status_code) self.assertEqual(expected_data, resp.data)
def test_invalid_usage_key(self): with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() post_data = [{ 'user_id': self.student.id, 'usage_id': 'not-a-valid-usage-key', 'grade': {}, # doesn't matter what we put here. }] resp = self.client.post( self.get_url(), data=json.dumps(post_data), content_type='application/json', ) expected_data = [ { 'user_id': self.student.id, 'usage_id': 'not-a-valid-usage-key', 'success': False, 'reason': "<class 'opaque_keys.edx.locator.BlockUsageLocator'>: not-a-valid-usage-key", }, ] self.assertEqual(status.HTTP_422_UNPROCESSABLE_ENTITY, resp.status_code) self.assertEqual(expected_data, resp.data)
def test_video_urls_rewrite(self, waffle_flag_value, video_data_patch): """ Verify the video blocks returned have their URL re-written for encoded videos. """ video_data_patch.return_value = { 'encoded_videos': { 'hls': { 'url': 'https://xyz123.cloudfront.net/XYZ123ABC.mp4', 'file_size': 0 }, 'mobile_low': { 'url': 'https://1234abcd.cloudfront.net/ABCD1234abcd.mp4', 'file_size': 0 } } } with override_waffle_flag(ENABLE_VIDEO_URL_REWRITE, waffle_flag_value): blocks = get_blocks(self.request, self.course.location, requested_fields=['student_view_data'], student_view_data=['video']) video_block_key = str( self.course.id.make_usage_key('video', 'sample_video')) video_block_data = blocks['blocks'][video_block_key] for video_data in six.itervalues( video_block_data['student_view_data']['encoded_videos']): if waffle_flag_value: self.assertNotIn('cloudfront', video_data['url']) else: self.assertIn('cloudfront', video_data['url'])
def test_subsection_does_not_exist(self): """ When trying to override a grade for a valid usage key that does not exist in the requested course, we should get an error reason specifying that the key does not exist in the course. """ with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() usage_id = 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@workflow' post_data = [ { 'user_id': self.student.id, 'usage_id': usage_id, 'grade': {}, # doesn't matter what we put here. } ] resp = self.client.post( self.get_url(), data=json.dumps(post_data), content_type='application/json', ) expected_data = [ { 'user_id': self.student.id, 'usage_id': usage_id, 'success': False, 'reason': 'usage_key {} does not exist in this course.'.format(usage_id), }, ] self.assertEqual(status.HTTP_422_UNPROCESSABLE_ENTITY, resp.status_code) self.assertEqual(expected_data, resp.data)
def test_user_does_not_exist(self): with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() post_data = [ { 'user_id': -123, 'usage_id': text_type(self.subsections[self.chapter_1.location][0].location), 'grade': {}, # doesn't matter what we put here. } ] resp = self.client.post( self.get_url(), data=json.dumps(post_data), content_type='application/json', ) expected_data = [ { 'user_id': -123, 'usage_id': text_type(self.subsections[self.chapter_1.location][0].location), 'success': False, 'reason': 'User matching query does not exist.', }, ] self.assertEqual(status.HTTP_422_UNPROCESSABLE_ENTITY, resp.status_code) self.assertEqual(expected_data, resp.data)
def test_user_not_enrolled(self): with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() resp = self.client.get( self.get_url(course_key=self.empty_course.id, username=self.student.username)) self.assertEqual(status.HTTP_404_NOT_FOUND, resp.status_code)
def test_course_does_not_exist(self): with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() resp = self.client.post( self.get_url(course_key='course-v1:MITx+8.MechCX+2014_T1') ) self.assertEqual(status.HTTP_404_NOT_FOUND, resp.status_code)
def test_page_size_parameter(self, page_size): user_size = 60 with patch( 'lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read' ) as mock_grade: users = UserFactory.create_batch(user_size) mocked_course_grades = [] for user in users: self._create_user_enrollments(user) mocked_course_grades.append(self.mock_course_grade(user, passed=True, letter_grade='A', percent=0.85)) mock_grade.side_effect = mocked_course_grades with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() query = '' if page_size: query = '?page_size={}'.format(page_size) resp = self.client.get( self.get_url(course_key=self.course.id) + query ) self.assertEqual(status.HTTP_200_OK, resp.status_code) actual_data = dict(resp.data) expected_page_size = page_size or CourseEnrollmentPagination.page_size if expected_page_size > user_size: expected_page_size = user_size self.assertEqual(len(actual_data['results']), expected_page_size)
def test_feature_not_enabled(self): self.client.login(username=self.global_staff.username, password=self.password) with override_waffle_flag(self.waffle_flag, active=False): resp = self.client.post( self.get_url(course_key=self.empty_course.id) ) self.assertEqual(status.HTTP_403_FORBIDDEN, resp.status_code)
def test_home_page( self, enable_unenrolled_access, course_visibility, user_type, expected_enroll_message, expected_course_outline, ): self.create_user_for_course(self.course, user_type) # Render the course home page with mock.patch( 'xmodule.course_module.CourseDescriptor.course_visibility', course_visibility): # Test access with anonymous flag and course visibility with override_waffle_flag(COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, enable_unenrolled_access): url = course_home_url(self.course) response = self.client.get(url) # Verify that the course tools and dates are always shown self.assertContains(response, TEST_COURSE_TOOLS) self.assertContains(response, TEST_COURSE_TODAY) is_anonymous = user_type is CourseUserType.ANONYMOUS is_enrolled = user_type is CourseUserType.ENROLLED is_enrolled_or_staff = is_enrolled or user_type in ( CourseUserType.UNENROLLED_STAFF, CourseUserType.GLOBAL_STAFF) self.assertContains(response, 'Learn About Verified Certificate', count=(1 if is_enrolled else 0)) # Verify that start button, course sock, and welcome message # are only shown to enrolled users or staff. self.assertContains(response, 'Start Course', count=(1 if is_enrolled_or_staff else 0)) self.assertContains(response, TEST_WELCOME_MESSAGE, count=(1 if is_enrolled_or_staff else 0)) # Verify the outline is shown to enrolled users, unenrolled_staff and anonymous users if allowed self.assertContains(response, TEST_CHAPTER_NAME, count=(1 if expected_course_outline else 0)) # Verify that the expected message is shown to the user if not enable_unenrolled_access or course_visibility != COURSE_VISIBILITY_PUBLIC: self.assertContains(response, 'To see course content', count=(1 if is_anonymous else 0)) self.assertContains(response, '<div class="user-messages"', count=(1 if expected_enroll_message else 0)) if expected_enroll_message: self.assertContains( response, 'You must be enrolled in the course to see course content.' )
def test_redirect_view(self): profile_url = "http://profile-spa/abc/" with override_settings(PROFILE_MICROFRONTEND_URL=profile_url): with override_waffle_flag(REDIRECT_TO_PROFILE_MICROFRONTEND, active=True): profile_path = reverse('learner_profile', kwargs={'username': self.USERNAME}) # Test with waffle flag active, site setting disabled response = self.client.get(path=profile_path) for attribute in self.CONTEXT_DATA: self.assertIn(attribute, response.content) # Test with waffle flag active, site setting enabled site_domain = 'othersite.example.com' self.set_up_site(site_domain, { 'SITE_NAME': site_domain, 'ENABLE_PROFILE_MICROFRONTEND': True }) self.client.login(username=self.USERNAME, password=self.PASSWORD) response = self.client.get(path=profile_path) self.assertRedirects(response, profile_url + self.USERNAME, target_status_code=404)
def test_visual_progress_happy_path_visual_switch_disabled(self): self._make_site_config(True) with waffle.waffle().override(waffle.ENABLE_COMPLETION_TRACKING, True): with waffle.waffle().override(waffle.ENABLE_VISUAL_PROGRESS, False): with override_waffle_flag(waffle.waffle_flag(), active=True): assert waffle.visual_progress_enabled( self.course_key) is True
def override(self, active=True): """ Shortcut method for `override_waffle_flag`. """ # TODO We can move this import to the top of the file once this code is # not all contained within the __init__ module. from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag with override_waffle_flag(self, active): yield
def test_staff_can_see_writable_gradebook(self): """ Test that, when the writable gradebook featue is enabled, a staff member can see it. """ waffle_flag = waffle_flags()[WRITABLE_GRADEBOOK] with override_waffle_flag(waffle_flag, active=True): response = self.client.get(self.url) expected_gradebook_url = 'http://gradebook.local.edx.org/{}'.format(self.course.id) self.assertIn(expected_gradebook_url, response.content) self.assertIn('View Gradebook', response.content)
def test_filter_cohort_id_does_not_exist(self): with patch('lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read') as mock_grade: mock_grade.return_value = self.mock_course_grade(self.student, passed=True, letter_grade='A', percent=0.85) empty_cohort = CohortFactory(course_id=self.course.id, name="TestCohort", users=[]) with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() resp = self.client.get( self.get_url(course_key=self.course.id) + '?cohort_id={}'.format(empty_cohort.id) ) self._assert_empty_response(resp)
def test_gradebook_data_filter_username_contains_no_match(self): with patch('lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read') as mock_grade: mock_grade.return_value = self.mock_course_grade( self.other_student, passed=True, letter_grade='A', percent=0.85 ) with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() resp = self.client.get( self.get_url(course_key=self.course.id, username_contains='fooooooooooooooooo') ) self._assert_empty_response(resp)
def test_course_no_enrollments(self): with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() resp = self.client.get( self.get_url(course_key=self.empty_course.id)) expected_data = { 'next': None, 'previous': None, 'results': [], } self.assertEqual(status.HTTP_200_OK, resp.status_code) self.assertEqual(expected_data, dict(resp.data))
def test_home_page(self, enable_unenrolled_access, course_visibility, user_type, expected_message): self.create_user_for_course(self.course, user_type) # Render the course home page with mock.patch( 'xmodule.course_module.CourseDescriptor.course_visibility', course_visibility): # Test access with anonymous flag and course visibility with override_waffle_flag(COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, enable_unenrolled_access): url = course_home_url(self.course) response = self.client.get(url) # Verify that the course tools and dates are always shown self.assertContains(response, 'Course Tools') self.assertContains(response, 'Today is') # Verify that start button, course sock, and welcome message # are only shown to enrolled users. is_enrolled = user_type is CourseUserType.ENROLLED is_unenrolled_staff = user_type is CourseUserType.UNENROLLED_STAFF expected_welcome = 1 if (is_enrolled or is_unenrolled_staff) else 0 self.assertContains(response, 'Start Course', count=expected_welcome) self.assertContains(response, 'Learn About Verified Certificate', count=(1 if is_enrolled else 0)) self.assertContains(response, TEST_WELCOME_MESSAGE, count=expected_welcome) # Verify the outline is shown to enrolled users, unenrolled_staff and anonymous users if allowed is_public = course_visibility == COURSE_VISIBILITY_PUBLIC and enable_unenrolled_access is_public_outline = course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE and enable_unenrolled_access expected_chapter = 1 if (is_public or is_public_outline) else expected_welcome self.assertContains(response, TEST_CHAPTER_NAME, count=expected_chapter) # Verify that the expected message is shown to the user self.assertContains( response, 'To see course content', count=1 if user_type is CourseUserType.ANONYMOUS else 0) self.assertContains(response, '<div class="user-messages">', count=1 if expected_message else 0) if expected_message: self.assertContains( response, 'You must be enrolled in the course to see course content.')
def test_gradebook_data_for_course(self): with patch('lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read') as mock_grade: mock_grade.side_effect = [ self.mock_course_grade(self.student, passed=True, letter_grade='A', percent=0.85), self.mock_course_grade(self.other_student, passed=False, letter_grade=None, percent=0.45), ] with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() resp = self.client.get( self.get_url(course_key=self.course.id) ) self._assert_data_all_users(resp)
def test_filter_enrollment_mode_no_students(self): with patch('lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read') as mock_grade: mock_grade.side_effect = [ self.mock_course_grade(self.student, passed=True, letter_grade='A', percent=0.85), self.mock_course_grade(self.other_student, passed=False, letter_grade=None, percent=0.45), ] with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() resp = self.client.get( self.get_url(course_key=self.course.id) + '?enrollment_mode={}'.format(CourseMode.VERIFIED) ) self._assert_empty_response(resp)
def test_timed_exam_gating_waffle_flag(self, mocked_user): """ Verify the code inside the waffle flag is not executed with the flag off Verify the code inside the waffle flag is executed with the flag on """ # the order of the overrides is important since the `assert_not_called` does # not appear to be limited to just the override_waffle_flag = False scope with override_waffle_flag(TIMED_EXAM_GATING_WAFFLE_FLAG, active=False): self._get_rendered_view(self.sequence_5_1, extra_context=dict( next_url='NextSequential', prev_url='PrevSequential'), view=STUDENT_VIEW) mocked_user.assert_not_called() with override_waffle_flag(TIMED_EXAM_GATING_WAFFLE_FLAG, active=True): self._get_rendered_view(self.sequence_5_1, extra_context=dict( next_url='NextSequential', prev_url='PrevSequential'), view=STUDENT_VIEW) mocked_user.assert_called_once()
def test_no_gradebook_learner_count_message(self): """ Test that, when the writable gradebook featue IS enabled, there is NOT a message that the feature is only available for courses with small numbers of learners. """ waffle_flag = waffle_flags()[WRITABLE_GRADEBOOK] with override_waffle_flag(waffle_flag, active=True): response = self.client.get(self.url) self.assertNotIn( TestInstructorDashboard.GRADEBOOK_LEARNER_COUNT_MESSAGE, response.content) self.assertIn('View Gradebook', response.content)
def test_about_page_public_view(self, course_visibility): """ Assert that anonymous or unenrolled users see View Course option when unenrolled access flag is set """ with mock.patch('xmodule.course_module.CourseDescriptor.course_visibility', course_visibility): with override_waffle_flag(COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, active=True): url = reverse('about_course', args=[text_type(self.course.id)]) resp = self.client.get(url) if course_visibility == COURSE_VISIBILITY_PUBLIC or course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE: self.assertContains(resp, "View Course") else: self.assertContains(resp, "Enroll Now")
def test_no_gradebook_learner_count_message(self): """ Test that, when the writable gradebook featue IS enabled, there is NOT a message that the feature is only available for courses with small numbers of learners. """ waffle_flag = waffle_flags()[WRITABLE_GRADEBOOK] with override_waffle_flag(waffle_flag, active=True): response = self.client.get(self.url) self.assertNotIn( TestInstructorDashboard.GRADEBOOK_LEARNER_COUNT_MESSAGE, response.content ) self.assertIn('View Gradebook', response.content)
def test_about_page_public_view(self, course_visibility): """ Assert that anonymous or unenrolled users see View Course option when unenrolled access flag is set """ with mock.patch('xmodule.course_module.CourseDescriptor.course_visibility', course_visibility): with override_waffle_flag(COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, active=True): url = reverse('about_course', args=[text_type(self.course.id)]) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) if course_visibility == COURSE_VISIBILITY_PUBLIC or course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE: self.assertIn("View Course", resp.content) else: self.assertIn("Enroll in", resp.content)
def test_home_page( self, enable_unenrolled_access, course_visibility, user_type, expected_enroll_message, expected_course_outline, ): self.create_user_for_course(self.course, user_type) # Render the course home page with mock.patch('xmodule.course_module.CourseDescriptor.course_visibility', course_visibility): # Test access with anonymous flag and course visibility with override_waffle_flag(COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, enable_unenrolled_access): url = course_home_url(self.course) response = self.client.get(url) private_url = course_home_url(self.private_course) private_response = self.client.get(private_url) # Verify that the course tools and dates are always shown self.assertContains(response, TEST_COURSE_TOOLS) self.assertContains(response, TEST_COURSE_TODAY) is_anonymous = user_type is CourseUserType.ANONYMOUS is_enrolled = user_type is CourseUserType.ENROLLED is_enrolled_or_staff = is_enrolled or user_type in ( CourseUserType.UNENROLLED_STAFF, CourseUserType.GLOBAL_STAFF ) self.assertContains(response, 'Learn About Verified Certificate', count=(1 if is_enrolled else 0)) # Verify that start button, course sock, and welcome message # are only shown to enrolled users or staff. self.assertContains(response, 'Start Course', count=(1 if is_enrolled_or_staff else 0)) self.assertContains(response, TEST_WELCOME_MESSAGE, count=(1 if is_enrolled_or_staff else 0)) # Verify the outline is shown to enrolled users, unenrolled_staff and anonymous users if allowed self.assertContains(response, TEST_CHAPTER_NAME, count=(1 if expected_course_outline else 0)) # Verify the message shown to the user if not enable_unenrolled_access or course_visibility != COURSE_VISIBILITY_PUBLIC: self.assertContains( response, 'To see course content', count=(1 if is_anonymous else 0) ) self.assertContains(response, '<div class="user-messages"', count=(1 if expected_enroll_message else 0)) if expected_enroll_message: self.assertContains(response, 'You must be enrolled in the course to see course content.') if enable_unenrolled_access and course_visibility == COURSE_VISIBILITY_PUBLIC: if user_type == CourseUserType.UNENROLLED and self.private_course.invitation_only: if expected_enroll_message: self.assertContains(private_response, 'You must be enrolled in the course to see course content.')
def test_logistration_mfe_redirects(self, url_name, path): """ Test that if Logistration MFE is enabled, then we redirect to the correct URL. """ site_domain = 'example.org' self.set_up_site(site_domain, {'ENABLE_ACCOUNT_MICROFRONTEND': True}) with override_waffle_flag(REDIRECT_TO_ACCOUNT_MICROFRONTEND, active=True): response = self.client.get(reverse(url_name)) self.assertEqual(response.url, settings.ACCOUNT_MICROFRONTEND_URL + path) self.assertEqual(response.status_code, 302)
def test_logistration_redirect_params(self, url_name, path, query_params): """ Test that if request is redirected to logistration MFE, query params are passed to the redirect url. """ site_domain = 'example.org' expected_url = settings.ACCOUNT_MICROFRONTEND_URL + path + '?' + urlencode( query_params) self.set_up_site(site_domain, {'ENABLE_ACCOUNT_MICROFRONTEND': True}) with override_waffle_flag(REDIRECT_TO_ACCOUNT_MICROFRONTEND, active=True): response = self.client.get(reverse(url_name), query_params) self.assertRedirects(response, expected_url)
def test_recalculate_subsection_grade_v3(self, freeze_flag_value, end_date_adjustment, mock_log): self.set_up_course(course_end=timezone.now() - timedelta(end_date_adjustment)) for user in self.users: CourseEnrollment.enroll(user, self.course.id) with override_waffle_flag(self.freeze_grade_flag, active=freeze_flag_value): modified_datetime = datetime.utcnow().replace(tzinfo=pytz.UTC) - timedelta(days=1) with patch('lms.djangoapps.grades.tasks._has_db_updated_with_new_score') as mock_has_db_updated: result = recalculate_subsection_grade_v3.apply_async(kwargs=self.recalculate_subsection_grade_kwargs) self._assert_for_freeze_grade_flag( result, freeze_flag_value, end_date_adjustment, mock_log, mock_has_db_updated, '_recalculate_subsection_grade' )
def test_compute_all_grades_for_course(self, freeze_flag_value, end_date_adjustment, mock_log): self.set_up_course(course_end=timezone.now() - timedelta(end_date_adjustment)) for user in self.users: CourseEnrollment.enroll(user, self.course.id) with override_waffle_flag(self.freeze_grade_flag, active=freeze_flag_value): with patch( 'lms.djangoapps.grades.tasks.compute_grades_for_course_v2.apply_async', return_value=None) as mock_compute_grades: result = compute_all_grades_for_course.apply_async( kwargs={'course_key': six.text_type(self.course.id)}) self._assert_for_freeze_grade_flag( result, freeze_flag_value, end_date_adjustment, mock_log, mock_compute_grades, 'compute_all_grades_for_course')
def test_redirect_view(self): with override_waffle_flag(REDIRECT_TO_ACCOUNT_MICROFRONTEND, active=True): old_url_path = reverse('account_settings') # Test with waffle flag active and site setting disabled, does not redirect response = self.client.get(path=old_url_path) for attribute in self.FIELDS: self.assertIn(attribute, response.content) # Test with waffle flag active and site setting enabled, redirects to microfrontend site_domain = 'othersite.example.com' self.set_up_site(site_domain, { 'SITE_NAME': site_domain, 'ENABLE_ACCOUNT_MICROFRONTEND': True }) self.client.login(username=self.USERNAME, password=self.PASSWORD) response = self.client.get(path=old_url_path) self.assertRedirects(response, settings.ACCOUNT_MICROFRONTEND_URL, fetch_redirect_response=False)
def test_redirect_view(self): with override_waffle_flag(REDIRECT_TO_PROFILE_MICROFRONTEND, active=True): profile_path = reverse('learner_profile', kwargs={'username': self.USERNAME}) # Test with waffle flag active and site setting disabled, does not redirect response = self.client.get(path=profile_path) for attribute in self.CONTEXT_DATA: self.assertIn(attribute, response.content) # Test with waffle flag active and site setting enabled, redirects to microfrontend site_domain = 'othersite.example.com' self.set_up_site(site_domain, { 'SITE_NAME': site_domain, 'ENABLE_PROFILE_MICROFRONTEND': True }) self.client.login(username=self.USERNAME, password=self.PASSWORD) response = self.client.get(path=profile_path) profile_url = settings.PROFILE_MICROFRONTEND_URL self.assertRedirects(response, profile_url + self.USERNAME, fetch_redirect_response=False)
def test_recalculate_subsection_grade_v3(self, freeze_flag_value, end_date_adjustment, mock_log): self.set_up_course(course_end=timezone.now() - timedelta(end_date_adjustment)) for user in self.users: CourseEnrollment.enroll(user, self.course.id) with override_waffle_flag(self.freeze_grade_flag, active=freeze_flag_value): modified_datetime = datetime.utcnow().replace(tzinfo=pytz.UTC) - timedelta(days=1) with patch( 'lms.djangoapps.grades.tasks.GradesService', return_value=MockGradesService(mocked_return_value=MagicMock(modified=modified_datetime)) ) as mock_grade_service: result = recalculate_subsection_grade_v3.apply_async(kwargs=self.recalculate_subsection_grade_kwargs) self._assert_for_freeze_grade_flag( result, freeze_flag_value, end_date_adjustment, mock_log, mock_grade_service, '_recalculate_subsection_grade' )
def test_recalculate_course_and_subsection_grades(self, freeze_flag_value, end_date_adjustment, mock_log): self.set_up_course(course_end=timezone.now() - timedelta(end_date_adjustment)) CourseEnrollment.enroll(self.user, self.course.id) with override_waffle_flag(self.freeze_grade_flag, active=freeze_flag_value): with patch('lms.djangoapps.grades.tasks.CourseGradeFactory') as mock_factory: factory = mock_factory.return_value kwargs = { 'user_id': self.user.id, 'course_key': six.text_type(self.course.id), } result = tasks.recalculate_course_and_subsection_grades_for_user.apply_async(kwargs=kwargs) self._assert_for_freeze_grade_flag( result, freeze_flag_value, end_date_adjustment, mock_log, factory.read, 'recalculate_course_and_subsection_grades_for_user' )
def test_grades_frozen(self): """ Should receive a 403 when grades have been frozen for a course. """ with patch('lms.djangoapps.grades.api.v1.views.are_grades_frozen', return_value=True): with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() post_data = [ { 'user_id': self.student.id, 'usage_id': text_type(self.subsections[self.chapter_1.location][0].location), 'grade': {}, # doesn't matter what we put here. } ] resp = self.client.post( self.get_url(), data=json.dumps(post_data), content_type='application/json', ) self.assertEqual(status.HTTP_403_FORBIDDEN, resp.status_code)
def test_filter_enrollment_mode(self): with patch('lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read') as mock_grade: mock_grade.side_effect = [ self.mock_course_grade(self.student, passed=True, letter_grade='A', percent=0.85), self.mock_course_grade(self.other_student, passed=False, letter_grade=None, percent=0.45), ] # Enroll a verified student, for whom data should not be returned. verified_student = UserFactory() _ = CourseEnrollmentFactory( course_id=self.course.id, user=verified_student, created=datetime(2013, 1, 1, tzinfo=UTC), mode=CourseMode.VERIFIED, ) with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() resp = self.client.get( self.get_url(course_key=self.course.id) + '?enrollment_mode={}'.format(CourseMode.AUDIT) ) self._assert_data_all_users(resp)
def test_compute_all_grades_for_course(self, freeze_flag_value, end_date_adjustment, mock_log): self.set_up_course(course_end=timezone.now() - timedelta(end_date_adjustment)) for user in self.users: CourseEnrollment.enroll(user, self.course.id) with override_waffle_flag(self.freeze_grade_flag, active=freeze_flag_value): with patch( 'lms.djangoapps.grades.tasks.compute_grades_for_course_v2.apply_async', return_value=None ) as mock_compute_grades: result = compute_all_grades_for_course.apply_async( kwargs={ 'course_key': six.text_type(self.course.id) } ) self._assert_for_freeze_grade_flag( result, freeze_flag_value, end_date_adjustment, mock_log, mock_compute_grades, 'compute_all_grades_for_course' )
def test_filter_cohort_id_and_enrollment_mode(self): with patch('lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read') as mock_grade: mock_grade.return_value = self.mock_course_grade(self.student, passed=True, letter_grade='A', percent=0.85) cohort = CohortFactory(course_id=self.course.id, name="TestCohort", users=[self.student]) with override_waffle_flag(self.waffle_flag, active=True): self.login_staff() # both of our test users are in the audit track, so this is functionally equivalent # to just `?cohort_id=cohort.id`. query = '?cohort_id={}&enrollment_mode={}'.format(cohort.id, CourseMode.AUDIT) resp = self.client.get( self.get_url(course_key=self.course.id) + query ) expected_results = [ OrderedDict([ ('course_id', text_type(self.course.id)), ('email', self.student.email), ('user_id', self.student.id), ('username', self.student.username), ('full_name', self.student.get_full_name()), ('passed', True), ('percent', 0.85), ('letter_grade', 'A'), ('progress_page_url', reverse( 'student_progress', kwargs=dict(course_id=text_type(self.course.id), student_id=self.student.id) )), ('section_breakdown', self.expected_subsection_grades(letter_grade='A')), ]), ] self.assertEqual(status.HTTP_200_OK, resp.status_code) actual_data = dict(resp.data) self.assertIsNone(actual_data['next']) self.assertIsNone(actual_data['previous']) self.assertEqual(expected_results, actual_data['results'])
def test_compute_grades_for_course(self, freeze_flag_value, end_date_adjustment, mock_log): self.set_up_course(course_end=timezone.now() - timedelta(end_date_adjustment)) for user in self.users: CourseEnrollment.enroll(user, self.course.id) with override_waffle_flag(self.freeze_grade_flag, active=freeze_flag_value): with patch('lms.djangoapps.grades.tasks.CourseGradeFactory') as mock_factory: factory = mock_factory.return_value with mock_get_score(1, 2): result = compute_grades_for_course.apply_async( kwargs={ 'course_key': six.text_type(self.course.id), 'batch_size': 2, 'offset': 4, } ) self._assert_for_freeze_grade_flag( result, freeze_flag_value, end_date_adjustment, mock_log, factory.iter, 'compute_grades_for_course' )
def override(self, active=True): # TODO We can move this import to the top of the file once this code is # not all contained within the __init__ module. from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag with override_waffle_flag(self, active): yield
def test_student(self): self.client.login(username=self.student.username, password=self.password) with override_waffle_flag(self.waffle_flag, active=True): resp = self.client.post(self.get_url()) self.assertEqual(status.HTTP_403_FORBIDDEN, resp.status_code)