# ... (same header as before) import os os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:") os.environ.setdefault("AUTH_DATABASE_URL", "sqlite:///:memory:") os.environ.setdefault("SECRET_KEY", "test-secret-key-for-tests") os.environ.setdefault("DEV_MODE", "true") import sys from pathlib import Path from unittest.mock import AsyncMock, MagicMock, patch import pytest from fastapi import FastAPI from fastapi.testclient import TestClient _src = str(Path(__file__).resolve().parent.parent.parent / "src") if _src not in sys.path: sys.path.insert(0, _src) def _make_client(overrides: dict | None = None) -> TestClient: from src.api.routes.tasks import router from src.core.database import get_db from src.dependencies import get_config_manager, get_current_user, get_task_manager from src.schemas.auth import User, RoleSchema app = FastAPI() app.include_router(router, prefix="/api/tasks") mock_user = User( id="admin-1", username="admin", email="admin@x.com", auth_source="LOCAL", created_at=__import__("datetime").datetime.now(), roles=[RoleSchema(id="r1", name="Admin", description="", permissions=[])], ) app.dependency_overrides[get_db] = lambda: MagicMock() app.dependency_overrides[get_current_user] = lambda: mock_user app.dependency_overrides[get_config_manager] = lambda: MagicMock() app.dependency_overrides[get_task_manager] = lambda: MagicMock() if overrides: for dep, fn in overrides.items(): app.dependency_overrides[dep] = fn return TestClient(app) # ── create_task ── class TestCreateTask: """POST /api/tasks""" def test_success(self): mock_tm = MagicMock() mock_tm.create_task = AsyncMock(return_value=MagicMock(id="task-1", plugin_id="backup", status="PENDING", params={}, created_at=None)) from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.post("/api/tasks", json={"plugin_id": "superset-backup", "params": {"dashboard_id": 42}}) assert resp.status_code == 201 def test_deprecated_llm_validation_returns_400(self): client = _make_client() resp = client.post("/api/tasks", json={ "plugin_id": "llm_dashboard_validation", "params": {"dashboard_ids": ["dash-1"], "environment_id": "env-1"}, }) assert resp.status_code == 400 def test_value_error(self): mock_tm = MagicMock() mock_tm.create_task = AsyncMock(side_effect=ValueError("Plugin not found")) from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.post("/api/tasks", json={"plugin_id": "unknown", "params": {}}) assert resp.status_code == 404 # ── list_tasks ── class TestListTasks: """GET /api/tasks""" def test_success(self): mock_tm = MagicMock() mock_tm.get_tasks.return_value = [] from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.get("/api/tasks") assert resp.status_code == 200 def test_with_filters(self): mock_tm = MagicMock() mock_tm.get_tasks.return_value = [] from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.get("/api/tasks?limit=5&offset=10&status=RUNNING&completed_only=true") assert resp.status_code == 200 def test_with_plugin_ids(self): mock_tm = MagicMock() mock_tm.get_tasks.return_value = [] from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.get("/api/tasks?plugin_id=backup&plugin_id=migration") assert resp.status_code == 200 def test_with_task_type(self): mock_tm = MagicMock() mock_tm.get_tasks.return_value = [] from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.get("/api/tasks?task_type=llm_validation") assert resp.status_code == 200 def test_unsupported_task_type(self): client = _make_client() resp = client.get("/api/tasks?task_type=unsupported") assert resp.status_code == 400 # ── get_task ── class TestGetTask: """GET /api/tasks/{task_id}""" def test_success(self): mock_tm = MagicMock() mock_tm.get_task.return_value = MagicMock(id="task-123", plugin_id="backup", status="PENDING", params={}, created_at=None) from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.get("/api/tasks/task-123") assert resp.status_code == 200 assert resp.json()["id"] == "task-123" def test_not_found(self): mock_tm = MagicMock() mock_tm.get_task.return_value = None from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.get("/api/tasks/unknown") assert resp.status_code == 404 # ── get_task_logs ── class TestGetTaskLogs: """GET /api/tasks/{task_id}/logs""" def test_success(self): mock_tm = MagicMock() mock_tm.get_task.return_value = MagicMock(id="task-123") mock_tm.get_task_logs.return_value = [{"level": "INFO", "message": "test"}] from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.get("/api/tasks/task-123/logs") assert resp.status_code == 200 def test_with_filters(self): mock_tm = MagicMock() mock_tm.get_task.return_value = MagicMock(id="task-123") from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.get("/api/tasks/task-123/logs?level=INFO&source=worker&search=error&offset=0&limit=50") assert resp.status_code == 200 def test_task_not_found(self): mock_tm = MagicMock() mock_tm.get_task.return_value = None from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.get("/api/tasks/unknown/logs") assert resp.status_code == 404 # ── get_task_log_stats ── class TestGetTaskLogStats: """GET /api/tasks/{task_id}/logs/stats""" def test_success_with_logstats(self): from src.core.task_manager.models import LogStats mock_tm = MagicMock() mock_tm.get_task.return_value = MagicMock(id="task-123") mock_tm.get_task_log_stats.return_value = LogStats(total_count=5, by_level={"INFO": 3}, by_source={"worker": 5}) from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.get("/api/tasks/task-123/logs/stats") assert resp.status_code == 200 assert resp.json()["total_count"] == 5 def test_success_with_dict_payload(self): mock_tm = MagicMock() mock_tm.get_task.return_value = MagicMock(id="task-123") mock_tm.get_task_log_stats.return_value = {"total_count": 3, "by_level": {"INFO": 2}, "by_source": {"worker": 3}} from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.get("/api/tasks/task-123/logs/stats") assert resp.status_code == 200 def test_success_with_flat_dict(self): mock_tm = MagicMock() mock_tm.get_task.return_value = MagicMock(id="task-123") mock_tm.get_task_log_stats.return_value = {"INFO": 3, "ERROR": 1} from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.get("/api/tasks/task-123/logs/stats") assert resp.status_code == 200 assert resp.json()["total_count"] == 4 def test_task_not_found(self): mock_tm = MagicMock() mock_tm.get_task.return_value = None from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.get("/api/tasks/unknown/logs/stats") assert resp.status_code == 404 # ── get_task_log_sources ── class TestGetTaskLogSources: """GET /api/tasks/{task_id}/logs/sources""" def test_success(self): mock_tm = MagicMock() mock_tm.get_task.return_value = MagicMock(id="task-123") mock_tm.get_task_log_sources.return_value = ["worker", "api"] from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.get("/api/tasks/task-123/logs/sources") assert resp.status_code == 200 assert resp.json() == ["worker", "api"] def test_task_not_found(self): mock_tm = MagicMock() mock_tm.get_task.return_value = None from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.get("/api/tasks/unknown/logs/sources") assert resp.status_code == 404 # ── resolve_task ── class TestResolveTask: """POST /api/tasks/{task_id}/resolve""" def test_success(self): mock_tm = MagicMock() mock_tm.resolve_task = AsyncMock() mock_tm.get_task.return_value = MagicMock(id="task-123") from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.post("/api/tasks/task-123/resolve", json={"resolution_params": {"key": "value"}}) assert resp.status_code == 200 def test_value_error(self): mock_tm = MagicMock() mock_tm.resolve_task = AsyncMock(side_effect=ValueError("Cannot resolve")) from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.post("/api/tasks/task-123/resolve", json={"resolution_params": {}}) assert resp.status_code == 400 # ── resume_task ── class TestResumeTask: """POST /api/tasks/{task_id}/resume""" def test_success(self): mock_tm = MagicMock() mock_tm.resume_task_with_password = MagicMock() mock_tm.get_task.return_value = MagicMock(id="task-123") from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.post("/api/tasks/task-123/resume", json={"passwords": {"db": "pwd"}}) assert resp.status_code == 200 def test_value_error(self): mock_tm = MagicMock() mock_tm.resume_task_with_password = MagicMock(side_effect=ValueError("Cannot resume")) from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.post("/api/tasks/task-123/resume", json={"passwords": {}}) assert resp.status_code == 400 # ── clear_tasks ── class TestClearTasks: """DELETE /api/tasks""" def test_success(self): mock_tm = MagicMock() from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.delete("/api/tasks") assert resp.status_code == 204 def test_with_status(self): mock_tm = MagicMock() from src.dependencies import get_task_manager client = _make_client({get_task_manager: lambda: mock_tm}) resp = client.delete("/api/tasks?status=COMPLETED") assert resp.status_code == 204 # #endregion Test.Api.Tasks