# #region Test.Api.DashboardActionRoutes [C:3] [TYPE Module] [SEMANTICS test,dashboard,action,migration,backup] # @BRIEF Unit tests for dashboard action routes — migrate and backup. # @RELATION BINDS_TO -> [DashboardActionRoutes] # @TEST_EDGE: empty_dashboard_ids -> 400 # @TEST_EDGE: source_env_not_found -> 404 # @TEST_EDGE: target_env_not_found -> 404 # @TEST_EDGE: env_not_found_backup -> 404 # @TEST_EDGE: task_creation_fail -> 503 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, HTTPException 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.dashboards._action_routes import router from src.dependencies import get_current_user, 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_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) # ── migrate_dashboards ── class TestMigrateDashboards: """POST /api/dashboards/migrate""" def _make_env(self, id: str): env = MagicMock() env.id = id return env def test_migrate_success(self): """Happy path: migration task created returns 200 with task_id.""" mock_config = MagicMock() mock_config.get_environments.return_value = [ self._make_env("src-1"), self._make_env("tgt-1"), ] mock_task = MagicMock() mock_task.id = "task-123" mock_task_manager = AsyncMock() mock_task_manager.create_task.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.post("/api/dashboards/migrate", json={ "source_env_id": "src-1", "target_env_id": "tgt-1", "dashboard_ids": [1, 2, 3], }) assert resp.status_code == 200 assert resp.json()["task_id"] == "task-123" mock_task_manager.create_task.assert_called_once() def test_migrate_empty_dashboard_ids(self): """Empty dashboard_ids returns 400.""" mock_config = MagicMock() 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.post("/api/dashboards/migrate", json={ "source_env_id": "src-1", "target_env_id": "tgt-1", "dashboard_ids": [], }) assert resp.status_code == 400 assert "At least one dashboard ID" in resp.text def test_migrate_source_not_found(self): """Non-existent source env returns 404.""" mock_config = MagicMock() mock_config.get_environments.return_value = [self._make_env("tgt-1")] 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.post("/api/dashboards/migrate", json={ "source_env_id": "src-missing", "target_env_id": "tgt-1", "dashboard_ids": [1], }) assert resp.status_code == 404 assert "Source environment not found" in resp.text def test_migrate_target_not_found(self): """Non-existent target env returns 404.""" mock_config = MagicMock() mock_config.get_environments.return_value = [self._make_env("src-1")] 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.post("/api/dashboards/migrate", json={ "source_env_id": "src-1", "target_env_id": "tgt-missing", "dashboard_ids": [1], }) assert resp.status_code == 404 assert "Target environment not found" in resp.text def test_migrate_task_creation_fail(self): """Task creation failure returns 503.""" mock_config = MagicMock() mock_config.get_environments.return_value = [ self._make_env("src-1"), self._make_env("tgt-1"), ] mock_task_manager = AsyncMock() mock_task_manager.create_task.side_effect = Exception("DB down") 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.post("/api/dashboards/migrate", json={ "source_env_id": "src-1", "target_env_id": "tgt-1", "dashboard_ids": [1], }) assert resp.status_code == 503 def test_migrate_with_db_mappings(self): """Migration with replace_db_config and db_mappings.""" mock_config = MagicMock() mock_config.get_environments.return_value = [ self._make_env("src-1"), self._make_env("tgt-1"), ] mock_task = MagicMock() mock_task.id = "task-dbm" mock_task_manager = AsyncMock() mock_task_manager.create_task.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.post("/api/dashboards/migrate", json={ "source_env_id": "src-1", "target_env_id": "tgt-1", "dashboard_ids": [1], "replace_db_config": True, "db_mappings": {"old_db": "new_db"}, }) assert resp.status_code == 200 # ── backup_dashboards ── class TestBackupDashboards: """POST /api/dashboards/backup""" def test_backup_success(self): """Happy path: backup task created.""" env = MagicMock() env.id = "env-1" mock_config = MagicMock() mock_config.get_environments.return_value = [env] mock_task = MagicMock() mock_task.id = "task-backup-1" mock_task_manager = AsyncMock() mock_task_manager.create_task.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.post("/api/dashboards/backup", json={ "env_id": "env-1", "dashboard_ids": [10, 20], }) assert resp.status_code == 200 assert resp.json()["task_id"] == "task-backup-1" def test_backup_empty_dashboard_ids(self): """Empty dashboard_ids returns 400.""" mock_config = MagicMock() 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.post("/api/dashboards/backup", json={ "env_id": "env-1", "dashboard_ids": [], }) assert resp.status_code == 400 assert "At least one dashboard ID" in resp.text def test_backup_env_not_found(self): """Non-existent env returns 404.""" 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.post("/api/dashboards/backup", json={ "env_id": "env-ghost", "dashboard_ids": [1], }) assert resp.status_code == 404 assert "Environment not found" in resp.text def test_backup_with_schedule(self): """Backup with schedule.""" env = MagicMock() env.id = "env-1" mock_config = MagicMock() mock_config.get_environments.return_value = [env] mock_task = MagicMock() mock_task.id = "task-sched" mock_task_manager = AsyncMock() mock_task_manager.create_task.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.post("/api/dashboards/backup", json={ "env_id": "env-1", "dashboard_ids": [1, 2], "schedule": "0 0 * * *", }) assert resp.status_code == 200 def test_backup_task_creation_fail(self): """Task creation failure returns 503.""" env = MagicMock() env.id = "env-1" mock_config = MagicMock() mock_config.get_environments.return_value = [env] mock_task_manager = AsyncMock() mock_task_manager.create_task.side_effect = Exception("Queue full") 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.post("/api/dashboards/backup", json={ "env_id": "env-1", "dashboard_ids": [1], }) assert resp.status_code == 503 # #endregion Test.Api.DashboardActionRoutes