""" Тесты работы с проектами - 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