import pytest from datetime import datetime, timezone, timedelta from httpx import AsyncClient from app.models.user import User from app.models.contest import Contest class TestListContests: """Tests for listing contests.""" async def test_list_contests_empty(self, client: AsyncClient, auth_headers: dict): """Test listing contests when none exist.""" response = await client.get("/api/contests/", headers=auth_headers) assert response.status_code == 200 assert response.json() == [] async def test_list_contests_with_data( self, client: AsyncClient, auth_headers: dict, test_contest: Contest ): """Test listing contests with existing data.""" response = await client.get("/api/contests/", headers=auth_headers) assert response.status_code == 200 data = response.json() assert len(data) == 1 assert data[0]["title"] == test_contest.title async def test_list_contests_unauthenticated(self, client: AsyncClient): """Test listing contests without authentication.""" response = await client.get("/api/contests/") assert response.status_code == 401 class TestGetContest: """Tests for getting a single contest.""" async def test_get_contest_success( self, client: AsyncClient, auth_headers: dict, test_contest: Contest ): """Test getting an existing contest.""" response = await client.get( f"/api/contests/{test_contest.id}", headers=auth_headers ) assert response.status_code == 200 data = response.json() assert data["id"] == test_contest.id assert data["title"] == test_contest.title assert data["description"] == test_contest.description async def test_get_contest_not_found(self, client: AsyncClient, auth_headers: dict): """Test getting a non-existent contest.""" response = await client.get("/api/contests/99999", headers=auth_headers) assert response.status_code == 404 class TestCreateContest: """Tests for creating contests.""" async def test_create_contest_as_admin( self, client: AsyncClient, admin_headers: dict ): """Test creating a contest as admin.""" now = datetime.now(timezone.utc) response = await client.post( "/api/contests/", headers=admin_headers, json={ "title": "New Contest", "description": "A new test contest", "start_time": (now + timedelta(hours=1)).isoformat(), "end_time": (now + timedelta(hours=3)).isoformat(), "is_active": False, }, ) assert response.status_code == 201 data = response.json() assert data["title"] == "New Contest" assert data["is_active"] is False async def test_create_contest_as_participant( self, client: AsyncClient, auth_headers: dict ): """Test that participants cannot create contests.""" now = datetime.now(timezone.utc) response = await client.post( "/api/contests/", headers=auth_headers, json={ "title": "New Contest", "description": "A new test contest", "start_time": (now + timedelta(hours=1)).isoformat(), "end_time": (now + timedelta(hours=3)).isoformat(), "is_active": False, }, ) assert response.status_code == 403 async def test_create_contest_without_title( self, client: AsyncClient, admin_headers: dict ): """Test creating a contest without required fields.""" now = datetime.now(timezone.utc) response = await client.post( "/api/contests/", headers=admin_headers, json={ "description": "A new test contest", "start_time": (now + timedelta(hours=1)).isoformat(), "end_time": (now + timedelta(hours=3)).isoformat(), }, ) assert response.status_code == 422 class TestUpdateContest: """Tests for updating contests.""" async def test_update_contest_as_admin( self, client: AsyncClient, admin_headers: dict, test_contest: Contest ): """Test updating a contest as admin.""" response = await client.put( f"/api/contests/{test_contest.id}", headers=admin_headers, json={"title": "Updated Title"}, ) assert response.status_code == 200 data = response.json() assert data["title"] == "Updated Title" async def test_update_contest_as_participant( self, client: AsyncClient, auth_headers: dict, test_contest: Contest ): """Test that participants cannot update contests.""" response = await client.put( f"/api/contests/{test_contest.id}", headers=auth_headers, json={"title": "Updated Title"}, ) assert response.status_code == 403 class TestDeleteContest: """Tests for deleting contests.""" async def test_delete_contest_as_admin( self, client: AsyncClient, admin_headers: dict, test_contest: Contest ): """Test deleting a contest as admin.""" response = await client.delete( f"/api/contests/{test_contest.id}", headers=admin_headers ) assert response.status_code == 204 # Verify it's deleted response = await client.get( f"/api/contests/{test_contest.id}", headers=admin_headers ) assert response.status_code == 404 async def test_delete_contest_as_participant( self, client: AsyncClient, auth_headers: dict, test_contest: Contest ): """Test that participants cannot delete contests.""" response = await client.delete( f"/api/contests/{test_contest.id}", headers=auth_headers ) assert response.status_code == 403 class TestJoinContest: """Tests for joining contests.""" async def test_join_contest_success( self, client: AsyncClient, auth_headers: dict, test_contest: Contest ): """Test successfully joining a contest.""" response = await client.post( f"/api/contests/{test_contest.id}/join", headers=auth_headers ) assert response.status_code in [200, 201] async def test_join_contest_twice( self, client: AsyncClient, auth_headers: dict, test_contest: Contest ): """Test joining a contest twice.""" # First join await client.post( f"/api/contests/{test_contest.id}/join", headers=auth_headers ) # Second join should fail or return success depending on implementation response = await client.post( f"/api/contests/{test_contest.id}/join", headers=auth_headers ) # Either 200 (idempotent) or 400 (already joined) assert response.status_code in [200, 400]