1010import ddt
1111from django .contrib .auth .models import User # lint-amnesty, pylint: disable=imported-auth-user
1212from django .contrib .sites .models import Site
13- from django .core import mail
1413from django .core .cache import cache
14+ from django .core import mail
15+ from django .db .models .signals import pre_delete
1516from django .test import TestCase
1617from django .urls import reverse
1718from opaque_keys .edx .keys import CourseKey
@@ -1365,10 +1366,8 @@ def test_retire_user_where_username_not_provided(self):
13651366
13661367 @mock .patch ('openedx.core.djangoapps.user_api.accounts.views.get_profile_image_names' )
13671368 @mock .patch ('openedx.core.djangoapps.user_api.accounts.views.remove_profile_images' )
1368- @mock .patch ('openedx.core.djangoapps.user_api.accounts.views.PendingEmailChange.redact_pending_email_by_user_value' )
13691369 def test_retire_user (
13701370 self ,
1371- mock_redact_pending_email ,
13721371 mock_remove_profile_images ,
13731372 mock_get_profile_image_names ,
13741373 ):
@@ -1402,7 +1401,6 @@ def test_retire_user(
14021401
14031402 self ._entitlement_support_detail_assertions ()
14041403
1405- mock_redact_pending_email .assert_called_once_with (self .test_user , field = "user" )
14061404 assert not PendingEmailChange .objects .filter (user = self .test_user ).exists ()
14071405 assert not UserOrgTag .objects .filter (user = self .test_user ).exists ()
14081406
@@ -1415,22 +1413,34 @@ def test_retire_user_twice_idempotent(self):
14151413 fake_completed_retirement (self .test_user )
14161414 self .post_and_assert_status (data )
14171415
1418- @mock .patch ('openedx.core.djangoapps.user_api.accounts.views.PendingEmailChange.delete_by_user_value' )
1419- def test_retire_user_redacts_pending_email_before_delete (self , mock_delete_pending_email ):
1420- pending_email_record = PendingEmailChange .objects .get (user = self .test_user )
1421- pending_email_before_retirement = pending_email_record .new_email
1422- expected_retired_pending_email = get_retired_email_by_email (pending_email_before_retirement )
1416+ def test_retire_user_redacts_pending_email_before_delete (self ):
1417+ """
1418+ Verify that delete_by_user_value redacts new_email using bulk update before deletion.
1419+ """
1420+ expected_redacted_email = '[email protected] ' 1421+ captured_state = {}
14231422
1424- def _assert_redacted_then_delete (value , field ):
1425- pending_record = PendingEmailChange .objects .get (user = self .test_user )
1426- assert pending_record .new_email == expected_retired_pending_email
1427- pending_record .delete ()
1428- return True
1423+ def capture_before_delete (sender , instance , ** kwargs ):
1424+ """Capture email value before it's deleted."""
1425+ captured_state ['new_email' ] = instance .new_email
14291426
1430- mock_delete_pending_email .side_effect = _assert_redacted_then_delete
1431- data = {'username' : self .original_username }
1432- self .post_and_assert_status (data )
1433- assert not PendingEmailChange .objects .filter (user = self .test_user ).exists ()
1427+ # Connect signal to capture pre-delete state
1428+ pre_delete .connect (capture_before_delete , sender = PendingEmailChange )
1429+ try :
1430+ # Verify the record exists with original email before retirement
1431+ assert PendingEmailChange .objects .filter (user = self .test_user ).exists ()
1432+
1433+ # Retire the user
1434+ data = {'username' : self .original_username }
1435+ self .post_and_assert_status (data )
1436+
1437+ # Verify the redaction happened before deletion
1438+ assert captured_state .get ('new_email' ) == expected_redacted_email
1439+
1440+ # Verify the record was deleted
1441+ assert not PendingEmailChange .objects .filter (user = self .test_user ).exists ()
1442+ finally :
1443+ pre_delete .disconnect (capture_before_delete , sender = PendingEmailChange )
14341444
14351445 @mock .patch ('openedx.core.djangoapps.user_api.accounts.views.USER_RETIRE_LMS_CRITICAL' )
14361446 def test_retirement_sends_critical_signal_with_retirement_data (self , mock_signal ):
0 commit comments