""" Tests for project management endpoints. """ from __future__ import annotations import uuid import pytest from httpx import AsyncClient from sqlalchemy.ext.asyncio import AsyncSession from cpv3.modules.projects.models import Project from cpv3.modules.users.models import User @pytest.fixture async def test_project(test_db_session: AsyncSession, test_user: User) -> Project: """Create a test project owned by test_user.""" project = Project( id=uuid.uuid4(), owner_id=test_user.id, name="Test Project", description="A test project", language="en", status="DRAFT", is_active=True, ) test_db_session.add(project) await test_db_session.commit() await test_db_session.refresh(project) return project @pytest.fixture async def other_project(test_db_session: AsyncSession, other_user: User) -> Project: """Create a project owned by another user.""" project = Project( id=uuid.uuid4(), owner_id=other_user.id, name="Other Project", description="Another user's project", language="en", status="DRAFT", is_active=True, ) test_db_session.add(project) await test_db_session.commit() await test_db_session.refresh(project) return project class TestListProjectsEndpoint: """Tests for GET /api/projects/.""" async def test_list_projects_authenticated( self, auth_client: AsyncClient, test_project: Project ): """Test listing projects as authenticated user.""" response = await auth_client.get("/api/projects/") assert response.status_code == 200 data = response.json() assert isinstance(data, list) async def test_list_projects_unauthenticated(self, async_client: AsyncClient): """Test listing projects without auth returns 401.""" response = await async_client.get("/api/projects/") assert response.status_code == 401 class TestCreateProjectEndpoint: """Tests for POST /api/projects/.""" async def test_create_project_success(self, auth_client: AsyncClient): """Test creating a new project.""" response = await auth_client.post( "/api/projects/", json={ "name": "New Project", "description": "A new project", "language": "en", }, ) assert response.status_code == 201 data = response.json() assert data["name"] == "New Project" assert data["description"] == "A new project" assert data["language"] == "en" assert data["status"] == "DRAFT" async def test_create_project_minimal(self, auth_client: AsyncClient): """Test creating a project with minimal fields.""" response = await auth_client.post( "/api/projects/", json={"name": "Minimal Project"}, ) assert response.status_code == 201 data = response.json() assert data["name"] == "Minimal Project" async def test_create_project_unauthenticated(self, async_client: AsyncClient): """Test creating project without auth returns 401.""" response = await async_client.post( "/api/projects/", json={"name": "Unauthorized Project"}, ) assert response.status_code == 401 class TestRetrieveProjectEndpoint: """Tests for GET /api/projects/{project_id}/.""" async def test_retrieve_own_project( self, auth_client: AsyncClient, test_project: Project ): """Test retrieving own project.""" response = await auth_client.get(f"/api/projects/{test_project.id}/") assert response.status_code == 200 data = response.json() assert data["id"] == str(test_project.id) assert data["name"] == test_project.name async def test_retrieve_other_project_as_staff( self, staff_client: AsyncClient, test_project: Project ): """Test staff can retrieve any project.""" response = await staff_client.get(f"/api/projects/{test_project.id}/") assert response.status_code == 200 data = response.json() assert data["id"] == str(test_project.id) async def test_retrieve_nonexistent_project(self, auth_client: AsyncClient): """Test retrieving nonexistent project returns 404.""" fake_id = uuid.uuid4() response = await auth_client.get(f"/api/projects/{fake_id}/") assert response.status_code == 404 assert response.json()["detail"] == "Not found" async def test_retrieve_other_project_forbidden( self, auth_client: AsyncClient, other_project: Project ): """Test regular user cannot retrieve other user's project.""" response = await auth_client.get(f"/api/projects/{other_project.id}/") assert response.status_code == 403 assert response.json()["detail"] == "Forbidden" class TestPatchProjectEndpoint: """Tests for PATCH /api/projects/{project_id}/.""" async def test_patch_own_project( self, auth_client: AsyncClient, test_project: Project ): """Test updating own project.""" response = await auth_client.patch( f"/api/projects/{test_project.id}/", json={"name": "Updated Project", "description": "Updated description"}, ) assert response.status_code == 200 data = response.json() assert data["name"] == "Updated Project" assert data["description"] == "Updated description" async def test_patch_project_status( self, auth_client: AsyncClient, test_project: Project ): """Test updating project status.""" response = await auth_client.patch( f"/api/projects/{test_project.id}/", json={"status": "PROCESSING"}, ) assert response.status_code == 200 data = response.json() assert data["status"] == "PROCESSING" async def test_patch_other_project_as_staff( self, staff_client: AsyncClient, test_project: Project ): """Test staff can update any project.""" response = await staff_client.patch( f"/api/projects/{test_project.id}/", json={"name": "Staff Updated"}, ) assert response.status_code == 200 data = response.json() assert data["name"] == "Staff Updated" async def test_patch_nonexistent_project(self, auth_client: AsyncClient): """Test patching nonexistent project returns 404.""" fake_id = uuid.uuid4() response = await auth_client.patch( f"/api/projects/{fake_id}/", json={"name": "Test"}, ) assert response.status_code == 404 async def test_patch_other_project_forbidden( self, auth_client: AsyncClient, other_project: Project ): """Test regular user cannot update other user's project.""" response = await auth_client.patch( f"/api/projects/{other_project.id}/", json={"name": "Hacked"}, ) assert response.status_code == 403 class TestDeleteProjectEndpoint: """Tests for DELETE /api/projects/{project_id}/.""" async def test_delete_own_project( self, auth_client: AsyncClient, test_project: Project ): """Test deleting (deactivating) own project.""" response = await auth_client.delete(f"/api/projects/{test_project.id}/") assert response.status_code == 204 async def test_delete_other_project_as_staff( self, staff_client: AsyncClient, test_project: Project ): """Test staff can delete any project.""" response = await staff_client.delete(f"/api/projects/{test_project.id}/") assert response.status_code == 204 async def test_delete_nonexistent_project(self, auth_client: AsyncClient): """Test deleting nonexistent project returns 404.""" fake_id = uuid.uuid4() response = await auth_client.delete(f"/api/projects/{fake_id}/") assert response.status_code == 404 async def test_delete_other_project_forbidden( self, auth_client: AsyncClient, other_project: Project ): """Test regular user cannot delete other user's project.""" response = await auth_client.delete(f"/api/projects/{other_project.id}/") assert response.status_code == 403