From 72a85ee0ffbaf94d7cb5f35528d15415e5bba63e Mon Sep 17 00:00:00 2001 From: hajorg Date: Thu, 14 Mar 2024 15:14:07 +0100 Subject: [PATCH] feat: address CR --- lms/djangoapps/support/tasks.py | 31 +++-- lms/djangoapps/support/tests/test_tasks.py | 126 ++++++++++++++++----- 2 files changed, 122 insertions(+), 35 deletions(-) diff --git a/lms/djangoapps/support/tasks.py b/lms/djangoapps/support/tasks.py index 82140124ea..fc33446887 100644 --- a/lms/djangoapps/support/tasks.py +++ b/lms/djangoapps/support/tasks.py @@ -9,20 +9,30 @@ from common.djangoapps.student.models.course_enrollment import CourseEnrollment from common.djangoapps.student.models.user import get_user_by_username_or_email from lms.djangoapps.courseware.courses import get_course from lms.djangoapps.courseware.models import StudentModule -from lms.djangoapps.grades.rest_api.v1.views import SubmissionHistoryView from lms.djangoapps.instructor.enrollment import reset_student_attempts from lms.djangoapps.support.models import CourseResetAudit log = logging.getLogger(__name__) -def update_audit_status(audit_instance, status, completed_at=False): +def update_audit_status(audit_instance, status): audit_instance.status = status - if completed_at: + if status == CourseResetAudit.CourseResetStatus.COMPLETE: audit_instance.completed_at = datetime.now() audit_instance.save() +def get_blocks(course): + """ Get a list of problem xblock for the course.""" + blocks = [] + for section in course.get_children(): + for subsection in section.get_children(): + for vertical in subsection.get_children(): + for block in vertical.get_children(): + blocks.append(block) + return blocks + + @shared_task @set_code_owner_attribute def reset_student_course(course_id, learner_email, reset_by_user_email): @@ -34,21 +44,24 @@ def reset_student_course(course_id, learner_email, reset_by_user_email): enrollment = CourseEnrollment.objects.get( course=course_id, user=user, - is_active=True + is_active=True, ) course_overview = enrollment.course_overview - course_reset_audit = CourseResetAudit.objects.filter(course_enrollment=enrollment).first() + course_reset_audit = CourseResetAudit.objects.get( + course_enrollment=enrollment, + status=CourseResetAudit.CourseResetStatus.ENQUEUED + ) update_audit_status(course_reset_audit, CourseResetAudit.CourseResetStatus.IN_PROGRESS) try: course = get_course(course_overview.id, depth=4) - history = SubmissionHistoryView.get_problem_blocks(course) - for data in history: + blocks = get_blocks(course) + for data in blocks: try: reset_student_attempts(course.id, user, data.scope_ids.usage_id, reset_by_user, True) except StudentModule.DoesNotExist: pass - update_audit_status(course_reset_audit, CourseResetAudit.CourseResetStatus.COMPLETE, True) + update_audit_status(course_reset_audit, CourseResetAudit.CourseResetStatus.COMPLETE) except Exception as e: # pylint: disable=broad-except - logging.exception(e) + logging.exception(f'Error occurred for Course Audit with ID {course_reset_audit.id}: {e}.') update_audit_status(course_reset_audit, CourseResetAudit.CourseResetStatus.FAILED) diff --git a/lms/djangoapps/support/tests/test_tasks.py b/lms/djangoapps/support/tests/test_tasks.py index 2b03bd2028..09c2f47056 100644 --- a/lms/djangoapps/support/tests/test_tasks.py +++ b/lms/djangoapps/support/tests/test_tasks.py @@ -2,10 +2,10 @@ Unit tests for reset_student_course task """ - -from unittest.mock import patch, Mock +from unittest.mock import patch, Mock, call from xmodule.modulestore.tests.factories import BlockFactory + from lms.djangoapps.courseware.tests.test_submitting_problems import TestSubmittingProblems from lms.djangoapps.courseware.models import StudentModule from lms.djangoapps.support.tasks import reset_student_course @@ -17,7 +17,7 @@ from common.djangoapps.student.tests.factories import UserFactory class ResetStudentCourse(TestSubmittingProblems): - """ Test expire_waiting_enrollments task """ + """ Test reset_student_course task """ USERNAME = "support" EMAIL = "support@example.com" PASSWORD = "support" @@ -36,10 +36,13 @@ class ResetStudentCourse(TestSubmittingProblems): course=self.opt_in, course_enrollment=self.enrollment, reset_by=self.user, - status=CourseResetAudit.CourseResetStatus.FAILED + status=CourseResetAudit.CourseResetStatus.ENQUEUED ) + self.p1 = '' + self.p2 = '' + self.p3 = '' - def basic_setup(self, late=False, reset=False, showanswer=False): + def basic_setup(self): """ Set up a simple course for testing basic grading functionality. """ @@ -59,41 +62,112 @@ class ResetStudentCourse(TestSubmittingProblems): self.add_grading_policy(grading_policy) # set up a simple course with four problems - homework = self.add_graded_section_to_course('homework', late=late, reset=reset, showanswer=showanswer) + homework = self.add_graded_section_to_course('homework') vertical = BlockFactory.create( parent_location=homework.location, category='vertical', - display_name='Subsection 1', + display_name='Unit 1', ) - self.add_dropdown_to_section(vertical.location, 'p1', 1) - self.add_dropdown_to_section(vertical.location, 'p2', 1) - self.add_dropdown_to_section(vertical.location, 'p3', 1) + + self.p1 = self.add_dropdown_to_section(vertical.location, 'p1', 1) + self.p2 = self.add_dropdown_to_section(vertical.location, 'p2', 1) + self.p3 = self.add_dropdown_to_section(vertical.location, 'p3', 1) self.refresh_course() def test_reset_student_course(self): - self.basic_setup() - reset_student_course(self.course_id, self.student_user.email, self.user.email) - course_reset_audit = CourseResetAudit.objects.filter(course_enrollment=self.enrollment).first() - self.assertTrue(course_reset_audit.completed_at) - self.assertEqual(course_reset_audit.status, CourseResetAudit.CourseResetStatus.COMPLETE) - - def test_reset_student_course_student_module_not_found(self): + """ Test that it resets student attempts """ with patch( 'lms.djangoapps.support.tasks.reset_student_attempts', - Mock(side_effect=StudentModule.DoesNotExist('An error occurred')) - ): + ) as mock_reset_student_attempts: + self.basic_setup() reset_student_course(self.course_id, self.student_user.email, self.user.email) - course_reset_audit = CourseResetAudit.objects.filter(course_enrollment=self.enrollment).first() - self.assertTrue(course_reset_audit.completed_at) + + mock_reset_student_attempts.assert_has_calls([ + call( + self.course.id, + self.student_user, + self.p1.location, + self.user, + True + ), + call( + self.course.id, + self.student_user, + self.p2.location, + self.user, + True + ), + call( + self.course.id, + self.student_user, + self.p3.location, + self.user, + True + ) + ]) + + course_reset_audit = CourseResetAudit.objects.get(course_enrollment=self.enrollment) + self.assertIsNotNone(course_reset_audit.completed_at) self.assertEqual(course_reset_audit.status, CourseResetAudit.CourseResetStatus.COMPLETE) - def test_reset_student_course_fail(self): + def test_reset_student_course_student_module_not_found(self): + with patch( - 'lms.djangoapps.support.tasks.SubmissionHistoryView.get_problem_blocks', - Mock(side_effect=Exception('An error occurred')) + 'lms.djangoapps.support.tasks.reset_student_attempts', + Mock(side_effect=StudentModule.DoesNotExist()) + ) as mock_reset_student_attempts: + self.basic_setup() + reset_student_course(self.course_id, self.student_user.email, self.user.email) + mock_reset_student_attempts.assert_has_calls([ + call( + self.course.id, + self.student_user, + self.p1.location, + self.user, + True + ), + call( + self.course.id, + self.student_user, + self.p2.location, + self.user, + True + ), + call( + self.course.id, + self.student_user, + self.p3.location, + self.user, + True + ) + ]) + + course_reset_audit = CourseResetAudit.objects.get(course_enrollment=self.enrollment) + self.assertRaises(StudentModule.DoesNotExist, mock_reset_student_attempts) + self.assertIsNotNone(course_reset_audit.completed_at) + self.assertEqual(course_reset_audit.status, CourseResetAudit.CourseResetStatus.COMPLETE) + + @patch('lms.djangoapps.support.tasks.reset_student_attempts') + def test_reset_student_course_fail(self, mock_reset_student_attempts): + with patch( + 'lms.djangoapps.support.tasks.get_blocks', + Mock(side_effect=Exception()) ): reset_student_course(self.course_id, self.student_user.email, self.user.email) - course_reset_audit = CourseResetAudit.objects.filter(course_enrollment=self.enrollment).first() - self.assertFalse(course_reset_audit.completed_at) + mock_reset_student_attempts.assert_not_called() + course_reset_audit = CourseResetAudit.objects.get(course_enrollment=self.enrollment) + self.assertIsNone(course_reset_audit.completed_at) + self.assertEqual(course_reset_audit.status, CourseResetAudit.CourseResetStatus.FAILED) + + def test_reset_student_attempts_raise_exception(self): + with patch( + 'lms.djangoapps.support.tasks.reset_student_attempts', + Mock(side_effect=Exception()) + ) as mock_reset_student_attempts: + self.basic_setup() + reset_student_course(self.course_id, self.student_user.email, self.user.email) + mock_reset_student_attempts.assert_called_once() + course_reset_audit = CourseResetAudit.objects.get(course_enrollment=self.enrollment) + self.assertIsNone(course_reset_audit.completed_at) self.assertEqual(course_reset_audit.status, CourseResetAudit.CourseResetStatus.FAILED)