1313from django .contrib .sites .models import Site
1414from django .core import mail
1515from django .core .cache import cache
16+ from django .db import connection
1617from django .test import TestCase
18+ from django .test .utils import CaptureQueriesContext
1719from django .urls import reverse
1820from enterprise .models import (
1921 EnterpriseCourseEnrollment ,
@@ -1095,17 +1097,32 @@ def test_simple_success(self):
10951097 def test_redaction_before_deletion (self ):
10961098 """
10971099 Verify that redaction (UPDATE) happens before deletion (DELETE).
1098- Uses assertNumQueries to verify UPDATE queries execute before DELETE queries.
1099- This protects PII from being exposed in soft-deletes to downstream data warehouses.
1100+ Captures actual SQL queries to ensure UPDATE queries contain redacted values.
11001101 """
1101- # Use assertNumQueries to capture and verify the SQL queries execute in correct order.
1102- with self .assertNumQueries (53 ): # Full request with 9 UPDATEs (redaction) + 9 DELETEs
1102+ with CaptureQueriesContext (connection ) as context :
11031103 self .cleanup_and_assert_status ()
11041104
11051105 # Verify records are deleted after redaction
11061106 retirements = UserRetirementStatus .objects .all ()
11071107 assert retirements .count () == 0
11081108
1109+ # Verify UPDATE queries exist with default 'redacted' value
1110+ queries = context .captured_queries
1111+ update_queries = [q for q in queries if 'UPDATE' in q ['sql' ] and 'user_api_userretirementstatus' in q ['sql' ]]
1112+ delete_queries = [q for q in queries if 'DELETE' in q ['sql' ] and 'user_api_userretirementstatus' in q ['sql' ]]
1113+
1114+ # Should have 9 UPDATE and 9 DELETE queries
1115+ assert len (update_queries ) == 9 , f"Expected 9 UPDATE queries, found { len (update_queries )} "
1116+ assert len (delete_queries ) == 9 , f"Expected 9 DELETE queries, found { len (delete_queries )} "
1117+
1118+ # Verify UPDATE queries contain the redacted values
1119+ for update_query in update_queries :
1120+ sql = update_query ['sql' ]
1121+ assert "'redacted'" in sql , f"UPDATE query missing 'redacted' value: { sql } "
1122+ assert 'original_username' in sql , f"UPDATE query missing original_username field: { sql } "
1123+ assert 'original_email' in sql , f"UPDATE query missing original_email field: { sql } "
1124+ assert 'original_name' in sql , f"UPDATE query missing original_name field: { sql } "
1125+
11091126 def test_custom_redacted_values (self ):
11101127 """Test that custom redacted values are applied before deletion."""
11111128 custom_username = 'username-redacted-12345'
@@ -1118,12 +1135,26 @@ def test_custom_redacted_values(self):
11181135 'redacted_email' : custom_email ,
11191136 'redacted_name' : custom_name
11201137 }
1121- self .cleanup_and_assert_status (data = data )
11221138
1123- # Records should be deleted after redaction
1139+ with CaptureQueriesContext (connection ) as context :
1140+ self .cleanup_and_assert_status (data = data )
1141+
1142+ # Verify records are deleted after redaction
11241143 retirements = UserRetirementStatus .objects .all ()
11251144 assert retirements .count () == 0
11261145
1146+ # Verify UPDATE queries contain the custom redacted values
1147+ queries = context .captured_queries
1148+ update_queries = [q for q in queries if 'UPDATE' in q ['sql' ] and 'user_api_userretirementstatus' in q ['sql' ]]
1149+
1150+ assert len (update_queries ) == 9 , f"Expected 9 UPDATE queries, found { len (update_queries )} "
1151+
1152+ for update_query in update_queries :
1153+ sql = update_query ['sql' ]
1154+ assert custom_username in sql , f"UPDATE query missing custom username '{ custom_username } ': { sql } "
1155+ assert custom_email in sql , f"UPDATE query missing custom email '{ custom_email } ': { sql } "
1156+ assert custom_name in sql , f"UPDATE query missing custom name '{ custom_name } ': { sql } "
1157+
11271158 def test_leaves_other_users (self ):
11281159 remaining_usernames = []
11291160
0 commit comments