test: 7 agents — plugins ~158 tests, extractor 98-100%, dashboard routes 99-100%, git services 94-100%, services 94-100%, dataset_review 100%. Fix test_api_key_routes.py sys.modules pollution
This commit is contained in:
235
backend/tests/services/maintenance/test_orchestrators.py
Normal file
235
backend/tests/services/maintenance/test_orchestrators.py
Normal file
@@ -0,0 +1,235 @@
|
||||
# #region Test.Maintenance.Orchestrators [C:4] [TYPE Module] [SEMANTICS test, maintenance, orchestration, coverage, edge]
|
||||
# @BRIEF Tests for maintenance orchestrators — start_maintenance, end_maintenance, end_all_maintenance.
|
||||
# @RELATION BINDS_TO -> [MaintenanceOrchestrators]
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock, patch, PropertyMock
|
||||
|
||||
import pytest
|
||||
|
||||
from src.models.maintenance import (
|
||||
MaintenanceDashboardState,
|
||||
MaintenanceEvent,
|
||||
MaintenanceEventStatus,
|
||||
MaintenanceSettings,
|
||||
)
|
||||
|
||||
|
||||
# ── start_maintenance coverage ──
|
||||
|
||||
class TestStartMaintenance:
|
||||
"""start_maintenance — event start orchestration."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dashboard_discovery_failure(self):
|
||||
"""find_affected_dashboards raises → FAILED status."""
|
||||
from src.services.maintenance._orchestrators import start_maintenance
|
||||
|
||||
db = MagicMock()
|
||||
event = MaintenanceEvent(
|
||||
id="evt1", status=MaintenanceEventStatus.PENDING,
|
||||
tables=["table_a"], environment_id="env1",
|
||||
)
|
||||
# Chain for MaintenanceEvent query
|
||||
db.query.return_value.filter.return_value.with_for_update.return_value.first.return_value = event
|
||||
# Chain for MaintenanceSettings query
|
||||
db.query.return_value.filter.return_value.first.return_value = MaintenanceSettings(
|
||||
id="default", target_environment_id="env1", banner_template="Tpl", display_timezone="UTC"
|
||||
)
|
||||
|
||||
superset = AsyncMock()
|
||||
|
||||
with patch("src.services.maintenance._orchestrators.find_affected_dashboards",
|
||||
new_callable=AsyncMock, side_effect=Exception("Discovery failed")):
|
||||
result = await start_maintenance("evt1", db, superset)
|
||||
assert result["status"] == "failed"
|
||||
assert "Discovery failed" in result["error"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_partial_success_some_failures(self):
|
||||
"""Some dashboards fail → PARTIAL status."""
|
||||
from src.services.maintenance._orchestrators import start_maintenance
|
||||
|
||||
db = MagicMock()
|
||||
event = MaintenanceEvent(
|
||||
id="evt1", status=MaintenanceEventStatus.PENDING,
|
||||
tables=["table_a"], environment_id="env1",
|
||||
)
|
||||
db.query.return_value.filter.return_value.with_for_update.return_value.first.return_value = event
|
||||
db.query.return_value.filter.return_value.first.return_value = MaintenanceSettings(
|
||||
id="default", target_environment_id="env1", banner_template="Tpl", display_timezone="UTC"
|
||||
)
|
||||
|
||||
superset = AsyncMock()
|
||||
|
||||
with patch("src.services.maintenance._orchestrators.find_affected_dashboards",
|
||||
new_callable=AsyncMock, return_value=["dash1", "dash2"]), \
|
||||
patch("src.services.maintenance._orchestrators._process_dashboards_for_start",
|
||||
new_callable=AsyncMock, return_value=(
|
||||
["dash1"], # successful
|
||||
["dash2"], # failed
|
||||
[], # unmatched
|
||||
)):
|
||||
result = await start_maintenance("evt1", db, superset)
|
||||
assert result["status"] == "partial"
|
||||
assert result["affected_dashboards"] == 1
|
||||
assert len(result["failed_dashboards"]) == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_all_failures(self):
|
||||
"""All dashboards fail → FAILED."""
|
||||
from src.services.maintenance._orchestrators import start_maintenance
|
||||
|
||||
db = MagicMock()
|
||||
event = MaintenanceEvent(
|
||||
id="evt1", status=MaintenanceEventStatus.PENDING,
|
||||
tables=["table_a"], environment_id="env1",
|
||||
)
|
||||
db.query.return_value.filter.return_value.with_for_update.return_value.first.return_value = event
|
||||
db.query.return_value.filter.return_value.first.return_value = MaintenanceSettings(
|
||||
id="default", target_environment_id="env1", banner_template="Tpl", display_timezone="UTC"
|
||||
)
|
||||
|
||||
superset = AsyncMock()
|
||||
|
||||
with patch("src.services.maintenance._orchestrators.find_affected_dashboards",
|
||||
new_callable=AsyncMock, return_value=["dash1"]), \
|
||||
patch("src.services.maintenance._orchestrators._process_dashboards_for_start",
|
||||
new_callable=AsyncMock, return_value=(
|
||||
[], # successful
|
||||
["dash1"], # failed
|
||||
[], # unmatched
|
||||
)):
|
||||
result = await start_maintenance("evt1", db, superset)
|
||||
assert result["status"] == "failed"
|
||||
assert result["affected_dashboards"] == 0
|
||||
|
||||
|
||||
# ── end_maintenance ──
|
||||
|
||||
class TestEndMaintenance:
|
||||
"""end_maintenance — event end orchestration."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_not_found(self):
|
||||
"""Event missing → failed."""
|
||||
from src.services.maintenance._orchestrators import end_maintenance
|
||||
db = MagicMock()
|
||||
db.query.return_value.filter.return_value.with_for_update.return_value.first.return_value = None
|
||||
result = await end_maintenance("nonexistent", db, AsyncMock())
|
||||
assert result["status"] == "failed"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_already_completed(self):
|
||||
"""Already completed → idempotent status."""
|
||||
from src.services.maintenance._orchestrators import end_maintenance
|
||||
db = MagicMock()
|
||||
event = MaintenanceEvent(id="evt1", status=MaintenanceEventStatus.COMPLETED)
|
||||
db.query.return_value.filter.return_value.with_for_update.return_value.first.return_value = event
|
||||
result = await end_maintenance("evt1", db, AsyncMock())
|
||||
assert result["status"] == "already_completed"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_success(self):
|
||||
"""Successful end → completed status."""
|
||||
from src.services.maintenance._orchestrators import end_maintenance
|
||||
db = MagicMock()
|
||||
event = MaintenanceEvent(id="evt1", status=MaintenanceEventStatus.ACTIVE)
|
||||
db.query.return_value.filter.return_value.with_for_update.return_value.first.return_value = event
|
||||
db.query.return_value.filter.return_value.all.return_value = []
|
||||
|
||||
with patch("src.services.maintenance._orchestrators._process_states_for_end",
|
||||
new_callable=AsyncMock, return_value=(0, [])):
|
||||
result = await end_maintenance("evt1", db, AsyncMock())
|
||||
assert result["status"] == "completed"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_partial_failed_removals(self):
|
||||
"""Some removals fail → partial status."""
|
||||
from src.services.maintenance._orchestrators import end_maintenance
|
||||
db = MagicMock()
|
||||
event = MaintenanceEvent(id="evt1", status=MaintenanceEventStatus.ACTIVE)
|
||||
db.query.return_value.filter.return_value.with_for_update.return_value.first.return_value = event
|
||||
db.query.return_value.filter.return_value.all.return_value = []
|
||||
|
||||
with patch("src.services.maintenance._orchestrators._process_states_for_end",
|
||||
new_callable=AsyncMock, return_value=(1, [{"id": 5, "title": "dash5", "error": "removal failed"}])):
|
||||
result = await end_maintenance("evt1", db, AsyncMock())
|
||||
assert result["status"] == "partial"
|
||||
assert len(result["failed_removals"]) == 1
|
||||
|
||||
|
||||
# ── end_all_maintenance ──
|
||||
|
||||
class TestEndAllMaintenance:
|
||||
"""end_all_maintenance — bulk end."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_no_active_events(self):
|
||||
"""No active events → immediate completion."""
|
||||
from src.services.maintenance._orchestrators import end_all_maintenance
|
||||
db = MagicMock()
|
||||
db.query.return_value.filter.return_value.in_.return_value.all.return_value = []
|
||||
result = await end_all_maintenance(db, AsyncMock())
|
||||
assert result["status"] == "completed"
|
||||
assert result["closed_events"] == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_one_event_ends_cleanly(self):
|
||||
"""One active event → ended cleanly."""
|
||||
from src.services.maintenance._orchestrators import end_all_maintenance
|
||||
db = MagicMock()
|
||||
event = MaintenanceEvent(id="evt1", status=MaintenanceEventStatus.ACTIVE)
|
||||
db.query.return_value.filter.return_value.all.return_value = [event]
|
||||
|
||||
with patch("src.services.maintenance._orchestrators.end_maintenance",
|
||||
new_callable=AsyncMock, return_value={"removed_from": 3, "failed_removals": []}):
|
||||
result = await end_all_maintenance(db, AsyncMock())
|
||||
assert result["status"] == "completed"
|
||||
assert result["closed_events"] == 1
|
||||
assert result["removed_from"] == 3
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_end_maintenance_raises_exception(self):
|
||||
"""end_maintenance raises → event skipped with failed_removals entry."""
|
||||
from src.services.maintenance._orchestrators import end_all_maintenance
|
||||
db = MagicMock()
|
||||
event = MaintenanceEvent(id="evt_fail", status=MaintenanceEventStatus.ACTIVE)
|
||||
db.query.return_value.filter.return_value.all.return_value = [event]
|
||||
|
||||
with patch("src.services.maintenance._orchestrators.end_maintenance",
|
||||
new_callable=AsyncMock, side_effect=Exception("end failure")):
|
||||
result = await end_all_maintenance(db, AsyncMock())
|
||||
assert result["status"] == "partial"
|
||||
assert result["closed_events"] == 0
|
||||
assert len(result["failed_removals"]) == 1
|
||||
assert result["failed_removals"][0]["title"] == "evt_fail"
|
||||
assert "end failure" in result["failed_removals"][0]["error"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mixed_success_failure(self):
|
||||
"""Multiple events: some succeed, some fail."""
|
||||
from src.services.maintenance._orchestrators import end_all_maintenance
|
||||
db = MagicMock()
|
||||
evt_ok = MaintenanceEvent(id="evt_ok", status=MaintenanceEventStatus.ACTIVE)
|
||||
evt_fail = MaintenanceEvent(id="evt_fail", status=MaintenanceEventStatus.PARTIAL)
|
||||
db.query.return_value.filter.return_value.all.return_value = [evt_ok, evt_fail]
|
||||
|
||||
call_count = [0]
|
||||
async def mock_end_maintenance(event_id, db_session, superset):
|
||||
call_count[0] += 1
|
||||
if call_count[0] == 1:
|
||||
return {"removed_from": 2, "failed_removals": []}
|
||||
raise Exception("second failed")
|
||||
|
||||
with patch("src.services.maintenance._orchestrators.end_maintenance",
|
||||
new_callable=AsyncMock, side_effect=mock_end_maintenance):
|
||||
result = await end_all_maintenance(db, AsyncMock())
|
||||
assert result["status"] == "partial"
|
||||
assert result["closed_events"] == 1
|
||||
assert result["removed_from"] == 2
|
||||
assert len(result["failed_removals"]) == 1
|
||||
# #endregion Test.Maintenance.Orchestrators
|
||||
Reference in New Issue
Block a user