from unittest import mock import pytest from infrasynth.features.models import FeatureFlag from infrasynth.scheduler.models import ScheduledTask, TaskExecution pytestmark = pytest.mark.django_db TASKS_URL = "/api/scheduler/tasks/" EXECUTIONS_URL = "/api/scheduler/executions/" STATUS_URL = "/api/scheduler/status/" @pytest.fixture def task(): return ScheduledTask.objects.create( name="cleanup", task_path="os.getpid", schedule_type=ScheduledTask.ScheduleType.CRON, schedule_config={"cron": "0 2 * * *"}, ) @pytest.fixture def execution(task): return TaskExecution.objects.create( task=task, status=TaskExecution.Status.SUCCESS, started_at="2026-01-01T00:00:00Z", completed_at="2026-01-01T00:00:01Z", ) class TestScheduledTaskViewSet: def test_list_tasks(self, authenticated_client, task): response = authenticated_client.get(TASKS_URL) assert response.status_code == 200 assert response.data["count"] == 1 assert response.data["results"][0]["name"] == "cleanup" def test_requires_auth(self, api_client, task): assert api_client.get(TASKS_URL).status_code == 401 def test_create_task(self, authenticated_client): response = authenticated_client.post( TASKS_URL, { "name": "backup", "task_path": "os.getpid", "schedule_type": "interval", "schedule_config": {"interval": 3600}, }, format="json", ) assert response.status_code == 201 assert ScheduledTask.objects.filter(name="backup").exists() def test_retrieve_update_delete(self, authenticated_client, task): response = authenticated_client.get(f"{TASKS_URL}{task.id}/") assert response.status_code == 200 response = authenticated_client.patch(f"{TASKS_URL}{task.id}/", {"name": "renamed"}, format="json") assert response.status_code == 200 task.refresh_from_db() assert task.name == "renamed" response = authenticated_client.delete(f"{TASKS_URL}{task.id}/") assert response.status_code == 204 assert not ScheduledTask.objects.filter(pk=task.pk).exists() def test_run_action(self, authenticated_client, task): response = authenticated_client.post(f"{TASKS_URL}{task.id}/run/") assert response.status_code == 200 assert response.data["status"] == "triggered" assert TaskExecution.objects.filter(task=task).exists() def test_toggle_action(self, authenticated_client, task): response = authenticated_client.post(f"{TASKS_URL}{task.id}/toggle/") assert response.status_code == 200 assert response.data["is_active"] is False response = authenticated_client.post(f"{TASKS_URL}{task.id}/toggle/") assert response.data["is_active"] is True def test_disabled_feature_returns_404(self, authenticated_client): FeatureFlag.objects.create(slug="scheduler", name="Scheduler", is_active=False) assert authenticated_client.get(TASKS_URL).status_code == 404 class TestTaskExecutionViewSet: def test_list_executions(self, authenticated_client, execution): response = authenticated_client.get(EXECUTIONS_URL) assert response.status_code == 200 assert response.data["count"] == 1 assert response.data["results"][0]["task_info"]["name"] == "cleanup" def test_retrieve_execution(self, authenticated_client, execution): response = authenticated_client.get(f"{EXECUTIONS_URL}{execution.id}/") assert response.status_code == 200 assert response.data["status"] == "success" def test_requires_auth(self, api_client, execution): assert api_client.get(EXECUTIONS_URL).status_code == 401 class TestSchedulerStatusViewSet: def test_queue_status(self, authenticated_client): with mock.patch("celery.current_app") as app: inspect = app.control.inspect() inspect.active.return_value = {} inspect.scheduled.return_value = {} inspect.reserved.return_value = {} response = authenticated_client.get(f"{STATUS_URL}queue_status/") assert response.status_code == 200 assert response.data["total_active"] == 0 def test_workers(self, authenticated_client): with mock.patch("celery.current_app") as app: inspect = app.control.inspect() inspect.stats.return_value = {} inspect.active.return_value = {} response = authenticated_client.get(f"{STATUS_URL}workers/") assert response.status_code == 200 assert response.data == [] def test_requires_auth(self, api_client): assert api_client.get(f"{STATUS_URL}queue_status/").status_code == 401