""" Тесты работы с участниками - 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_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 == 200 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 member.get("token") is None # 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 == 200 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 == 200 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)