feature: create multitasking

This commit is contained in:
Daniil
2026-02-04 02:19:50 +03:00
parent 67e0f22b4f
commit a25bf623ea
24 changed files with 5227 additions and 21 deletions
+365
View File
@@ -0,0 +1,365 @@
"""
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