298 lines
11 KiB
Python
298 lines
11 KiB
Python
"""
|
||
Тесты работы с проектами - 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 == 200
|
||
|
||
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 False # По умолчанию
|
||
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 == 200
|
||
|
||
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 == 200
|
||
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 |