From c18c9de3cd60d03256cec3fccb6ba1aa6fe3a680 Mon Sep 17 00:00:00 2001
From: hajorg <jorg2all@yahoo.com>
Date: Wed, 28 Feb 2024 15:23:28 +0100
Subject: [PATCH 1/2] feat: add post endpoint for course reset

---
 lms/djangoapps/support/tests/test_views.py   | 97 +++++++++++++++++++-
 lms/djangoapps/support/views/course_reset.py | 66 ++++++++++++-
 2 files changed, 161 insertions(+), 2 deletions(-)

diff --git a/lms/djangoapps/support/tests/test_views.py b/lms/djangoapps/support/tests/test_views.py
index 6338935470f4..758e51fdf5f9 100644
--- a/lms/djangoapps/support/tests/test_views.py
+++ b/lms/djangoapps/support/tests/test_views.py
@@ -54,7 +54,7 @@
 from common.djangoapps.third_party_auth.tests.factories import SAMLProviderConfigFactory
 from common.test.utils import disable_signal
 from lms.djangoapps.program_enrollments.tests.factories import ProgramCourseEnrollmentFactory, ProgramEnrollmentFactory
-from lms.djangoapps.support.models import CourseResetAudit
+from lms.djangoapps.support.models import CourseResetAudit, CourseResetCourseOptIn
 from lms.djangoapps.support.serializers import ProgramEnrollmentSerializer
 from lms.djangoapps.support.tests.factories import CourseResetCourseOptInFactory, CourseResetAuditFactory
 from lms.djangoapps.verify_student.models import VerificationDeadline
@@ -2331,3 +2331,98 @@ def test_multiple_failed_audits(self):
             'can_reset': True,
             'status': most_recent_audit.status_message()
         }])
+
+
+class TestResetCourseViewPost(SupportViewTestCase):
+    """
+    Tests for creating course request
+    """
+
+    def setUp(self):
+        super().setUp()
+        SupportStaffRole().add_users(self.user)
+
+        self.course_id = 'course-v1:a+b+c'
+
+        self.other_user = User.objects.create(username='otheruser', password='test')
+
+        self.course = CourseFactory.create(
+            org='a',
+            course='b',
+            run='c',
+            enable_proctored_exams=True,
+            proctoring_provider=settings.PROCTORING_BACKENDS['DEFAULT'],
+        )
+        self.enrollment = CourseEnrollmentFactory(
+            is_active=True,
+            mode='verified',
+            course_id=self.course.id,
+            user=self.user
+        )
+        CourseResetCourseOptIn.objects.create(
+            course_id=self.course_id,
+            active=True
+        )
+
+        self.other_course = CourseFactory.create(
+            org='x',
+            course='y',
+            run='z',
+        )
+
+    def _url(self, username):
+        return reverse("support:course_reset", kwargs={'username_or_email': username})
+
+    def test_wrong_username(self):
+        """
+        Test that a request with a username which does not exits returns 404
+        """
+        response = self.client.post(self._url(username='does_not_exist'), data={'course_id': 'course-v1:aa+bb+c'})
+        self.assertEqual(response.status_code, 404)
+
+    def test_learner_course_reset(self):
+        response = self.client.post(self._url(username=self.user.username), data={'course_id': self.course_id})
+        self.assertEqual(response.status_code, 201)
+        self.assertEqual(response.data, {
+            'course_id': self.course_id,
+            'status': response.data['status'],
+            'can_reset': False,
+            'display_name': self.course.display_name
+        })
+
+    def test_course_not_opt_in(self):
+        response = self.client.post(self._url(username=self.user.username), data={'course_id': 'course-v1:aa+bb+c'})
+        self.assertEqual(response.status_code, 404)
+
+    def test_course_reset_failed(self):
+        course = CourseFactory.create(
+            org='xx',
+            course='yy',
+            run='zz',
+        )
+        enrollment = CourseEnrollmentFactory(
+            is_active=True,
+            mode='verified',
+            course_id=course.id,
+            user=self.user
+        )
+
+        opt_in_course = CourseResetCourseOptIn.objects.create(
+            course_id=course.id,
+            active=True
+        )
+
+        CourseResetAudit.objects.create(
+            course=opt_in_course,
+            course_enrollment=enrollment,
+            reset_by=self.other_user,
+            status=CourseResetAudit.CourseResetStatus.FAILED
+        )
+        response = self.client.post(self._url(username=self.user.username), data={'course_id': course.id})
+        self.assertEqual(response.status_code, 200)
+
+    def test_course_reset_dupe(self):
+        response = self.client.post(self._url(username=self.user.username), data={'course_id': self.course_id})
+        self.assertEqual(response.status_code, 201)
+        resp = self.client.post(self._url(username=self.user.username), data={'course_id': self.course_id})
+        self.assertEqual(resp.status_code, 204)
diff --git a/lms/djangoapps/support/views/course_reset.py b/lms/djangoapps/support/views/course_reset.py
index b274d4a97fa1..6ed705548ffe 100644
--- a/lms/djangoapps/support/views/course_reset.py
+++ b/lms/djangoapps/support/views/course_reset.py
@@ -95,6 +95,70 @@ def get(self, request, username_or_email):
             })
         return Response(result)
 
+
     @method_decorator(require_support_permission)
     def post(self, request, username_or_email):
-        """ Other Ticket """
+        """
+        Resets a course for the given learner
+
+        returns a dicts with the format {
+            'course_id': <course id>
+            'display_name': <course display name>
+            'status': <status of the enrollment wrt/reset, to be displayed to user>
+            'can_reset': (boolean) <can the course be reset for this learner>
+        }
+        """
+        course_id = request.data['course_id']
+        try:
+            user = get_user_by_username_or_email(username_or_email)
+        except User.DoesNotExist:
+            return Response({'error': 'User does not exist'}, status=404)
+        try:
+            opt_in_course = CourseResetCourseOptIn.objects.get(course_id=course_id)
+        except CourseResetCourseOptIn.DoesNotExist:
+            return Response({'error': 'Course is not eligible'}, status=404)
+        enrollment = CourseEnrollment.objects.get(
+            course=course_id,
+            user=user,
+            is_active=True
+        )
+        user_passed = user_has_passing_grade_in_course(enrollment=enrollment)
+        course_overview = enrollment.course_overview
+        course_reset_audit = CourseResetAudit.objects.filter(course_enrollment=enrollment).first()
+
+        if course_reset_audit and course_reset_audit.status == CourseResetAudit.CourseResetStatus.FAILED and not user_passed:
+            course_reset_audit.status = CourseResetAudit.CourseResetStatus.ENQUEUED
+            course_reset_audit.save()
+            status = f"In progress - Started on {course_reset_audit.modified} by {course_reset_audit.reset_by.username}"
+            # Call celery task
+            resp = {
+                'course_id': course_id,
+                'status': status,
+                'can_reset': False,
+                'display_name': course_overview.display_name
+            }
+            return Response(resp, status=200)
+
+        elif course_reset_audit and \
+            (course_reset_audit.status == CourseResetAudit.CourseResetStatus.IN_PROGRESS or
+             course_reset_audit.status == CourseResetAudit.CourseResetStatus.ENQUEUED):
+            return Response(None, status=204)
+
+        if enrollment and opt_in_course and not user_passed:
+            course_reset_audit = CourseResetAudit.objects.create(
+                course=opt_in_course,
+                course_enrollment=enrollment,
+                reset_by=request.user,
+            )
+            status = f"In progress - Started on {course_reset_audit.modified} by {course_reset_audit.reset_by.username}"
+            resp = {
+                'course_id': course_id,
+                'status': status,
+                'can_reset': False,
+                'display_name': course_overview.display_name
+            }
+            # Call celery task
+
+            return Response(resp, status=201)
+        else:
+            return Response(None, status=400)

From 45648c911fb8e977356e32b9de562767ebd2ad3b Mon Sep 17 00:00:00 2001
From: hajorg <jorg2all@yahoo.com>
Date: Wed, 28 Feb 2024 16:58:52 +0100
Subject: [PATCH 2/2] fix: resolve lint issues

---
 lms/djangoapps/support/tests/test_views.py   | 15 +++++++-------
 lms/djangoapps/support/views/course_reset.py | 21 ++++++++++----------
 2 files changed, 18 insertions(+), 18 deletions(-)

diff --git a/lms/djangoapps/support/tests/test_views.py b/lms/djangoapps/support/tests/test_views.py
index 758e51fdf5f9..4bf71a16d8c6 100644
--- a/lms/djangoapps/support/tests/test_views.py
+++ b/lms/djangoapps/support/tests/test_views.py
@@ -2359,10 +2359,7 @@ def setUp(self):
             course_id=self.course.id,
             user=self.user
         )
-        CourseResetCourseOptIn.objects.create(
-            course_id=self.course_id,
-            active=True
-        )
+        self.opt_in = CourseResetCourseOptInFactory.create(course_id=self.course.id)
 
         self.other_course = CourseFactory.create(
             org='x',
@@ -2422,7 +2419,9 @@ def test_course_reset_failed(self):
         self.assertEqual(response.status_code, 200)
 
     def test_course_reset_dupe(self):
-        response = self.client.post(self._url(username=self.user.username), data={'course_id': self.course_id})
-        self.assertEqual(response.status_code, 201)
-        resp = self.client.post(self._url(username=self.user.username), data={'course_id': self.course_id})
-        self.assertEqual(resp.status_code, 204)
+        CourseResetAuditFactory.create(
+            course=self.opt_in,
+            course_enrollment=self.enrollment,
+        )
+        response2 = self.client.post(self._url(username=self.user.username), data={'course_id': self.course_id})
+        self.assertEqual(response2.status_code, 204)
diff --git a/lms/djangoapps/support/views/course_reset.py b/lms/djangoapps/support/views/course_reset.py
index 6ed705548ffe..c8d4abb2cec1 100644
--- a/lms/djangoapps/support/views/course_reset.py
+++ b/lms/djangoapps/support/views/course_reset.py
@@ -95,7 +95,6 @@ def get(self, request, username_or_email):
             })
         return Response(result)
 
-
     @method_decorator(require_support_permission)
     def post(self, request, username_or_email):
         """
@@ -126,22 +125,25 @@ def post(self, request, username_or_email):
         course_overview = enrollment.course_overview
         course_reset_audit = CourseResetAudit.objects.filter(course_enrollment=enrollment).first()
 
-        if course_reset_audit and course_reset_audit.status == CourseResetAudit.CourseResetStatus.FAILED and not user_passed:
+        if course_reset_audit and (
+            course_reset_audit.status == CourseResetAudit.CourseResetStatus.FAILED
+            and not user_passed
+        ):
             course_reset_audit.status = CourseResetAudit.CourseResetStatus.ENQUEUED
             course_reset_audit.save()
-            status = f"In progress - Started on {course_reset_audit.modified} by {course_reset_audit.reset_by.username}"
             # Call celery task
             resp = {
                 'course_id': course_id,
-                'status': status,
+                'status': course_reset_audit.status_message(),
                 'can_reset': False,
                 'display_name': course_overview.display_name
             }
             return Response(resp, status=200)
 
-        elif course_reset_audit and \
-            (course_reset_audit.status == CourseResetAudit.CourseResetStatus.IN_PROGRESS or
-             course_reset_audit.status == CourseResetAudit.CourseResetStatus.ENQUEUED):
+        elif course_reset_audit and course_reset_audit.status in (
+            CourseResetAudit.CourseResetStatus.IN_PROGRESS,
+            CourseResetAudit.CourseResetStatus.ENQUEUED
+        ):
             return Response(None, status=204)
 
         if enrollment and opt_in_course and not user_passed:
@@ -150,15 +152,14 @@ def post(self, request, username_or_email):
                 course_enrollment=enrollment,
                 reset_by=request.user,
             )
-            status = f"In progress - Started on {course_reset_audit.modified} by {course_reset_audit.reset_by.username}"
             resp = {
                 'course_id': course_id,
-                'status': status,
+                'status': course_reset_audit.status_message(),
                 'can_reset': False,
                 'display_name': course_overview.display_name
             }
-            # Call celery task
 
+            # Call celery task
             return Response(resp, status=201)
         else:
             return Response(None, status=400)