303 lines
13 KiB
Python
303 lines
13 KiB
Python
# tests/integration/test_commands.py
|
||
"""
|
||
Интеграционные тесты для переработанных команд бота.
|
||
Проверяют, что каждая команда вызывает правильный метод RAGClient с корректными аргументами.
|
||
Используются моки для RAGClient, чтобы изолировать тесты от реального HTTP-сервера.
|
||
"""
|
||
|
||
import pytest
|
||
from unittest.mock import AsyncMock, MagicMock, patch
|
||
|
||
# Импортируем все классы команд
|
||
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()
|
||
# Для команд, использующих 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()
|
||
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() |