@@ -209,8 +209,8 @@ async def test_uses_correct_time_threshold(self):
209209 mock_db .get_warnings_past_time_threshold .assert_called_once_with (300 )
210210
211211 @pytest .mark .asyncio
212- async def test_skips_kicked_user (self ):
213- """Test that kicked users are skipped and marked as unrestricted ."""
212+ async def test_skips_kicked_user_and_deletes_warning (self ):
213+ """Test that kicked users have their warning deleted so they don't reappear ."""
214214 mock_warning = UserWarning (
215215 id = 1 ,
216216 user_id = 123 ,
@@ -224,7 +224,7 @@ async def test_skips_kicked_user(self):
224224
225225 mock_db = MagicMock ()
226226 mock_db .get_warnings_past_time_threshold .return_value = [mock_warning ]
227- mock_db .mark_user_unrestricted = MagicMock ()
227+ mock_db .delete_user_warnings = MagicMock ()
228228
229229 mock_bot = AsyncMock ()
230230 mock_bot .restrict_chat_member = AsyncMock ()
@@ -246,13 +246,71 @@ async def test_skips_kicked_user(self):
246246 ):
247247 await auto_restrict_expired_warnings (mock_context )
248248
249- # Verify user was marked as unrestricted
250- mock_db .mark_user_unrestricted .assert_called_once_with (123 , - 100999 )
249+ # Verify warning was deleted (not just marked unrestricted)
250+ mock_db .delete_user_warnings .assert_called_once_with (123 , - 100999 )
251251
252252 # Verify restriction was NOT applied
253253 mock_bot .restrict_chat_member .assert_not_called ()
254254 mock_bot .send_message .assert_not_called ()
255255
256+ @pytest .mark .asyncio
257+ async def test_kicked_user_not_in_subsequent_queries (self ):
258+ """Test that deleted warnings don't appear in subsequent threshold queries."""
259+ mock_warning = UserWarning (
260+ id = 1 ,
261+ user_id = 123 ,
262+ group_id = - 100999 ,
263+ message_count = 1 ,
264+ first_warned_at = datetime .now (UTC ) - timedelta (hours = 4 ),
265+ last_message_at = datetime .now (UTC ),
266+ is_restricted = False ,
267+ restricted_by_bot = False ,
268+ )
269+
270+ # Track calls to simulate deletion effect
271+ call_count = 0
272+
273+ def get_warnings_side_effect (threshold ):
274+ nonlocal call_count
275+ call_count += 1
276+ # First call returns the warning, subsequent calls return empty
277+ # (simulating that delete_user_warnings removed it)
278+ if call_count == 1 :
279+ return [mock_warning ]
280+ return []
281+
282+ mock_db = MagicMock ()
283+ mock_db .get_warnings_past_time_threshold .side_effect = get_warnings_side_effect
284+ mock_db .delete_user_warnings = MagicMock ()
285+
286+ mock_bot = AsyncMock ()
287+ mock_bot .restrict_chat_member = AsyncMock ()
288+ mock_bot .send_message = AsyncMock ()
289+
290+ mock_context = MagicMock ()
291+ mock_context .bot = mock_bot
292+
293+ mock_settings = MagicMock ()
294+ mock_settings .warning_time_threshold_minutes = 180
295+ mock_settings .group_id = - 100999
296+
297+ with patch ("bot.services.scheduler.get_database" , return_value = mock_db ):
298+ with patch ("bot.services.scheduler.get_settings" , return_value = mock_settings ):
299+ with patch (
300+ "bot.services.scheduler.get_user_status" ,
301+ new_callable = AsyncMock ,
302+ return_value = ChatMemberStatus .BANNED ,
303+ ):
304+ # First run - should process and delete warning
305+ await auto_restrict_expired_warnings (mock_context )
306+ # Second run - should find no warnings
307+ await auto_restrict_expired_warnings (mock_context )
308+
309+ # Verify delete was called exactly once (first run only)
310+ mock_db .delete_user_warnings .assert_called_once_with (123 , - 100999 )
311+ # Verify the query was called twice
312+ assert mock_db .get_warnings_past_time_threshold .call_count == 2
313+
256314 @pytest .mark .asyncio
257315 async def test_handles_get_chat_member_failure (self ):
258316 """Test fallback user mention when get_chat_member fails."""
0 commit comments