Files
telegram-helper-bot/tests/test_callback_services.py
Andrey d0c8dab24a
Some checks failed
CI pipeline / Test & Code Quality (push) Failing after 19s
fix imports
2026-02-28 23:01:21 +03:00

810 lines
35 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Тесты для 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_publish_text")
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_publish_text")
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_publish_text")
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_publish_text")
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_publish_text")
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"