Добавлены методы для работы с настройками авто-модерации, включая получение и установку значений, а также переключение состояний авто-публикации и авто-отклонения. Обновлены соответствующие репозитории и обработчики для интеграции новых функций в админ-панели.
Some checks are pending
CI pipeline / Test & Code Quality (push) Waiting to run
Some checks are pending
CI pipeline / Test & Code Quality (push) Waiting to run
This commit is contained in:
222
tests/test_auto_moderation_service.py
Normal file
222
tests/test_auto_moderation_service.py
Normal file
@@ -0,0 +1,222 @@
|
||||
"""Тесты для AutoModerationService."""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from helper_bot.handlers.private.services import AutoModerationService, BotSettings
|
||||
|
||||
|
||||
class TestAutoModerationService:
|
||||
"""Тесты для сервиса авто-модерации."""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db(self):
|
||||
"""Создает мок базы данных."""
|
||||
db = MagicMock()
|
||||
db.get_auto_moderation_settings = AsyncMock()
|
||||
return db
|
||||
|
||||
@pytest.fixture
|
||||
def settings(self):
|
||||
"""Создает настройки бота."""
|
||||
return BotSettings(
|
||||
group_for_posts="-123",
|
||||
group_for_message="-456",
|
||||
main_public="@test_channel",
|
||||
group_for_logs="-789",
|
||||
important_logs="-999",
|
||||
preview_link="false",
|
||||
logs="false",
|
||||
test="false",
|
||||
)
|
||||
|
||||
@pytest.fixture
|
||||
def service(self, mock_db, settings):
|
||||
"""Создает экземпляр сервиса."""
|
||||
return AutoModerationService(mock_db, settings)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_auto_action_returns_manual_when_score_is_none(
|
||||
self, service, mock_db
|
||||
):
|
||||
"""Тест: возвращает manual когда score равен None."""
|
||||
result = await service.check_auto_action(None)
|
||||
assert result == "manual"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_auto_action_returns_publish_when_score_above_threshold(
|
||||
self, service, mock_db
|
||||
):
|
||||
"""Тест: возвращает publish когда score выше порога."""
|
||||
mock_db.get_auto_moderation_settings.return_value = {
|
||||
"auto_publish_enabled": True,
|
||||
"auto_decline_enabled": False,
|
||||
"auto_publish_threshold": 0.8,
|
||||
"auto_decline_threshold": 0.4,
|
||||
}
|
||||
|
||||
result = await service.check_auto_action(0.9)
|
||||
|
||||
assert result == "publish"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_auto_action_returns_decline_when_score_below_threshold(
|
||||
self, service, mock_db
|
||||
):
|
||||
"""Тест: возвращает decline когда score ниже порога."""
|
||||
mock_db.get_auto_moderation_settings.return_value = {
|
||||
"auto_publish_enabled": False,
|
||||
"auto_decline_enabled": True,
|
||||
"auto_publish_threshold": 0.8,
|
||||
"auto_decline_threshold": 0.4,
|
||||
}
|
||||
|
||||
result = await service.check_auto_action(0.3)
|
||||
|
||||
assert result == "decline"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_auto_action_returns_manual_when_disabled(
|
||||
self, service, mock_db
|
||||
):
|
||||
"""Тест: возвращает manual когда авто-действия отключены."""
|
||||
mock_db.get_auto_moderation_settings.return_value = {
|
||||
"auto_publish_enabled": False,
|
||||
"auto_decline_enabled": False,
|
||||
"auto_publish_threshold": 0.8,
|
||||
"auto_decline_threshold": 0.4,
|
||||
}
|
||||
|
||||
result = await service.check_auto_action(0.9)
|
||||
|
||||
assert result == "manual"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_auto_action_returns_manual_when_score_in_middle(
|
||||
self, service, mock_db
|
||||
):
|
||||
"""Тест: возвращает manual когда score между порогами."""
|
||||
mock_db.get_auto_moderation_settings.return_value = {
|
||||
"auto_publish_enabled": True,
|
||||
"auto_decline_enabled": True,
|
||||
"auto_publish_threshold": 0.8,
|
||||
"auto_decline_threshold": 0.4,
|
||||
}
|
||||
|
||||
result = await service.check_auto_action(0.6)
|
||||
|
||||
assert result == "manual"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_auto_action_publish_at_exact_threshold(
|
||||
self, service, mock_db
|
||||
):
|
||||
"""Тест: возвращает publish когда score равен порогу."""
|
||||
mock_db.get_auto_moderation_settings.return_value = {
|
||||
"auto_publish_enabled": True,
|
||||
"auto_decline_enabled": False,
|
||||
"auto_publish_threshold": 0.8,
|
||||
"auto_decline_threshold": 0.4,
|
||||
}
|
||||
|
||||
result = await service.check_auto_action(0.8)
|
||||
|
||||
assert result == "publish"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_auto_action_decline_at_exact_threshold(
|
||||
self, service, mock_db
|
||||
):
|
||||
"""Тест: возвращает decline когда score равен порогу."""
|
||||
mock_db.get_auto_moderation_settings.return_value = {
|
||||
"auto_publish_enabled": False,
|
||||
"auto_decline_enabled": True,
|
||||
"auto_publish_threshold": 0.8,
|
||||
"auto_decline_threshold": 0.4,
|
||||
}
|
||||
|
||||
result = await service.check_auto_action(0.4)
|
||||
|
||||
assert result == "decline"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_log_auto_action_publish(self, service, settings):
|
||||
"""Тест отправки лога для авто-публикации."""
|
||||
mock_bot = MagicMock()
|
||||
mock_bot.send_message = AsyncMock()
|
||||
|
||||
await service.log_auto_action(
|
||||
bot=mock_bot,
|
||||
action="publish",
|
||||
author_id=12345,
|
||||
author_name="Test User",
|
||||
author_username="testuser",
|
||||
rag_score=0.85,
|
||||
post_text="Test post text",
|
||||
)
|
||||
|
||||
mock_bot.send_message.assert_called_once()
|
||||
call_kwargs = mock_bot.send_message.call_args[1]
|
||||
assert call_kwargs["chat_id"] == settings.important_logs
|
||||
assert "АВТО-ПУБЛИКАЦИЯ" in call_kwargs["text"]
|
||||
assert "Test User" in call_kwargs["text"]
|
||||
assert "0.85" in call_kwargs["text"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_log_auto_action_decline(self, service, settings):
|
||||
"""Тест отправки лога для авто-отклонения."""
|
||||
mock_bot = MagicMock()
|
||||
mock_bot.send_message = AsyncMock()
|
||||
|
||||
await service.log_auto_action(
|
||||
bot=mock_bot,
|
||||
action="decline",
|
||||
author_id=12345,
|
||||
author_name="Test User",
|
||||
author_username="testuser",
|
||||
rag_score=0.25,
|
||||
post_text="Test post text",
|
||||
)
|
||||
|
||||
mock_bot.send_message.assert_called_once()
|
||||
call_kwargs = mock_bot.send_message.call_args[1]
|
||||
assert "АВТО-ОТКЛОНЕНИЕ" in call_kwargs["text"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_log_auto_action_handles_exception(self, service):
|
||||
"""Тест обработки исключения при отправке лога."""
|
||||
mock_bot = MagicMock()
|
||||
mock_bot.send_message = AsyncMock(side_effect=Exception("Network error"))
|
||||
|
||||
# Не должно выбрасывать исключение
|
||||
await service.log_auto_action(
|
||||
bot=mock_bot,
|
||||
action="publish",
|
||||
author_id=12345,
|
||||
author_name="Test User",
|
||||
author_username="testuser",
|
||||
rag_score=0.85,
|
||||
post_text="Test post text",
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_log_auto_action_truncates_long_text(self, service):
|
||||
"""Тест обрезки длинного текста в логе."""
|
||||
mock_bot = MagicMock()
|
||||
mock_bot.send_message = AsyncMock()
|
||||
|
||||
long_text = "a" * 300
|
||||
|
||||
await service.log_auto_action(
|
||||
bot=mock_bot,
|
||||
action="publish",
|
||||
author_id=12345,
|
||||
author_name="Test User",
|
||||
author_username="testuser",
|
||||
rag_score=0.85,
|
||||
post_text=long_text,
|
||||
)
|
||||
|
||||
call_kwargs = mock_bot.send_message.call_args[1]
|
||||
# Текст должен быть обрезан до 200 символов + "..."
|
||||
assert "..." in call_kwargs["text"]
|
||||
161
tests/test_bot_settings_repository.py
Normal file
161
tests/test_bot_settings_repository.py
Normal file
@@ -0,0 +1,161 @@
|
||||
"""Тесты для BotSettingsRepository."""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from database.repositories.bot_settings_repository import BotSettingsRepository
|
||||
|
||||
|
||||
class TestBotSettingsRepository:
|
||||
"""Тесты для репозитория настроек бота."""
|
||||
|
||||
@pytest.fixture
|
||||
def repository(self):
|
||||
"""Создает экземпляр репозитория с замоканным путем к БД."""
|
||||
return BotSettingsRepository("test.db")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_setting_returns_value(self, repository):
|
||||
"""Тест получения настройки по ключу."""
|
||||
with patch.object(
|
||||
repository, "_execute_query_with_result", new_callable=AsyncMock
|
||||
) as mock_query:
|
||||
mock_query.return_value = [("true",)]
|
||||
|
||||
result = await repository.get_setting("auto_publish_enabled")
|
||||
|
||||
assert result == "true"
|
||||
mock_query.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_setting_returns_none_when_not_found(self, repository):
|
||||
"""Тест получения несуществующей настройки."""
|
||||
with patch.object(
|
||||
repository, "_execute_query_with_result", new_callable=AsyncMock
|
||||
) as mock_query:
|
||||
mock_query.return_value = []
|
||||
|
||||
result = await repository.get_setting("nonexistent_key")
|
||||
|
||||
assert result is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_setting(self, repository):
|
||||
"""Тест установки настройки."""
|
||||
with patch.object(
|
||||
repository, "_execute_query", new_callable=AsyncMock
|
||||
) as mock_query:
|
||||
await repository.set_setting("auto_publish_enabled", "true")
|
||||
|
||||
mock_query.assert_called_once()
|
||||
call_args = mock_query.call_args[0]
|
||||
assert "auto_publish_enabled" in str(call_args)
|
||||
assert "true" in str(call_args)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_bool_setting_true(self, repository):
|
||||
"""Тест получения булевой настройки со значением true."""
|
||||
with patch.object(
|
||||
repository, "get_setting", new_callable=AsyncMock
|
||||
) as mock_get:
|
||||
mock_get.return_value = "true"
|
||||
|
||||
result = await repository.get_bool_setting("auto_publish_enabled")
|
||||
|
||||
assert result is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_bool_setting_false(self, repository):
|
||||
"""Тест получения булевой настройки со значением false."""
|
||||
with patch.object(
|
||||
repository, "get_setting", new_callable=AsyncMock
|
||||
) as mock_get:
|
||||
mock_get.return_value = "false"
|
||||
|
||||
result = await repository.get_bool_setting("auto_publish_enabled")
|
||||
|
||||
assert result is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_bool_setting_default(self, repository):
|
||||
"""Тест получения булевой настройки с дефолтным значением."""
|
||||
with patch.object(
|
||||
repository, "get_setting", new_callable=AsyncMock
|
||||
) as mock_get:
|
||||
mock_get.return_value = None
|
||||
|
||||
result = await repository.get_bool_setting("auto_publish_enabled", True)
|
||||
|
||||
assert result is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_float_setting(self, repository):
|
||||
"""Тест получения числовой настройки."""
|
||||
with patch.object(
|
||||
repository, "get_setting", new_callable=AsyncMock
|
||||
) as mock_get:
|
||||
mock_get.return_value = "0.8"
|
||||
|
||||
result = await repository.get_float_setting("auto_publish_threshold")
|
||||
|
||||
assert result == 0.8
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_float_setting_invalid_value(self, repository):
|
||||
"""Тест получения числовой настройки с некорректным значением."""
|
||||
with patch.object(
|
||||
repository, "get_setting", new_callable=AsyncMock
|
||||
) as mock_get:
|
||||
mock_get.return_value = "invalid"
|
||||
|
||||
result = await repository.get_float_setting("auto_publish_threshold", 0.5)
|
||||
|
||||
assert result == 0.5
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_auto_moderation_settings(self, repository):
|
||||
"""Тест получения всех настроек авто-модерации."""
|
||||
with patch.object(
|
||||
repository, "get_bool_setting", new_callable=AsyncMock
|
||||
) as mock_bool, patch.object(
|
||||
repository, "get_float_setting", new_callable=AsyncMock
|
||||
) as mock_float:
|
||||
mock_bool.side_effect = [True, False]
|
||||
mock_float.side_effect = [0.8, 0.4]
|
||||
|
||||
result = await repository.get_auto_moderation_settings()
|
||||
|
||||
assert result["auto_publish_enabled"] is True
|
||||
assert result["auto_decline_enabled"] is False
|
||||
assert result["auto_publish_threshold"] == 0.8
|
||||
assert result["auto_decline_threshold"] == 0.4
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_toggle_auto_publish(self, repository):
|
||||
"""Тест переключения авто-публикации."""
|
||||
with patch.object(
|
||||
repository, "get_bool_setting", new_callable=AsyncMock
|
||||
) as mock_get, patch.object(
|
||||
repository, "set_bool_setting", new_callable=AsyncMock
|
||||
) as mock_set:
|
||||
mock_get.return_value = False
|
||||
|
||||
result = await repository.toggle_auto_publish()
|
||||
|
||||
assert result is True
|
||||
mock_set.assert_called_once_with("auto_publish_enabled", True)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_toggle_auto_decline(self, repository):
|
||||
"""Тест переключения авто-отклонения."""
|
||||
with patch.object(
|
||||
repository, "get_bool_setting", new_callable=AsyncMock
|
||||
) as mock_get, patch.object(
|
||||
repository, "set_bool_setting", new_callable=AsyncMock
|
||||
) as mock_set:
|
||||
mock_get.return_value = True
|
||||
|
||||
result = await repository.toggle_auto_decline()
|
||||
|
||||
assert result is False
|
||||
mock_set.assert_called_once_with("auto_decline_enabled", False)
|
||||
@@ -115,7 +115,7 @@ class TestKeyboards:
|
||||
|
||||
assert isinstance(keyboard, ReplyKeyboardMarkup)
|
||||
assert keyboard.keyboard is not None
|
||||
assert len(keyboard.keyboard) == 3 # Три строки
|
||||
assert len(keyboard.keyboard) == 4 # Четыре строки
|
||||
|
||||
# Проверяем первую строку (3 кнопки)
|
||||
first_row = keyboard.keyboard[0]
|
||||
@@ -130,10 +130,15 @@ class TestKeyboards:
|
||||
assert second_row[0].text == "Разбан (список)"
|
||||
assert second_row[1].text == "📊 ML Статистика"
|
||||
|
||||
# Проверяем третью строку (1 кнопка)
|
||||
# Проверяем третью строку (1 кнопка - авто-модерация)
|
||||
third_row = keyboard.keyboard[2]
|
||||
assert len(third_row) == 1
|
||||
assert third_row[0].text == "Вернуться в бота"
|
||||
assert third_row[0].text == "⚙️ Авто-модерация"
|
||||
|
||||
# Проверяем четвертую строку (1 кнопка)
|
||||
fourth_row = keyboard.keyboard[3]
|
||||
assert len(fourth_row) == 1
|
||||
assert fourth_row[0].text == "Вернуться в бота"
|
||||
|
||||
def test_get_reply_keyboard_for_post(self):
|
||||
"""Тест клавиатуры для постов"""
|
||||
|
||||
Reference in New Issue
Block a user