From 951ff738b4991772b71e7be2a96b837304f241d7 Mon Sep 17 00:00:00 2001 From: Zach Hancock Date: Tue, 15 Aug 2023 14:18:56 -0400 Subject: [PATCH] test: staff unit tests --- edx_exams/apps/api/v1/tests/test_views.py | 64 +++++++++++++++++++---- edx_exams/apps/lti/tests/test_views.py | 13 +++-- 2 files changed, 62 insertions(+), 15 deletions(-) diff --git a/edx_exams/apps/api/v1/tests/test_views.py b/edx_exams/apps/api/v1/tests/test_views.py index 0d6004bc..8bca430f 100644 --- a/edx_exams/apps/api/v1/tests/test_views.py +++ b/edx_exams/apps/api/v1/tests/test_views.py @@ -17,7 +17,7 @@ from edx_exams.apps.api.test_utils import ExamsAPITestCase from edx_exams.apps.core.exam_types import get_exam_type from edx_exams.apps.core.exceptions import ExamAttemptOnPastDueExam, ExamIllegalStatusTransition -from edx_exams.apps.core.models import CourseExamConfiguration, Exam, ExamAttempt, ProctoringProvider +from edx_exams.apps.core.models import CourseExamConfiguration, CourseStaffRole, Exam, ExamAttempt, ProctoringProvider from edx_exams.apps.core.statuses import ExamAttemptStatus from edx_exams.apps.core.test_utils.factories import ( AssessmentControlResultFactory, @@ -100,6 +100,14 @@ def test_auth_failures(self): random_user = UserFactory() self.get_response(random_user, [], 403) + def test_course_staff_access(self): + """ + Verify course staff can access endpoint + """ + course_staff_user = UserFactory() + CourseStaffRole.objects.create(user=course_staff_user, course_id=self.course_id) + self.get_response(course_staff_user, [], 200) + def test_exam_empty_exam_list(self): """ Test that exams not included in request are marked as inactive @@ -342,6 +350,17 @@ def test_patch_auth_failures(self): response = self.patch_api(random_user, {}) self.assertEqual(403, response.status_code) + def test_course_staff_write_access(self): + """ + Verify course staff have write access + """ + course_staff_user = UserFactory() + CourseStaffRole.objects.create(user=course_staff_user, course_id=self.course_id) + response = self.patch_api(course_staff_user, { + 'provider': None, + }) + self.assertEqual(204, response.status_code) + def test_patch_invalid_data(self): """ Assert that endpoint returns 400 if provider is missing @@ -1083,7 +1102,8 @@ def setUp(self): ) self.non_staff_user = UserFactory() - self.staff_user = UserFactory(is_staff=True) + self.course_staff_user = UserFactory() + CourseStaffRole.objects.create(user=self.course_staff_user, course_id=self.exam.course_id) def delete_api(self, user, attempt_id): """ @@ -1181,13 +1201,14 @@ def test_put_staff_update_exam_attempt(self, action, expected_status, mock_updat mock_update_attempt_status.return_value = attempt.id - response = self.put_api(self.staff_user, attempt.id, {'action': action}) + response = self.put_api(self.course_staff_user, attempt.id, {'action': action}) self.assertEqual(response.status_code, 200) mock_update_attempt_status.assert_called_once_with(attempt.id, expected_status) def test_put_learner_verify(self): """ Test that a learner account cannot verify an attempt + but course staff can """ # create exam attempt for user attempt = ExamAttemptFactory( @@ -1198,6 +1219,9 @@ def test_put_learner_verify(self): response = self.put_api(self.non_staff_user, attempt.id, {'action': 'verify'}) self.assertEqual(response.status_code, 400) + response = self.put_api(self.course_staff_user, attempt.id, {'action': 'verify'}) + self.assertEqual(response.status_code, 200) + @patch('edx_exams.apps.api.v1.views.update_attempt_status') def test_put_exception_raised(self, mock_update_attempt_status): """ @@ -1309,7 +1333,7 @@ def test_staff_delete_attempt(self): exam=self.exam, ) - response = self.delete_api(self.staff_user, attempt.id) + response = self.delete_api(self.course_staff_user, attempt.id) self.assertEqual(response.status_code, 204) with self.assertRaises(ExamAttempt.DoesNotExist): attempt.refresh_from_db() @@ -1318,7 +1342,7 @@ def test_delete_attempt_with_bad_attempt_id(self): """ Test that a bad attempt ID returns 400 """ - response = self.delete_api(self.staff_user, 9999999) + response = self.delete_api(self.course_staff_user, 9999999) self.assertEqual(response.status_code, 400) @@ -1329,7 +1353,8 @@ class ExamAttemptListViewTests(ExamsAPITestCase): def setUp(self): super().setUp() - CourseExamConfigurationFactory.create() + config = CourseExamConfigurationFactory.create() + self.course_id = config.course_id self.exam_1 = ExamFactory.create() self.exam_2 = ExamFactory.create() @@ -1341,19 +1366,29 @@ def get_api(self, exam_id, user=None, page_limit=20): user = user or self.user headers = self.build_jwt_headers(user) url = reverse( - 'api:v1:instructor-attempts-list' + 'api:v1:instructor-attempts-list', + kwargs={'course_id': self.course_id} ) return self.client.get(f'{url}?exam_id={exam_id}&limit={page_limit}', **headers) def test_requires_staff_user(self): """ - Test that only staff users can access this endpoint + Users that are neither staff nor course staff access this endpoint """ non_staff_user = UserFactory.create() response = self.get_api(self.exam_1.id, user=non_staff_user) self.assertEqual(response.status_code, 403) + def test_course_staff_access(self): + """ + Course staff can access this endpoint + """ + course_staff_user = UserFactory.create() + CourseStaffRole.objects.create(user=course_staff_user, course_id=self.course_id) + response = self.get_api(self.exam_1.id, user=course_staff_user) + self.assertEqual(response.status_code, 200) + def test_get_attempt_list_response_data(self): """ Test that a list of attempts includes the expected fields @@ -1426,7 +1461,10 @@ def test_get_attempt_list_response_pagination(self): response = self.get_api(self.exam_1.id, page_limit=5) next_url = response.data.get('next') - self.assertEqual(next_url, 'http://testserver/api/v1/instructor_view/attempts?exam_id=1&limit=5&offset=5') + self.assertEqual( + next_url, + f'http://testserver/api/v1/instructor_view/course_id/{self.course_id}/attempts?exam_id=1&limit=5&offset=5', + ) self.assertEqual(response.data.get('count'), 12) self.assertEqual(len(response.data.get('results')), 5) @@ -1434,9 +1472,13 @@ def test_get_attempt_list_response_pagination(self): response = self.client.get(next_url, **headers) next_url = response.data.get('next') - self.assertEqual(next_url, 'http://testserver/api/v1/instructor_view/attempts?exam_id=1&limit=5&offset=10') self.assertEqual( - response.data.get('previous'), 'http://testserver/api/v1/instructor_view/attempts?exam_id=1&limit=5' + next_url, + f'http://testserver/api/v1/instructor_view/course_id/{self.course_id}/attempts?exam_id=1&limit=5&offset=10', + ) + self.assertEqual( + response.data.get('previous'), + f'http://testserver/api/v1/instructor_view/course_id/{self.course_id}/attempts?exam_id=1&limit=5', ) self.assertEqual(response.data.get('count'), 12) self.assertEqual(len(response.data.get('results')), 5) diff --git a/edx_exams/apps/lti/tests/test_views.py b/edx_exams/apps/lti/tests/test_views.py index d0f5663d..46b1ed6b 100644 --- a/edx_exams/apps/lti/tests/test_views.py +++ b/edx_exams/apps/lti/tests/test_views.py @@ -15,7 +15,7 @@ from lti_consumer.models import LtiConfiguration, LtiProctoringConsumer from edx_exams.apps.api.test_utils import ExamsAPITestCase, UserFactory -from edx_exams.apps.core.models import AssessmentControlResult +from edx_exams.apps.core.models import AssessmentControlResult, CourseStaffRole from edx_exams.apps.core.statuses import ExamAttemptStatus from edx_exams.apps.core.test_utils.factories import ( CourseExamConfigurationFactory, @@ -609,6 +609,11 @@ def setUp(self): lti_configuration_id=self.lti_configuration.id ), ) + self.course_staff_user = UserFactory() + CourseStaffRole.objects.create( + user=self.course_staff_user, + course_id=self.exam.course_id, + ) def _get_launch_url(self, exam_id): return reverse('lti:instructor_tool', kwargs={'exam_id': exam_id}) @@ -617,16 +622,16 @@ def test_lti_launch(self, mock_create_launch_url): """ Test that the view calls get_lti_1p3_launch_start_url with the correct data. """ - headers = self.build_jwt_headers(self.user) + headers = self.build_jwt_headers(self.course_staff_user) response = self.client.get(self._get_launch_url(self.exam.id), **headers) mock_create_launch_url.assert_called_with( Lti1p3LaunchData( - user_id=self.user.id, + user_id=self.course_staff_user.id, user_role='instructor', config_id=self.lti_configuration.config_id, resource_link_id=self.exam.resource_id, - external_user_id=str(self.user.anonymous_user_id), + external_user_id=str(self.course_staff_user.anonymous_user_id), context_id=self.exam.course_id, ) )