Редактировать test_endpoints.py
This commit is contained in:
@@ -1,21 +1,293 @@
|
|||||||
|
# tests/integration/test_endpoints.py
|
||||||
"""
|
"""
|
||||||
Интеграционные тесты для новых эндпоинтов RAG-сервера.
|
Интеграционные тесты для новых эндпоинтов RAG-сервера.
|
||||||
|
|
||||||
|
Проверяются все эндпоинты, добавленные для команд бота:
|
||||||
|
- /rag/clear (личная, комнатная, глобальная)
|
||||||
|
- /rag/list
|
||||||
|
- /rag/delete_global
|
||||||
|
- /rag/template/* (save, list, delete)
|
||||||
|
- /rag/reset_history
|
||||||
|
- /rag/room/* (add, list)
|
||||||
|
- /rag/stats
|
||||||
|
- /rag/generate_document
|
||||||
|
|
||||||
|
Используется httpx.AsyncClient для отправки запросов к FastAPI приложению.
|
||||||
|
Все зависимости (оркестратор, БД, Qdrant) замоканы, чтобы тесты были изолированы.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import httpx
|
import httpx
|
||||||
from unittest.mock import AsyncMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from rag.rag_server import app
|
||||||
|
from rag.config_models import AppConfig
|
||||||
|
from rag.services.postgres_service import PostgresService
|
||||||
|
from rag.services.kb_service import KBService
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_orchestrator():
|
||||||
|
"""Создаёт мок для RAGOrchestrator с необходимыми методами."""
|
||||||
|
with patch("rag.rag_server.get_orchestrator") as mock_get:
|
||||||
|
orch = AsyncMock()
|
||||||
|
# Методы для очистки
|
||||||
|
orch.kb = AsyncMock()
|
||||||
|
orch.kb.clear_global_kb = AsyncMock(return_value=None)
|
||||||
|
orch.kb.clear_room_kb = AsyncMock(return_value=None)
|
||||||
|
orch.kb.clear_user_kb = AsyncMock(return_value=None)
|
||||||
|
orch.kb.get_sources_with_type = AsyncMock(return_value=[])
|
||||||
|
|
||||||
|
# Методы для шаблонов
|
||||||
|
orch.db = AsyncMock()
|
||||||
|
orch.db.save_template = AsyncMock(return_value=True)
|
||||||
|
orch.db.list_templates = AsyncMock(return_value=["template1", "template2"])
|
||||||
|
orch.db.delete_template = AsyncMock(return_value=True)
|
||||||
|
orch.db.add_room = AsyncMock(return_value=None)
|
||||||
|
orch.db.get_rooms = AsyncMock(return_value=["room1@conference.domain", "room2@conference.domain"])
|
||||||
|
orch.db.clear_room_history = AsyncMock(return_value=None)
|
||||||
|
orch.db.clear_user_history = AsyncMock(return_value=None)
|
||||||
|
orch.db.get_template = AsyncMock(return_value={"file_path": "/tmp/template.docx", "file_hash": "abc123"})
|
||||||
|
|
||||||
|
# Метод для генерации документа
|
||||||
|
orch.generate_document_from_template = AsyncMock(return_value="/tmp/generated.docx")
|
||||||
|
|
||||||
|
# Метод для статистики
|
||||||
|
orch.db.pool = AsyncMock()
|
||||||
|
orch.db.pool.acquire = AsyncMock()
|
||||||
|
conn = AsyncMock()
|
||||||
|
conn.fetchval = AsyncMock(return_value=42)
|
||||||
|
orch.db.pool.acquire.return_value.__aenter__.return_value = conn
|
||||||
|
|
||||||
|
mock_get.return_value = orch
|
||||||
|
yield orch
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client():
|
||||||
|
"""Создаёт асинхронный HTTP-клиент для тестового приложения."""
|
||||||
|
return httpx.AsyncClient(app=app, base_url="http://test")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_clear_endpoint():
|
async def test_clear_personal(client, mock_orchestrator):
|
||||||
async with httpx.AsyncClient() as client:
|
"""Тест очистки личной БЗ (room_jid=None, is_global=False)."""
|
||||||
with patch("rag.rag_server.get_orchestrator") as mock_orch:
|
payload = {
|
||||||
mock_orch.return_value.clear_kb = AsyncMock(return_value={"status": "ok"})
|
"user_jid": "user@domain",
|
||||||
response = await client.post(
|
"room_jid": None,
|
||||||
"http://localhost:8080/rag/clear",
|
"is_global": False
|
||||||
json={"user_jid": "test@domain", "room_jid": None, "is_global": False},
|
}
|
||||||
headers={"X-API-Key": "testkey"}
|
headers = {"X-API-Key": "testkey"}
|
||||||
)
|
response = await client.post("/rag/clear", json=payload, headers=headers)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
data = response.json()
|
data = response.json()
|
||||||
assert data.get("status") == "ok"
|
assert data.get("status") == "ok"
|
||||||
|
mock_orchestrator.kb.clear_user_kb.assert_called_once_with("user@domain")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_clear_room(client, mock_orchestrator):
|
||||||
|
"""Тест очистки комнатной БЗ (room_jid указан)."""
|
||||||
|
payload = {
|
||||||
|
"user_jid": "user@domain",
|
||||||
|
"room_jid": "room@conference.domain",
|
||||||
|
"is_global": False
|
||||||
|
}
|
||||||
|
headers = {"X-API-Key": "testkey"}
|
||||||
|
response = await client.post("/rag/clear", json=payload, headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data.get("status") == "ok"
|
||||||
|
mock_orchestrator.kb.clear_room_kb.assert_called_once_with("room@conference.domain")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_clear_global(client, mock_orchestrator):
|
||||||
|
"""Тест очистки глобальной БЗ (is_global=True)."""
|
||||||
|
payload = {
|
||||||
|
"user_jid": "admin@domain",
|
||||||
|
"room_jid": None,
|
||||||
|
"is_global": True
|
||||||
|
}
|
||||||
|
headers = {"X-API-Key": "testkey"}
|
||||||
|
response = await client.post("/rag/clear", json=payload, headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data.get("status") == "ok"
|
||||||
|
mock_orchestrator.kb.clear_global_kb.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_list_documents(client, mock_orchestrator):
|
||||||
|
"""Тест получения списка документов."""
|
||||||
|
mock_orchestrator.kb.get_sources_with_type.return_value = [
|
||||||
|
{"source_name": "doc1.docx", "is_global": False},
|
||||||
|
{"source_name": "doc2.pdf", "is_global": True}
|
||||||
|
]
|
||||||
|
payload = {
|
||||||
|
"user_jid": "user@domain",
|
||||||
|
"room_jid": None
|
||||||
|
}
|
||||||
|
headers = {"X-API-Key": "testkey"}
|
||||||
|
response = await client.post("/rag/list", json=payload, headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert "documents" in data
|
||||||
|
assert len(data["documents"]) == 2
|
||||||
|
assert data["documents"][0]["source_name"] == "doc1.docx"
|
||||||
|
assert data["documents"][1]["source_name"] == "doc2.pdf"
|
||||||
|
mock_orchestrator.kb.get_sources_with_type.assert_called_once_with("user@domain", None)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_delete_global(client, mock_orchestrator):
|
||||||
|
"""Тест удаления глобального документа."""
|
||||||
|
# Мокаем поиск doc_id
|
||||||
|
mock_orchestrator.db.pool.acquire.return_value.__aenter__.return_value.fetch = AsyncMock(return_value=[
|
||||||
|
{"id": 123}
|
||||||
|
])
|
||||||
|
mock_orchestrator.qdrant = AsyncMock()
|
||||||
|
mock_orchestrator.qdrant.delete_by_doc_id = MagicMock(return_value=None)
|
||||||
|
mock_orchestrator.db.delete_documents_by_ids = AsyncMock(return_value=None)
|
||||||
|
|
||||||
|
payload = {"source_name": "ГОСТ Р 12345"}
|
||||||
|
headers = {"X-API-Key": "testkey"}
|
||||||
|
response = await client.post("/rag/delete_global", json=payload, headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data.get("status") == "ok"
|
||||||
|
# Проверяем вызовы
|
||||||
|
mock_orchestrator.qdrant.delete_by_doc_id.assert_called_once_with(123)
|
||||||
|
mock_orchestrator.db.delete_documents_by_ids.assert_called_once_with([123])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_template_save(client, mock_orchestrator):
|
||||||
|
"""Тест сохранения шаблона."""
|
||||||
|
payload = {
|
||||||
|
"room_jid": "room@conference.domain",
|
||||||
|
"name": "Шаблон договора",
|
||||||
|
"file_path": "/tmp/template.docx",
|
||||||
|
"file_hash": "abc123"
|
||||||
|
}
|
||||||
|
headers = {"X-API-Key": "testkey"}
|
||||||
|
response = await client.post("/rag/template/save", json=payload, headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data.get("status") == "ok"
|
||||||
|
mock_orchestrator.db.save_template.assert_called_once_with(
|
||||||
|
"room@conference.domain", "Шаблон договора", "/tmp/template.docx", "abc123"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_template_list(client, mock_orchestrator):
|
||||||
|
"""Тест получения списка шаблонов."""
|
||||||
|
payload = {"room_jid": "room@conference.domain"}
|
||||||
|
headers = {"X-API-Key": "testkey"}
|
||||||
|
response = await client.post("/rag/template/list", json=payload, headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert "templates" in data
|
||||||
|
assert data["templates"] == ["template1", "template2"]
|
||||||
|
mock_orchestrator.db.list_templates.assert_called_once_with("room@conference.domain")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_template_delete(client, mock_orchestrator):
|
||||||
|
"""Тест удаления шаблона."""
|
||||||
|
payload = {
|
||||||
|
"room_jid": "room@conference.domain",
|
||||||
|
"name": "Шаблон договора"
|
||||||
|
}
|
||||||
|
headers = {"X-API-Key": "testkey"}
|
||||||
|
response = await client.post("/rag/template/delete", json=payload, headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data.get("status") == "ok"
|
||||||
|
mock_orchestrator.db.delete_template.assert_called_once_with("room@conference.domain", "Шаблон договора")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_reset_history_personal(client, mock_orchestrator):
|
||||||
|
"""Тест сброса личной истории."""
|
||||||
|
payload = {
|
||||||
|
"user_jid": "user@domain",
|
||||||
|
"room_jid": None
|
||||||
|
}
|
||||||
|
headers = {"X-API-Key": "testkey"}
|
||||||
|
response = await client.post("/rag/reset_history", json=payload, headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data.get("status") == "ok"
|
||||||
|
mock_orchestrator.db.clear_user_history.assert_called_once_with("user@domain")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_reset_history_room(client, mock_orchestrator):
|
||||||
|
"""Тест сброса истории комнаты."""
|
||||||
|
payload = {
|
||||||
|
"user_jid": "user@domain",
|
||||||
|
"room_jid": "room@conference.domain"
|
||||||
|
}
|
||||||
|
headers = {"X-API-Key": "testkey"}
|
||||||
|
response = await client.post("/rag/reset_history", json=payload, headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data.get("status") == "ok"
|
||||||
|
mock_orchestrator.db.clear_room_history.assert_called_once_with("room@conference.domain")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_add_room(client, mock_orchestrator):
|
||||||
|
"""Тест добавления комнаты."""
|
||||||
|
payload = {"room_jid": "newroom@conference.domain"}
|
||||||
|
headers = {"X-API-Key": "testkey"}
|
||||||
|
response = await client.post("/rag/room/add", json=payload, headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert data.get("status") == "ok"
|
||||||
|
mock_orchestrator.db.add_room.assert_called_once_with("newroom@conference.domain")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_rooms(client, mock_orchestrator):
|
||||||
|
"""Тест получения списка комнат."""
|
||||||
|
headers = {"X-API-Key": "testkey"}
|
||||||
|
response = await client.get("/rag/room/list", headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert "rooms" in data
|
||||||
|
assert data["rooms"] == ["room1@conference.domain", "room2@conference.domain"]
|
||||||
|
mock_orchestrator.db.get_rooms.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_stats(client, mock_orchestrator):
|
||||||
|
"""Тест получения статистики."""
|
||||||
|
headers = {"X-API-Key": "testkey"}
|
||||||
|
response = await client.post("/rag/stats", json={}, headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert "documents" in data
|
||||||
|
assert data["documents"] == 42
|
||||||
|
# Также проверяем uptime (может быть 0 или другое)
|
||||||
|
assert "uptime" in data
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_generate_document(client, mock_orchestrator):
|
||||||
|
"""Тест генерации документа по шаблону."""
|
||||||
|
payload = {
|
||||||
|
"template_name": "Шаблон договора",
|
||||||
|
"user_jid": "user@domain",
|
||||||
|
"room_jid": "room@conference.domain"
|
||||||
|
}
|
||||||
|
headers = {"X-API-Key": "testkey"}
|
||||||
|
response = await client.post("/rag/generate_document", json=payload, headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
data = response.json()
|
||||||
|
assert "document" in data
|
||||||
|
assert data["document"] == "/tmp/generated.docx"
|
||||||
|
mock_orchestrator.db.get_template.assert_called_once_with("room@conference.domain", "Шаблон договора")
|
||||||
|
mock_orchestrator.generate_document_from_template.assert_called_once()
|
||||||
Reference in New Issue
Block a user