# #region Test.Api.DashboardDetailRoutes [C:3] [TYPE Module] [SEMANTICS test,dashboard,detail,api] # @BRIEF Unit tests for dashboard detail routes — db-mappings, detail, tasks history, thumbnail. # @RELATION BINDS_TO -> [DashboardDetailRoutes] # @TEST_EDGE: source_env_not_found -> 404 # @TEST_EDGE: target_env_not_found -> 404 # @TEST_EDGE: db_mapping_fail -> 503 # @TEST_EDGE: dashboard_not_found -> 503 # @TEST_EDGE: env_not_found_detail -> 404 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") 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_mock_env(id: str = "env-1", name: str = "TestEnv"): env = MagicMock() env.id = id env.name = name env.url = "https://superset.example.com" env.username = "admin" env.password = "pass" env.verify_ssl = True return env def _make_client(overrides: dict | None = None) -> TestClient: from src.api.routes.dashboards._detail_routes import router from src.dependencies import get_config_manager, get_current_user, get_mapping_service, get_task_manager, has_permission from src.schemas.auth import User, RoleSchema app = FastAPI() app.include_router(router) 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=[])], ) defaults = { get_config_manager: lambda: MagicMock(), get_mapping_service: lambda: MagicMock(), get_task_manager: lambda: MagicMock(), get_current_user: lambda: mock_user, } if overrides: defaults.update(overrides) for dep, mock_fn in defaults.items(): app.dependency_overrides[dep] = mock_fn return TestClient(app) # ── get_database_mappings ── class TestGetDatabaseMappings: """GET /api/dashboards/db-mappings""" def test_mappings_success(self): mock_config = MagicMock() mock_config.get_environments.return_value = [ _make_mock_env(id="src-env"), _make_mock_env(id="tgt-env"), ] mock_mapping = AsyncMock() mock_mapping.get_suggestions.return_value = [ {"source_db": "db1", "target_db": "db2", "source_db_uuid": "u1", "target_db_uuid": "u2", "confidence": 0.95}, ] from src.dependencies import get_config_manager, get_mapping_service client = _make_client({ get_config_manager: lambda: mock_config, get_mapping_service: lambda: mock_mapping, }) resp = client.get("/api/dashboards/db-mappings?source_env_id=src-env&target_env_id=tgt-env") assert resp.status_code == 200 data = resp.json() assert len(data["mappings"]) == 1 assert data["mappings"][0]["source_db"] == "db1" def test_mappings_source_not_found(self): mock_config = MagicMock() mock_config.get_environments.return_value = [_make_mock_env(id="tgt-env")] from src.dependencies import get_config_manager client = _make_client({get_config_manager: lambda: mock_config}) resp = client.get("/api/dashboards/db-mappings?source_env_id=src-ghost&target_env_id=tgt-env") assert resp.status_code == 404 assert "Source environment not found" in resp.text def test_mappings_target_not_found(self): mock_config = MagicMock() mock_config.get_environments.return_value = [_make_mock_env(id="src-env")] from src.dependencies import get_config_manager client = _make_client({get_config_manager: lambda: mock_config}) resp = client.get("/api/dashboards/db-mappings?source_env_id=src-env&target_env_id=tgt-ghost") assert resp.status_code == 404 assert "Target environment not found" in resp.text def test_mappings_service_fail(self): mock_config = MagicMock() mock_config.get_environments.return_value = [ _make_mock_env(id="src-env"), _make_mock_env(id="tgt-env"), ] mock_mapping = AsyncMock() mock_mapping.get_suggestions.side_effect = Exception("Service unavailable") from src.dependencies import get_config_manager, get_mapping_service client = _make_client({ get_config_manager: lambda: mock_config, get_mapping_service: lambda: mock_mapping, }) resp = client.get("/api/dashboards/db-mappings?source_env_id=src-env&target_env_id=tgt-env") assert resp.status_code == 503 assert "Failed to get database mappings" in resp.text # ── get_dashboard_detail ── class TestGetDashboardDetail: """GET /api/dashboards/{dashboard_ref}""" def test_detail_success(self): mock_config = MagicMock() mock_config.get_environments.return_value = [_make_mock_env(id="env-1")] mock_client = AsyncMock() mock_client.get_dashboards_page.return_value = (1, [{"id": 42}]) mock_client.get_dashboard_detail.return_value = { "id": 42, "title": "Test Dash", "chart_count": 5, "dataset_count": 3, "charts": [{"id": 1, "title": "Chart 1", "viz_type": "line"}], "datasets": [{"id": 1, "table_name": "sales", "database": "prod"}], } from src.dependencies import get_config_manager with patch("src.api.routes.dashboards._detail_routes.SupersetClient", return_value=mock_client), \ patch("src.api.routes.dashboards._detail_routes.get_superset_client") as mock_reg: mock_reg.return_value = AsyncMock() client = _make_client({get_config_manager: lambda: mock_config}) resp = client.get("/api/dashboards/42?env_id=env-1") assert resp.status_code == 200 data = resp.json() assert data["id"] == 42 def test_detail_env_not_found(self): mock_config = MagicMock() mock_config.get_environments.return_value = [] from src.dependencies import get_config_manager client = _make_client({get_config_manager: lambda: mock_config}) resp = client.get("/api/dashboards/42?env_id=env-ghost") assert resp.status_code == 404 assert "Environment not found" in resp.text # ── get_dashboard_tasks_history ── class TestGetDashboardTasksHistory: """GET /api/dashboards/{dashboard_ref}/tasks""" def test_tasks_history_success(self): mock_config = MagicMock() mock_config.get_environments.return_value = [_make_mock_env(id="env-1")] mock_task = MagicMock() mock_task.id = "task-1" mock_task.plugin_id = "superset-backup" mock_task.status = "SUCCESS" mock_task.params = {"dashboards": ["42"], "environment_id": "env-1"} mock_task.result = {"status": "ok", "summary": "Backup done"} mock_task.started_at = __import__("datetime").datetime(2024, 1, 1) mock_task.finished_at = __import__("datetime").datetime(2024, 1, 1, 0, 1) mock_task_manager = MagicMock() mock_task_manager.get_all_tasks.return_value = [mock_task] from src.dependencies import get_config_manager, get_task_manager client = _make_client({ get_config_manager: lambda: mock_config, get_task_manager: lambda: mock_task_manager, }) resp = client.get("/api/dashboards/42/tasks?env_id=env-1") assert resp.status_code == 200 data = resp.json() assert data["dashboard_id"] == 42 assert len(data["items"]) == 1 assert data["items"][0]["id"] == "task-1" def test_tasks_history_empty(self): mock_config = MagicMock() mock_config.get_environments.return_value = [_make_mock_env(id="env-1")] mock_task_manager = MagicMock() mock_task_manager.get_all_tasks.return_value = [] from src.dependencies import get_config_manager, get_task_manager client = _make_client({ get_config_manager: lambda: mock_config, get_task_manager: lambda: mock_task_manager, }) resp = client.get("/api/dashboards/42/tasks?env_id=env-1") assert resp.status_code == 200 assert resp.json()["items"] == [] def test_tasks_history_slug_no_env_id(self): mock_task_manager = MagicMock() from src.dependencies import get_config_manager, get_task_manager client = _make_client({ get_config_manager: lambda: MagicMock(), get_task_manager: lambda: mock_task_manager, }) resp = client.get("/api/dashboards/slug-ref/tasks") assert resp.status_code == 400 assert "env_id is required" in resp.text def test_tasks_history_env_not_found(self): mock_config = MagicMock() mock_config.get_environments.return_value = [] from src.dependencies import get_config_manager, get_task_manager client = _make_client({ get_config_manager: lambda: mock_config, get_task_manager: lambda: MagicMock(), }) resp = client.get("/api/dashboards/slug-ref/tasks?env_id=env-ghost") assert resp.status_code == 404 assert "Environment not found" in resp.text # ── get_dashboard_thumbnail ── class TestGetDashboardThumbnail: """GET /api/dashboards/{dashboard_ref}/thumbnail""" def test_thumbnail_success(self): mock_config = MagicMock() mock_config.get_environments.return_value = [_make_mock_env(id="env-1")] mock_client = AsyncMock() mock_client.get_dashboards_page.return_value = (1, [{"id": 42}]) mock_async_client = AsyncMock() async def _fake_request(method, endpoint, **kwargs): if "cache_dashboard_screenshot" in endpoint: return {"result": {"image_url": "/api/v1/dashboard/42/thumbnail/digest-1/"}} if "raw_response" in kwargs and kwargs["raw_response"]: mock_resp = MagicMock() mock_resp.status_code = 200 mock_resp.headers = {"Content-Type": "image/png"} mock_resp.content = b"fake-image-bytes" return mock_resp return {} mock_async_client.request.side_effect = _fake_request mock_async_client.get_dashboards_page.return_value = (1, [{"id": 42}]) from src.dependencies import get_config_manager with patch("src.api.routes.dashboards._detail_routes.SupersetClient", return_value=mock_client), \ patch("src.api.routes.dashboards._detail_routes.get_superset_client", return_value=mock_async_client): client = _make_client({get_config_manager: lambda: mock_config}) resp = client.get("/api/dashboards/42/thumbnail?env_id=env-1") assert resp.status_code == 200 assert resp.content == b"fake-image-bytes" def test_thumbnail_env_not_found(self): mock_config = MagicMock() mock_config.get_environments.return_value = [] from src.dependencies import get_config_manager client = _make_client({get_config_manager: lambda: mock_config}) resp = client.get("/api/dashboards/42/thumbnail?env_id=env-ghost") assert resp.status_code == 404 assert "Environment not found" in resp.text # #endregion Test.Api.DashboardDetailRoutes