docs/tests/test_members.py
markov 2bab3cf60a Добавлены E2E тесты для Team Board API
- Полный набор тестов для всех модулей 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
2026-03-13 22:47:19 +01:00

287 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Тесты работы с участниками - CRUD операции, смена slug, токены агентов
"""
import pytest
import httpx
import uuid
from conftest import assert_uuid, assert_timestamp
@pytest.mark.asyncio
async def test_get_members_list(http_client: httpx.AsyncClient):
"""Test getting list of all members"""
response = await http_client.get("/members")
assert response.status_code == 200
members = response.json()
assert isinstance(members, list)
assert len(members) > 0
# Проверяем структуру первого участника
member = members[0]
assert "id" in member
assert "name" in member
assert "slug" in member
assert "type" in member
assert "role" in member
assert "status" in member
assert "is_active" in member
assert "last_seen_at" in member
assert_uuid(member["id"])
assert member["type"] in ["human", "agent", "bridge"]
assert member["role"] in ["owner", "admin", "member"]
assert member["status"] in ["online", "offline", "busy"]
assert isinstance(member["is_active"], bool)
@pytest.mark.asyncio
async def test_get_member_by_id(http_client: httpx.AsyncClient, test_user: dict):
"""Test getting specific member by ID"""
response = await http_client.get(f"/members/{test_user['id']}")
assert response.status_code == 200
member = response.json()
assert member["id"] == test_user["id"]
assert member["name"] == test_user["name"]
assert member["slug"] == test_user["slug"]
assert member["type"] == "human"
@pytest.mark.asyncio
async def test_get_member_not_found(http_client: httpx.AsyncClient):
"""Test getting non-existent member returns 404"""
fake_id = str(uuid.uuid4())
response = await http_client.get(f"/members/{fake_id}")
assert response.status_code == 404
@pytest.mark.asyncio
async def test_create_human_member(http_client: httpx.AsyncClient):
"""Test creating new human member"""
member_slug = f"test-human-{uuid.uuid4().hex[:8]}"
member_data = {
"name": f"Test Human {member_slug}",
"slug": member_slug,
"type": "human",
"role": "member"
}
response = await http_client.post("/members", json=member_data)
assert response.status_code == 201
member = response.json()
assert member["name"] == member_data["name"]
assert member["slug"] == member_data["slug"]
assert member["type"] == "human"
assert member["role"] == "member"
assert_uuid(member["id"])
# У человека не должно быть токена
assert "token" not in member
# Cleanup
await http_client.delete(f"/members/{member['id']}")
@pytest.mark.asyncio
async def test_create_agent_member(http_client: httpx.AsyncClient):
"""Test creating new agent member with configuration"""
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", "documentation"],
"labels": ["backend", "python", "api"],
"chat_listen": "mentions",
"task_listen": "assigned",
"prompt": "You are a test automation agent",
"model": "gpt-4"
}
}
response = await http_client.post("/members", json=agent_data)
assert response.status_code == 201
agent = response.json()
assert agent["name"] == agent_data["name"]
assert agent["slug"] == agent_data["slug"]
assert agent["type"] == "agent"
assert agent["role"] == "member"
assert_uuid(agent["id"])
# У агента должен быть токен при создании
assert "token" in agent
assert agent["token"].startswith("tb-")
# Проверяем конфигурацию агента
config = agent["agent_config"]
assert config["capabilities"] == agent_data["agent_config"]["capabilities"]
assert config["labels"] == agent_data["agent_config"]["labels"]
assert config["chat_listen"] == "mentions"
assert config["task_listen"] == "assigned"
# Cleanup
await http_client.delete(f"/members/{agent['id']}")
@pytest.mark.asyncio
async def test_create_member_duplicate_slug(http_client: httpx.AsyncClient, test_user: dict):
"""Test creating member with duplicate slug returns 409"""
member_data = {
"name": "Another User",
"slug": test_user["slug"], # Используем существующий slug
"type": "human",
"role": "member"
}
response = await http_client.post("/members", json=member_data)
assert response.status_code == 409
@pytest.mark.asyncio
async def test_update_member_info(http_client: httpx.AsyncClient, test_user: dict):
"""Test updating member information"""
update_data = {
"name": "Updated Test User",
"role": "admin"
}
response = await http_client.patch(f"/members/{test_user['id']}", json=update_data)
assert response.status_code == 200
member = response.json()
assert member["name"] == "Updated Test User"
assert member["role"] == "admin"
assert member["id"] == test_user["id"]
assert member["slug"] == test_user["slug"] # slug не изменился
@pytest.mark.asyncio
async def test_update_member_slug(http_client: httpx.AsyncClient, test_user: dict):
"""Test updating member slug"""
new_slug = f"updated-slug-{uuid.uuid4().hex[:8]}"
update_data = {"slug": new_slug}
response = await http_client.patch(f"/members/{test_user['id']}", json=update_data)
assert response.status_code == 200
member = response.json()
assert member["slug"] == new_slug
@pytest.mark.asyncio
async def test_update_agent_config(http_client: httpx.AsyncClient, test_agent: dict):
"""Test updating agent configuration"""
new_config = {
"agent_config": {
"capabilities": ["testing", "debugging"],
"labels": ["frontend", "javascript"],
"chat_listen": "all",
"task_listen": "mentions",
"prompt": "Updated test agent",
"model": "claude-3"
}
}
response = await http_client.patch(f"/members/{test_agent['id']}", json=new_config)
assert response.status_code == 200
agent = response.json()
config = agent["agent_config"]
assert config["capabilities"] == new_config["agent_config"]["capabilities"]
assert config["labels"] == new_config["agent_config"]["labels"]
assert config["chat_listen"] == "all"
assert config["task_listen"] == "mentions"
@pytest.mark.asyncio
async def test_regenerate_agent_token(http_client: httpx.AsyncClient, test_agent: dict):
"""Test regenerating agent token"""
response = await http_client.post(f"/members/{test_agent['id']}/regenerate-token")
assert response.status_code == 200
data = response.json()
assert "token" in data
assert data["token"].startswith("tb-")
assert data["token"] != test_agent["token"] # Новый токен должен отличаться
@pytest.mark.asyncio
async def test_regenerate_token_for_human_fails(http_client: httpx.AsyncClient, test_user: dict):
"""Test that regenerating token for human returns 400"""
response = await http_client.post(f"/members/{test_user['id']}/regenerate-token")
assert response.status_code == 400
@pytest.mark.asyncio
async def test_revoke_agent_token(http_client: httpx.AsyncClient, test_agent: dict):
"""Test revoking agent token"""
response = await http_client.post(f"/members/{test_agent['id']}/revoke-token")
assert response.status_code == 200
data = response.json()
assert data["ok"] is True
@pytest.mark.asyncio
async def test_revoke_token_for_human_fails(http_client: httpx.AsyncClient, test_user: dict):
"""Test that revoking token for human returns 400"""
response = await http_client.post(f"/members/{test_user['id']}/revoke-token")
assert response.status_code == 400
@pytest.mark.asyncio
async def test_update_member_status(agent_client: httpx.AsyncClient):
"""Test updating member status (for agents)"""
response = await agent_client.patch("/members/me/status?status=busy")
assert response.status_code == 200
data = response.json()
assert data["status"] == "busy"
@pytest.mark.asyncio
async def test_delete_member(http_client: httpx.AsyncClient):
"""Test soft deleting member (sets is_active=false)"""
# Создаём временного пользователя для удаления
member_slug = f"to-delete-{uuid.uuid4().hex[:8]}"
member_data = {
"name": f"User to Delete {member_slug}",
"slug": member_slug,
"type": "human",
"role": "member"
}
response = await http_client.post("/members", json=member_data)
assert response.status_code == 201
member = response.json()
# Удаляем пользователя
response = await http_client.delete(f"/members/{member['id']}")
assert response.status_code == 200
data = response.json()
assert data["ok"] is True
# Проверяем что пользователь помечен неактивным
response = await http_client.get(f"/members/{member['id']}")
assert response.status_code == 200
deleted_member = response.json()
assert deleted_member["is_active"] is False
@pytest.mark.asyncio
async def test_get_members_include_inactive(http_client: httpx.AsyncClient):
"""Test getting members list with inactive ones"""
response = await http_client.get("/members?include_inactive=true")
assert response.status_code == 200
members = response.json()
# Должен содержать как активных, так и неактивных
has_inactive = any(not m["is_active"] for m in members)
# Может и не быть неактивных, но запрос должен пройти
assert isinstance(members, list)