test: 5 final agents — fix 40+ failures, llm_analysis 80%+, git_plugin 90%+, routes 90-98%, services 98-100%, core 90-100%. Coverage: real 87%, target 95%+

This commit is contained in:
2026-06-15 19:31:56 +03:00
parent 51d90f58c1
commit 010edfcfdc
20 changed files with 5275 additions and 231 deletions

View File

@@ -0,0 +1,362 @@
# #region Test.Maintenance.Routes.Edge [C:3] [TYPE Module] [SEMANTICS test,maintenance,routes,edge,coverage]
# @BRIEF Edge-case tests for Maintenance Banner API routes — covers uncovered branches from _routes.py.
# @RELATION BINDS_TO -> [MaintenanceRoutesModule]
# @TEST_EDGE: settings_not_found -> GET /settings returns 404 when no settings row exists
# @TEST_EDGE: settings_upsert -> PUT /settings creates new settings when none exist
# @TEST_EDGE: settings_all_fields -> PUT /settings with all fields updates correctly
# @TEST_EDGE: end_already_completed -> POST /{id}/end returns idempotent success for already-completed
# @TEST_EDGE: start_past_time -> POST /start with start_time too far in past returns 400
# @TEST_EDGE: start_html_message -> POST /start with HTML in message is accepted
# @TEST_EDGE: end_all_with_env_body -> POST /end-all with environment_id in body
# @TEST_EDGE: banners_with_linked_states -> GET /dashboard-banners with linked states/events
from datetime import UTC, datetime, timedelta
from unittest.mock import MagicMock
import pytest
# ── Shared fixtures ────────────────────────────────────────────
@pytest.fixture
def client():
from fastapi.testclient import TestClient
from src.app import app
return TestClient(app)
@pytest.fixture(autouse=True)
def auth_mock(client, mock_db):
from src.app import app
from src.core.auth.api_key import generate_api_key
from src.dependencies import get_current_user
from src.models.api_key import APIKey
raw, prefix, key_hash = generate_api_key()
api_key = APIKey(
key_hash=key_hash, prefix=prefix, name="Test Key",
permissions=["maintenance:start", "maintenance:end", "maintenance:end_all"],
active=True,
)
mock_db.add(api_key)
mock_db.commit()
client.headers.update({"X-API-Key": raw})
mock_role = MagicMock()
mock_role.name = "Admin"
mock_role.permissions = []
mock_user = MagicMock()
mock_user.username = "test-admin"
mock_user.roles = [mock_role]
async def _mock_get_current_user():
return mock_user
app.dependency_overrides[get_current_user] = _mock_get_current_user
yield
client.headers.pop("X-API-Key", None)
app.dependency_overrides = {}
@pytest.fixture
def mock_db():
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool
from src.dependencies import get_db
from src.models.mapping import Base
engine = create_engine(
"sqlite:///:memory:",
poolclass=StaticPool,
connect_args={"check_same_thread": False},
)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
def _get_db_override():
db = Session()
try:
yield db
finally:
db.close()
from src.app import app
app.dependency_overrides[get_db] = _get_db_override
yield session
app.dependency_overrides.pop(get_db, None)
session.close()
@pytest.fixture
def mock_task_manager():
from src.dependencies import get_task_manager
from unittest.mock import AsyncMock
mock_tm = MagicMock()
mock_task = MagicMock()
mock_task.id = "test-task-id-edge"
mock_tm.create_task = AsyncMock(return_value=mock_task)
from src.app import app
app.dependency_overrides[get_task_manager] = lambda: mock_tm
yield mock_tm
app.dependency_overrides.pop(get_task_manager, None)
# ── GET /api/maintenance/settings — not found ─────────────────
class TestSettingsNotFound:
"""GET /api/maintenance/settings when no settings exist."""
# #region test_get_settings_not_found [C:2] [TYPE Function]
# @BRIEF No MaintenanceSettings row -> 404.
# @TEST_EDGE: settings_not_found -> GET /settings returns 404
def test_get_settings_not_found(self, client, mock_db, mock_task_manager):
# Remove the default settings created by the fixture
from src.models.maintenance import MaintenanceSettings
mock_db.query(MaintenanceSettings).delete()
mock_db.commit()
response = client.get("/api/maintenance/settings")
assert response.status_code == 404
assert "not configured" in response.json()["detail"]
# #endregion test_get_settings_not_found
# ── PUT /api/maintenance/settings — upsert (no existing) ──────
class TestSettingsUpsert:
"""PUT /api/maintenance/settings when no settings row exists."""
# #region test_upsert_settings [C:2] [TYPE Function]
# @BRIEF No settings row -> upsert creates one.
def test_upsert_settings(self, client, mock_db, mock_task_manager):
from src.models.maintenance import MaintenanceSettings
mock_db.query(MaintenanceSettings).delete()
mock_db.commit()
response = client.put(
"/api/maintenance/settings",
json={"target_environment_id": "new-env", "display_timezone": "Asia/Tokyo"},
)
assert response.status_code == 200
data = response.json()
assert data["target_environment_id"] == "new-env"
assert data["display_timezone"] == "Asia/Tokyo"
# #endregion test_upsert_settings
# #region test_upsert_settings_all_fields [C:2] [TYPE Function]
# @BRIEF All fields provided -> upsert with complete data.
def test_upsert_settings_all_fields(self, client, mock_db, mock_task_manager):
from src.models.maintenance import MaintenanceSettings
mock_db.query(MaintenanceSettings).delete()
mock_db.commit()
response = client.put(
"/api/maintenance/settings",
json={
"target_environment_id": "env-2",
"display_timezone": "Europe/London",
"banner_template": "Custom: {message}",
"dashboard_scope": "all",
"excluded_dashboard_ids": [1, 2],
"forced_dashboard_ids": [3],
},
)
assert response.status_code == 200
data = response.json()
assert data["target_environment_id"] == "env-2"
assert data["display_timezone"] == "Europe/London"
assert data["banner_template"] == "Custom: {message}"
assert data["dashboard_scope"] == "all"
assert 1 in data["excluded_dashboard_ids"]
assert 3 in data["forced_dashboard_ids"]
# #endregion test_upsert_settings_all_fields
# ── POST /api/maintenance/{id}/end — already completed ────────
class TestEndAlreadyCompleted:
"""POST /api/maintenance/{id}/end when event is already COMPLETED."""
# #region test_end_idempotent_completed [C:2] [TYPE Function]
# @BRIEF Event already COMPLETED -> returns 202 with already_completed status.
def test_end_idempotent_completed(self, client, mock_db, mock_task_manager):
from src.models.maintenance import MaintenanceEvent, MaintenanceEventStatus
event = MaintenanceEvent(
tables=["raw.sales"],
start_time=datetime.now(UTC),
status=MaintenanceEventStatus.COMPLETED,
environment_id="test-env",
task_id="existing-task",
)
mock_db.add(event)
mock_db.commit()
response = client.post(f"/api/maintenance/{event.id}/end")
assert response.status_code == 202
data = response.json()
assert data["status"] == "already_completed"
assert data["task_id"] == "existing-task"
# #endregion test_end_idempotent_completed
# ── POST /api/maintenance/start — edge cases ──────────────────
class TestStartEdgeCases:
"""POST /api/maintenance/start — edge paths."""
# #region test_start_too_far_past [C:2] [TYPE Function]
# @BRIEF start_time more than 1h in the past -> 400.
def test_start_too_far_past(self, client, mock_db, mock_task_manager):
response = client.post(
"/api/maintenance/start",
json={
"tables": ["raw.sales"],
"start_time": (datetime.now(UTC) - timedelta(hours=5)).isoformat(),
"environment_id": "test-env",
},
)
assert response.status_code == 400
assert "too far in the past" in response.json()["detail"]
# #endregion test_start_too_far_past
# #region test_start_with_html_message [C:2] [TYPE Function]
# @BRIEF Message with HTML content -> accepted (HTML will be escaped at render).
def test_start_with_html_message(self, client, mock_db, mock_task_manager):
response = client.post(
"/api/maintenance/start",
json={
"tables": ["raw.sales"],
"start_time": (datetime.now(UTC) + timedelta(hours=1)).isoformat(),
"end_time": (datetime.now(UTC) + timedelta(hours=3)).isoformat(),
"message": "<script>alert('xss')</script>",
"environment_id": "test-env",
},
)
# HTML in message is accepted — rendering layer escapes it
assert response.status_code == 202
# #endregion test_start_with_html_message
# ── POST /api/maintenance/end-all — with env in body ──────────
class TestEndAllWithEnv:
"""POST /api/maintenance/end-all with environment_id in body."""
# #region test_end_all_with_env_body [C:2] [TYPE Function]
# @BRIEF environment_id in JSON body -> passed to task params.
def test_end_all_with_env_body(self, client, mock_db, mock_task_manager):
response = client.post(
"/api/maintenance/end-all",
json={"environment_id": "env-42"},
)
assert response.status_code == 202
data = response.json()
assert data["status"] == "pending"
# Verify environment_id was passed through
call_kwargs = mock_task_manager.create_task.call_args[1]
assert call_kwargs["params"]["environment_id"] == "env-42"
# #endregion test_end_all_with_env_body
# ── GET /api/maintenance/dashboard-banners — with linked states ─
class TestBannersWithLinkedStates:
"""GET /api/maintenance/dashboard-banners with linked dashboard states and events."""
# #region test_banners_with_linked_states [C:2] [TYPE Function]
# @BRIEF Banners with linked MaintenanceDashboardState records -> events aggregated.
def test_banners_with_linked_states(self, client, mock_db, mock_task_manager):
from src.models.maintenance import (
MaintenanceDashboardBanner,
MaintenanceDashboardBannerStatus,
MaintenanceDashboardState,
MaintenanceDashboardStateStatus,
MaintenanceEvent,
MaintenanceEventStatus,
)
event = MaintenanceEvent(
tables=["raw.test"],
start_time=datetime.now(UTC),
end_time=datetime.now(UTC) + timedelta(hours=2),
status=MaintenanceEventStatus.ACTIVE,
environment_id="test-env",
)
mock_db.add(event)
mock_db.flush()
banner = MaintenanceDashboardBanner(
environment_id="test-env",
dashboard_id=201,
chart_id=67890,
banner_text="Linked test",
status=MaintenanceDashboardBannerStatus.ACTIVE,
)
mock_db.add(banner)
mock_db.flush()
state = MaintenanceDashboardState(
banner_id=banner.id,
event_id=event.id,
dashboard_id=201,
status=MaintenanceDashboardStateStatus.ACTIVE,
)
mock_db.add(state)
mock_db.commit()
response = client.get("/api/maintenance/dashboard-banners")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
assert len(data) >= 1
banners_for_dash = [b for b in data if b["dashboard_id"] == 201]
assert len(banners_for_dash) == 1
assert len(banners_for_dash[0]["events"]) == 1
assert banners_for_dash[0]["active"] is True
# #endregion test_banners_with_linked_states
# ── GET /api/maintenance/dashboard-banners — no linked states ─
class TestBannersNoLinkedStates:
"""GET /api/maintenance/dashboard-banners with no linked states (just banner)."""
# #region test_banners_no_linked_states [C:2] [TYPE Function]
# @BRIEF Active banner without linked states -> event lists are empty.
def test_banners_no_linked_states(self, client, mock_db, mock_task_manager):
from src.models.maintenance import (
MaintenanceDashboardBanner,
MaintenanceDashboardBannerStatus,
)
banner = MaintenanceDashboardBanner(
environment_id="test-env",
dashboard_id=301,
chart_id=11111,
banner_text="No linked state",
status=MaintenanceDashboardBannerStatus.ACTIVE,
)
mock_db.add(banner)
mock_db.commit()
response = client.get("/api/maintenance/dashboard-banners")
assert response.status_code == 200
data = response.json()
banners_for_dash = [b for b in data if b["dashboard_id"] == 301]
assert len(banners_for_dash) == 1
assert banners_for_dash[0]["events"] == []
# #endregion test_banners_no_linked_states
# #endregion Test.Maintenance.Routes.Edge