Skip to content

Commit ffc0661

Browse files
committed
Update test statistics and fix scheduler cleanup for kicked users
- Update README test statistics: 310 tests (was 309), 100% pass rate, 99% coverage maintained - Fix scheduler to properly delete warnings for kicked users instead of marking as unrestricted - Kicked users can't rejoin the group without admin re-invite, so warnings should be deleted - This prevents warnings from reappearing in subsequent threshold queries - Add new test case to verify deleted warnings don't reappear in future scheduler runs
1 parent f537935 commit ffc0661

3 files changed

Lines changed: 66 additions & 8 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ uv run pytest -v
151151

152152
The project maintains comprehensive test coverage:
153153
- **Coverage**: 99% across all modules (1,057 statements, 2 missed)
154-
- **Tests**: 309 total
155-
- **Pass Rate**: 100% (309/309 passed)
154+
- **Tests**: 310 total
155+
- **Pass Rate**: 100% (310/310 passed)
156156
- **All modules**: 99% coverage including JobQueue scheduler integration, captcha verification, and anti-spam enforcement
157157
- Services: `bot_info.py`, `scheduler.py`, `user_checker.py`, `telegram_utils.py`, `captcha_recovery.py`
158158
- Handlers: `anti_spam.py`, `captcha.py`, `dm.py`, `message.py`, `topic_guard.py`, `verify.py`

src/bot/services/scheduler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ async def auto_restrict_expired_warnings(context: ContextTypes.DEFAULT_TYPE) ->
6262

6363
# Skip if user is kicked (can't rejoin without admin re-invite)
6464
if user_status == ChatMemberStatus.BANNED:
65-
db.mark_user_unrestricted(warning.user_id, settings.group_id)
65+
db.delete_user_warnings(warning.user_id, warning.group_id)
6666
logger.info(
6767
f"Skipped auto-restriction for user {warning.user_id} - user kicked (group_id={settings.group_id})"
6868
)

tests/test_scheduler.py

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)