# #region Test.Assistant.ToolMaintenance [C:2] [TYPE Module] [SEMANTICS test,assistant,maintenance,events] # @BRIEF Tests for _tool_maintenance.py — maintenance handlers. # @RELATION BINDS_TO -> [AssistantToolMaintenance] # @TEST_EDGE: list_maintenance_events -> Lists active and completed events # @TEST_EDGE: list_maintenance_no_events -> Empty maintenance response # @TEST_EDGE: start_maintenance -> Start with tables and start_time # @TEST_EDGE: start_maintenance_missing_params -> 422 on missing params # @TEST_EDGE: start_maintenance_invalid_time -> 422 on bad datetime # @TEST_EDGE: start_maintenance_too_many_tables -> 422 on >100 tables # @TEST_EDGE: end_maintenance_by_id -> End specific event # @TEST_EDGE: end_maintenance_not_found -> 404 on missing event # @TEST_EDGE: end_maintenance_all_active -> End all active events from pathlib import Path import sys sys.path.insert(0, str(Path(__file__).parent.parent / "src")) from datetime import UTC, datetime, timedelta from unittest.mock import AsyncMock, MagicMock, patch import pytest from fastapi import HTTPException def _make_maintenance_event(event_id="evt-1", status="ACTIVE", tables=None, environment_id="env-1"): ev = MagicMock() ev.id = event_id ev.status = status ev.tables = tables or ["users"] ev.environment_id = environment_id ev.created_at = datetime.now(UTC) ev.end_time = None return ev class TestHandleListMaintenanceEvents: """handle_list_maintenance_events — listing events.""" @pytest.fixture def intent(self): return {"entities": {}} @pytest.fixture def current_user(self): from src.schemas.auth import User return User(id="user-1", username="test", email="test@x.com", is_active=True, auth_source="internal", created_at=datetime.now(UTC), last_login=datetime.now(UTC), roles=[]) @pytest.mark.asyncio async def test_lists_active_and_completed(self, intent, current_user): from src.api.routes.assistant._tool_maintenance import handle_list_maintenance_events from src.models.maintenance import MaintenanceEventStatus task_manager = MagicMock() task_manager.create_task = AsyncMock(return_value=MagicMock(id="task-99")) config_manager = MagicMock() db = MagicMock() active_events = [_make_maintenance_event("evt-1", MaintenanceEventStatus.ACTIVE)] completed_events = [_make_maintenance_event("evt-2", MaintenanceEventStatus.COMPLETED)] # Mock the queries db.query.return_value.filter.return_value = MagicMock() db.query.return_value.filter.return_value.filter.return_value.all.return_value = [] # Separate mocks for each query def query_side_effect(model): q = MagicMock() if model.__name__ == "MaintenanceEvent": # Active events q.filter.return_value = MagicMock() q.filter.return_value.filter.return_value.all.side_effect = [ [], # expired active_events, # active ] q.filter.return_value.filter.return_value.order_by.return_value.limit.return_value.all.return_value = completed_events q.filter.return_value.filter.return_value.order_by.return_value.all.return_value = active_events q.filter.return_value.in_.return_value = q.filter.return_value q.filter.return_value.isnot.return_value = q.filter.return_value return q db.query.side_effect = query_side_effect # Mock dashboard state count db.query.return_value.filter.return_value.filter.return_value = MagicMock() db.query.return_value.filter.return_value.filter.return_value.count.return_value = 3 text, task_id, actions = await handle_list_maintenance_events( intent, current_user, task_manager, config_manager, db ) assert "Активные" in text or "Завершённые" in text assert actions is not None @pytest.mark.asyncio async def test_no_events(self, intent, current_user): from src.api.routes.assistant._tool_maintenance import handle_list_maintenance_events task_manager = MagicMock() task_manager.create_task = AsyncMock(return_value=MagicMock(id="task-99")) config_manager = MagicMock() db = MagicMock() def query_side_effect(model): q = MagicMock() if model.__name__ == "MaintenanceEvent": q.filter.return_value = MagicMock() q.filter.return_value.filter.return_value.all.return_value = [] q.filter.return_value.filter.return_value.order_by.return_value.all.return_value = [] q.filter.return_value.filter.return_value.order_by.return_value.limit.return_value.all.return_value = [] else: q.filter.return_value.filter.return_value.count.return_value = 0 return q db.query.side_effect = query_side_effect text, task_id, actions = await handle_list_maintenance_events( intent, current_user, task_manager, config_manager, db ) assert "Нет событий" in text class TestHandleStartMaintenance: """handle_start_maintenance — starting maintenance events.""" @pytest.fixture def intent(self): return {"entities": {}} @pytest.fixture def current_user(self): from src.schemas.auth import User return User(id="user-1", username="test", email="test@x.com", is_active=True, auth_source="internal", created_at=datetime.now(UTC), last_login=datetime.now(UTC), roles=[]) @pytest.mark.asyncio async def test_start_success(self, intent, current_user): from src.api.routes.assistant._tool_maintenance import handle_start_maintenance task_manager = MagicMock() task_manager.create_task = AsyncMock(return_value=MagicMock(id="task-42")) config_manager = MagicMock() db = MagicMock() intent["entities"] = { "tables": ["users", "orders"], "start_time": "2025-06-15T10:00", "end_time": "2025-06-15T12:00", "message": "Scheduled maintenance", } text, task_id, actions = await handle_start_maintenance( intent, current_user, task_manager, config_manager, db ) assert "Обслуживание запущено" in text assert task_id == "task-42" @pytest.mark.asyncio async def test_start_without_end_time(self, intent, current_user): from src.api.routes.assistant._tool_maintenance import handle_start_maintenance task_manager = MagicMock() task_manager.create_task = AsyncMock(return_value=MagicMock(id="task-43")) config_manager = MagicMock() db = MagicMock() intent["entities"] = { "tables": ["users"], "start_time": "2025-06-15T10:00", } text, task_id, actions = await handle_start_maintenance( intent, current_user, task_manager, config_manager, db ) assert "Обслуживание запущено" in text @pytest.mark.asyncio async def test_start_missing_params_raises_422(self, intent, current_user): from src.api.routes.assistant._tool_maintenance import handle_start_maintenance task_manager = MagicMock() config_manager = MagicMock() db = MagicMock() intent["entities"] = {} with pytest.raises(HTTPException) as exc: await handle_start_maintenance( intent, current_user, task_manager, config_manager, db ) assert exc.value.status_code == 422 @pytest.mark.asyncio async def test_start_missing_tables_raises_422(self, intent, current_user): from src.api.routes.assistant._tool_maintenance import handle_start_maintenance task_manager = MagicMock() config_manager = MagicMock() db = MagicMock() intent["entities"] = {"start_time": "2025-06-15T10:00"} with pytest.raises(HTTPException) as exc: await handle_start_maintenance( intent, current_user, task_manager, config_manager, db ) assert exc.value.status_code == 422 @pytest.mark.asyncio async def test_start_invalid_time_raises_422(self, intent, current_user): from src.api.routes.assistant._tool_maintenance import handle_start_maintenance task_manager = MagicMock() config_manager = MagicMock() db = MagicMock() intent["entities"] = { "tables": ["users"], "start_time": "not-a-date", } with pytest.raises(HTTPException) as exc: await handle_start_maintenance( intent, current_user, task_manager, config_manager, db ) assert exc.value.status_code == 422 assert "start_time" in exc.value.detail.lower() @pytest.mark.asyncio async def test_start_invalid_end_time_raises_422(self, intent, current_user): from src.api.routes.assistant._tool_maintenance import handle_start_maintenance task_manager = MagicMock() config_manager = MagicMock() db = MagicMock() intent["entities"] = { "tables": ["users"], "start_time": "2025-06-15T10:00", "end_time": "not-a-date", } with pytest.raises(HTTPException) as exc: await handle_start_maintenance( intent, current_user, task_manager, config_manager, db ) assert exc.value.status_code == 422 assert "end_time" in exc.value.detail.lower() @pytest.mark.asyncio async def test_start_too_many_tables_raises_422(self, intent, current_user): from src.api.routes.assistant._tool_maintenance import handle_start_maintenance task_manager = MagicMock() config_manager = MagicMock() db = MagicMock() intent["entities"] = { "tables": [f"table_{i}" for i in range(101)], "start_time": "2025-06-15T10:00", } with pytest.raises(HTTPException) as exc: await handle_start_maintenance( intent, current_user, task_manager, config_manager, db ) assert exc.value.status_code == 422 assert "100" in exc.value.detail @pytest.mark.asyncio async def test_start_with_tables_as_string(self, intent, current_user): from src.api.routes.assistant._tool_maintenance import handle_start_maintenance task_manager = MagicMock() task_manager.create_task = AsyncMock(return_value=MagicMock(id="task-44")) config_manager = MagicMock() db = MagicMock() intent["entities"] = { "tables": "users,orders,products", "start_time": "2025-06-15T10:00", } text, task_id, actions = await handle_start_maintenance( intent, current_user, task_manager, config_manager, db ) assert "Обслуживание запущено" in text # Should have split the string task_params = task_manager.create_task.call_args[1]["params"] assert task_params["tables"] == ["users", "orders", "products"] class TestHandleEndMaintenance: """handle_end_maintenance — ending maintenance events.""" @pytest.fixture def intent(self): return {"entities": {}} @pytest.fixture def current_user(self): from src.schemas.auth import User return User(id="user-1", username="test", email="test@x.com", is_active=True, auth_source="internal", created_at=datetime.now(UTC), last_login=datetime.now(UTC), roles=[]) @pytest.mark.asyncio async def test_end_by_id(self, intent, current_user): from src.api.routes.assistant._tool_maintenance import handle_end_maintenance task_manager = MagicMock() task_manager.create_task = AsyncMock(return_value=MagicMock(id="task-45")) config_manager = MagicMock() db = MagicMock() event = _make_maintenance_event("evt-1") db.query.return_value.filter.return_value.first.return_value = event intent["entities"] = {"event_id": "evt-1"} text, task_id, actions = await handle_end_maintenance( intent, current_user, task_manager, config_manager, db ) assert "Завершение" in text assert task_id == "task-45" @pytest.mark.asyncio async def test_end_not_found_raises_404(self, intent, current_user): from src.api.routes.assistant._tool_maintenance import handle_end_maintenance task_manager = MagicMock() config_manager = MagicMock() db = MagicMock() db.query.return_value.filter.return_value.first.return_value = None intent["entities"] = {"event_id": "evt-nonexistent"} with pytest.raises(HTTPException) as exc: await handle_end_maintenance( intent, current_user, task_manager, config_manager, db ) assert exc.value.status_code == 404 @pytest.mark.asyncio async def test_end_all_active_success(self, intent, current_user): from src.api.routes.assistant._tool_maintenance import handle_end_maintenance from src.models.maintenance import MaintenanceEventStatus task_manager = MagicMock() task_manager.create_task = AsyncMock(return_value=MagicMock(id="task-46")) config_manager = MagicMock() db = MagicMock() active_events = [ _make_maintenance_event("evt-1", MaintenanceEventStatus.ACTIVE), _make_maintenance_event("evt-2", MaintenanceEventStatus.PARTIAL), ] db.query.return_value.filter.return_value.filter.return_value.all.return_value = active_events intent["entities"] = {} # No event_id -> end all text, task_id, actions = await handle_end_maintenance( intent, current_user, task_manager, config_manager, db ) assert "Завершение" in text assert "2" in text # Should mention 2 events @pytest.mark.asyncio async def test_end_all_no_active(self, intent, current_user): from src.api.routes.assistant._tool_maintenance import handle_end_maintenance task_manager = MagicMock() config_manager = MagicMock() db = MagicMock() db.query.return_value.filter.return_value.filter.return_value.all.return_value = [] intent["entities"] = {} # No event_id text, task_id, actions = await handle_end_maintenance( intent, current_user, task_manager, config_manager, db ) assert "Нет активных" in text assert task_id is None # #endregion Test.Assistant.ToolMaintenance