Merge pull request #18405 from edx/jeskew/PLAT_2185_idempotent_endpoints
Make retirement endpoints idempotent.
This commit is contained in:
@@ -31,12 +31,20 @@ from enrollment.views import EnrollmentUserThrottle
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.embargo.models import Country, CountryAccessRule, RestrictedCourse
|
||||
from openedx.core.djangoapps.embargo.test_utils import restrict_course
|
||||
from openedx.core.djangoapps.user_api.models import UserOrgTag
|
||||
from openedx.core.djangoapps.user_api.models import (
|
||||
RetirementState,
|
||||
UserRetirementStatus,
|
||||
UserOrgTag
|
||||
)
|
||||
from openedx.core.lib.django_test_client_utils import get_absolute_url
|
||||
from openedx.core.lib.token_utils import JwtBuilder
|
||||
from openedx.features.enterprise_support.tests import FAKE_ENTERPRISE_CUSTOMER
|
||||
from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseServiceMockMixin
|
||||
from student.models import CourseEnrollment
|
||||
from student.models import (
|
||||
CourseEnrollment,
|
||||
get_retired_username_by_username,
|
||||
get_retired_email_by_email,
|
||||
)
|
||||
from student.roles import CourseStaffRole
|
||||
from student.tests.factories import AdminFactory, UserFactory, SuperuserFactory
|
||||
from util.models import RateLimitConfiguration
|
||||
@@ -1288,6 +1296,20 @@ class UnenrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase):
|
||||
for course in self.courses:
|
||||
self.assert_enrollment_status(course_id=str(course.id), username=self.USERNAME, is_active=True)
|
||||
|
||||
def _create_test_retirement(self, user=None):
|
||||
"""
|
||||
Helper method to create a RetirementStatus with useful defaults
|
||||
"""
|
||||
pending_state = RetirementState.objects.create(
|
||||
state_name='PENDING',
|
||||
state_execution_order=1,
|
||||
is_dead_end_state=False,
|
||||
required=False
|
||||
)
|
||||
if user is None:
|
||||
user = UserFactory()
|
||||
return UserRetirementStatus.create_retirement(user)
|
||||
|
||||
def build_jwt_headers(self, user):
|
||||
"""
|
||||
Helper function for creating headers for the JWT authentication.
|
||||
@@ -1299,6 +1321,7 @@ class UnenrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase):
|
||||
|
||||
def test_deactivate_enrollments(self):
|
||||
self._assert_active()
|
||||
self._create_test_retirement(self.user)
|
||||
response = self._submit_unenroll(self.superuser, self.user.username)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
data = json.loads(response.content)
|
||||
@@ -1306,6 +1329,11 @@ class UnenrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase):
|
||||
self.assertEqual(set(data), self.orgs)
|
||||
self._assert_inactive()
|
||||
|
||||
def test_deactivate_enrollments_no_retirement_status(self):
|
||||
self._assert_active()
|
||||
response = self._submit_unenroll(self.superuser, self.user.username)
|
||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def test_deactivate_enrollments_unauthorized(self):
|
||||
self._assert_active()
|
||||
response = self._submit_unenroll(self.user, self.user.username)
|
||||
@@ -1322,26 +1350,25 @@ class UnenrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase):
|
||||
|
||||
def test_deactivate_enrollments_empty_username(self):
|
||||
self._assert_active()
|
||||
self._create_test_retirement(self.user)
|
||||
response = self._submit_unenroll(self.superuser, "")
|
||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
data = json.loads(response.content)
|
||||
self.assertEqual(data, u'The user "" does not exist.')
|
||||
self._assert_active()
|
||||
|
||||
def test_deactivate_enrollments_invalid_username(self):
|
||||
self._assert_active()
|
||||
self._create_test_retirement(self.user)
|
||||
response = self._submit_unenroll(self.superuser, "a made up username")
|
||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
data = json.loads(response.content)
|
||||
self.assertEqual(data, u'The user "a made up username" does not exist.')
|
||||
self._assert_active()
|
||||
|
||||
def test_deactivate_enrollments_called_twice(self):
|
||||
self._assert_active()
|
||||
self._create_test_retirement(self.user)
|
||||
response = self._submit_unenroll(self.superuser, self.user.username)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
response = self._submit_unenroll(self.superuser, self.user.username)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||
self.assertEqual(response.content, "")
|
||||
self._assert_inactive()
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ from openedx.core.djangoapps.cors_csrf.authentication import SessionAuthenticati
|
||||
from openedx.core.djangoapps.cors_csrf.decorators import ensure_csrf_cookie_cross_domain
|
||||
from openedx.core.djangoapps.embargo import api as embargo_api
|
||||
from openedx.core.djangoapps.user_api.accounts.permissions import CanRetireUser
|
||||
from openedx.core.djangoapps.user_api.models import UserRetirementStatus
|
||||
from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in
|
||||
from openedx.core.lib.api.authentication import (
|
||||
OAuth2AuthenticationAllowInactiveUser,
|
||||
@@ -316,21 +317,23 @@ class UnenrollmentView(APIView):
|
||||
"username": "username12345"
|
||||
}
|
||||
|
||||
**POST Parameters**
|
||||
**POST Parameters**
|
||||
|
||||
A POST request must include the following parameter.
|
||||
A POST request must include the following parameter.
|
||||
|
||||
* username: The username of the user being unenrolled.
|
||||
This will never match the username from the request,
|
||||
since the request is issued as a privileged service user.
|
||||
* username: The username of the user being unenrolled.
|
||||
This will never match the username from the request,
|
||||
since the request is issued as a privileged service user.
|
||||
|
||||
**POST Response Values**
|
||||
|
||||
If the user does not exist, or the user is already unenrolled
|
||||
from all courses, the request returns an HTTP 404 "Does Not Exist"
|
||||
response.
|
||||
If the user has not requested retirement and does not have a retirement
|
||||
request status, the request returns an HTTP 404 "Does Not Exist" response.
|
||||
|
||||
If an unexpected error occurs, the request returns an HTTP 500 response.
|
||||
If the user is already unenrolled from all courses, the request returns
|
||||
an HTTP 204 "No Content" response.
|
||||
|
||||
If an unexpected error occurs, the request returns an HTTP 500 response.
|
||||
|
||||
If the request is successful, an HTTP 200 "OK" response is
|
||||
returned along with a list of all courses from which the user was unenrolled.
|
||||
@@ -342,23 +345,20 @@ class UnenrollmentView(APIView):
|
||||
"""
|
||||
Unenrolls the specified user from all courses.
|
||||
"""
|
||||
username = None
|
||||
user_model = get_user_model()
|
||||
|
||||
try:
|
||||
# Get the username from the request and check that it exists
|
||||
# Get the username from the request.
|
||||
username = request.data['username']
|
||||
user_model.objects.get(username=username)
|
||||
|
||||
# Ensure that a retirement request status row exists for this username.
|
||||
UserRetirementStatus.get_retirement_for_retirement_action(username)
|
||||
enrollments = api.get_enrollments(username)
|
||||
active_enrollments = [enrollment for enrollment in enrollments if enrollment['is_active']]
|
||||
if len(active_enrollments) < 1:
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
return Response(api.unenroll_user_from_all_courses(username))
|
||||
except KeyError:
|
||||
return Response(u'Username not specified.', status=status.HTTP_404_NOT_FOUND)
|
||||
except user_model.DoesNotExist:
|
||||
return Response(u'The user "{}" does not exist.'.format(username), status=status.HTTP_404_NOT_FOUND)
|
||||
except UserRetirementStatus.DoesNotExist:
|
||||
return Response(u'No retirement request status for username.', status=status.HTTP_404_NOT_FOUND)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user