""" Тесты для helper_bot.handlers.callback.services: PostPublishService, BanService. """ from unittest.mock import AsyncMock, MagicMock, patch import pytest from aiogram.types import CallbackQuery, Message from helper_bot.handlers.callback.constants import CONTENT_TYPE_MEDIA_GROUP from helper_bot.handlers.callback.exceptions import (PostNotFoundError, PublishError, UserBlockedBotError, UserNotFoundError) from helper_bot.handlers.callback.services import (BanService, PostPublishService) @pytest.mark.unit @pytest.mark.asyncio class TestPostPublishService: """Тесты для PostPublishService.""" @pytest.fixture def mock_bot(self): bot = MagicMock() bot.delete_message = AsyncMock() return bot @pytest.fixture def mock_db(self): db = MagicMock() db.get_author_id_by_message_id = AsyncMock(return_value=123) db.update_status_by_message_id = AsyncMock(return_value=1) db.get_post_text_and_anonymity_by_message_id = AsyncMock( return_value=("text", False) ) db.get_user_by_id = AsyncMock( return_value=MagicMock(first_name="U", username="u") ) db.update_published_message_id = AsyncMock() db.get_post_content_by_message_id = AsyncMock(return_value=[("/path", "photo")]) db.add_published_post_content = AsyncMock(return_value=True) db.get_post_text_by_message_id = AsyncMock(return_value="post text") return db @pytest.fixture def settings(self): return { "Telegram": { "group_for_posts": "-100", "main_public": "-200", "important_logs": "-300", } } @pytest.fixture def service(self, mock_bot, mock_db, settings): return PostPublishService(mock_bot, mock_db, settings) def test_get_bot_returns_bot_when_set(self, service, mock_bot): """_get_bot при установленном bot возвращает его.""" message = MagicMock() assert service._get_bot(message) is mock_bot def test_get_bot_returns_message_bot_when_bot_none(self, mock_db, settings): """_get_bot при bot=None возвращает message.bot.""" service = PostPublishService(None, mock_db, settings) message = MagicMock() message.bot = MagicMock() assert service._get_bot(message) is message.bot @pytest.fixture def mock_call_text(self): call = MagicMock(spec=CallbackQuery) call.message = MagicMock(spec=Message) call.message.message_id = 1 call.message.text = None call.message.media_group_id = None call.message.content_type = "text" call.message.from_user = MagicMock() call.message.from_user.full_name = "User" call.message.from_user.id = 123 return call @patch("helper_bot.handlers.callback.services.send_text_message") @patch("helper_bot.handlers.callback.services.get_text_message") async def test_publish_post_text_success( self, mock_get_text, mock_send_text, service, mock_call_text, mock_db ): """publish_post для текстового поста вызывает _publish_text_post и отправляет в канал.""" mock_get_text.return_value = "Formatted" sent = MagicMock() sent.message_id = 999 mock_send_text.return_value = sent await service.publish_post(mock_call_text) mock_db.update_status_by_message_id.assert_awaited_once_with(1, "approved") assert mock_send_text.await_count >= 1 mock_db.update_published_message_id.assert_awaited_once() @patch("helper_bot.handlers.callback.services.send_text_message") async def test_get_author_id_raises_when_not_found( self, mock_send, service, mock_db ): """_get_author_id при отсутствии автора выбрасывает PostNotFoundError.""" mock_db.get_author_id_by_message_id = AsyncMock(return_value=None) with pytest.raises(PostNotFoundError): await service._get_author_id(1) @patch("helper_bot.handlers.callback.services.send_text_message") async def test_get_author_id_returns_author_id(self, mock_send, service, mock_db): """_get_author_id возвращает ID автора.""" mock_db.get_author_id_by_message_id = AsyncMock(return_value=456) result = await service._get_author_id(1) assert result == 456 @patch("helper_bot.handlers.callback.services.send_text_message") async def test_get_author_id_for_media_group_returns_from_helper( self, mock_send, service, mock_db ): """_get_author_id_for_media_group при нахождении по helper_id возвращает author_id.""" mock_db.get_author_id_by_helper_message_id = AsyncMock(return_value=789) result = await service._get_author_id_for_media_group(100) assert result == 789 @patch("helper_bot.handlers.callback.services.send_text_message") async def test_train_on_published_skips_when_no_scoring_manager( self, mock_send, service, mock_db ): """_train_on_published при отсутствии scoring_manager ничего не делает.""" service.scoring_manager = None await service._train_on_published(1) mock_db.get_post_text_by_message_id.assert_not_called() @patch("helper_bot.handlers.callback.services.send_text_message") async def test_train_on_published_calls_on_post_published( self, mock_send, service, mock_db ): """_train_on_published при наличии scoring_manager вызывает on_post_published.""" mock_scoring = MagicMock() mock_scoring.on_post_published = AsyncMock() service.scoring_manager = mock_scoring mock_db.get_post_text_by_message_id = AsyncMock(return_value="post text") await service._train_on_published(1) mock_scoring.on_post_published.assert_awaited_once_with("post text") @patch("helper_bot.handlers.callback.services.send_text_message") async def test_train_on_declined_skips_when_no_scoring_manager( self, mock_send, service ): """_train_on_declined при отсутствии scoring_manager ничего не делает.""" service.scoring_manager = None await service._train_on_declined(1) @patch("helper_bot.handlers.callback.services.send_text_message") async def test_save_published_post_content_copies_path( self, mock_send, service, mock_db ): """_save_published_post_content копирует путь контента в published.""" published_message = MagicMock() mock_db.get_post_content_by_message_id = AsyncMock( return_value=[("/path/file", "photo")] ) mock_db.add_published_post_content = AsyncMock(return_value=True) await service._save_published_post_content(published_message, 100, 1) mock_db.add_published_post_content.assert_awaited_once_with( published_message_id=100, content_path="/path/file", content_type="photo" ) @patch("helper_bot.handlers.callback.services.send_text_message") async def test_save_published_post_content_empty_content( self, mock_send, service, mock_db ): """_save_published_post_content при пустом контенте не падает.""" published_message = MagicMock() mock_db.get_post_content_by_message_id = AsyncMock(return_value=[]) await service._save_published_post_content(published_message, 100, 1) mock_db.add_published_post_content.assert_not_called() @patch("helper_bot.handlers.callback.services.send_text_message") async def test_save_published_post_content_add_fails( self, mock_send, service, mock_db ): """_save_published_post_content при add_published_post_content=False не падает.""" published_message = MagicMock() mock_db.get_post_content_by_message_id = AsyncMock( return_value=[("/path", "photo")] ) mock_db.add_published_post_content = AsyncMock(return_value=False) await service._save_published_post_content(published_message, 100, 1) mock_db.add_published_post_content.assert_awaited_once() @patch("helper_bot.handlers.callback.services.send_text_message") async def test_publish_post_unsupported_content_raises( self, mock_send, service, mock_call_text ): """publish_post при неподдерживаемом типе контента выбрасывает PublishError.""" mock_call_text.message.content_type = "document" with pytest.raises(PublishError, match="Неподдерживаемый тип контента"): await service.publish_post(mock_call_text) @patch("helper_bot.handlers.callback.services.send_photo_message") @patch("helper_bot.handlers.callback.services.send_text_message") @patch("helper_bot.handlers.callback.services.get_text_message") async def test_publish_post_photo_success( self, mock_get_text, mock_send_text, mock_send_photo, service, mock_db ): """publish_post для фото вызывает _publish_photo_post.""" mock_get_text.return_value = "Formatted" call = MagicMock(spec=CallbackQuery) call.message = MagicMock(spec=Message) call.message.message_id = 1 call.message.text = None call.message.media_group_id = None call.message.content_type = "photo" call.message.photo = [MagicMock(), MagicMock(file_id="fid")] call.message.from_user = MagicMock(full_name="U", id=1) sent = MagicMock() sent.message_id = 999 mock_send_photo.return_value = sent await service.publish_post(call) mock_send_photo.assert_awaited_once() mock_db.update_published_message_id.assert_awaited_once() @patch("helper_bot.handlers.callback.services.send_video_message") @patch("helper_bot.handlers.callback.services.send_text_message") @patch("helper_bot.handlers.callback.services.get_text_message") async def test_publish_post_video_success( self, mock_get_text, mock_send_text, mock_send_video, service, mock_db ): """publish_post для видео вызывает _publish_video_post.""" mock_get_text.return_value = "Formatted" call = MagicMock(spec=CallbackQuery) call.message = MagicMock(spec=Message) call.message.message_id = 1 call.message.text = None call.message.media_group_id = None call.message.content_type = "video" call.message.video = MagicMock(file_id="vid") call.message.from_user = MagicMock(full_name="U", id=1) sent = MagicMock() sent.message_id = 999 mock_send_video.return_value = sent await service.publish_post(call) mock_send_video.assert_awaited_once() @patch("helper_bot.handlers.callback.services.send_video_note_message") @patch("helper_bot.handlers.callback.services.send_text_message") async def test_publish_post_video_note_success( self, mock_send_text, mock_send_vn, service, mock_db ): """publish_post для кружка вызывает _publish_video_note_post.""" call = MagicMock(spec=CallbackQuery) call.message = MagicMock(spec=Message) call.message.message_id = 1 call.message.text = None call.message.media_group_id = None call.message.content_type = "video_note" call.message.video_note = MagicMock(file_id="vnid") call.message.from_user = MagicMock(full_name="U", id=1) sent = MagicMock() sent.message_id = 999 mock_send_vn.return_value = sent await service.publish_post(call) mock_send_vn.assert_awaited_once() @patch("helper_bot.handlers.callback.services.send_audio_message") @patch("helper_bot.handlers.callback.services.send_text_message") @patch("helper_bot.handlers.callback.services.get_text_message") async def test_publish_post_audio_success( self, mock_get_text, mock_send_text, mock_send_audio, service, mock_db ): """publish_post для аудио вызывает _publish_audio_post.""" mock_get_text.return_value = "Formatted" call = MagicMock(spec=CallbackQuery) call.message = MagicMock(spec=Message) call.message.message_id = 1 call.message.text = None call.message.media_group_id = None call.message.content_type = "audio" call.message.audio = MagicMock(file_id="aid") call.message.from_user = MagicMock(full_name="U", id=1) sent = MagicMock() sent.message_id = 999 mock_send_audio.return_value = sent await service.publish_post(call) mock_send_audio.assert_awaited_once() @patch("helper_bot.handlers.callback.services.send_voice_message") @patch("helper_bot.handlers.callback.services.send_text_message") async def test_publish_post_voice_success( self, mock_send_text, mock_send_voice, service, mock_db ): """publish_post для войса вызывает _publish_voice_post.""" call = MagicMock(spec=CallbackQuery) call.message = MagicMock(spec=Message) call.message.message_id = 1 call.message.text = None call.message.media_group_id = None call.message.content_type = "voice" call.message.voice = MagicMock(file_id="vid") call.message.from_user = MagicMock(full_name="U", id=1) sent = MagicMock() sent.message_id = 999 mock_send_voice.return_value = sent await service.publish_post(call) mock_send_voice.assert_awaited_once() @patch("helper_bot.handlers.callback.services.send_text_message") async def test_publish_text_post_updated_rows_zero_raises( self, mock_send, service, mock_call_text, mock_db ): """_publish_text_post при updated_rows=0 выбрасывает PostNotFoundError.""" mock_db.update_status_by_message_id = AsyncMock(return_value=0) with pytest.raises(PostNotFoundError, match="не найден в базе данных"): await service._publish_text_post(mock_call_text) @patch("helper_bot.handlers.callback.services.send_text_message") async def test_publish_text_post_user_none_raises( self, mock_send, service, mock_call_text, mock_db ): """_publish_text_post при отсутствии пользователя выбрасывает PostNotFoundError.""" mock_db.get_user_by_id = AsyncMock(return_value=None) with pytest.raises(PostNotFoundError, match="не найден в базе данных"): await service._publish_text_post(mock_call_text) @patch("helper_bot.handlers.callback.services.send_text_message") async def test_publish_text_post_raw_text_none_uses_empty( self, mock_send, service, mock_call_text, mock_db ): """_publish_text_post при raw_text=None использует пустую строку.""" mock_db.get_post_text_and_anonymity_by_message_id = AsyncMock( return_value=(None, False) ) sent = MagicMock() sent.message_id = 999 mock_send.return_value = sent await service._publish_text_post(mock_call_text) mock_db.update_status_by_message_id.assert_awaited_once_with(1, "approved") @patch("helper_bot.handlers.callback.services.send_text_message") async def test_delete_post_and_notify_author_user_blocked_raises( self, mock_send, service, mock_call_text ): """_delete_post_and_notify_author при заблокированном боте выбрасывает UserBlockedBotError.""" from helper_bot.handlers.callback.constants import ERROR_BOT_BLOCKED mock_send.side_effect = Exception(ERROR_BOT_BLOCKED) with pytest.raises(UserBlockedBotError): await service._delete_post_and_notify_author(mock_call_text, 123) @patch("helper_bot.handlers.callback.services.send_text_message") async def test_train_on_published_skips_empty_text( self, mock_send, service, mock_db ): """_train_on_published пропускает пустой текст.""" mock_scoring = MagicMock() mock_scoring.on_post_published = AsyncMock() service.scoring_manager = mock_scoring mock_db.get_post_text_by_message_id = AsyncMock(return_value=" ") await service._train_on_published(1) mock_scoring.on_post_published.assert_not_called() @patch("helper_bot.handlers.callback.services.send_text_message") async def test_train_on_published_skips_caret(self, mock_send, service, mock_db): """_train_on_published пропускает текст '^'.""" mock_scoring = MagicMock() mock_scoring.on_post_published = AsyncMock() service.scoring_manager = mock_scoring mock_db.get_post_text_by_message_id = AsyncMock(return_value="^") await service._train_on_published(1) mock_scoring.on_post_published.assert_not_called() @patch("helper_bot.handlers.callback.services.send_text_message") async def test_train_on_declined_calls_on_post_declined( self, mock_send, service, mock_db ): """_train_on_declined вызывает on_post_declined.""" mock_scoring = MagicMock() mock_scoring.on_post_declined = AsyncMock() service.scoring_manager = mock_scoring mock_db.get_post_text_by_message_id = AsyncMock(return_value="declined text") await service._train_on_declined(1) mock_scoring.on_post_declined.assert_awaited_once_with("declined text") @patch("helper_bot.handlers.callback.services.send_text_message") async def test_get_author_id_for_media_group_fallback_via_post_ids( self, mock_send, service, mock_db ): """_get_author_id_for_media_group fallback через get_post_ids_from_telegram_by_last_id.""" mock_db.get_author_id_by_helper_message_id = AsyncMock(return_value=None) mock_db.get_post_ids_from_telegram_by_last_id = AsyncMock(return_value=[50, 51]) mock_db.get_author_id_by_message_id = AsyncMock(side_effect=[None, 777]) result = await service._get_author_id_for_media_group(100) assert result == 777 mock_db.get_author_id_by_message_id.assert_any_call(50) @patch("helper_bot.handlers.callback.services.send_text_message") async def test_get_author_id_for_media_group_fallback_direct( self, mock_send, service, mock_db ): """_get_author_id_for_media_group fallback напрямую по message_id.""" mock_db.get_author_id_by_helper_message_id = AsyncMock(return_value=None) mock_db.get_post_ids_from_telegram_by_last_id = AsyncMock(return_value=[]) mock_db.get_author_id_by_message_id = AsyncMock(return_value=888) result = await service._get_author_id_for_media_group(100) assert result == 888 @patch("helper_bot.handlers.callback.services.send_text_message") async def test_get_author_id_for_media_group_raises_when_not_found( self, mock_send, service, mock_db ): """_get_author_id_for_media_group при отсутствии автора выбрасывает PostNotFoundError.""" mock_db.get_author_id_by_helper_message_id = AsyncMock(return_value=None) mock_db.get_post_ids_from_telegram_by_last_id = AsyncMock(return_value=[]) mock_db.get_author_id_by_message_id = AsyncMock(return_value=None) with pytest.raises(PostNotFoundError, match="Автор не найден для медиагруппы"): await service._get_author_id_for_media_group(100) @patch("helper_bot.handlers.callback.services.send_media_group_to_channel") @patch("helper_bot.handlers.callback.services.send_text_message") async def test_publish_post_media_group_by_media_group_id( self, mock_send_text, mock_send_media, service, mock_db ): """publish_post при media_group_id идёт в _publish_media_group.""" call = MagicMock(spec=CallbackQuery) call.message = MagicMock(spec=Message) call.message.message_id = 10 call.message.text = None call.message.media_group_id = "mg_123" call.message.from_user = MagicMock() mock_db.get_post_ids_by_helper_id = AsyncMock(return_value=[1]) mock_db.get_post_content_by_helper_id = AsyncMock( return_value=[("/p", "photo")] ) mock_db.get_post_text_and_anonymity_by_helper_id = AsyncMock( return_value=("", False) ) mock_db.get_author_id_by_helper_message_id = AsyncMock(return_value=123) mock_db.get_user_by_id = AsyncMock( return_value=MagicMock(first_name="U", username="u") ) mock_db.update_published_message_id = AsyncMock() mock_db.update_status_for_media_group_by_helper_id = AsyncMock() mock_db.get_post_content_by_message_id = AsyncMock( return_value=[("/path", "photo")] ) mock_db.add_published_post_content = AsyncMock(return_value=True) bot = MagicMock() bot.delete_messages = AsyncMock() bot.delete_message = AsyncMock() call.message.bot = bot service.bot = bot mock_send_media.return_value = [MagicMock(message_id=101)] mock_send_text.return_value = MagicMock() await service.publish_post(call) mock_send_media.assert_awaited_once() @patch("helper_bot.handlers.callback.services.send_media_group_to_channel") @patch("helper_bot.handlers.callback.services.send_text_message") @patch("helper_bot.handlers.callback.services.get_text_message") async def test_publish_media_group_success( self, mock_get_text, mock_send_text, mock_send_media, service, mock_db ): """_publish_media_group успешно публикует медиагруппу.""" mock_get_text.return_value = "Formatted" call = MagicMock(spec=CallbackQuery) call.message = MagicMock(spec=Message) call.message.message_id = 10 call.message.text = ( CONTENT_TYPE_MEDIA_GROUP # маршрутизация в _publish_media_group ) call.message.from_user = MagicMock() mock_db.get_post_ids_by_helper_id = AsyncMock(return_value=[1, 2]) mock_db.get_post_content_by_helper_id = AsyncMock( return_value=[("/p1", "photo"), ("/p2", "photo")] ) mock_db.get_post_text_and_anonymity_by_helper_id = AsyncMock( return_value=("text", False) ) mock_db.get_author_id_by_helper_message_id = AsyncMock(return_value=123) mock_db.get_user_by_id = AsyncMock( return_value=MagicMock(first_name="U", username="u") ) mock_db.update_published_message_id = AsyncMock() mock_db.update_status_for_media_group_by_helper_id = AsyncMock() mock_db.get_post_content_by_message_id = AsyncMock( return_value=[("/path", "photo")] ) mock_db.add_published_post_content = AsyncMock(return_value=True) bot = MagicMock() bot.delete_messages = AsyncMock() bot.delete_message = AsyncMock() call.message.bot = bot service.bot = bot sent_msgs = [MagicMock(message_id=101), MagicMock(message_id=102)] mock_send_media.return_value = sent_msgs mock_send_text.return_value = MagicMock() await service.publish_post(call) mock_send_media.assert_awaited_once() mock_db.update_status_for_media_group_by_helper_id.assert_awaited_once_with( 10, "approved" ) @patch("helper_bot.handlers.callback.services.send_text_message") async def test_publish_media_group_empty_ids_raises( self, mock_send, service, mock_db ): """_publish_media_group при пустых media_group_message_ids выбрасывает PublishError.""" call = MagicMock(spec=CallbackQuery) call.message = MagicMock(spec=Message) call.message.message_id = 10 call.message.text = CONTENT_TYPE_MEDIA_GROUP call.message.from_user = MagicMock() mock_db.get_post_ids_by_helper_id = AsyncMock(return_value=[]) with pytest.raises(PublishError, match="Не найдены message_id медиагруппы"): await service.publish_post(call) @patch("helper_bot.handlers.callback.services.send_text_message") async def test_decline_post_media_group_calls_decline_media_group( self, mock_send, service, mock_db ): """decline_post для медиагруппы вызывает _decline_media_group.""" call = MagicMock(spec=CallbackQuery) call.message = MagicMock(spec=Message) call.message.message_id = 10 call.message.text = CONTENT_TYPE_MEDIA_GROUP call.message.content_type = "text" call.message.from_user = MagicMock(full_name="A", id=1) mock_db.get_author_id_by_helper_message_id = AsyncMock(return_value=123) mock_db.update_status_for_media_group_by_helper_id = AsyncMock() mock_db.get_post_ids_by_helper_id = AsyncMock(return_value=[1, 2]) bot = MagicMock() bot.delete_messages = AsyncMock() call.message.bot = bot service.bot = bot mock_send.return_value = MagicMock() await service.decline_post(call) mock_db.update_status_for_media_group_by_helper_id.assert_awaited_once_with( 10, "declined" ) mock_send.assert_awaited_once() @patch("helper_bot.handlers.callback.services.send_text_message") async def test_decline_post_unsupported_type_raises( self, mock_send, service, mock_call_text ): """decline_post при неподдерживаемом типе выбрасывает PublishError.""" mock_call_text.message.text = None mock_call_text.message.content_type = "document" with pytest.raises( PublishError, match="Неподдерживаемый тип контента для отклонения" ): await service.decline_post(mock_call_text) @patch("helper_bot.handlers.callback.services.send_text_message") async def test_decline_single_post_post_not_found_raises( self, mock_send, service, mock_call_text, mock_db ): """_decline_single_post при updated_rows=0 выбрасывает PostNotFoundError.""" mock_db.update_status_by_message_id = AsyncMock(return_value=0) with pytest.raises(PostNotFoundError, match="не найден в базе данных"): await service._decline_single_post(mock_call_text) @patch("helper_bot.handlers.callback.services.send_text_message") async def test_decline_single_post_user_blocked_raises( self, mock_send, service, mock_call_text, mock_db ): """_decline_single_post при заблокированном боте выбрасывает UserBlockedBotError.""" from helper_bot.handlers.callback.constants import ERROR_BOT_BLOCKED mock_send.side_effect = Exception(ERROR_BOT_BLOCKED) with pytest.raises(UserBlockedBotError): await service._decline_single_post(mock_call_text) @patch("helper_bot.handlers.callback.services.send_text_message") async def test_decline_media_group_user_blocked_raises( self, mock_send, service, mock_db ): """_decline_media_group при заблокированном боте выбрасывает UserBlockedBotError.""" from helper_bot.handlers.callback.constants import ERROR_BOT_BLOCKED call = MagicMock(spec=CallbackQuery) call.message = MagicMock(spec=Message) call.message.message_id = 10 call.message.from_user = MagicMock() mock_db.update_status_for_media_group_by_helper_id = AsyncMock() mock_db.get_post_ids_by_helper_id = AsyncMock(return_value=[1]) mock_db.get_author_id_by_helper_message_id = AsyncMock(return_value=123) bot = MagicMock() bot.delete_messages = AsyncMock() call.message.bot = bot service.bot = bot mock_send.side_effect = Exception(ERROR_BOT_BLOCKED) with pytest.raises(UserBlockedBotError): await service._decline_media_group(call) @patch("helper_bot.handlers.callback.services.send_text_message") async def test_delete_media_group_and_notify_author_success( self, mock_send, service, mock_db ): """_delete_media_group_and_notify_author удаляет сообщения и уведомляет автора.""" call = MagicMock(spec=CallbackQuery) call.message = MagicMock(spec=Message) call.message.message_id = 10 mock_db.get_post_ids_by_helper_id = AsyncMock(return_value=[1, 2]) bot = MagicMock() bot.delete_messages = AsyncMock() call.message.bot = bot service.bot = bot mock_send.return_value = MagicMock() await service._delete_media_group_and_notify_author(call, 123) bot.delete_messages.assert_awaited_once() mock_send.assert_awaited_once() @pytest.mark.unit @pytest.mark.asyncio class TestBanService: """Тесты для BanService.""" @pytest.fixture def mock_db(self): db = MagicMock() db.get_author_id_by_message_id = AsyncMock(return_value=111) db.get_author_id_by_helper_message_id = AsyncMock(return_value=None) db.set_user_blacklist = AsyncMock() db.update_status_by_message_id = AsyncMock(return_value=1) db.update_status_for_media_group_by_helper_id = AsyncMock(return_value=1) db.get_username = AsyncMock(return_value="user") return db @pytest.fixture def settings(self): return {"Telegram": {"group_for_posts": "-100", "important_logs": "-200"}} @pytest.fixture def ban_service(self, mock_db, settings): bot = MagicMock() bot.delete_message = AsyncMock() return BanService(bot, mock_db, settings) def test_get_bot_returns_bot_when_set(self, ban_service): """_get_bot при установленном bot возвращает его.""" message = MagicMock() assert ban_service._get_bot(message) is ban_service.bot @pytest.fixture def mock_call(self): call = MagicMock(spec=CallbackQuery) call.message = MagicMock() call.message.message_id = 1 call.message.text = None call.from_user = MagicMock() call.from_user.id = 999 call.bot = MagicMock() call.bot.delete_message = AsyncMock() return call @patch("helper_bot.handlers.callback.services.send_text_message") async def test_ban_user_from_post_success( self, mock_send, ban_service, mock_call, mock_db ): """ban_user_from_post устанавливает blacklist и обновляет статус поста.""" mock_call.message.text = None await ban_service.ban_user_from_post(mock_call) mock_db.set_user_blacklist.assert_awaited_once() mock_db.update_status_by_message_id.assert_awaited_once_with(1, "declined") mock_send.assert_awaited_once() @patch("helper_bot.handlers.callback.services.send_text_message") async def test_ban_user_from_post_media_group( self, mock_send, ban_service, mock_call, mock_db ): """ban_user_from_post для медиагруппы обновляет статус по helper_id.""" mock_call.message.text = CONTENT_TYPE_MEDIA_GROUP mock_call.message.message_id = 10 mock_db.get_author_id_by_helper_message_id = AsyncMock(return_value=111) mock_db.get_author_id_by_message_id = AsyncMock(return_value=None) mock_db.update_status_for_media_group_by_helper_id = AsyncMock(return_value=1) await ban_service.ban_user_from_post(mock_call) mock_db.set_user_blacklist.assert_awaited_once() mock_db.update_status_for_media_group_by_helper_id.assert_awaited_once_with( 10, "declined" ) mock_send.assert_awaited_once() @patch("helper_bot.handlers.callback.services.send_text_message") async def test_ban_user_from_post_user_blocked_raises( self, mock_send, ban_service, mock_call, mock_db ): """ban_user_from_post при заблокированном боте выбрасывает UserBlockedBotError.""" from helper_bot.handlers.callback.constants import ERROR_BOT_BLOCKED mock_send.side_effect = Exception(ERROR_BOT_BLOCKED) with pytest.raises(UserBlockedBotError): await ban_service.ban_user_from_post(mock_call) @patch("helper_bot.handlers.callback.services.send_text_message") async def test_ban_user_from_post_author_not_found_raises( self, mock_send, ban_service, mock_call, mock_db ): """ban_user_from_post при отсутствии автора выбрасывает UserNotFoundError.""" mock_db.get_author_id_by_message_id = AsyncMock(return_value=None) mock_db.get_author_id_by_helper_message_id = AsyncMock(return_value=None) with pytest.raises(UserNotFoundError, match="Автор не найден"): await ban_service.ban_user_from_post(mock_call) @patch("helper_bot.handlers.callback.services.send_text_message") async def test_ban_user_raises_when_not_found( self, mock_send, ban_service, mock_db ): """ban_user при отсутствии пользователя выбрасывает UserNotFoundError.""" mock_db.get_username = AsyncMock(return_value=None) with pytest.raises(UserNotFoundError): await ban_service.ban_user("999", "") @patch("helper_bot.handlers.callback.services.send_text_message") async def test_ban_user_returns_username(self, mock_send, ban_service, mock_db): """ban_user возвращает username пользователя.""" mock_db.get_username = AsyncMock(return_value="found_user") result = await ban_service.ban_user("123", "") assert result == "found_user" @patch( "helper_bot.handlers.callback.services.delete_user_blacklist", new_callable=AsyncMock, ) async def test_unlock_user_raises_when_not_found( self, mock_delete, ban_service, mock_db ): """unlock_user при отсутствии пользователя выбрасывает UserNotFoundError.""" mock_db.get_username = AsyncMock(return_value=None) with pytest.raises(UserNotFoundError): await ban_service.unlock_user("999") @patch( "helper_bot.handlers.callback.services.delete_user_blacklist", new_callable=AsyncMock, ) async def test_unlock_user_returns_username( self, mock_delete, ban_service, mock_db ): """unlock_user удаляет из blacklist и возвращает username.""" mock_db.get_username = AsyncMock(return_value="unlocked_user") result = await ban_service.unlock_user("123") mock_delete.assert_awaited_once() assert result == "unlocked_user"