366 lines
12 KiB
Python
366 lines
12 KiB
Python
"""
|
|
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
|