830 lines
30 KiB
Python
830 lines
30 KiB
Python
# #region Test.Api.Settings [C:3] [TYPE Module] [SEMANTICS test,settings,routes]
|
|
# @BRIEF Tests for settings API routes — get, update, storage, environments, logging, connections, policies, schedules.
|
|
# @RELATION BINDS_TO -> [SettingsRouter]
|
|
|
|
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_env(id_val="env-1", name="Test Env", url="https://example.com"):
|
|
env = MagicMock()
|
|
env.id = id_val
|
|
env.name = name
|
|
env.url = url
|
|
env.password = "real-password"
|
|
return env
|
|
|
|
|
|
def _make_client(overrides: dict | None = None) -> TestClient:
|
|
from src.api.routes.settings import router
|
|
from src.core.database import get_db
|
|
from src.dependencies import get_config_manager, get_current_user
|
|
from src.schemas.auth import User, RoleSchema
|
|
|
|
app = FastAPI()
|
|
app.include_router(router, prefix="/api/settings")
|
|
|
|
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()
|
|
if overrides:
|
|
for dep, fn in overrides.items():
|
|
app.dependency_overrides[dep] = fn
|
|
return TestClient(app)
|
|
|
|
|
|
# ── get_settings ──
|
|
|
|
def _make_real_config(environments=None):
|
|
"""Build a real AppConfig with default settings for mock testing."""
|
|
from src.core.config_models import (
|
|
AppConfig, GlobalSettings, StorageConfig, LoggingConfig, CleanReleaseConfig,
|
|
)
|
|
gs = GlobalSettings(
|
|
storage=StorageConfig(
|
|
root_path="/tmp/storage",
|
|
backup_path="/tmp/backup",
|
|
repo_path="/tmp/repo",
|
|
backup_structure_pattern="{env_id}/{backup_id}",
|
|
repo_structure_pattern="{env_id}/{repo_id}",
|
|
filename_pattern="{name}_{timestamp}",
|
|
),
|
|
logging=LoggingConfig(
|
|
level="INFO",
|
|
task_log_level="DEBUG",
|
|
file_path="/tmp/log.log",
|
|
enable_belief_state=True,
|
|
),
|
|
clean_release=CleanReleaseConfig(
|
|
active_policy_id="",
|
|
active_registry_id="",
|
|
),
|
|
default_environment_id="",
|
|
migration_sync_cron="0 0 * * *",
|
|
app_timezone="UTC",
|
|
)
|
|
return AppConfig(settings=gs, environments=environments or [])
|
|
|
|
|
|
class TestGetSettings:
|
|
"""GET /settings"""
|
|
|
|
def test_success(self):
|
|
mock_config = MagicMock()
|
|
mock_config.get_config.return_value = _make_real_config()
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.get("/api/settings")
|
|
assert resp.status_code == 200
|
|
|
|
def test_password_masked(self):
|
|
from src.core.config_models import Environment
|
|
env = Environment(
|
|
id="env-1", name="Test Env", url="https://example.com",
|
|
username="admin", password="real-password", stage="DEV",
|
|
)
|
|
real_config = _make_real_config(environments=[env])
|
|
|
|
mock_config = MagicMock()
|
|
mock_config.get_config.return_value = real_config
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.get("/api/settings")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
masked_env = data["environments"][0]
|
|
assert masked_env["password"] == "********"
|
|
|
|
|
|
class TestGetFeatures:
|
|
"""GET /api/settings/features"""
|
|
|
|
def test_success(self):
|
|
mock_config = MagicMock()
|
|
mock_cfg = MagicMock()
|
|
mock_cfg.settings.features.model_dump.return_value = {"translate": True, "migration": False}
|
|
mock_config.get_config.return_value = mock_cfg
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.get("/api/settings/features")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["translate"] is True
|
|
|
|
|
|
# ── get_allowed_languages ──
|
|
|
|
class TestGetAllowedLanguages:
|
|
"""GET /settings/allowed-languages"""
|
|
|
|
def test_success(self):
|
|
mock_config = MagicMock()
|
|
mock_cfg = MagicMock()
|
|
mock_cfg.settings.allowed_languages = ["en", "ru"]
|
|
mock_config.get_config.return_value = mock_cfg
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.get("/api/settings/allowed-languages")
|
|
assert resp.status_code == 200
|
|
assert resp.json() == ["en", "ru"]
|
|
|
|
|
|
# ── update_global_settings ──
|
|
|
|
class TestUpdateGlobalSettings:
|
|
"""PATCH /settings/global"""
|
|
|
|
def test_success(self):
|
|
mock_config = MagicMock()
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.patch("/api/settings/global", json={"app_name": "Test", "log_level": "INFO"})
|
|
assert resp.status_code == 200
|
|
|
|
|
|
# ── get_storage_settings ──
|
|
|
|
class TestGetStorageSettings:
|
|
"""GET /settings/storage"""
|
|
|
|
def test_success(self):
|
|
from src.core.config_models import StorageConfig
|
|
mock_config = MagicMock()
|
|
mock_cfg = MagicMock()
|
|
mock_cfg.settings.storage = StorageConfig(
|
|
root_path="/tmp/storage",
|
|
backup_path="/tmp/backup",
|
|
repo_path="/tmp/repo",
|
|
backup_structure_pattern="{env_id}/{backup_id}",
|
|
repo_structure_pattern="{env_id}/{repo_id}",
|
|
filename_pattern="{name}_{timestamp}",
|
|
)
|
|
mock_config.get_config.return_value = mock_cfg
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.get("/api/settings/storage")
|
|
assert resp.status_code == 200
|
|
|
|
|
|
# ── update_storage_settings ──
|
|
|
|
class TestUpdateStorageSettings:
|
|
"""PUT /settings/storage"""
|
|
|
|
def test_success(self):
|
|
mock_config = MagicMock()
|
|
mock_cfg = MagicMock()
|
|
mock_cfg.settings = MagicMock()
|
|
mock_cfg.settings.storage = MagicMock()
|
|
mock_config.get_config.return_value = mock_cfg
|
|
mock_config.validate_path.return_value = (True, "")
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.put("/api/settings/storage", json={
|
|
"root_path": "/data/storage",
|
|
"max_file_size_mb": 100,
|
|
"allowed_extensions": [".txt"],
|
|
"backup_retention_days": 30,
|
|
})
|
|
assert resp.status_code == 200
|
|
|
|
def test_invalid_path(self):
|
|
mock_config = MagicMock()
|
|
mock_cfg = MagicMock()
|
|
mock_cfg.settings = MagicMock()
|
|
mock_config.get_config.return_value = mock_cfg
|
|
mock_config.validate_path.return_value = (False, "Path is invalid")
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.put("/api/settings/storage", json={
|
|
"root_path": "/invalid",
|
|
"max_file_size_mb": 100,
|
|
"allowed_extensions": [],
|
|
"backup_retention_days": 30,
|
|
})
|
|
assert resp.status_code == 400
|
|
|
|
|
|
# ── get_environments ──
|
|
|
|
class TestGetEnvironments:
|
|
"""GET /settings/environments"""
|
|
|
|
def test_success(self):
|
|
from src.core.config_models import Environment
|
|
mock_config = MagicMock()
|
|
real_env = Environment(
|
|
id="env-1", name="Test Env", url="https://example.com",
|
|
username="admin", password="secret", stage="DEV",
|
|
)
|
|
mock_config.get_environments.return_value = [real_env]
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.get("/api/settings/environments")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert len(data) == 1
|
|
assert data[0]["id"] == "env-1"
|
|
|
|
|
|
# ── add_environment ──
|
|
|
|
class TestAddEnvironment:
|
|
"""POST /settings/environments"""
|
|
|
|
ENV_PAYLOAD = {
|
|
"id": "env-new",
|
|
"name": "New Env",
|
|
"url": "https://superset.example.com",
|
|
"username": "admin",
|
|
"password": "secret",
|
|
"stage": "DEV",
|
|
}
|
|
|
|
def test_success(self):
|
|
mock_config = MagicMock()
|
|
|
|
with patch("src.api.routes.settings.AsyncSupersetClient") as mock_client_cls:
|
|
mock_client = MagicMock()
|
|
mock_client.authenticate = AsyncMock()
|
|
mock_client.get_dashboards_page = AsyncMock(return_value=(0, []))
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.post("/api/settings/environments", json=self.ENV_PAYLOAD)
|
|
assert resp.status_code == 200
|
|
mock_config.add_environment.assert_called_once()
|
|
|
|
def test_connection_failure(self):
|
|
mock_config = MagicMock()
|
|
|
|
with patch("src.api.routes.settings.AsyncSupersetClient") as mock_client_cls:
|
|
mock_client = MagicMock()
|
|
mock_client.authenticate = AsyncMock(side_effect=RuntimeError("Connection refused"))
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.post("/api/settings/environments", json=self.ENV_PAYLOAD)
|
|
assert resp.status_code == 400
|
|
|
|
|
|
# ── update_environment ──
|
|
|
|
class TestUpdateEnvironment:
|
|
"""PUT /settings/environments/{id}"""
|
|
|
|
ENV_PAYLOAD = {
|
|
"id": "env-1",
|
|
"name": "Updated Env",
|
|
"url": "https://superset.example.com",
|
|
"username": "admin",
|
|
"password": "new-password",
|
|
"stage": "DEV",
|
|
}
|
|
|
|
def test_success(self):
|
|
mock_config = MagicMock()
|
|
mock_config.get_environments.return_value = [_make_env("env-1")]
|
|
mock_config.update_environment.return_value = True
|
|
|
|
with patch("src.api.routes.settings.AsyncSupersetClient") as mock_client_cls:
|
|
mock_client = MagicMock()
|
|
mock_client.authenticate = AsyncMock()
|
|
mock_client.get_dashboards_page = AsyncMock(return_value=(0, []))
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.put("/api/settings/environments/env-1", json=self.ENV_PAYLOAD)
|
|
assert resp.status_code == 200
|
|
|
|
def test_success_masked_password(self):
|
|
"""When password is '********', use the existing password for validation."""
|
|
mock_config = MagicMock()
|
|
old_env = _make_env("env-1")
|
|
old_env.password = "existing-real-pwd"
|
|
mock_config.get_environments.return_value = [old_env]
|
|
mock_config.update_environment.return_value = True
|
|
|
|
with patch("src.api.routes.settings.AsyncSupersetClient") as mock_client_cls:
|
|
mock_client = MagicMock()
|
|
mock_client.authenticate = AsyncMock()
|
|
mock_client.get_dashboards_page = AsyncMock(return_value=(0, []))
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.put("/api/settings/environments/env-1", json={
|
|
"id": "env-1", "name": "Updated", "url": "https://x.com",
|
|
"username": "admin", "password": "********", "stage": "DEV",
|
|
})
|
|
assert resp.status_code == 200
|
|
|
|
def test_connection_failure(self):
|
|
mock_config = MagicMock()
|
|
mock_config.get_environments.return_value = [_make_env("env-1")]
|
|
|
|
with patch("src.api.routes.settings.AsyncSupersetClient") as mock_client_cls:
|
|
mock_client = MagicMock()
|
|
mock_client.authenticate = AsyncMock(side_effect=RuntimeError("fail"))
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.put("/api/settings/environments/env-1", json=self.ENV_PAYLOAD)
|
|
assert resp.status_code == 400
|
|
|
|
def test_not_found(self):
|
|
mock_config = MagicMock()
|
|
mock_config.get_environments.return_value = [_make_env("env-1")]
|
|
mock_config.update_environment.return_value = False
|
|
|
|
with patch("src.api.routes.settings.AsyncSupersetClient") as mock_client_cls:
|
|
mock_client = MagicMock()
|
|
mock_client.authenticate = AsyncMock()
|
|
mock_client.get_dashboards_page = AsyncMock(return_value=(0, []))
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.put("/api/settings/environments/env-999", json=self.ENV_PAYLOAD)
|
|
assert resp.status_code == 404
|
|
|
|
|
|
# ── delete_environment ──
|
|
|
|
class TestDeleteEnvironment:
|
|
"""DELETE /settings/environments/{id}"""
|
|
|
|
def test_success(self):
|
|
mock_config = MagicMock()
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.delete("/api/settings/environments/env-1")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "deleted" in data["message"]
|
|
mock_config.delete_environment.assert_called_once_with("env-1")
|
|
|
|
|
|
# ── test_environment_connection ──
|
|
|
|
class TestTestEnvironmentConnection:
|
|
"""POST /settings/environments/{id}/test"""
|
|
|
|
def test_success(self):
|
|
mock_config = MagicMock()
|
|
mock_config.get_environments.return_value = [_make_env("env-1")]
|
|
|
|
with patch("src.api.routes.settings.AsyncSupersetClient") as mock_client_cls:
|
|
mock_client = MagicMock()
|
|
mock_client.authenticate = AsyncMock()
|
|
mock_client.get_dashboards_page = AsyncMock(return_value=(0, []))
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.post("/api/settings/environments/env-1/test")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["status"] == "success"
|
|
|
|
def test_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.post("/api/settings/environments/env-unknown/test")
|
|
assert resp.status_code == 404
|
|
|
|
def test_connection_error(self):
|
|
mock_config = MagicMock()
|
|
mock_config.get_environments.return_value = [_make_env("env-1")]
|
|
|
|
with patch("src.api.routes.settings.AsyncSupersetClient") as mock_client_cls:
|
|
mock_client = MagicMock()
|
|
mock_client.authenticate = AsyncMock(side_effect=RuntimeError("Connection failed"))
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.post("/api/settings/environments/env-1/test")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["status"] == "error"
|
|
|
|
|
|
# ── get_logging_config ──
|
|
|
|
class TestGetLoggingConfig:
|
|
"""GET /settings/logging"""
|
|
|
|
def test_success(self):
|
|
mock_config = MagicMock()
|
|
mock_cfg = MagicMock()
|
|
mock_logging = MagicMock()
|
|
mock_logging.level = "INFO"
|
|
mock_logging.task_log_level = "DEBUG"
|
|
mock_logging.enable_belief_state = True
|
|
mock_cfg.settings.logging = mock_logging
|
|
mock_config.get_config.return_value = mock_cfg
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.get("/api/settings/logging")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["level"] == "INFO"
|
|
assert data["task_log_level"] == "DEBUG"
|
|
assert data["enable_belief_state"] is True
|
|
|
|
|
|
# ── update_logging_config ──
|
|
|
|
class TestUpdateLoggingConfig:
|
|
"""PATCH /settings/logging"""
|
|
|
|
def test_success(self):
|
|
mock_config = MagicMock()
|
|
mock_cfg = MagicMock()
|
|
mock_cfg.settings = MagicMock()
|
|
mock_config.get_config.return_value = mock_cfg
|
|
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({get_config_manager: lambda: mock_config})
|
|
resp = client.patch("/api/settings/logging", json={
|
|
"level": "WARNING", "task_log_level": "INFO", "enable_belief_state": False,
|
|
})
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["level"] == "WARNING"
|
|
assert data["task_log_level"] == "INFO"
|
|
|
|
|
|
# ── get_validation_policies ──
|
|
|
|
class TestGetValidationPolicies:
|
|
"""GET /settings/automation/policies"""
|
|
|
|
def test_success(self):
|
|
from datetime import time, datetime, timezone
|
|
from src.models.llm import ValidationPolicy
|
|
|
|
mock_db = MagicMock()
|
|
mock_db.query.return_value.all.return_value = [
|
|
ValidationPolicy(
|
|
id="p1",
|
|
name="Test Policy",
|
|
environment_id="env-1",
|
|
is_active=True,
|
|
notify_owners=True,
|
|
alert_condition="FAIL_ONLY",
|
|
dashboard_ids=["d1"],
|
|
schedule_days=[1, 3, 5],
|
|
window_start=time(9, 0),
|
|
window_end=time(18, 0),
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc),
|
|
)
|
|
]
|
|
|
|
from src.core.database import get_db
|
|
client = _make_client({get_db: lambda: mock_db})
|
|
resp = client.get("/api/settings/automation/policies")
|
|
assert resp.status_code == 200
|
|
|
|
|
|
# ── create_validation_policy ──
|
|
|
|
class TestCreateValidationPolicy:
|
|
"""POST /settings/automation/policies"""
|
|
|
|
def test_success(self):
|
|
from datetime import datetime, timezone
|
|
|
|
mock_db = MagicMock()
|
|
now = datetime.now(timezone.utc)
|
|
|
|
# Simulate that db.refresh populates auto-generated fields (id, created_at, updated_at)
|
|
def _refresh(obj):
|
|
obj.id = getattr(obj, "id", None) or "new-id"
|
|
if getattr(obj, "created_at", None) is None:
|
|
obj.created_at = now
|
|
if getattr(obj, "updated_at", None) is None:
|
|
obj.updated_at = now
|
|
|
|
mock_db.refresh.side_effect = _refresh
|
|
|
|
from src.core.database import get_db
|
|
client = _make_client({get_db: lambda: mock_db})
|
|
resp = client.post("/api/settings/automation/policies", json={
|
|
"name": "Test Policy", "environment_id": "env-1", "dashboard_ids": [],
|
|
"schedule_days": [1, 3, 5], "window_start": "09:00", "window_end": "18:00",
|
|
})
|
|
assert resp.status_code == 200
|
|
mock_db.add.assert_called_once()
|
|
mock_db.commit.assert_called_once()
|
|
|
|
|
|
# ── update_validation_policy ──
|
|
|
|
class TestUpdateValidationPolicy:
|
|
"""PATCH /settings/automation/policies/{id}"""
|
|
|
|
def test_success(self):
|
|
from datetime import time, datetime, timezone
|
|
from src.models.llm import ValidationPolicy
|
|
|
|
mock_db = MagicMock()
|
|
mock_policy = ValidationPolicy(
|
|
id="p1", name="Test", environment_id="env-1",
|
|
is_active=True, notify_owners=True, alert_condition="FAIL_ONLY",
|
|
dashboard_ids=[], schedule_days=[1],
|
|
window_start=time(9, 0), window_end=time(18, 0),
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc),
|
|
)
|
|
mock_db.query.return_value.filter.return_value.first.return_value = mock_policy
|
|
|
|
from src.core.database import get_db
|
|
client = _make_client({get_db: lambda: mock_db})
|
|
resp = client.patch("/api/settings/automation/policies/p1", json={"name": "Updated"})
|
|
assert resp.status_code == 200
|
|
mock_db.commit.assert_called_once()
|
|
|
|
def test_not_found(self):
|
|
mock_db = MagicMock()
|
|
mock_db.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
from src.core.database import get_db
|
|
client = _make_client({get_db: lambda: mock_db})
|
|
resp = client.patch("/api/settings/automation/policies/unknown", json={"name": "X"})
|
|
assert resp.status_code == 404
|
|
|
|
|
|
# ── delete_validation_policy ──
|
|
|
|
class TestDeleteValidationPolicy:
|
|
"""DELETE /settings/automation/policies/{id}"""
|
|
|
|
def test_success(self):
|
|
mock_db = MagicMock()
|
|
mock_policy = MagicMock()
|
|
mock_db.query.return_value.filter.return_value.first.return_value = mock_policy
|
|
|
|
from src.core.database import get_db
|
|
client = _make_client({get_db: lambda: mock_db})
|
|
resp = client.delete("/api/settings/automation/policies/p1")
|
|
assert resp.status_code == 200
|
|
mock_db.delete.assert_called_once()
|
|
mock_db.commit.assert_called_once()
|
|
|
|
def test_not_found(self):
|
|
mock_db = MagicMock()
|
|
mock_db.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
from src.core.database import get_db
|
|
client = _make_client({get_db: lambda: mock_db})
|
|
resp = client.delete("/api/settings/automation/policies/unknown")
|
|
assert resp.status_code == 404
|
|
|
|
|
|
# ── get_translation_schedules ──
|
|
|
|
class TestGetTranslationSchedules:
|
|
"""GET /settings/automation/translation-schedules"""
|
|
|
|
def test_success(self):
|
|
mock_db = MagicMock()
|
|
mock_ts = MagicMock()
|
|
mock_ts.id = "sched-1"
|
|
mock_ts.job_id = "job-1"
|
|
mock_ts.cron_expression = "0 * * * *"
|
|
mock_ts.timezone = "UTC"
|
|
mock_ts.is_active = True
|
|
mock_ts.last_run_at = None
|
|
mock_ts.next_run_at = None
|
|
mock_ts.created_by = "admin"
|
|
mock_ts.created_at = None
|
|
mock_db.query.return_value.outerjoin.return_value.all.return_value = [(mock_ts, "Test Job")]
|
|
|
|
from src.core.database import get_db
|
|
client = _make_client({get_db: lambda: mock_db})
|
|
resp = client.get("/api/settings/automation/translation-schedules")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert len(data) == 1
|
|
assert data[0]["schedule_id"] == "sched-1"
|
|
assert data[0]["job_name"] == "Test Job"
|
|
|
|
|
|
# ── list_connections ──
|
|
|
|
class TestListConnections:
|
|
"""GET /settings/connections"""
|
|
|
|
def test_success(self):
|
|
mock_conn_svc = MagicMock()
|
|
mock_conn_svc.list_connections.return_value = [{"id": "conn-1", "name": "Test"}]
|
|
|
|
from src.api.routes.settings import _get_connection_service
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({
|
|
get_config_manager: lambda: MagicMock(),
|
|
_get_connection_service: lambda: mock_conn_svc,
|
|
})
|
|
resp = client.get("/api/settings/connections")
|
|
assert resp.status_code == 200
|
|
|
|
|
|
# ── get_connection ──
|
|
|
|
class TestGetConnection:
|
|
"""GET /settings/connections/{connection_id}"""
|
|
|
|
def test_success(self):
|
|
mock_conn_svc = MagicMock()
|
|
mock_conn = MagicMock()
|
|
mock_conn.model_dump.return_value = {"id": "conn-1", "name": "Test", "password": "real-pwd"}
|
|
mock_conn_svc.get_connection.return_value = mock_conn
|
|
mock_conn_svc._count_job_references.return_value = 2
|
|
|
|
from src.api.routes.settings import _get_connection_service
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({
|
|
get_config_manager: lambda: MagicMock(),
|
|
_get_connection_service: lambda: mock_conn_svc,
|
|
})
|
|
resp = client.get("/api/settings/connections/conn-1")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["password"] == "********"
|
|
assert data["used_by"] == 2
|
|
|
|
def test_not_found(self):
|
|
mock_conn_svc = MagicMock()
|
|
mock_conn_svc.get_connection.return_value = None
|
|
|
|
from src.api.routes.settings import _get_connection_service
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({
|
|
get_config_manager: lambda: MagicMock(),
|
|
_get_connection_service: lambda: mock_conn_svc,
|
|
})
|
|
resp = client.get("/api/settings/connections/unknown")
|
|
assert resp.status_code == 404
|
|
|
|
|
|
# ── create_connection ──
|
|
|
|
class TestCreateConnection:
|
|
"""POST /settings/connections"""
|
|
|
|
def test_success(self):
|
|
mock_conn_svc = MagicMock()
|
|
mock_conn_svc.create_connection.return_value = {"id": "conn-new", "name": "New"}
|
|
|
|
from src.api.routes.settings import _get_connection_service
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({
|
|
get_config_manager: lambda: MagicMock(),
|
|
_get_connection_service: lambda: mock_conn_svc,
|
|
})
|
|
resp = client.post("/api/settings/connections", json={
|
|
"name": "New", "host": "localhost", "port": 5432,
|
|
"database": "db", "username": "u", "password": "p", "dialect": "postgresql",
|
|
})
|
|
assert resp.status_code == 201
|
|
|
|
def test_value_error(self):
|
|
mock_conn_svc = MagicMock()
|
|
mock_conn_svc.create_connection.side_effect = ValueError("Invalid")
|
|
|
|
from src.api.routes.settings import _get_connection_service
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({
|
|
get_config_manager: lambda: MagicMock(),
|
|
_get_connection_service: lambda: mock_conn_svc,
|
|
})
|
|
resp = client.post("/api/settings/connections", json={"name": "Bad"})
|
|
assert resp.status_code == 422
|
|
|
|
|
|
# ── update_connection ──
|
|
|
|
class TestUpdateConnection:
|
|
"""PUT /settings/connections/{connection_id}"""
|
|
|
|
def test_success(self):
|
|
mock_conn_svc = MagicMock()
|
|
mock_conn_svc.update_connection.return_value = {"id": "conn-1", "name": "Updated"}
|
|
|
|
from src.api.routes.settings import _get_connection_service
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({
|
|
get_config_manager: lambda: MagicMock(),
|
|
_get_connection_service: lambda: mock_conn_svc,
|
|
})
|
|
resp = client.put("/api/settings/connections/conn-1", json={"name": "Updated"})
|
|
assert resp.status_code == 200
|
|
|
|
def test_value_error(self):
|
|
mock_conn_svc = MagicMock()
|
|
mock_conn_svc.update_connection.side_effect = ValueError("Invalid")
|
|
|
|
from src.api.routes.settings import _get_connection_service
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({
|
|
get_config_manager: lambda: MagicMock(),
|
|
_get_connection_service: lambda: mock_conn_svc,
|
|
})
|
|
resp = client.put("/api/settings/connections/conn-1", json={"name": ""})
|
|
assert resp.status_code == 422
|
|
|
|
|
|
# ── delete_connection ──
|
|
|
|
class TestDeleteConnection:
|
|
"""DELETE /settings/connections/{connection_id}"""
|
|
|
|
def test_success(self):
|
|
mock_conn_svc = MagicMock()
|
|
mock_conn_svc.delete_connection.return_value = {"success": True}
|
|
|
|
from src.api.routes.settings import _get_connection_service
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({
|
|
get_config_manager: lambda: MagicMock(),
|
|
_get_connection_service: lambda: mock_conn_svc,
|
|
})
|
|
resp = client.delete("/api/settings/connections/conn-1")
|
|
assert resp.status_code == 200
|
|
|
|
def test_in_use(self):
|
|
mock_conn_svc = MagicMock()
|
|
mock_conn_svc.delete_connection.return_value = {
|
|
"success": False, "blocking_jobs": ["job-1"],
|
|
}
|
|
|
|
from src.api.routes.settings import _get_connection_service
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({
|
|
get_config_manager: lambda: MagicMock(),
|
|
_get_connection_service: lambda: mock_conn_svc,
|
|
})
|
|
resp = client.delete("/api/settings/connections/conn-1")
|
|
assert resp.status_code == 409
|
|
|
|
|
|
# ── test_connection_endpoint ──
|
|
|
|
class TestTestConnectionEndpoint:
|
|
"""POST /settings/connections/{connection_id}/test"""
|
|
|
|
def test_success(self):
|
|
mock_conn_svc = MagicMock()
|
|
mock_conn_svc.test_connection = AsyncMock(return_value={"success": True})
|
|
|
|
from src.api.routes.settings import _get_connection_service
|
|
from src.dependencies import get_config_manager
|
|
client = _make_client({
|
|
get_config_manager: lambda: MagicMock(),
|
|
_get_connection_service: lambda: mock_conn_svc,
|
|
})
|
|
resp = client.post("/api/settings/connections/conn-1/test")
|
|
assert resp.status_code == 200
|
|
# #endregion Test.Api.Settings
|