fix: 5 agents — core 703/703, settings 38/38, git edges 191/191, API routes 1139/1140, plugins +70 coverage

This commit is contained in:
2026-06-15 18:02:09 +03:00
parent 9cf2d6400a
commit 3de67c258a
18 changed files with 1432 additions and 154 deletions

View File

@@ -134,6 +134,11 @@ class TestCreateMapping:
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = None
# Simulate db.refresh setting the id
def _refresh(obj):
obj.id = "map-new-1"
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/mappings", json=self.CREATE_PAYLOAD)
@@ -166,7 +171,7 @@ class TestSuggestMappingsApi:
mock_suggestions = [{"source": "db1", "target": "db2", "score": 0.95}]
from src.dependencies import get_config_manager
with patch("src.api.routes.mappings.MappingService") as MockMappingSvc:
with patch("src.services.mapping_service.MappingService") as MockMappingSvc:
instance = MagicMock()
instance.get_suggestions = AsyncMock(return_value=mock_suggestions)
MockMappingSvc.return_value = instance
@@ -180,7 +185,7 @@ class TestSuggestMappingsApi:
def test_error(self):
mock_config = MagicMock()
from src.dependencies import get_config_manager
with patch("src.api.routes.mappings.MappingService") as MockMappingSvc:
with patch("src.services.mapping_service.MappingService") as MockMappingSvc:
instance = MagicMock()
instance.get_suggestions = AsyncMock(side_effect=RuntimeError("suggestion failed"))
MockMappingSvc.return_value = instance

View File

@@ -65,7 +65,7 @@ class TestGetDashboards:
mock_config = MagicMock()
mock_config.get_environments.return_value = [_make_env()]
dashboards = [{"id": 1, "dashboard_title": "Test Dash"}]
dashboards = [{"id": 1, "title": "Test Dash", "last_modified": "2024-01-01T00:00:00", "status": "published"}]
mock_client = MagicMock()
mock_client.get_dashboards_summary = AsyncMock(return_value=dashboards)
@@ -300,10 +300,15 @@ class TestGetResourceMappings:
mock_db.query.return_value = mock_query
mock_query.filter.return_value = mock_query
mock_query.count.return_value = 2
def _mock_resource_type(value: str):
m = MagicMock()
m.value = value
return m
mock_query.order_by.return_value.offset.return_value.limit.return_value.all.return_value = [
MagicMock(id=1, environment_id="env-1", resource_type="DATABASE", uuid="u1",
MagicMock(id=1, environment_id="env-1", resource_type=_mock_resource_type("database"), uuid="u1",
remote_integer_id=100, resource_name="DB", last_synced_at=None),
MagicMock(id=2, environment_id="env-1", resource_type="DATABASE", uuid="u2",
MagicMock(id=2, environment_id="env-1", resource_type=_mock_resource_type("database"), uuid="u2",
remote_integer_id=101, resource_name="DB2", last_synced_at=None),
]
@@ -368,7 +373,7 @@ class TestTriggerSyncNow:
from src.dependencies import get_config_manager
client = _make_client({get_config_manager: lambda: mock_config})
resp = client.post("/migration/sync-now")
resp = client.post("/api/migration/sync-now")
assert resp.status_code == 400
def test_sync_with_failures(self):

View File

@@ -105,7 +105,7 @@ class TestGetSettings:
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",
username="admin", password="real-password", stage="DEV",
)
real_config = _make_real_config(environments=[env])
@@ -245,7 +245,7 @@ class TestGetEnvironments:
mock_config = MagicMock()
real_env = Environment(
id="env-1", name="Test Env", url="https://example.com",
username="admin", password="secret", stage="dev",
username="admin", password="secret", stage="DEV",
)
mock_config.get_environments.return_value = [real_env]
@@ -267,7 +267,9 @@ class TestAddEnvironment:
"id": "env-new",
"name": "New Env",
"url": "https://superset.example.com",
"username": "admin",
"password": "secret",
"stage": "DEV",
}
def test_success(self):
@@ -308,7 +310,9 @@ class TestUpdateEnvironment:
"id": "env-1",
"name": "Updated Env",
"url": "https://superset.example.com",
"username": "admin",
"password": "new-password",
"stage": "DEV",
}
def test_success(self):
@@ -344,7 +348,8 @@ class TestUpdateEnvironment:
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", "password": "********",
"id": "env-1", "name": "Updated", "url": "https://x.com",
"username": "admin", "password": "********", "stage": "DEV",
})
assert resp.status_code == 200
@@ -365,6 +370,7 @@ class TestUpdateEnvironment:
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()
@@ -494,8 +500,26 @@ 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 = [MagicMock(id="p1")]
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})
@@ -509,12 +533,26 @@ 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()
@@ -527,8 +565,18 @@ 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 = 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
@@ -612,10 +660,13 @@ class TestListConnections:
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
with patch("src.api.routes.settings._get_connection_service", return_value=mock_conn_svc):
client = _make_client({get_config_manager: lambda: MagicMock()})
resp = client.get("/api/settings/connections")
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
@@ -631,10 +682,13 @@ class TestGetConnection:
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
with patch("src.api.routes.settings._get_connection_service", return_value=mock_conn_svc):
client = _make_client({get_config_manager: lambda: MagicMock()})
resp = client.get("/api/settings/connections/conn-1")
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"] == "********"
@@ -644,10 +698,13 @@ class TestGetConnection:
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
with patch("src.api.routes.settings._get_connection_service", return_value=mock_conn_svc):
client = _make_client({get_config_manager: lambda: MagicMock()})
resp = client.get("/api/settings/connections/unknown")
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
@@ -660,23 +717,29 @@ class TestCreateConnection:
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
with patch("src.api.routes.settings._get_connection_service", return_value=mock_conn_svc):
client = _make_client({get_config_manager: lambda: MagicMock()})
resp = client.post("/api/settings/connections", json={
"name": "New", "host": "localhost", "port": 5432,
"database": "db", "username": "u", "password": "p", "dialect": "postgresql",
})
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
with patch("src.api.routes.settings._get_connection_service", return_value=mock_conn_svc):
client = _make_client({get_config_manager: lambda: MagicMock()})
resp = client.post("/api/settings/connections", json={"name": "Bad"})
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
@@ -689,20 +752,26 @@ class TestUpdateConnection:
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
with patch("src.api.routes.settings._get_connection_service", return_value=mock_conn_svc):
client = _make_client({get_config_manager: lambda: MagicMock()})
resp = client.put("/api/settings/connections/conn-1", json={"name": "Updated"})
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
with patch("src.api.routes.settings._get_connection_service", return_value=mock_conn_svc):
client = _make_client({get_config_manager: lambda: MagicMock()})
resp = client.put("/api/settings/connections/conn-1", json={"name": ""})
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
@@ -715,10 +784,13 @@ class TestDeleteConnection:
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
with patch("src.api.routes.settings._get_connection_service", return_value=mock_conn_svc):
client = _make_client({get_config_manager: lambda: MagicMock()})
resp = client.delete("/api/settings/connections/conn-1")
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):
@@ -727,10 +799,13 @@ class TestDeleteConnection:
"success": False, "blocking_jobs": ["job-1"],
}
from src.api.routes.settings import _get_connection_service
from src.dependencies import get_config_manager
with patch("src.api.routes.settings._get_connection_service", return_value=mock_conn_svc):
client = _make_client({get_config_manager: lambda: MagicMock()})
resp = client.delete("/api/settings/connections/conn-1")
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
@@ -743,9 +818,12 @@ class TestTestConnectionEndpoint:
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
with patch("src.api.routes.settings._get_connection_service", return_value=mock_conn_svc):
client = _make_client({get_config_manager: lambda: MagicMock()})
resp = client.post("/api/settings/connections/conn-1/test")
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

View File

@@ -56,7 +56,10 @@ class TestListFiles:
def test_success(self):
client, mock_loader = _make_client()
mock_plugin = MagicMock()
mock_plugin.list_files.return_value = [{"name": "file1.txt"}]
mock_plugin.list_files.return_value = [{
"name": "file1.txt", "path": "/files/file1.txt", "size": 1024,
"created_at": "2024-01-01T00:00:00", "category": "backups", "mime_type": "text/plain",
}]
mock_loader.get_plugin.return_value = mock_plugin
resp = client.get(f"{P}/files")
assert resp.status_code == 200
@@ -66,7 +69,7 @@ class TestListFiles:
mock_plugin = MagicMock()
mock_plugin.list_files.return_value = []
mock_loader.get_plugin.return_value = mock_plugin
resp = client.get(f"{P}/files?category=BACKUPS&path=/test&recursive=true")
resp = client.get(f"{P}/files?category=backups&path=/test&recursive=true")
assert resp.status_code == 200
mock_plugin.list_files.assert_called_once_with("backups", "/test", True)
@@ -83,7 +86,10 @@ class TestUploadFile:
def test_success(self):
client, mock_loader = _make_client()
mock_plugin = MagicMock()
mock_plugin.save_file = AsyncMock(return_value={"name": "test.txt"})
mock_plugin.save_file = AsyncMock(return_value={
"name": "test.txt", "path": "/uploads/test.txt", "size": 5,
"created_at": "2024-01-01T00:00:00", "category": "backups", "mime_type": "text/plain",
})
mock_loader.get_plugin.return_value = mock_plugin
resp = client.post(f"{P}/upload", data={"category": "backups"}, files={"file": ("test.txt", b"hello")})
assert resp.status_code == 201
@@ -110,7 +116,7 @@ class TestDeleteFile:
client, mock_loader = _make_client()
mock_plugin = MagicMock()
mock_loader.get_plugin.return_value = mock_plugin
resp = client.delete(f"{P}/files/BACKUPS/test.txt")
resp = client.delete(f"{P}/files/backups/test.txt")
assert resp.status_code == 204
def test_not_found(self):
@@ -118,7 +124,7 @@ class TestDeleteFile:
mock_plugin = MagicMock()
mock_plugin.delete_file.side_effect = FileNotFoundError
mock_loader.get_plugin.return_value = mock_plugin
resp = client.delete(f"{P}/files/BACKUPS/missing.txt")
resp = client.delete(f"{P}/files/backups/missing.txt")
assert resp.status_code == 404
def test_value_error(self):
@@ -126,13 +132,13 @@ class TestDeleteFile:
mock_plugin = MagicMock()
mock_plugin.delete_file.side_effect = ValueError("bad")
mock_loader.get_plugin.return_value = mock_plugin
resp = client.delete(f"{P}/files/BACKUPS/bad.txt")
resp = client.delete(f"{P}/files/backups/bad.txt")
assert resp.status_code == 400
def test_plugin_not_loaded(self):
client, mock_loader = _make_client()
mock_loader.get_plugin.return_value = None
resp = client.delete(f"{P}/files/BACKUPS/test.txt")
resp = client.delete(f"{P}/files/backups/test.txt")
assert resp.status_code == 500
@@ -144,7 +150,7 @@ class TestDownloadFile:
mock_plugin = MagicMock()
mock_plugin.get_file_path.side_effect = FileNotFoundError
mock_loader.get_plugin.return_value = mock_plugin
resp = client.get(f"{P}/download/BACKUPS/missing.txt")
resp = client.get(f"{P}/download/backups/missing.txt")
assert resp.status_code == 404
def test_value_error(self):
@@ -152,13 +158,13 @@ class TestDownloadFile:
mock_plugin = MagicMock()
mock_plugin.get_file_path.side_effect = ValueError("bad")
mock_loader.get_plugin.return_value = mock_plugin
resp = client.get(f"{P}/download/BACKUPS/bad.txt")
resp = client.get(f"{P}/download/backups/bad.txt")
assert resp.status_code == 400
def test_plugin_not_loaded(self):
client, mock_loader = _make_client()
mock_loader.get_plugin.return_value = None
resp = client.get(f"{P}/download/BACKUPS/test.txt")
resp = client.get(f"{P}/download/backups/test.txt")
assert resp.status_code == 500

View File

@@ -350,6 +350,6 @@ class TestClearTasks:
from src.dependencies import get_task_manager
client = _make_client({get_task_manager: lambda: mock_tm})
resp = client.delete("/api/tasks?status=COMPLETED")
resp = client.delete("/api/tasks?status=SUCCESS")
assert resp.status_code == 204
# #endregion Test.Api.Tasks