255 lines
8.2 KiB
Python
255 lines
8.2 KiB
Python
"""
|
|
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
|