11# tests/test_handlers.py — Тесты для handlers/pyrogram_handlers.py
22
3+ import asyncio
34from unittest .mock import AsyncMock , MagicMock , patch
45
56import pytest
3637
3738from config import DEFAULT_STYLE , STYLE_TO_EMOJI
3839TYPING_TEXT = SYSTEM_MESSAGES ["draft_typing" ].format (emoji = STYLE_TO_EMOJI [DEFAULT_STYLE ])
40+ REAL_ASYNCIO_SLEEP = asyncio .sleep
3941
4042
4143def _close_coroutine_task (coro ):
@@ -337,13 +339,15 @@ async def test_poll_qr_login_success_saves_session_and_starts_listening(self, mo
337339 patch ("handlers.connect_handler.get_system_message" , new_callable = AsyncMock , return_value = "Connected" ):
338340 mock_pc .start_listening = AsyncMock (return_value = True )
339341
340- await _poll_qr_login (mock_client , 123 , "en" , mock_bot , 456 )
342+ await _poll_qr_login (mock_client , 123 , "en" , mock_bot , 456 , sensitive_msg_ids = [ 500 ] )
341343
342344 mock_client .export_session_string .assert_called_once ()
343345 mock_client .disconnect .assert_called_once ()
344346 mock_save_session .assert_called_once_with (123 , "session-123" )
345347 mock_pc .start_listening .assert_called_once_with (123 , "session-123" )
346348 mock_bot .send_message .assert_called_once_with (chat_id = 456 , text = "Connected" )
349+ # QR-сообщение удалено
350+ mock_bot .delete_message .assert_called_once_with (chat_id = 456 , message_id = 500 )
347351
348352 @pytest .mark .asyncio
349353 async def test_poll_qr_login_stops_when_save_session_fails (self , mock_bot ):
@@ -362,7 +366,7 @@ async def test_poll_qr_login_stops_when_save_session_fails(self, mock_bot):
362366 patch ("handlers.connect_handler.get_system_message" , new_callable = AsyncMock , return_value = "Connect failed" ):
363367 mock_pc .start_listening = AsyncMock (return_value = True )
364368
365- await _poll_qr_login (mock_client , 123 , "en" , mock_bot , 456 )
369+ await _poll_qr_login (mock_client , 123 , "en" , mock_bot , 456 , sensitive_msg_ids = [ 500 ] )
366370
367371 mock_pc .start_listening .assert_not_called ()
368372 mock_bot .send_message .assert_called_once_with (chat_id = 456 , text = "Connect failed" )
@@ -385,7 +389,7 @@ async def test_poll_qr_login_clears_session_when_listener_start_fails(self, mock
385389 patch ("handlers.connect_handler.get_system_message" , new_callable = AsyncMock , return_value = "Connect failed" ):
386390 mock_pc .start_listening = AsyncMock (return_value = False )
387391
388- await _poll_qr_login (mock_client , 123 , "en" , mock_bot , 456 )
392+ await _poll_qr_login (mock_client , 123 , "en" , mock_bot , 456 , sensitive_msg_ids = [ 500 ] )
389393
390394 mock_clear .assert_called_once_with (123 )
391395 mock_bot .send_message .assert_called_once_with (chat_id = 456 , text = "Connect failed" )
@@ -409,7 +413,7 @@ async def test_poll_qr_login_stores_user_from_success_result(self, mock_bot):
409413 patch ("handlers.connect_handler.get_system_message" , new_callable = AsyncMock , return_value = "OK" ):
410414 mock_pc .start_listening = AsyncMock (return_value = True )
411415
412- await _poll_qr_login (mock_client , 123 , "en" , mock_bot , 456 )
416+ await _poll_qr_login (mock_client , 123 , "en" , mock_bot , 456 , sensitive_msg_ids = [ 500 ] )
413417
414418 mock_client .storage .user_id .assert_called_with (777 )
415419 mock_client .storage .is_bot .assert_called_with (False )
@@ -437,17 +441,67 @@ async def test_poll_qr_migration_2fa_enters_pending_2fa(self, mock_bot):
437441 mock_auth_cls .return_value .create = AsyncMock (return_value = b"new_key" )
438442 mock_pyro_session .return_value = AsyncMock ()
439443
440- await _poll_qr_login (mock_client , 42 , "en" , mock_bot , 456 )
444+ await _poll_qr_login (mock_client , 42 , "en" , mock_bot , 456 , sensitive_msg_ids = [ 500 ] )
441445
442446 # Клиент сохранён в _pending_2fa (не отключен)
443447 assert 42 in _pending_2fa
444448 assert _pending_2fa [42 ]["client" ] is mock_client
445449 mock_bot .send_message .assert_called_once_with (chat_id = 456 , text = "Enter 2FA password" )
446450 mock_client .disconnect .assert_not_called ()
451+ # QR-сообщение удалено даже при переходе в 2FA
452+ mock_bot .delete_message .assert_called_once_with (chat_id = 456 , message_id = 500 )
447453
448454 # Cleanup
449455 _pending_2fa .pop (42 , None )
450456
457+ @pytest .mark .asyncio
458+ async def test_poll_qr_login_deletes_qr_message_on_timeout (self , mock_bot ):
459+ """QR-сообщение удаляется при таймауте."""
460+ # Все poll-ы возвращают LoginToken (не авторизован)
461+ login_token = type ("LoginToken" , (), {"token" : b"tok" })()
462+
463+ mock_client = AsyncMock ()
464+ mock_client .invoke = AsyncMock (return_value = login_token )
465+ mock_client .disconnect = AsyncMock ()
466+
467+ with patch ("handlers.connect_handler.asyncio.sleep" , new_callable = AsyncMock ), \
468+ patch ("handlers.connect_handler.get_system_message" , new_callable = AsyncMock , return_value = "QR expired" ):
469+
470+ await _poll_qr_login (mock_client , 123 , "en" , mock_bot , 456 , sensitive_msg_ids = [500 ])
471+
472+ # QR-сообщение удалено при таймауте
473+ mock_bot .delete_message .assert_called_once_with (chat_id = 456 , message_id = 500 )
474+
475+ @pytest .mark .asyncio
476+ async def test_poll_qr_login_deletes_qr_message_on_task_cancel (self , mock_bot ):
477+ """При отмене фоновой QR-задачи cleanup удаляет QR-сообщение ровно один раз."""
478+ login_token = type ("LoginToken" , (), {"token" : b"tok" })()
479+
480+ mock_client = AsyncMock ()
481+ mock_client .invoke = AsyncMock (return_value = login_token )
482+ mock_client .disconnect = AsyncMock ()
483+
484+ sleep_started = asyncio .Event ()
485+ release_sleep = asyncio .Event ()
486+
487+ async def blocked_sleep (_ : int ) -> None :
488+ sleep_started .set ()
489+ await release_sleep .wait ()
490+
491+ with patch ("handlers.connect_handler.asyncio.sleep" , side_effect = blocked_sleep ):
492+ task = asyncio .create_task (
493+ _poll_qr_login (mock_client , 123 , "en" , mock_bot , 456 , sensitive_msg_ids = [500 ])
494+ )
495+ await sleep_started .wait ()
496+ await REAL_ASYNCIO_SLEEP (0 )
497+ task .cancel ()
498+
499+ with pytest .raises (asyncio .CancelledError ):
500+ await task
501+
502+ mock_client .disconnect .assert_called_once ()
503+ mock_bot .delete_message .assert_called_once_with (chat_id = 456 , message_id = 500 )
504+
451505
452506class TestOnPyrogramMessage :
453507 """Тесты для on_pyrogram_message()."""
0 commit comments