""" Tests for jobs and events endpoints. """ from __future__ import annotations import uuid import pytest from httpx import AsyncClient from sqlalchemy.ext.asyncio import AsyncSession from cpv3.modules.jobs.models import Job, JobEvent from cpv3.modules.users.models import User @pytest.fixture async def test_job(test_db_session: AsyncSession, test_user: User) -> Job: """Create a test job owned by test_user.""" job = Job( id=uuid.uuid4(), broker_id="test-broker-123", user_id=test_user.id, status="PENDING", job_type="PENDING", is_active=True, ) test_db_session.add(job) await test_db_session.commit() await test_db_session.refresh(job) return job @pytest.fixture async def other_job(test_db_session: AsyncSession, other_user: User) -> Job: """Create a job owned by another user.""" job = Job( id=uuid.uuid4(), broker_id="other-broker-456", user_id=other_user.id, status="RUNNING", job_type="RUNNING", is_active=True, ) test_db_session.add(job) await test_db_session.commit() await test_db_session.refresh(job) return job @pytest.fixture async def test_event(test_db_session: AsyncSession, test_job: Job) -> JobEvent: """Create a test job event linked to test_job.""" event = JobEvent( id=uuid.uuid4(), job_id=test_job.id, event_type="started", payload={"message": "Job started"}, is_active=True, ) test_db_session.add(event) await test_db_session.commit() await test_db_session.refresh(event) return event class TestListJobsEndpoint: """Tests for GET /api/jobs/jobs/.""" async def test_list_jobs(self, auth_client: AsyncClient, test_job: Job): """Test listing jobs.""" response = await auth_client.get("/api/jobs/jobs/") assert response.status_code == 200 data = response.json() assert isinstance(data, list) async def test_list_jobs_unauthenticated(self, async_client: AsyncClient): """Test listing jobs without auth returns 401.""" response = await async_client.get("/api/jobs/jobs/") assert response.status_code == 401 class TestCreateJobEndpoint: """Tests for POST /api/jobs/jobs/.""" async def test_create_job_success(self, auth_client: AsyncClient): """Test creating a job.""" response = await auth_client.post( "/api/jobs/jobs/", json={ "broker_id": "new-broker-789", "status": "PENDING", "job_type": "PENDING", }, ) assert response.status_code == 201 data = response.json() assert data["broker_id"] == "new-broker-789" assert data["status"] == "PENDING" async def test_create_job_with_input_data(self, auth_client: AsyncClient): """Test creating a job with input data.""" response = await auth_client.post( "/api/jobs/jobs/", json={ "broker_id": "broker-with-data", "input_data": {"file_path": "uploads/test.mp4"}, }, ) assert response.status_code == 201 data = response.json() assert data["input_data"]["file_path"] == "uploads/test.mp4" async def test_create_job_unauthenticated(self, async_client: AsyncClient): """Test creating job without auth returns 401.""" response = await async_client.post( "/api/jobs/jobs/", json={"broker_id": "test"}, ) assert response.status_code == 401 class TestRetrieveJobEndpoint: """Tests for GET /api/jobs/jobs/{job_id}/.""" async def test_retrieve_own_job(self, auth_client: AsyncClient, test_job: Job): """Test retrieving own job.""" response = await auth_client.get(f"/api/jobs/jobs/{test_job.id}/") assert response.status_code == 200 data = response.json() assert data["id"] == str(test_job.id) assert data["broker_id"] == test_job.broker_id async def test_retrieve_other_job_as_staff( self, staff_client: AsyncClient, test_job: Job ): """Test staff can retrieve any job.""" response = await staff_client.get(f"/api/jobs/jobs/{test_job.id}/") assert response.status_code == 200 async def test_retrieve_nonexistent_job(self, auth_client: AsyncClient): """Test retrieving nonexistent job returns 404.""" fake_id = uuid.uuid4() response = await auth_client.get(f"/api/jobs/jobs/{fake_id}/") assert response.status_code == 404 async def test_retrieve_other_job_forbidden( self, auth_client: AsyncClient, other_job: Job ): """Test regular user cannot retrieve other user's job.""" response = await auth_client.get(f"/api/jobs/jobs/{other_job.id}/") assert response.status_code == 403 class TestPatchJobEndpoint: """Tests for PATCH /api/jobs/jobs/{job_id}/.""" async def test_patch_own_job(self, auth_client: AsyncClient, test_job: Job): """Test updating own job.""" response = await auth_client.patch( f"/api/jobs/jobs/{test_job.id}/", json={"status": "RUNNING", "current_message": "Processing..."}, ) assert response.status_code == 200 data = response.json() assert data["status"] == "RUNNING" assert data["current_message"] == "Processing..." async def test_patch_job_progress(self, auth_client: AsyncClient, test_job: Job): """Test updating job progress.""" response = await auth_client.patch( f"/api/jobs/jobs/{test_job.id}/", json={"project_pct": 50.0}, ) assert response.status_code == 200 data = response.json() assert data["project_pct"] == 50.0 async def test_patch_other_job_as_staff( self, staff_client: AsyncClient, test_job: Job ): """Test staff can update any job.""" response = await staff_client.patch( f"/api/jobs/jobs/{test_job.id}/", json={"status": "DONE"}, ) assert response.status_code == 200 async def test_patch_nonexistent_job(self, auth_client: AsyncClient): """Test patching nonexistent job returns 404.""" fake_id = uuid.uuid4() response = await auth_client.patch( f"/api/jobs/jobs/{fake_id}/", json={"status": "DONE"}, ) assert response.status_code == 404 async def test_patch_other_job_forbidden( self, auth_client: AsyncClient, other_job: Job ): """Test regular user cannot update other user's job.""" response = await auth_client.patch( f"/api/jobs/jobs/{other_job.id}/", json={"status": "CANCELLED"}, ) assert response.status_code == 403 class TestDeleteJobEndpoint: """Tests for DELETE /api/jobs/jobs/{job_id}/.""" async def test_delete_own_job(self, auth_client: AsyncClient, test_job: Job): """Test deleting own job.""" response = await auth_client.delete(f"/api/jobs/jobs/{test_job.id}/") assert response.status_code == 204 async def test_delete_other_job_as_staff( self, staff_client: AsyncClient, test_job: Job ): """Test staff can delete any job.""" response = await staff_client.delete(f"/api/jobs/jobs/{test_job.id}/") assert response.status_code == 204 async def test_delete_nonexistent_job(self, auth_client: AsyncClient): """Test deleting nonexistent job returns 404.""" fake_id = uuid.uuid4() response = await auth_client.delete(f"/api/jobs/jobs/{fake_id}/") assert response.status_code == 404 async def test_delete_other_job_forbidden( self, auth_client: AsyncClient, other_job: Job ): """Test regular user cannot delete other user's job.""" response = await auth_client.delete(f"/api/jobs/jobs/{other_job.id}/") assert response.status_code == 403 class TestListEventsEndpoint: """Tests for GET /api/jobs/events/.""" async def test_list_events(self, auth_client: AsyncClient, test_event: JobEvent): """Test listing job events.""" response = await auth_client.get("/api/jobs/events/") assert response.status_code == 200 data = response.json() assert isinstance(data, list) async def test_list_events_unauthenticated(self, async_client: AsyncClient): """Test listing events without auth returns 401.""" response = await async_client.get("/api/jobs/events/") assert response.status_code == 401 class TestCreateEventEndpoint: """Tests for POST /api/jobs/events/.""" async def test_create_event_success(self, auth_client: AsyncClient, test_job: Job): """Test creating a job event.""" response = await auth_client.post( "/api/jobs/events/", json={ "job_id": str(test_job.id), "event_type": "progress", "payload": {"percentage": 25}, }, ) assert response.status_code == 201 data = response.json() assert data["event_type"] == "progress" assert data["payload"]["percentage"] == 25 async def test_create_event_unauthenticated(self, async_client: AsyncClient): """Test creating event without auth returns 401.""" response = await async_client.post( "/api/jobs/events/", json={ "job_id": str(uuid.uuid4()), "event_type": "test", }, ) assert response.status_code == 401 class TestRetrieveEventEndpoint: """Tests for GET /api/jobs/events/{event_id}/.""" async def test_retrieve_event(self, auth_client: AsyncClient, test_event: JobEvent): """Test retrieving a job event.""" response = await auth_client.get(f"/api/jobs/events/{test_event.id}/") assert response.status_code == 200 data = response.json() assert data["id"] == str(test_event.id) assert data["event_type"] == test_event.event_type async def test_retrieve_nonexistent_event(self, auth_client: AsyncClient): """Test retrieving nonexistent event returns 404.""" fake_id = uuid.uuid4() response = await auth_client.get(f"/api/jobs/events/{fake_id}/") assert response.status_code == 404 class TestPatchEventEndpoint: """Tests for PATCH /api/jobs/events/{event_id}/.""" async def test_patch_event(self, auth_client: AsyncClient, test_event: JobEvent): """Test updating a job event.""" response = await auth_client.patch( f"/api/jobs/events/{test_event.id}/", json={"payload": {"updated": True}}, ) assert response.status_code == 200 data = response.json() assert data["payload"]["updated"] is True async def test_patch_nonexistent_event(self, auth_client: AsyncClient): """Test patching nonexistent event returns 404.""" fake_id = uuid.uuid4() response = await auth_client.patch( f"/api/jobs/events/{fake_id}/", json={"payload": {}}, ) assert response.status_code == 404 class TestDeleteEventEndpoint: """Tests for DELETE /api/jobs/events/{event_id}/.""" async def test_delete_event(self, auth_client: AsyncClient, test_event: JobEvent): """Test deleting a job event.""" response = await auth_client.delete(f"/api/jobs/events/{test_event.id}/") assert response.status_code == 204 async def test_delete_nonexistent_event(self, auth_client: AsyncClient): """Test deleting nonexistent event returns 404.""" fake_id = uuid.uuid4() response = await auth_client.delete(f"/api/jobs/events/{fake_id}/") assert response.status_code == 404