1010from sqlalchemy .exc import IntegrityError
1111
1212from models .database .user_mapping import UserMapping
13- from utils .user_anonymization import (
14- get_anonymous_user_id ,
15- get_user_count ,
16- find_anonymous_user_id ,
17- _hash_user_id ,
18- )
13+
14+ # Functions under test are accessed via a module fixture (see `ua` below)
1915
2016
2117# Set up test environment variable for each test
@@ -31,29 +27,40 @@ def setup_test_pepper():
3127 yield
3228
3329
34- class TestUserAnonymization :
30+ @pytest .fixture
31+ def user_anon_module ():
32+ """Return a fresh reference to the reloaded utils.user_anonymization module."""
33+ import utils .user_anonymization as _ua # pylint: disable=import-outside-toplevel
34+
35+ importlib .reload (_ua )
36+ return _ua
37+
38+
39+ class TestUserAnonymization : # pylint: disable=redefined-outer-name,protected-access
3540 """Test user anonymization functionality."""
3641
37- def test_hash_user_id_consistency (self ):
42+ def test_hash_user_id_consistency (self , user_anon_module ):
3843 """Test that user ID hashing is consistent."""
394440- hash1 = _hash_user_id (user_id )
41- hash2 = _hash_user_id (user_id )
45+ hash1 = user_anon_module . _hash_user_id (user_id )
46+ hash2 = user_anon_module . _hash_user_id (user_id )
4247
4348 assert hash1 == hash2
4449 assert len (hash1 ) == 64 # SHA-256 hex length
4550 assert hash1 != user_id # Should be different from original
4651
47- def test_hash_user_id_different_for_different_users (self ):
52+ def test_hash_user_id_different_for_different_users (self , user_anon_module ):
4853 """Test that different user IDs produce different hashes."""
49- user1_hash = _hash_user_id (
"[email protected] " )
50- user2_hash = _hash_user_id (
"[email protected] " )
54+ user1_hash = user_anon_module . _hash_user_id (
"[email protected] " )
55+ user2_hash = user_anon_module . _hash_user_id (
"[email protected] " )
5156
5257 assert user1_hash != user2_hash
5358
5459 @patch ("utils.user_anonymization.get_session" )
5560 @patch ("utils.user_anonymization.get_suid" )
56- def test_get_anonymous_user_id_new_user (self , mock_get_suid , mock_get_session ):
61+ def test_get_anonymous_user_id_new_user (
62+ self , mock_get_suid , mock_get_session , user_anon_module
63+ ):
5764 """Test creating new anonymous ID for first-time user."""
5865 # Setup mocks
5966 mock_session = MagicMock ()
@@ -62,7 +69,7 @@ def test_get_anonymous_user_id_new_user(self, mock_get_suid, mock_get_session):
6269 mock_get_suid .return_value = "anon-123-456"
6370
647165- result = get_anonymous_user_id (user_id )
72+ result = user_anon_module . get_anonymous_user_id (user_id )
6673
6774 # Verify result
6875 assert result == "anon-123-456"
@@ -75,23 +82,27 @@ def test_get_anonymous_user_id_new_user(self, mock_get_suid, mock_get_session):
7582 added_mapping = mock_session .add .call_args [0 ][0 ]
7683 assert isinstance (added_mapping , UserMapping )
7784 assert added_mapping .anonymous_id == "anon-123-456"
78- assert added_mapping .user_id_hash == _hash_user_id (user_id )
85+ assert added_mapping .user_id_hash == user_anon_module . _hash_user_id (user_id )
7986
8087 @patch ("utils.user_anonymization.get_session" )
81- def test_get_anonymous_user_id_existing_user (self , mock_get_session ):
88+ def test_get_anonymous_user_id_existing_user (
89+ self , mock_get_session , user_anon_module
90+ ):
8291 """Test retrieving existing anonymous ID for returning user."""
8392 # Setup existing mapping
8493 existing_mapping = UserMapping ()
8594 existing_mapping .anonymous_id = "existing-anon-789"
86- existing_mapping .
user_id_hash = _hash_user_id (
"[email protected] " )
95+ existing_mapping .user_id_hash = user_anon_module ._hash_user_id (
96+ 97+ )
8798
8899 mock_session = MagicMock ()
89100 mock_get_session .return_value .__enter__ .return_value = mock_session
90101 mock_session .query .return_value .filter_by .return_value .first .return_value = (
91102 existing_mapping
92103 )
93104
94- result = get_anonymous_user_id (
"[email protected] " )
105+ result = user_anon_module . get_anonymous_user_id (
"[email protected] " )
95106
96107 # Verify result
97108 assert result == "existing-anon-789"
@@ -103,7 +114,7 @@ def test_get_anonymous_user_id_existing_user(self, mock_get_session):
103114 @patch ("utils.user_anonymization.get_session" )
104115 @patch ("utils.user_anonymization.get_suid" )
105116 def test_get_anonymous_user_id_race_condition (
106- self , mock_get_suid , mock_get_session
117+ self , mock_get_suid , mock_get_session , user_anon_module
107118 ):
108119 """Test handling race condition when creating user mapping."""
109120 # Setup mocks for race condition scenario
@@ -123,7 +134,7 @@ def test_get_anonymous_user_id_race_condition(
123134 mock_session .add .side_effect = IntegrityError ("Duplicate key" , None , None )
124135 mock_get_suid .return_value = "new-uuid"
125136
126- result = get_anonymous_user_id (
"[email protected] " )
137+ result = user_anon_module . get_anonymous_user_id (
"[email protected] " )
127138
128139 # Should return the mapping created by the other thread
129140 assert result == "race-condition-uuid"
@@ -132,7 +143,7 @@ def test_get_anonymous_user_id_race_condition(
132143 @patch ("utils.user_anonymization.get_session" )
133144 @patch ("utils.user_anonymization.get_suid" )
134145 def test_get_anonymous_user_id_race_condition_failure (
135- self , mock_get_suid , mock_get_session
146+ self , mock_get_suid , mock_get_session , user_anon_module
136147 ):
137148 """Test handling race condition where retrieval also fails."""
138149 mock_session = MagicMock ()
@@ -149,22 +160,22 @@ def test_get_anonymous_user_id_race_condition_failure(
149160 with pytest .raises (
150161 RuntimeError , match = "Unable to create or retrieve anonymous user ID"
151162 ):
152- get_anonymous_user_id (
"[email protected] " )
163+ user_anon_module . get_anonymous_user_id (
"[email protected] " )
153164
154165 @patch ("utils.user_anonymization.get_session" )
155- def test_get_user_count (self , mock_get_session ):
166+ def test_get_user_count (self , mock_get_session , user_anon_module ):
156167 """Test getting total user count."""
157168 mock_session = MagicMock ()
158169 mock_get_session .return_value .__enter__ .return_value = mock_session
159170 mock_session .query .return_value .count .return_value = 42
160171
161- result = get_user_count ()
172+ result = user_anon_module . get_user_count ()
162173
163174 assert result == 42
164175 mock_session .query .assert_called_once_with (UserMapping )
165176
166177 @patch ("utils.user_anonymization.get_session" )
167- def test_find_anonymous_user_id_existing (self , mock_get_session ):
178+ def test_find_anonymous_user_id_existing (self , mock_get_session , user_anon_module ):
168179 """Test finding existing anonymous ID without creating new one."""
169180 existing_mapping = UserMapping ()
170181 existing_mapping .anonymous_id = "found-uuid"
@@ -175,28 +186,28 @@ def test_find_anonymous_user_id_existing(self, mock_get_session):
175186 existing_mapping
176187 )
177188
178- result = find_anonymous_user_id (
"[email protected] " )
189+ result = user_anon_module . find_anonymous_user_id (
"[email protected] " )
179190
180191 assert result == "found-uuid"
181192
182193 @patch ("utils.user_anonymization.get_session" )
183- def test_find_anonymous_user_id_not_found (self , mock_get_session ):
194+ def test_find_anonymous_user_id_not_found (self , mock_get_session , user_anon_module ):
184195 """Test finding non-existing anonymous ID returns None."""
185196 mock_session = MagicMock ()
186197 mock_get_session .return_value .__enter__ .return_value = mock_session
187198 mock_session .query .return_value .filter_by .return_value .first .return_value = None
188199
189- result = find_anonymous_user_id (
"[email protected] " )
200+ result = user_anon_module . find_anonymous_user_id (
"[email protected] " )
190201
191202 assert result is None
192203
193- def test_hmac_prevents_rainbow_attacks (self ):
204+ def test_hmac_prevents_rainbow_attacks (self , user_anon_module ):
194205 """Test that HMAC makes rainbow table attacks impractical."""
195206 # Common passwords/emails that might be in rainbow tables
196207 common_ids = [
"admin" ,
"[email protected] " ,
"user123" ,
"[email protected] " ]
197208
198209 for user_id in common_ids :
199- hash_result = _hash_user_id (user_id )
210+ hash_result = user_anon_module . _hash_user_id (user_id )
200211 # Hash should not match simple SHA-256 of just the user ID
201212 simple_hash = hashlib .sha256 (user_id .encode ()).hexdigest ()
202213 assert hash_result != simple_hash
@@ -207,7 +218,7 @@ def test_hmac_prevents_rainbow_attacks(self):
207218 ).hexdigest ()
208219 assert hash_result != hmac_without_pepper
209220
210- def test_anonymization_preserves_uniqueness (self ):
221+ def test_anonymization_preserves_uniqueness (self , user_anon_module ):
211222 """Test that different users get different anonymous IDs."""
212223 users = [
213224@@ -216,12 +227,12 @@ def test_anonymization_preserves_uniqueness(self):
216227217228 ]
218229
219- hashes = [_hash_user_id (user ) for user in users ]
230+ hashes = [user_anon_module . _hash_user_id (user ) for user in users ]
220231
221232 # All hashes should be unique
222233 assert len (set (hashes )) == len (hashes )
223234
224- def test_user_id_normalization (self ):
235+ def test_user_id_normalization (self , user_anon_module ):
225236 """Test that user ID normalization works correctly."""
226237 # Test case variations should produce the same hash
227238 variations = [
@@ -232,15 +243,15 @@ def test_user_id_normalization(self):
232243233244 ]
234245
235- hashes = [_hash_user_id (variation ) for variation in variations ]
246+ hashes = [user_anon_module . _hash_user_id (variation ) for variation in variations ]
236247
237248 # All variations should produce the same hash
238249 assert (
239250 len (set (hashes )) == 1
240251 ), "All case/whitespace variations should hash the same"
241252
242253 # But different actual users should still be different
243- different_user = _hash_user_id (
"[email protected] " )
254+ different_user = user_anon_module . _hash_user_id (
"[email protected] " )
244255 assert different_user not in hashes
245256
246257 def test_missing_pepper_env_var (self ):
@@ -256,7 +267,7 @@ def test_missing_pepper_env_var(self):
256267 importlib .reload (utils .user_anonymization )
257268
258269
259- class TestUserMappingModel :
270+ class TestUserMappingModel : # pylint: disable=redefined-outer-name
260271 """Test the UserMapping database model."""
261272
262273 def test_user_mapping_attributes (self ):
0 commit comments