Mgmt command to cancel user retirement request.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
Test signal handlers for the survey app
|
||||
"""
|
||||
|
||||
from openedx.core.djangoapps.user_api.accounts.tests.retirement_helpers import fake_retirement
|
||||
from openedx.core.djangoapps.user_api.accounts.tests.retirement_helpers import fake_completed_retirement
|
||||
from student.tests.factories import UserFactory
|
||||
from survey.models import SurveyAnswer
|
||||
from survey.tests.factories import SurveyAnswerFactory
|
||||
@@ -43,7 +43,7 @@ class SurveyRetireSignalTests(ModuleStoreTestCase):
|
||||
|
||||
# Run twice to make sure no errors are raised
|
||||
_listen_for_lms_retire(sender=self.__class__, user=answer.user)
|
||||
fake_retirement(answer.user)
|
||||
fake_completed_retirement(answer.user)
|
||||
_listen_for_lms_retire(sender=self.__class__, user=answer.user)
|
||||
|
||||
# All values for this user should still be here and just be an empty string
|
||||
|
||||
@@ -9,7 +9,7 @@ from pytz import UTC
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline
|
||||
from lms.djangoapps.verify_student.signals import _listen_for_course_publish, _listen_for_lms_retire
|
||||
from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory
|
||||
from openedx.core.djangoapps.user_api.accounts.tests.retirement_helpers import fake_retirement
|
||||
from openedx.core.djangoapps.user_api.accounts.tests.retirement_helpers import fake_completed_retirement
|
||||
from student.tests.factories import UserFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
@@ -95,7 +95,7 @@ class RetirementSignalTest(ModuleStoreTestCase):
|
||||
|
||||
# Run this twice to make sure there are no errors raised 2nd time through
|
||||
_listen_for_lms_retire(sender=self.__class__, user=verification.user)
|
||||
fake_retirement(verification.user)
|
||||
fake_completed_retirement(verification.user)
|
||||
_listen_for_lms_retire(sender=self.__class__, user=verification.user)
|
||||
|
||||
ver_obj = SoftwareSecurePhotoVerification.objects.get(user=verification.user)
|
||||
|
||||
@@ -15,7 +15,10 @@ from openedx.core.djangoapps.credit.models import (
|
||||
CreditRequirement,
|
||||
CreditRequirementStatus
|
||||
)
|
||||
from openedx.core.djangoapps.user_api.accounts.tests.retirement_helpers import RetirementTestCase
|
||||
from openedx.core.djangoapps.user_api.accounts.tests.retirement_helpers import ( # pylint: disable=unused-import
|
||||
RetirementTestCase,
|
||||
setup_retirement_states
|
||||
)
|
||||
from openedx.core.djangoapps.user_api.models import UserRetirementStatus
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
@@ -97,7 +100,7 @@ class CreditEligibilityModelTests(TestCase):
|
||||
self.assertEqual(len(requirements), 1)
|
||||
|
||||
|
||||
class CreditRequirementStatusTests(TestCase):
|
||||
class CreditRequirementStatusTests(RetirementTestCase):
|
||||
"""
|
||||
Tests for credit requirement status models.
|
||||
"""
|
||||
@@ -105,7 +108,6 @@ class CreditRequirementStatusTests(TestCase):
|
||||
def setUp(self):
|
||||
super(CreditRequirementStatusTests, self).setUp()
|
||||
self.course_key = CourseKey.from_string("edX/DemoX/Demo_Course")
|
||||
RetirementTestCase.setup_states()
|
||||
self.old_username = "username"
|
||||
self.user = UserFactory(username=self.old_username)
|
||||
self.retirement = UserRetirementStatus.create_retirement(self.user)
|
||||
@@ -170,14 +172,13 @@ class CreditRequirementStatusTests(TestCase):
|
||||
self.assertFalse(retirement_succeeded)
|
||||
|
||||
|
||||
class CreditRequestTest(TestCase):
|
||||
class CreditRequestTest(RetirementTestCase):
|
||||
"""
|
||||
The CreditRequest model's test suite.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(CreditRequestTest, self).setUp()
|
||||
RetirementTestCase.setup_states()
|
||||
self.user = UserFactory.create()
|
||||
self.retirement = UserRetirementStatus.create_retirement(self.user)
|
||||
self.credit_course = CreditCourse.objects.create()
|
||||
|
||||
@@ -3,6 +3,7 @@ Helpers for testing retirement functionality
|
||||
"""
|
||||
import datetime
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
from django.test import TestCase
|
||||
from social_django.models import UserSocialAuth
|
||||
@@ -21,75 +22,92 @@ from student.tests.factories import UserFactory
|
||||
from ..views import AccountRetirementView
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def setup_retirement_states(scope="module"): # pylint: disable=unused-argument
|
||||
"""
|
||||
Create basic states that mimic the retirement process.
|
||||
"""
|
||||
default_states = [
|
||||
('PENDING', 1, False, True),
|
||||
('LOCKING_ACCOUNT', 20, False, False),
|
||||
('LOCKING_COMPLETE', 30, False, False),
|
||||
('RETIRING_CREDENTIALS', 40, False, False),
|
||||
('CREDENTIALS_COMPLETE', 50, False, False),
|
||||
('RETIRING_ECOM', 60, False, False),
|
||||
('ECOM_COMPLETE', 70, False, False),
|
||||
('RETIRING_FORUMS', 80, False, False),
|
||||
('FORUMS_COMPLETE', 90, False, False),
|
||||
('RETIRING_EMAIL_LISTS', 100, False, False),
|
||||
('EMAIL_LISTS_COMPLETE', 110, False, False),
|
||||
('RETIRING_ENROLLMENTS', 120, False, False),
|
||||
('ENROLLMENTS_COMPLETE', 130, False, False),
|
||||
('RETIRING_NOTES', 140, False, False),
|
||||
('NOTES_COMPLETE', 150, False, False),
|
||||
('RETIRING_LMS', 160, False, False),
|
||||
('LMS_COMPLETE', 170, False, False),
|
||||
('ADDING_TO_PARTNER_QUEUE', 180, False, False),
|
||||
('PARTNER_QUEUE_COMPLETE', 190, False, False),
|
||||
('ERRORED', 200, True, True),
|
||||
('ABORTED', 210, True, True),
|
||||
('COMPLETE', 220, True, True),
|
||||
]
|
||||
|
||||
for name, ex, dead, req in default_states:
|
||||
RetirementState.objects.create(
|
||||
state_name=name,
|
||||
state_execution_order=ex,
|
||||
is_dead_end_state=dead,
|
||||
required=req
|
||||
)
|
||||
|
||||
yield
|
||||
|
||||
RetirementState.objects.all().delete()
|
||||
|
||||
|
||||
def create_retirement_status(state=None, create_datetime=None):
|
||||
"""
|
||||
Helper method to create a RetirementStatus with useful defaults.
|
||||
Assumes that retirement states have been setup before calling.
|
||||
"""
|
||||
if create_datetime is None:
|
||||
create_datetime = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=8)
|
||||
|
||||
user = UserFactory()
|
||||
retirement = UserRetirementStatus.create_retirement(user)
|
||||
if state:
|
||||
retirement.current_state = state
|
||||
retirement.last_state = state
|
||||
retirement.created = create_datetime
|
||||
retirement.modified = create_datetime
|
||||
retirement.save()
|
||||
return retirement
|
||||
|
||||
|
||||
def _fake_logged_out_user(user):
|
||||
# Simulate the initial logout retirement endpoint.
|
||||
user.username = get_retired_username_by_username(user.username)
|
||||
user.email = get_retired_email_by_email(user.email)
|
||||
user.set_unusable_password()
|
||||
user.save()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def logged_out_retirement_request():
|
||||
"""
|
||||
Returns a UserRetirementStatus test fixture object that has been logged out and email-changed,
|
||||
which is the first step which happens to a user being added to the retirement queue.
|
||||
"""
|
||||
retirement = create_retirement_status()
|
||||
_fake_logged_out_user(retirement.user)
|
||||
return retirement
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("setup_retirement_states")
|
||||
class RetirementTestCase(TestCase):
|
||||
"""
|
||||
Test case with a helper methods for retirement
|
||||
"""
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(RetirementTestCase, cls).setUpClass()
|
||||
cls.setup_states()
|
||||
|
||||
@staticmethod
|
||||
def setup_states():
|
||||
"""
|
||||
Create basic states that mimic our current understanding of the retirement process
|
||||
"""
|
||||
default_states = [
|
||||
('PENDING', 1, False, True),
|
||||
('LOCKING_ACCOUNT', 20, False, False),
|
||||
('LOCKING_COMPLETE', 30, False, False),
|
||||
('RETIRING_CREDENTIALS', 40, False, False),
|
||||
('CREDENTIALS_COMPLETE', 50, False, False),
|
||||
('RETIRING_ECOM', 60, False, False),
|
||||
('ECOM_COMPLETE', 70, False, False),
|
||||
('RETIRING_FORUMS', 80, False, False),
|
||||
('FORUMS_COMPLETE', 90, False, False),
|
||||
('RETIRING_EMAIL_LISTS', 100, False, False),
|
||||
('EMAIL_LISTS_COMPLETE', 110, False, False),
|
||||
('RETIRING_ENROLLMENTS', 120, False, False),
|
||||
('ENROLLMENTS_COMPLETE', 130, False, False),
|
||||
('RETIRING_NOTES', 140, False, False),
|
||||
('NOTES_COMPLETE', 150, False, False),
|
||||
('RETIRING_LMS', 160, False, False),
|
||||
('LMS_COMPLETE', 170, False, False),
|
||||
('ADDING_TO_PARTNER_QUEUE', 180, False, False),
|
||||
('PARTNER_QUEUE_COMPLETE', 190, False, False),
|
||||
('ERRORED', 200, True, True),
|
||||
('ABORTED', 210, True, True),
|
||||
('COMPLETE', 220, True, True),
|
||||
]
|
||||
|
||||
for name, ex, dead, req in default_states:
|
||||
RetirementState.objects.create(
|
||||
state_name=name,
|
||||
state_execution_order=ex,
|
||||
is_dead_end_state=dead,
|
||||
required=req
|
||||
)
|
||||
|
||||
def _create_retirement(self, state, create_datetime=None):
|
||||
"""
|
||||
Helper method to create a RetirementStatus with useful defaults
|
||||
"""
|
||||
if create_datetime is None:
|
||||
create_datetime = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=8)
|
||||
|
||||
user = UserFactory()
|
||||
return UserRetirementStatus.objects.create(
|
||||
user=user,
|
||||
original_username=user.username,
|
||||
original_email=user.email,
|
||||
original_name=user.profile.name,
|
||||
retired_username=get_retired_username_by_username(user.username),
|
||||
retired_email=get_retired_email_by_email(user.email),
|
||||
current_state=state,
|
||||
last_state=state,
|
||||
responses="",
|
||||
created=create_datetime,
|
||||
modified=create_datetime
|
||||
)
|
||||
|
||||
def _retirement_to_dict(self, retirement, all_fields=False):
|
||||
"""
|
||||
Return a dict format of this model to a consistent format for serialization, removing the long text field
|
||||
@@ -131,7 +149,7 @@ class RetirementTestCase(TestCase):
|
||||
return retirement_dict
|
||||
|
||||
def _create_users_all_states(self):
|
||||
return [self._create_retirement(state) for state in RetirementState.objects.all()]
|
||||
return [create_retirement_status(state) for state in RetirementState.objects.all()]
|
||||
|
||||
def _get_non_dead_end_states(self):
|
||||
return [state for state in RetirementState.objects.filter(is_dead_end_state=False)]
|
||||
@@ -140,7 +158,7 @@ class RetirementTestCase(TestCase):
|
||||
return [state for state in RetirementState.objects.filter(is_dead_end_state=True)]
|
||||
|
||||
|
||||
def fake_retirement(user):
|
||||
def fake_completed_retirement(user):
|
||||
"""
|
||||
Makes an attempt to put user for the given user into a "COMPLETED"
|
||||
retirement state by faking important parts of retirement.
|
||||
@@ -151,12 +169,10 @@ def fake_retirement(user):
|
||||
"""
|
||||
# Deactivate / logout and hash username & email
|
||||
UserSocialAuth.objects.filter(user_id=user.id).delete()
|
||||
_fake_logged_out_user(user)
|
||||
user.first_name = ''
|
||||
user.last_name = ''
|
||||
user.is_active = False
|
||||
user.username = get_retired_username_by_username(user.username)
|
||||
user.email = get_retired_email_by_email(user.email)
|
||||
user.set_unusable_password()
|
||||
user.save()
|
||||
|
||||
# Clear profile
|
||||
|
||||
@@ -11,38 +11,13 @@ from openedx.core.djangoapps.user_api.models import (
|
||||
)
|
||||
from student.models import get_retired_email_by_email, get_retired_username_by_username
|
||||
from student.tests.factories import UserFactory
|
||||
from .retirement_helpers import setup_retirement_states # pylint: disable=unused-import
|
||||
|
||||
|
||||
# Tell pytest it's ok to use the database
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def setup_retirement_states():
|
||||
"""
|
||||
Pytest fixture to create some basic states for testing. Duplicates functionality of the
|
||||
Django test runner in test_views.py unfortunately, but they're not compatible.
|
||||
"""
|
||||
default_states = [
|
||||
('PENDING', 1, False, True),
|
||||
('LOCKING_ACCOUNT', 20, False, False),
|
||||
('LOCKING_COMPLETE', 30, False, False),
|
||||
('RETIRING_LMS', 40, False, False),
|
||||
('LMS_COMPLETE', 50, False, False),
|
||||
('ERRORED', 60, True, True),
|
||||
('ABORTED', 70, True, True),
|
||||
('COMPLETE', 80, True, True),
|
||||
]
|
||||
|
||||
for name, ex, dead, req in default_states:
|
||||
RetirementState.objects.create(
|
||||
state_name=name,
|
||||
state_execution_order=ex,
|
||||
is_dead_end_state=dead,
|
||||
required=req
|
||||
)
|
||||
|
||||
|
||||
def _assert_retirementstatus_is_user(retirement, user):
|
||||
"""
|
||||
Helper function to compare a newly created UserRetirementStatus object to expected values for
|
||||
|
||||
@@ -78,7 +78,12 @@ from student.tests.factories import (
|
||||
|
||||
from ..views import AccountRetirementView, USER_PROFILE_PII
|
||||
from ...tests.factories import UserOrgTagFactory
|
||||
from .retirement_helpers import RetirementTestCase, fake_retirement
|
||||
from .retirement_helpers import ( # pylint: disable=unused-import
|
||||
RetirementTestCase,
|
||||
fake_completed_retirement,
|
||||
create_retirement_status,
|
||||
setup_retirement_states
|
||||
)
|
||||
|
||||
|
||||
def build_jwt_headers(user):
|
||||
@@ -255,8 +260,7 @@ class TestAccountRetireMailings(RetirementTestCase):
|
||||
|
||||
# Should be created in parent setUpClass
|
||||
retiring_email_lists = RetirementState.objects.get(state_name='RETIRING_EMAIL_LISTS')
|
||||
|
||||
self.retirement = self._create_retirement(retiring_email_lists)
|
||||
self.retirement = create_retirement_status(retiring_email_lists)
|
||||
self.test_user = self.retirement.user
|
||||
|
||||
self.url = reverse('accounts_retire_mailings')
|
||||
@@ -456,7 +460,7 @@ class TestPartnerReportingPut(RetirementTestCase, ModuleStoreTestCase):
|
||||
Checks the simple success case of creating a user, enrolling in a course, and doing the partner
|
||||
report PUT. User should then have the appropriate row in UserRetirementPartnerReportingStatus
|
||||
"""
|
||||
retirement = self._create_retirement(self.partner_queue_state)
|
||||
retirement = create_retirement_status(self.partner_queue_state)
|
||||
for course in self.courses:
|
||||
CourseEnrollment.enroll(user=retirement.user, course_key=course.id)
|
||||
|
||||
@@ -467,7 +471,7 @@ class TestPartnerReportingPut(RetirementTestCase, ModuleStoreTestCase):
|
||||
"""
|
||||
Runs the success test twice to make sure that re-running the step still succeeds.
|
||||
"""
|
||||
retirement = self._create_retirement(self.partner_queue_state)
|
||||
retirement = create_retirement_status(self.partner_queue_state)
|
||||
for course in self.courses:
|
||||
CourseEnrollment.enroll(user=retirement.user, course_key=course.id)
|
||||
|
||||
@@ -475,7 +479,7 @@ class TestPartnerReportingPut(RetirementTestCase, ModuleStoreTestCase):
|
||||
self.put_and_assert_status({'username': retirement.original_username})
|
||||
|
||||
# Do our basic other retirement step fakery
|
||||
fake_retirement(retirement.user)
|
||||
fake_completed_retirement(retirement.user)
|
||||
|
||||
# Try running our step again
|
||||
self.put_and_assert_status({'username': retirement.original_username})
|
||||
@@ -500,7 +504,7 @@ class TestPartnerReportingPut(RetirementTestCase, ModuleStoreTestCase):
|
||||
the enrollment.course.org. We now just use the enrollment.course_id.org
|
||||
since for this purpose we don't care if the course exists.
|
||||
"""
|
||||
retirement = self._create_retirement(self.partner_queue_state)
|
||||
retirement = create_retirement_status(self.partner_queue_state)
|
||||
user = retirement.user
|
||||
enrollment = CourseEnrollment.enroll(user=user, course_key=CourseKey.from_string('edX/Test201/2018_Fall'))
|
||||
|
||||
@@ -717,7 +721,7 @@ class TestAccountRetirementList(RetirementTestCase):
|
||||
Verify that users in dead end states are not returned
|
||||
"""
|
||||
for state in self._get_dead_end_states():
|
||||
self._create_retirement(state)
|
||||
create_retirement_status(state)
|
||||
self.assert_status_and_user_list([], states_to_request=self._get_non_dead_end_states())
|
||||
|
||||
def test_users_retrieved_in_multiple_states(self):
|
||||
@@ -726,7 +730,7 @@ class TestAccountRetirementList(RetirementTestCase):
|
||||
"""
|
||||
multiple_states = ['PENDING', 'FORUMS_COMPLETE']
|
||||
for state in multiple_states:
|
||||
self._create_retirement(RetirementState.objects.get(state_name=state))
|
||||
create_retirement_status(RetirementState.objects.get(state_name=state))
|
||||
data = {'cool_off_days': 0, 'states': multiple_states}
|
||||
response = self.client.get(self.url, data, **self.headers)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
@@ -763,7 +767,7 @@ class TestAccountRetirementList(RetirementTestCase):
|
||||
pending_state = RetirementState.objects.get(state_name='PENDING')
|
||||
for days_back in range(1, days_back_to_test, -1):
|
||||
create_datetime = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=days_back)
|
||||
retirements.append(self._create_retirement(state=pending_state, create_datetime=create_datetime))
|
||||
retirements.append(create_retirement_status(state=pending_state, create_datetime=create_datetime))
|
||||
|
||||
# Confirm we get the correct number and data back for each day we add to cool off days
|
||||
# For each day we add to `cool_off_days` we expect to get one fewer retirement.
|
||||
@@ -886,7 +890,7 @@ class TestAccountRetirementsByStatusAndDate(RetirementTestCase):
|
||||
Verify that users in non-requested states are not returned
|
||||
"""
|
||||
state = RetirementState.objects.get(state_name='PENDING')
|
||||
self._create_retirement(state=state)
|
||||
create_retirement_status(state=state)
|
||||
self.assert_status_and_user_list([])
|
||||
|
||||
def test_users_exist(self):
|
||||
@@ -917,7 +921,7 @@ class TestAccountRetirementsByStatusAndDate(RetirementTestCase):
|
||||
# Create retirements for the last 10 days
|
||||
for days_back in range(0, 10):
|
||||
create_datetime = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=days_back)
|
||||
ret = self._create_retirement(state=complete_state, create_datetime=create_datetime)
|
||||
ret = create_retirement_status(state=complete_state, create_datetime=create_datetime)
|
||||
retirements.append(self._retirement_to_dict(ret))
|
||||
|
||||
# Go back in time adding days to the query, assert the correct retirements are present
|
||||
@@ -1010,7 +1014,7 @@ class TestAccountRetirementRetrieve(RetirementTestCase):
|
||||
retirements = []
|
||||
|
||||
for state in RetirementState.objects.all():
|
||||
retirements.append(self._create_retirement(state))
|
||||
retirements.append(create_retirement_status(state))
|
||||
|
||||
for retirement in retirements:
|
||||
values = self._retirement_to_dict(retirement)
|
||||
@@ -1021,7 +1025,7 @@ class TestAccountRetirementRetrieve(RetirementTestCase):
|
||||
Simulate retrieving a retirement by the old username, after the name has been changed to the hashed one
|
||||
"""
|
||||
pending_state = RetirementState.objects.get(state_name='PENDING')
|
||||
retirement = self._create_retirement(pending_state)
|
||||
retirement = create_retirement_status(pending_state)
|
||||
original_username = retirement.user.username
|
||||
|
||||
hashed_username = get_retired_username_by_username(original_username)
|
||||
@@ -1044,7 +1048,7 @@ class TestAccountRetirementUpdate(RetirementTestCase):
|
||||
self.pending_state = RetirementState.objects.get(state_name='PENDING')
|
||||
self.locking_state = RetirementState.objects.get(state_name='LOCKING_ACCOUNT')
|
||||
|
||||
self.retirement = self._create_retirement(self.pending_state)
|
||||
self.retirement = create_retirement_status(self.pending_state)
|
||||
self.test_user = self.retirement.user
|
||||
self.test_superuser = SuperuserFactory()
|
||||
self.headers = build_jwt_headers(self.test_superuser)
|
||||
@@ -1354,7 +1358,7 @@ class TestAccountRetirementPost(RetirementTestCase):
|
||||
def test_retire_user_twice_idempotent(self):
|
||||
data = {'username': self.original_username}
|
||||
self.post_and_assert_status(data)
|
||||
fake_retirement(self.test_user)
|
||||
fake_completed_retirement(self.test_user)
|
||||
self.post_and_assert_status(data)
|
||||
|
||||
def test_deletes_pii_from_user_profile(self):
|
||||
@@ -1574,5 +1578,5 @@ class TestLMSAccountRetirementPost(RetirementTestCase, ModuleStoreTestCase):
|
||||
# check that a second call to the retire_misc endpoint will work
|
||||
data = {'username': self.original_username}
|
||||
self.post_and_assert_status(data)
|
||||
fake_retirement(self.test_user)
|
||||
fake_completed_retirement(self.test_user)
|
||||
self.post_and_assert_status(data)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
Use this mgmt command when a user requests retirement mistakenly, then requests
|
||||
for the retirement request to be cancelled. The command can't cancel a retirement
|
||||
that has already commenced - only pending retirements.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from openedx.core.djangoapps.user_api.models import UserRetirementStatus
|
||||
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""
|
||||
Implementation of the cancel_user_retirement_request command.
|
||||
"""
|
||||
help = 'Cancels the retirement of a user who has requested retirement - but has not yet been retired.'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('email_address',
|
||||
help='Email address of user whose retirement request will be cancelled.')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""
|
||||
Execute the command.
|
||||
"""
|
||||
email_address = options['email_address'].lower()
|
||||
|
||||
try:
|
||||
# Load the user retirement status.
|
||||
retirement_status = UserRetirementStatus.objects.select_related('current_state').select_related('user').get(
|
||||
original_email=email_address
|
||||
)
|
||||
except UserRetirementStatus.DoesNotExist:
|
||||
raise CommandError("No retirement request with email address '{}' exists.".format(email_address))
|
||||
|
||||
# Check if the user has started the retirement process -or- not.
|
||||
if retirement_status.current_state.state_name != 'PENDING':
|
||||
raise CommandError(
|
||||
"Retirement requests can only be cancelled for users in the PENDING state."
|
||||
" Current request state for '{}': {}".format(
|
||||
email_address,
|
||||
retirement_status.current_state.state_name
|
||||
)
|
||||
)
|
||||
|
||||
# Load the user record using the retired email address -and- change the email address back.
|
||||
retirement_status.user.email = email_address
|
||||
retirement_status.user.save()
|
||||
|
||||
# Delete the user retirement status record.
|
||||
# No need to delete the accompanying "permanent" retirement request record - it gets done via Django signal.
|
||||
retirement_status.delete()
|
||||
|
||||
print("Successfully cancelled retirement request for user with email address '{}'.")
|
||||
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Test the cancel_user_retirement_request management command
|
||||
"""
|
||||
import pytest
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management import CommandError, call_command
|
||||
|
||||
from openedx.core.djangoapps.user_api.accounts.tests.retirement_helpers import ( # pylint: disable=unused-import
|
||||
logged_out_retirement_request,
|
||||
setup_retirement_states
|
||||
)
|
||||
from openedx.core.djangoapps.user_api.models import RetirementState, UserRetirementRequest, UserRetirementStatus
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_successful_cancellation(setup_retirement_states, logged_out_retirement_request): # pylint: disable=redefined-outer-name, unused-argument
|
||||
"""
|
||||
Test a successfully cancelled retirement request.
|
||||
"""
|
||||
call_command('cancel_user_retirement_request', logged_out_retirement_request.original_email)
|
||||
# Confirm that no retirement status exists for the user.
|
||||
with pytest.raises(UserRetirementStatus.DoesNotExist):
|
||||
UserRetirementStatus.objects.get(original_email=logged_out_retirement_request.user.email)
|
||||
# Confirm that no retirement request exists for the user.
|
||||
with pytest.raises(UserRetirementRequest.DoesNotExist):
|
||||
UserRetirementRequest.objects.get(user=logged_out_retirement_request.user)
|
||||
# Ensure user can be retrieved using the original email address.
|
||||
User.objects.get(email=logged_out_retirement_request.original_email)
|
||||
|
||||
|
||||
def test_cancellation_in_unrecoverable_state(setup_retirement_states, logged_out_retirement_request): # pylint: disable=redefined-outer-name, unused-argument
|
||||
"""
|
||||
Test a failed cancellation of a retirement request due to the retirement already beginning.
|
||||
"""
|
||||
retiring_lms_state = RetirementState.objects.get(state_name='RETIRING_LMS')
|
||||
logged_out_retirement_request.current_state = retiring_lms_state
|
||||
logged_out_retirement_request.save()
|
||||
with pytest.raises(CommandError, match=r'Retirement requests can only be cancelled for users in the PENDING state'):
|
||||
call_command('cancel_user_retirement_request', logged_out_retirement_request.original_email)
|
||||
|
||||
|
||||
def test_cancellation_unknown_email_address(setup_retirement_states, logged_out_retirement_request): # pylint: disable=redefined-outer-name, unused-argument
|
||||
"""
|
||||
Test attempting to cancel a non-existent request of a user.
|
||||
"""
|
||||
user = UserFactory()
|
||||
with pytest.raises(CommandError, match=r'No retirement request with email address'):
|
||||
call_command('cancel_user_retirement_request', user.email)
|
||||
@@ -41,6 +41,7 @@ from ..accounts import (
|
||||
NAME_MAX_LENGTH, EMAIL_MIN_LENGTH, EMAIL_MAX_LENGTH,
|
||||
USERNAME_MIN_LENGTH, USERNAME_MAX_LENGTH, USERNAME_BAD_LENGTH_MSG
|
||||
)
|
||||
from ..accounts.tests.retirement_helpers import setup_retirement_states # pylint: disable=unused-import
|
||||
from ..accounts.api import get_account_settings
|
||||
from ..models import UserOrgTag
|
||||
from ..tests.factories import UserPreferenceFactory
|
||||
|
||||
Reference in New Issue
Block a user