test: add 7 more test modules — assistant cmd parser, history, dispatch, admin routes, dataset review, maintenance + semantic resolver
This commit is contained in:
384
backend/tests/api/test_assistant_tool_maintenance.py
Normal file
384
backend/tests/api/test_assistant_tool_maintenance.py
Normal file
@@ -0,0 +1,384 @@
|
||||
# #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
|
||||
Reference in New Issue
Block a user