- Полный набор тестов для всех модулей API - test_auth.py: аутентификация и JWT токены - test_members.py: CRUD участников, агенты, токены - test_projects.py: CRUD проектов, участники проектов - test_tasks.py: CRUD задач, этапы, назначения, зависимости - test_chat.py: сообщения, комментарии, mentions - test_files.py: upload/download файлов проектов - test_labels.py: CRUD лейблов, привязка к задачам - test_websocket.py: WebSocket подключения и события - test_streaming.py: агентный стриминг через WebSocket - conftest.py: фикстуры для подключения к API - requirements.txt: зависимости pytest, httpx, websockets - pytest.ini: настройки asyncio для pytest
174 lines
5.5 KiB
Python
174 lines
5.5 KiB
Python
"""
|
||
Conftest для E2E тестов Team Board API
|
||
Содержит фикстуры для подключения к API и создания тестовых данных
|
||
"""
|
||
import pytest
|
||
import pytest_asyncio
|
||
import httpx
|
||
import uuid
|
||
from typing import Dict, Any, Optional
|
||
|
||
|
||
@pytest.fixture(scope="session")
|
||
def base_url():
|
||
"""Базовый URL для API"""
|
||
return "http://localhost:8100/api/v1"
|
||
|
||
|
||
@pytest_asyncio.fixture
|
||
async def admin_token(base_url: str) -> str:
|
||
"""Получает JWT токен администратора"""
|
||
async with httpx.AsyncClient() as client:
|
||
response = await client.post(f"{base_url}/auth/login", json={
|
||
"login": "admin",
|
||
"password": "teamboard"
|
||
})
|
||
assert response.status_code == 200
|
||
data = response.json()
|
||
return data["token"]
|
||
|
||
|
||
@pytest.fixture
|
||
def agent_token():
|
||
"""Bearer токен для агента из документации"""
|
||
return "tb-coder-dev-token"
|
||
|
||
|
||
@pytest.fixture
|
||
def http_client(base_url: str, admin_token: str):
|
||
"""HTTP клиент с авторизацией для админа"""
|
||
headers = {"Authorization": f"Bearer {admin_token}"}
|
||
return httpx.AsyncClient(base_url=base_url, headers=headers, timeout=30.0)
|
||
|
||
|
||
@pytest.fixture
|
||
def agent_client(base_url: str, agent_token: str):
|
||
"""HTTP клиент с авторизацией для агента"""
|
||
headers = {"Authorization": f"Bearer {agent_token}"}
|
||
return httpx.AsyncClient(base_url=base_url, headers=headers, timeout=30.0)
|
||
|
||
|
||
@pytest_asyncio.fixture
|
||
async def test_project(http_client: httpx.AsyncClient) -> Dict[str, Any]:
|
||
"""Создаёт тестовый проект и удаляет его после теста"""
|
||
project_slug = f"test-project-{uuid.uuid4().hex[:8]}"
|
||
project_data = {
|
||
"name": f"Test Project {project_slug}",
|
||
"slug": project_slug,
|
||
"description": "Test project for E2E tests",
|
||
"repo_urls": ["https://github.com/test/repo"]
|
||
}
|
||
|
||
response = await http_client.post("/projects", json=project_data)
|
||
assert response.status_code == 201
|
||
project = response.json()
|
||
|
||
yield project
|
||
|
||
# Cleanup - удаляем проект
|
||
await http_client.delete(f"/projects/{project['id']}")
|
||
|
||
|
||
@pytest_asyncio.fixture
|
||
async def test_user(http_client: httpx.AsyncClient) -> Dict[str, Any]:
|
||
"""Создаёт тестового пользователя и удаляет его после теста"""
|
||
user_slug = f"test-user-{uuid.uuid4().hex[:8]}"
|
||
user_data = {
|
||
"name": f"Test User {user_slug}",
|
||
"slug": user_slug,
|
||
"type": "human",
|
||
"role": "member"
|
||
}
|
||
|
||
response = await http_client.post("/members", json=user_data)
|
||
assert response.status_code == 201
|
||
user = response.json()
|
||
|
||
yield user
|
||
|
||
# Cleanup - помечаем пользователя неактивным
|
||
await http_client.delete(f"/members/{user['id']}")
|
||
|
||
|
||
@pytest_asyncio.fixture
|
||
async def test_agent(http_client: httpx.AsyncClient) -> Dict[str, Any]:
|
||
"""Создаёт тестового агента и удаляет его после теста"""
|
||
agent_slug = f"test-agent-{uuid.uuid4().hex[:8]}"
|
||
agent_data = {
|
||
"name": f"Test Agent {agent_slug}",
|
||
"slug": agent_slug,
|
||
"type": "agent",
|
||
"role": "member",
|
||
"agent_config": {
|
||
"capabilities": ["coding", "testing"],
|
||
"labels": ["backend", "python"],
|
||
"chat_listen": "mentions",
|
||
"task_listen": "assigned",
|
||
"prompt": "Test agent for E2E tests"
|
||
}
|
||
}
|
||
|
||
response = await http_client.post("/members", json=agent_data)
|
||
assert response.status_code == 201
|
||
agent = response.json()
|
||
|
||
yield agent
|
||
|
||
# Cleanup
|
||
await http_client.delete(f"/members/{agent['id']}")
|
||
|
||
|
||
@pytest_asyncio.fixture
|
||
async def test_task(http_client: httpx.AsyncClient, test_project: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""Создаёт тестовую задачу в проекте"""
|
||
task_data = {
|
||
"title": "Test Task",
|
||
"description": "Test task for E2E tests",
|
||
"type": "task",
|
||
"status": "backlog",
|
||
"priority": "medium"
|
||
}
|
||
|
||
response = await http_client.post(f"/tasks?project_id={test_project['id']}", json=task_data)
|
||
assert response.status_code == 201
|
||
task = response.json()
|
||
|
||
yield task
|
||
|
||
# Cleanup - задача удалится вместе с проектом
|
||
|
||
|
||
@pytest_asyncio.fixture
|
||
async def test_label(http_client: httpx.AsyncClient) -> Dict[str, Any]:
|
||
"""Создаёт тестовый лейбл"""
|
||
label_slug = f"test-label-{uuid.uuid4().hex[:8]}"
|
||
label_data = {
|
||
"name": label_slug,
|
||
"color": "#ff5733"
|
||
}
|
||
|
||
response = await http_client.post("/labels", json=label_data)
|
||
assert response.status_code == 201
|
||
label = response.json()
|
||
|
||
yield label
|
||
|
||
# Cleanup
|
||
await http_client.delete(f"/labels/{label['id']}")
|
||
|
||
|
||
def assert_uuid(value: str):
|
||
"""Проверяет что строка является валидным UUID"""
|
||
try:
|
||
uuid.UUID(value)
|
||
except (ValueError, TypeError):
|
||
pytest.fail(f"'{value}' is not a valid UUID")
|
||
|
||
|
||
def assert_timestamp(value: str):
|
||
"""Проверяет что строка является валидным ISO timestamp"""
|
||
import datetime
|
||
try:
|
||
datetime.datetime.fromisoformat(value.replace('Z', '+00:00'))
|
||
except ValueError:
|
||
pytest.fail(f"'{value}' is not a valid ISO timestamp") |