docs/tests/test_projects.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

298 lines
11 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 операции, участники проектов
"""
import pytest
import httpx
import uuid
from conftest import assert_uuid, assert_timestamp
@pytest.mark.asyncio
async def test_get_projects_list(http_client: httpx.AsyncClient):
"""Test getting list of all projects"""
response = await http_client.get("/projects")
assert response.status_code == 200
projects = response.json()
assert isinstance(projects, list)
if projects: # Если есть проекты
project = projects[0]
assert "id" in project
assert "name" in project
assert "slug" in project
assert "description" in project
assert "repo_urls" in project
assert "status" in project
assert "task_counter" in project
assert "chat_id" in project
assert "auto_assign" in project
assert_uuid(project["id"])
assert project["status"] in ["active", "archived", "paused"]
assert isinstance(project["task_counter"], int)
assert isinstance(project["auto_assign"], bool)
@pytest.mark.asyncio
async def test_get_project_by_id(http_client: httpx.AsyncClient, test_project: dict):
"""Test getting specific project by ID"""
response = await http_client.get(f"/projects/{test_project['id']}")
assert response.status_code == 200
project = response.json()
assert project["id"] == test_project["id"]
assert project["name"] == test_project["name"]
assert project["slug"] == test_project["slug"]
assert project["status"] == "active"
@pytest.mark.asyncio
async def test_get_project_not_found(http_client: httpx.AsyncClient):
"""Test getting non-existent project returns 404"""
fake_id = str(uuid.uuid4())
response = await http_client.get(f"/projects/{fake_id}")
assert response.status_code == 404
@pytest.mark.asyncio
async def test_create_project(http_client: httpx.AsyncClient):
"""Test creating new project"""
project_slug = f"new-project-{uuid.uuid4().hex[:8]}"
project_data = {
"name": f"New Test Project {project_slug}",
"slug": project_slug,
"description": "A brand new test project",
"repo_urls": [
"https://github.com/test/repo1",
"https://github.com/test/repo2"
]
}
response = await http_client.post("/projects", json=project_data)
assert response.status_code == 201
project = response.json()
assert project["name"] == project_data["name"]
assert project["slug"] == project_data["slug"]
assert project["description"] == project_data["description"]
assert project["repo_urls"] == project_data["repo_urls"]
assert project["status"] == "active"
assert project["task_counter"] == 0
assert project["auto_assign"] is True # По умолчанию
assert_uuid(project["id"])
assert_uuid(project["chat_id"]) # Автоматически создаётся основной чат
# Cleanup
await http_client.delete(f"/projects/{project['id']}")
@pytest.mark.asyncio
async def test_create_project_duplicate_slug(http_client: httpx.AsyncClient, test_project: dict):
"""Test creating project with duplicate slug returns 409"""
project_data = {
"name": "Another Project",
"slug": test_project["slug"], # Используем существующий slug
"description": "Should fail"
}
response = await http_client.post("/projects", json=project_data)
assert response.status_code == 409
@pytest.mark.asyncio
async def test_create_project_minimal_data(http_client: httpx.AsyncClient):
"""Test creating project with minimal required data"""
project_slug = f"minimal-{uuid.uuid4().hex[:8]}"
project_data = {
"name": f"Minimal Project {project_slug}",
"slug": project_slug
# description и repo_urls опциональны
}
response = await http_client.post("/projects", json=project_data)
assert response.status_code == 201
project = response.json()
assert project["name"] == project_data["name"]
assert project["slug"] == project_data["slug"]
assert project["description"] is None
assert project["repo_urls"] == []
# Cleanup
await http_client.delete(f"/projects/{project['id']}")
@pytest.mark.asyncio
async def test_update_project(http_client: httpx.AsyncClient, test_project: dict):
"""Test updating project information"""
update_data = {
"name": "Updated Project Name",
"description": "Updated description",
"repo_urls": ["https://github.com/updated/repo"],
"auto_assign": False
}
response = await http_client.patch(f"/projects/{test_project['id']}", json=update_data)
assert response.status_code == 200
project = response.json()
assert project["name"] == "Updated Project Name"
assert project["description"] == "Updated description"
assert project["repo_urls"] == ["https://github.com/updated/repo"]
assert project["auto_assign"] is False
assert project["id"] == test_project["id"]
assert project["slug"] == test_project["slug"] # slug не изменился
@pytest.mark.asyncio
async def test_update_project_slug(http_client: httpx.AsyncClient, test_project: dict):
"""Test updating project slug"""
new_slug = f"updated-slug-{uuid.uuid4().hex[:8]}"
update_data = {"slug": new_slug}
response = await http_client.patch(f"/projects/{test_project['id']}", json=update_data)
assert response.status_code == 200
project = response.json()
assert project["slug"] == new_slug
@pytest.mark.asyncio
async def test_update_project_status(http_client: httpx.AsyncClient, test_project: dict):
"""Test updating project status"""
update_data = {"status": "archived"}
response = await http_client.patch(f"/projects/{test_project['id']}", json=update_data)
assert response.status_code == 200
project = response.json()
assert project["status"] == "archived"
@pytest.mark.asyncio
async def test_delete_project(http_client: httpx.AsyncClient):
"""Test deleting project"""
# Создаём временный проект для удаления
project_slug = f"to-delete-{uuid.uuid4().hex[:8]}"
project_data = {
"name": f"Project to Delete {project_slug}",
"slug": project_slug,
"description": "Will be deleted"
}
response = await http_client.post("/projects", json=project_data)
assert response.status_code == 201
project = response.json()
# Удаляем проект
response = await http_client.delete(f"/projects/{project['id']}")
assert response.status_code == 200
data = response.json()
assert data["ok"] is True
# Проверяем что проект действительно удалён
response = await http_client.get(f"/projects/{project['id']}")
assert response.status_code == 404
@pytest.mark.asyncio
async def test_get_project_members(http_client: httpx.AsyncClient, test_project: dict):
"""Test getting project members list"""
response = await http_client.get(f"/projects/{test_project['id']}/members")
assert response.status_code == 200
members = response.json()
assert isinstance(members, list)
if members: # Если есть участники
member = members[0]
assert "id" in member
assert "name" in member
assert "slug" in member
assert "type" in member
assert "role" in member
assert_uuid(member["id"])
assert member["type"] in ["human", "agent", "bridge"]
assert member["role"] in ["owner", "admin", "member"]
@pytest.mark.asyncio
async def test_add_member_to_project(http_client: httpx.AsyncClient, test_project: dict, test_user: dict):
"""Test adding member to project"""
response = await http_client.post(f"/projects/{test_project['id']}/members", json={
"member_id": test_user["id"]
})
assert response.status_code == 200
data = response.json()
assert data["ok"] is True
# Проверяем что участник добавлен
response = await http_client.get(f"/projects/{test_project['id']}/members")
assert response.status_code == 200
members = response.json()
member_ids = [m["id"] for m in members]
assert test_user["id"] in member_ids
@pytest.mark.asyncio
async def test_add_nonexistent_member_to_project(http_client: httpx.AsyncClient, test_project: dict):
"""Test adding non-existent member to project returns 404"""
fake_member_id = str(uuid.uuid4())
response = await http_client.post(f"/projects/{test_project['id']}/members", json={
"member_id": fake_member_id
})
assert response.status_code == 404
@pytest.mark.asyncio
async def test_add_duplicate_member_to_project(http_client: httpx.AsyncClient, test_project: dict, test_user: dict):
"""Test adding already existing member to project returns 409"""
# Добавляем участника первый раз
response = await http_client.post(f"/projects/{test_project['id']}/members", json={
"member_id": test_user["id"]
})
assert response.status_code == 200
# Пытаемся добавить его же повторно
response = await http_client.post(f"/projects/{test_project['id']}/members", json={
"member_id": test_user["id"]
})
assert response.status_code == 409
@pytest.mark.asyncio
async def test_remove_member_from_project(http_client: httpx.AsyncClient, test_project: dict, test_user: dict):
"""Test removing member from project"""
# Сначала добавляем участника
response = await http_client.post(f"/projects/{test_project['id']}/members", json={
"member_id": test_user["id"]
})
assert response.status_code == 200
# Затем удаляем
response = await http_client.delete(f"/projects/{test_project['id']}/members/{test_user['id']}")
assert response.status_code == 200
data = response.json()
assert data["ok"] is True
# Проверяем что участник удалён
response = await http_client.get(f"/projects/{test_project['id']}/members")
assert response.status_code == 200
members = response.json()
member_ids = [m["id"] for m in members]
assert test_user["id"] not in member_ids
@pytest.mark.asyncio
async def test_remove_nonexistent_member_from_project(http_client: httpx.AsyncClient, test_project: dict):
"""Test removing non-existent member from project returns 404"""
fake_member_id = str(uuid.uuid4())
response = await http_client.delete(f"/projects/{test_project['id']}/members/{fake_member_id}")
assert response.status_code == 404