Редактировать test_commands.py
This commit is contained in:
@@ -1,18 +1,303 @@
|
||||
# tests/integration/test_commands.py
|
||||
"""
|
||||
Интеграционные тесты для команд бота (проверяют вызовы RAGClient).
|
||||
Интеграционные тесты для переработанных команд бота.
|
||||
Проверяют, что каждая команда вызывает правильный метод RAGClient с корректными аргументами.
|
||||
Используются моки для RAGClient, чтобы изолировать тесты от реального HTTP-сервера.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from core.commands.clear import ClearCommand
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_clear_command():
|
||||
# Импортируем все классы команд
|
||||
from core.commands.clear import ClearCommand
|
||||
from core.commands.kb import KbCommand
|
||||
from core.commands.global_remove import GlobalRemoveCommand
|
||||
from core.commands.other import ResetCommand
|
||||
from core.commands.template import TemplateSaveCommand, TemplateListCommand, TemplateDeleteCommand
|
||||
from core.commands.stats import StatsCommand, StatusCommand, CleanCommand
|
||||
from core.commands.create import CreateCommand
|
||||
from core.commands.expert import SummaryCommand, MetricsCommand
|
||||
from core.commands.learn import LearnCommand, StopLearnCommand, GlobalLearnCommand
|
||||
from core.commands.info import InfoCommand
|
||||
from core.commands.help import HelpCommand
|
||||
from core.commands.base import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_bot():
|
||||
"""Создаёт мок бота с мокнутым rag_client и минимальными атрибутами."""
|
||||
bot = MagicMock()
|
||||
bot.rag_client = AsyncMock()
|
||||
bot.rag_client.clear = AsyncMock(return_value={"status": "ok"})
|
||||
cmd = ClearCommand(bot)
|
||||
# Для команд, использующих config
|
||||
bot.config = MagicMock()
|
||||
bot.config.name = "testbot"
|
||||
bot.config.admin_jids = ["admin@domain"]
|
||||
# Для команд, использующих learning_mode
|
||||
bot.learning_mode = {}
|
||||
# Для команд, использующих _last_file_path
|
||||
bot._last_file_path = {}
|
||||
# Для команд, использующих last_file_texts
|
||||
bot.last_file_texts = {}
|
||||
# Для старта времени (stats)
|
||||
bot.start_time = MagicMock()
|
||||
# Для команды info (не использует rag_client)
|
||||
return bot
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_msg():
|
||||
"""Создаёт мок XMPP сообщения с методом reply."""
|
||||
msg = MagicMock()
|
||||
msg.reply.return_value = MagicMock()
|
||||
await cmd.execute(msg, "", "user@domain", None)
|
||||
bot.rag_client.clear.assert_called_once_with("user@domain", None, False)
|
||||
return msg
|
||||
|
||||
|
||||
# ---------- Тесты для команды !clear ----------
|
||||
@pytest.mark.asyncio
|
||||
async def test_clear_command(mock_bot, mock_msg):
|
||||
cmd = ClearCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, "", "user@domain", None)
|
||||
mock_bot.rag_client.clear.assert_called_once_with("user@domain", None, False)
|
||||
|
||||
# Проверка с комнатой
|
||||
mock_bot.rag_client.clear.reset_mock()
|
||||
await cmd.execute(mock_msg, "", "user@domain", "room@domain")
|
||||
mock_bot.rag_client.clear.assert_called_once_with("user@domain", "room@domain", False)
|
||||
|
||||
|
||||
# ---------- Тесты для команды !kb ----------
|
||||
@pytest.mark.asyncio
|
||||
async def test_kb_command(mock_bot, mock_msg):
|
||||
mock_bot.rag_client.list_documents.return_value = {
|
||||
"documents": [
|
||||
{"source_name": "doc1.docx", "is_global": False},
|
||||
{"source_name": "doc2.pdf", "is_global": True}
|
||||
]
|
||||
}
|
||||
cmd = KbCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, "", "user@domain", None)
|
||||
mock_bot.rag_client.list_documents.assert_called_once_with("user@domain", None)
|
||||
|
||||
# Проверка с комнатой
|
||||
mock_bot.rag_client.list_documents.reset_mock()
|
||||
await cmd.execute(mock_msg, "", "user@domain", "room@domain")
|
||||
mock_bot.rag_client.list_documents.assert_called_once_with("user@domain", "room@domain")
|
||||
|
||||
|
||||
# ---------- Тесты для команды !global_remove ----------
|
||||
@pytest.mark.asyncio
|
||||
async def test_global_remove_command(mock_bot, mock_msg):
|
||||
# Проверка прав (админ)
|
||||
mock_bot.config.admin_jids = ["admin@domain"]
|
||||
cmd = GlobalRemoveCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, '!global_remove "ГОСТ Р 12345"', "admin@domain", None)
|
||||
mock_bot.rag_client.delete_global.assert_called_once_with("ГОСТ Р 12345")
|
||||
|
||||
# Проверка без прав
|
||||
mock_bot.rag_client.delete_global.reset_mock()
|
||||
await cmd.execute(mock_msg, '!global_remove "ГОСТ Р 12345"', "user@domain", None)
|
||||
mock_bot.rag_client.delete_global.assert_not_called()
|
||||
|
||||
# Проверка в комнате (должна отклонить)
|
||||
mock_bot.rag_client.delete_global.reset_mock()
|
||||
await cmd.execute(mock_msg, '!global_remove "ГОСТ Р 12345"', "admin@domain", "room@domain")
|
||||
mock_bot.rag_client.delete_global.assert_not_called()
|
||||
|
||||
|
||||
# ---------- Тесты для команды !reset ----------
|
||||
@pytest.mark.asyncio
|
||||
async def test_reset_command(mock_bot, mock_msg):
|
||||
cmd = ResetCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, "", "user@domain", None)
|
||||
mock_bot.rag_client.reset_history.assert_called_once_with("user@domain", None)
|
||||
|
||||
mock_bot.rag_client.reset_history.reset_mock()
|
||||
await cmd.execute(mock_msg, "", "user@domain", "room@domain")
|
||||
mock_bot.rag_client.reset_history.assert_called_once_with("user@domain", "room@domain")
|
||||
|
||||
|
||||
# ---------- Тесты для команд шаблонов ----------
|
||||
@pytest.mark.asyncio
|
||||
async def test_template_save_command(mock_bot, mock_msg):
|
||||
# Подготовка: бот должен иметь _last_file_path
|
||||
mock_bot._last_file_path = {("user@domain", "room@domain"): "/tmp/test.docx"}
|
||||
cmd = TemplateSaveCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, '!template_save "Шаблон"', "user@domain", "room@domain")
|
||||
# Проверяем, что вызывается template_save с правильными аргументами
|
||||
mock_bot.rag_client.template_save.assert_called_once()
|
||||
args, kwargs = mock_bot.rag_client.template_save.call_args
|
||||
assert args[0] == "room@domain" # room_jid
|
||||
assert args[1] == "Шаблон" # name
|
||||
assert args[2] == "/tmp/test.docx" # file_path
|
||||
# file_hash проверять не будем, т.к. он вычисляется
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_template_list_command(mock_bot, mock_msg):
|
||||
cmd = TemplateListCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, "", "user@domain", "room@domain")
|
||||
mock_bot.rag_client.template_list.assert_called_once_with("room@domain")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_template_delete_command(mock_bot, mock_msg):
|
||||
cmd = TemplateDeleteCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, '!template_delete "Шаблон"', "user@domain", "room@domain")
|
||||
mock_bot.rag_client.template_delete.assert_called_once_with("room@domain", "Шаблон")
|
||||
|
||||
|
||||
# ---------- Тесты для команд статистики ----------
|
||||
@pytest.mark.asyncio
|
||||
async def test_stats_command(mock_bot, mock_msg):
|
||||
# Проверка прав (админ)
|
||||
mock_bot.config.admin_jids = ["admin@domain"]
|
||||
mock_bot.rag_client.get_stats.return_value = {"documents": 42, "uptime": 123}
|
||||
cmd = StatsCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, "", "admin@domain", None)
|
||||
mock_bot.rag_client.get_stats.assert_called_once()
|
||||
|
||||
# Без прав
|
||||
mock_bot.rag_client.get_stats.reset_mock()
|
||||
await cmd.execute(mock_msg, "", "user@domain", None)
|
||||
mock_bot.rag_client.get_stats.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_status_command(mock_bot, mock_msg):
|
||||
mock_bot.config.admin_jids = ["admin@domain"]
|
||||
mock_bot.rag_client.health_check.return_value = True
|
||||
cmd = StatusCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, "", "admin@domain", None)
|
||||
mock_bot.rag_client.health_check.assert_called_once()
|
||||
|
||||
mock_bot.rag_client.health_check.reset_mock()
|
||||
await cmd.execute(mock_msg, "", "user@domain", None)
|
||||
mock_bot.rag_client.health_check.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_clean_command(mock_bot, mock_msg):
|
||||
mock_bot.config.admin_jids = ["admin@domain"]
|
||||
# clean не использует rag_client, просто проверяет права и очищает temp_dir
|
||||
# Проверим, что команда не вызывает rag_client
|
||||
cmd = CleanCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, "", "admin@domain", None)
|
||||
# Никаких вызовов rag_client не должно быть
|
||||
mock_bot.rag_client.assert_not_called()
|
||||
|
||||
|
||||
# ---------- Тесты для команды !create ----------
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_command(mock_bot, mock_msg):
|
||||
mock_bot.rag_client.generate_document.return_value = {"document": "/tmp/out.docx"}
|
||||
cmd = CreateCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, '!create "Шаблон"', "user@domain", "room@domain")
|
||||
mock_bot.rag_client.generate_document.assert_called_once_with("Шаблон", "user@domain", "room@domain")
|
||||
|
||||
|
||||
# ---------- Тесты для экспертных команд ----------
|
||||
@pytest.mark.asyncio
|
||||
async def test_summary_command(mock_bot, mock_msg):
|
||||
mock_bot.rag_client.query.return_value = {"answer": "Суммаризация документа..."}
|
||||
mock_bot.last_file_texts = {"user@domain": "Текст документа"}
|
||||
cmd = SummaryCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, "", "user@domain", None)
|
||||
mock_bot.rag_client.query.assert_called_once_with(
|
||||
query="Сделай суммаризацию последнего документа",
|
||||
user_jid="user@domain",
|
||||
room_jid=None,
|
||||
prompts=mock_bot.prompts,
|
||||
intent_override="SUMMARY",
|
||||
last_file_path=None,
|
||||
last_file_text="Текст документа"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_metrics_command(mock_bot, mock_msg):
|
||||
mock_bot.rag_client.query.return_value = {"answer": "KPI: ..."}
|
||||
cmd = MetricsCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, "", "user@domain", None)
|
||||
mock_bot.rag_client.query.assert_called_once_with(
|
||||
query="Извлеки все KPI из документов",
|
||||
user_jid="user@domain",
|
||||
room_jid=None,
|
||||
prompts=mock_bot.prompts,
|
||||
intent_override="METRICS",
|
||||
last_file_path=None,
|
||||
last_file_text=None
|
||||
)
|
||||
|
||||
|
||||
# ---------- Тесты для команд обучения ----------
|
||||
@pytest.mark.asyncio
|
||||
async def test_learn_command(mock_bot, mock_msg):
|
||||
cmd = LearnCommand(mock_bot)
|
||||
# Личный чат, личное обучение
|
||||
await cmd.execute(mock_msg, '!learn', "user@domain", None)
|
||||
assert mock_bot.learning_mode.get("user@domain") == ("personal", None)
|
||||
|
||||
# Глобальное обучение (админ)
|
||||
mock_bot.config.admin_jids = ["admin@domain"]
|
||||
await cmd.execute(mock_msg, '!learn --global', "admin@domain", None)
|
||||
assert mock_bot.learning_mode.get("admin@domain") == ("global", None)
|
||||
|
||||
# Комнатное обучение
|
||||
await cmd.execute(mock_msg, '!learn', "user@domain", "room@domain")
|
||||
assert mock_bot.learning_mode.get("user@domain") == ("room", "room@domain")
|
||||
|
||||
# С --title
|
||||
await cmd.execute(mock_msg, '!learn --title "Мой заголовок"', "user@domain", None)
|
||||
# Проверим, что заголовок не влияет на режим (он сохраняется в learning_mode)
|
||||
# Но команда отправляет сообщение, мы не проверяем содержимое
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stop_learn_command(mock_bot, mock_msg):
|
||||
mock_bot.learning_mode = {"user@domain": ("personal", None)}
|
||||
cmd = StopLearnCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, "", "user@domain", None)
|
||||
assert "user@domain" not in mock_bot.learning_mode
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_global_learn_command(mock_bot, mock_msg):
|
||||
mock_bot.config.admin_jids = ["admin@domain"]
|
||||
cmd = GlobalLearnCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, "", "admin@domain", None)
|
||||
assert mock_bot.learning_mode.get("admin@domain") == ("global", None)
|
||||
|
||||
# Без прав
|
||||
mock_bot.learning_mode = {}
|
||||
await cmd.execute(mock_msg, "", "user@domain", None)
|
||||
assert "user@domain" not in mock_bot.learning_mode
|
||||
|
||||
# В комнате
|
||||
await cmd.execute(mock_msg, "", "admin@domain", "room@domain")
|
||||
assert mock_bot.learning_mode.get("admin@domain") == ("global", None) # не изменилось
|
||||
|
||||
|
||||
# ---------- Тесты для info и help (не используют rag_client) ----------
|
||||
@pytest.mark.asyncio
|
||||
async def test_info_command(mock_bot, mock_msg):
|
||||
cmd = InfoCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, "", "user@domain", None)
|
||||
# Просто проверяем, что отправлено сообщение
|
||||
mock_msg.reply.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_help_command(mock_bot, mock_msg):
|
||||
# Подготовка: бот должен иметь команды
|
||||
mock_bot.commands = {
|
||||
"!clear": ClearCommand(mock_bot),
|
||||
"!help": HelpCommand(mock_bot),
|
||||
}
|
||||
cmd = HelpCommand(mock_bot)
|
||||
await cmd.execute(mock_msg, "", "user@domain", None)
|
||||
mock_msg.reply.assert_called_once()
|
||||
|
||||
# Проверка справки по конкретной команде
|
||||
mock_msg.reply.reset_mock()
|
||||
await cmd.execute(mock_msg, "!help !clear", "user@domain", None)
|
||||
mock_msg.reply.assert_called_once()
|
||||
Reference in New Issue
Block a user