feat: add parser source dashboard and scheduling
Some checks failed
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 10m20s
CI/CD Pipeline / Run Tests (pull_request) Failing after 11m5s
CI/CD Pipeline / Build Docker Images (pull_request) Has been skipped
CI/CD Pipeline / Push to Gitea Registry (pull_request) Has been skipped

This commit is contained in:
2026-04-27 23:36:28 +02:00
parent 199d871923
commit 7879d54958
95 changed files with 15004 additions and 259 deletions

View File

@@ -1,9 +1,16 @@
"""Tests for core views (health checks)"""
"""Tests for core views and API endpoints."""
from unittest.mock import patch
from apps.core.models import JobStatus
from apps.core.services import BackgroundJobService
from apps.user.services import UserService
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from tests.apps.user.factories import UserFactory
class HealthCheckViewTest(APITestCase):
"""Tests for HealthCheckView"""
@@ -99,3 +106,121 @@ class APIVersioningURLTest(APITestCase):
"""Test reverse URL for password change"""
url = reverse("api_v1:user:password_change")
self.assertEqual(url, "/api/v1/users/password/change/")
class BackgroundJobApiTest(APITestCase):
"""Tests for background job API access rules."""
def setUp(self):
self.user = UserFactory.create_user()
tokens = UserService.get_tokens_for_user(self.user)
self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {tokens['access']}")
def test_job_status_denies_other_user_job(self):
"""Test users cannot read jobs owned by another user."""
other_user = UserFactory.create_user()
job = BackgroundJobService.create_job(
task_id="task-other-user",
task_name="test.task",
user_id=other_user.id,
)
response = self.client.get(
reverse("api_v1:jobs:job-status", kwargs={"task_id": job.task_id})
)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_job_status_denies_system_job_for_regular_user(self):
"""Test user_id=NULL jobs are not visible to regular users."""
job = BackgroundJobService.create_job(
task_id="task-system",
task_name="test.task",
)
response = self.client.get(
reverse("api_v1:jobs:job-status", kwargs={"task_id": job.task_id})
)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_job_status_allows_system_job_for_staff(self):
"""Test staff can inspect system jobs."""
staff = UserFactory.create_user(is_staff=True)
tokens = UserService.get_tokens_for_user(staff)
self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {tokens['access']}")
job = BackgroundJobService.create_job(
task_id="task-system-staff",
task_name="test.task",
)
response = self.client.get(
reverse("api_v1:jobs:job-status", kwargs={"task_id": job.task_id})
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_job_list_rejects_invalid_limit(self):
"""Test job list validates limit query parameter."""
response = self.client.get(
reverse("api_v1:jobs:job-list"),
{"limit": "bad"},
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
@patch("apps.core.views.current_app.control.revoke")
def test_job_control_revoke_marks_user_job_revoked(self, mock_revoke):
"""Test owner can revoke a running background job."""
job = BackgroundJobService.create_job(
task_id="task-revoke",
task_name="test.task",
user_id=self.user.id,
)
job.mark_started()
response = self.client.post(
reverse("api_v1:jobs:job-control", kwargs={"task_id": job.task_id}),
{"action": "revoke"},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
mock_revoke.assert_called_once_with(job.task_id, terminate=True)
job.refresh_from_db()
self.assertEqual(job.status, JobStatus.REVOKED)
def test_job_control_denies_finished_job(self):
"""Test finished jobs cannot be revoked again."""
job = BackgroundJobService.create_job(
task_id="task-finished",
task_name="test.task",
user_id=self.user.id,
)
job.complete()
response = self.client.post(
reverse("api_v1:jobs:job-control", kwargs={"task_id": job.task_id}),
{"action": "revoke"},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_job_stream_returns_completed_sse_event(self):
"""Test dev-compatible SSE job stream endpoint."""
job = BackgroundJobService.create_job(
task_id="task-stream",
task_name="test.task",
user_id=self.user.id,
)
job.complete(result={"ok": True})
response = self.client.get(
reverse("api_v1:jobs:job-stream", kwargs={"task_id": job.task_id})
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
content = b"".join(response.streaming_content).decode("utf-8")
self.assertIn("event: completed", content)
self.assertIn('"task_id": "task-stream"', content)