Files
ss-tools/backend/tests/api/test_agent_lifecycle_routes.py
busya 63d82df53b test(coverage): add 200+ tests to push frontend + backend coverage above thresholds
Backend (4 files, 73 tests):
- test_agent_superset_routes.py (27 tests, 35% -> 92%)
- test_agent_lifecycle_routes.py (11 tests, 50% -> 100%)
- test_agent_status_routes.py (6 tests, 57% -> 100%)
- test_git_release_routes.py (31 tests, 30% -> 99%)

Frontend (~15 files, ~120 tests):
- cron.ts: 0% -> 100%
- ReportsLogModel: 0% -> 99%
- parseCot.ts: 10% -> 100%
- sessionTimeout.ts: 64% -> 93%
- MappingsModel: 65% -> 100%
- TranslateHistoryModel: 65% -> 93%
- Migration.ExecutorModel: 70% -> 100%
- GitManagerModel: 78% -> 90%
- TranslationJobModel: 77% -> 80%
- ConfirmDialog: 59% -> 80%
- api.ts: 78% -> 80%

Coverage: frontend 0 violations, backend 7518 passed.
2026-07-23 15:49:45 +03:00

281 lines
10 KiB
Python

# #region Test.Api.AgentLifecycleRoutes [C:3] [TYPE Module] [SEMANTICS test,agent,lifecycle,events,api]
# @BRIEF Unit tests for Agent Lifecycle event API — write and list endpoints.
# @RELATION BINDS_TO -> [Api.AgentLifecycle]
# @TEST_EDGE: non_admin_cannot_filter_by_user -> 403
import os
os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:")
os.environ.setdefault("AUTH_DATABASE_URL", "sqlite:///:memory:")
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-tests")
os.environ.setdefault("DEV_MODE", "true")
import sys
from datetime import datetime
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
_src = str(Path(__file__).resolve().parent.parent.parent / "src")
if _src not in sys.path:
sys.path.insert(0, _src)
def _make_mock_user(is_admin: bool = True, user_id: str = "user-1") -> MagicMock:
"""Build a mock user for auth bypass."""
from src.schemas.auth import RoleSchema, User as UserSchema
roles = []
if is_admin:
roles.append(
RoleSchema(id="r1", name="Admin", description="", is_admin=True, permissions=[])
)
return UserSchema(
id=user_id,
username="admin" if is_admin else "user",
email="admin@x.com" if is_admin else "user@x.com",
auth_source="LOCAL",
created_at=datetime.now(),
roles=roles,
)
def _make_client(user_mock=None, db_mock=None, overrides=None) -> TestClient:
"""Build a TestClient with the agent lifecycle router."""
from src.api.routes.agent_lifecycle import router
from src.core.database import get_db
from src.dependencies import get_current_user
app = FastAPI()
app.include_router(router)
if user_mock is None:
user_mock = _make_mock_user(is_admin=True)
if db_mock is None:
db_mock = MagicMock()
app.dependency_overrides[get_current_user] = lambda: user_mock
app.dependency_overrides[get_db] = lambda: db_mock
if overrides:
for dep, fn in overrides.items():
app.dependency_overrides[dep] = fn
return TestClient(app, raise_server_exceptions=False)
# ── create_event (POST) ──
class TestCreateEvent:
"""POST /api/agent/events"""
EVENT_PAYLOAD = {
"trace_id": "trace-1",
"conversation_id": "conv-1",
"event_type": "tool_call",
"tool_name": "superset_query",
"status": "success",
"elapsed_ms": 1500,
"payload": {"action": "query", "attempt": 1},
}
def test_success(self):
"""Creates a lifecycle event successfully (admin user)."""
from src.schemas.agent_lifecycle import EventWriteResponse
mock_write = MagicMock(return_value=EventWriteResponse(id="evt-1"))
with patch("src.api.routes.agent_lifecycle.write_event", mock_write):
client = _make_client()
resp = client.post("/api/agent/events", json=self.EVENT_PAYLOAD)
assert resp.status_code == 201
data = resp.json()
assert data["id"] == "evt-1"
assert data["written"] is True
mock_write.assert_called_once()
def test_success_regular_user(self):
"""Non-admin user can also create events."""
from src.schemas.agent_lifecycle import EventWriteResponse
mock_write = MagicMock(return_value=EventWriteResponse(id="evt-2"))
user_mock = _make_mock_user(is_admin=False)
with patch("src.api.routes.agent_lifecycle.write_event", mock_write):
client = _make_client(user_mock=user_mock)
resp = client.post("/api/agent/events", json=self.EVENT_PAYLOAD)
assert resp.status_code == 201
def test_payload_reduced(self):
"""Payload is reduced to safe keys via schema."""
from src.schemas.agent_lifecycle import EventWriteResponse
mock_write = MagicMock(return_value=EventWriteResponse(id="evt-3"))
payload_with_sensitive = {
**self.EVENT_PAYLOAD,
"payload": {"action": "query", "password": "secret", "token": "abc"},
}
with patch("src.api.routes.agent_lifecycle.write_event", mock_write):
client = _make_client()
resp = client.post("/api/agent/events", json=payload_with_sensitive)
assert resp.status_code == 201
# The write_event should receive payload with sensitive keys reduced
call_kwargs = mock_write.call_args
if call_kwargs:
written_body = call_kwargs[0][1] # body arg
if written_body.payload:
assert "password" not in written_body.payload
assert "action" in written_body.payload
def test_db_rollback_on_error(self):
"""Rolls back DB transaction when write_event raises."""
mock_write = MagicMock(side_effect=ValueError("DB error"))
db_mock = MagicMock()
with patch("src.api.routes.agent_lifecycle.write_event", mock_write):
client = _make_client(db_mock=db_mock)
resp = client.post("/api/agent/events", json=self.EVENT_PAYLOAD)
assert resp.status_code == 500
db_mock.rollback.assert_called_once()
def test_missing_required_fields(self):
"""Returns 422 when required fields are missing."""
client = _make_client()
resp = client.post("/api/agent/events", json={"event_type": "test"})
assert resp.status_code == 422
# ── read_events (GET) ──
class TestReadEvents:
"""GET /api/agent/events"""
def _make_event_item(self, **overrides):
from src.schemas.agent_lifecycle import EventItem
return EventItem(
id=overrides.get("id", "evt-1"),
trace_id=overrides.get("trace_id", "trace-1"),
conversation_id=overrides.get("conversation_id", "conv-1"),
user_id=overrides.get("user_id", "user-1"),
event_type=overrides.get("event_type", "tool_call"),
tool_name=overrides.get("tool_name", "superset_query"),
status=overrides.get("status", "success"),
created_at=datetime.now(),
)
def test_success_admin(self):
"""Admin can list all events."""
from src.schemas.agent_lifecycle import EventListResponse
items = [self._make_event_item()]
mock_list = MagicMock(
return_value=EventListResponse(items=items, total=1, page=1, page_size=50, has_next=False)
)
with patch("src.api.routes.agent_lifecycle.list_events", mock_list):
client = _make_client()
resp = client.get("/api/agent/events")
assert resp.status_code == 200
data = resp.json()
assert data["total"] == 1
assert len(data["items"]) == 1
assert data["items"][0]["id"] == "evt-1"
def test_success_regular_user(self):
"""Non-admin sees only own events (no user_id filter)."""
from src.schemas.agent_lifecycle import EventListResponse
items = [self._make_event_item(id="evt-2")]
mock_list = MagicMock(
return_value=EventListResponse(items=items, total=1, page=1, page_size=50, has_next=False)
)
user_mock = _make_mock_user(is_admin=False)
with patch("src.api.routes.agent_lifecycle.list_events", mock_list):
client = _make_client(user_mock=user_mock)
resp = client.get("/api/agent/events")
assert resp.status_code == 200
assert resp.json()["total"] == 1
def test_non_admin_cannot_filter_by_user(self):
"""Non-admin gets 403 when trying to filter by user_id."""
user_mock = _make_mock_user(is_admin=False)
client = _make_client(user_mock=user_mock)
resp = client.get("/api/agent/events?user_id=other-user")
assert resp.status_code == 403
assert "Only admin users can query events by user_id" in resp.json()["detail"]
def test_admin_can_filter_by_user(self):
"""Admin can filter events by user_id."""
from src.schemas.agent_lifecycle import EventListResponse
items = [self._make_event_item(user_id="other-user")]
mock_list = MagicMock(
return_value=EventListResponse(items=items, total=1, page=1, page_size=50, has_next=False)
)
with patch("src.api.routes.agent_lifecycle.list_events", mock_list):
client = _make_client()
resp = client.get("/api/agent/events?user_id=other-user")
assert resp.status_code == 200
assert resp.json()["total"] == 1
# Verify list_events received the correct args
call_kwargs = mock_list.call_args[1]
assert call_kwargs["user_id"] == "other-user"
def test_with_filters(self):
"""Filters are passed through to list_events."""
from src.schemas.agent_lifecycle import EventListResponse
mock_list = MagicMock(
return_value=EventListResponse(items=[], total=0, page=1, page_size=50, has_next=False)
)
with patch("src.api.routes.agent_lifecycle.list_events", mock_list):
client = _make_client()
resp = client.get(
"/api/agent/events?event_type=tool_call&conversation_id=conv-1&status=success&tool_name=superset_query"
)
assert resp.status_code == 200
call_kwargs = mock_list.call_args[1]
assert call_kwargs["event_type"] == "tool_call"
assert call_kwargs["conversation_id"] == "conv-1"
def test_pagination(self):
"""Page and page_size are passed through."""
from src.schemas.agent_lifecycle import EventListResponse
mock_list = MagicMock(
return_value=EventListResponse(items=[], total=0, page=2, page_size=25, has_next=False)
)
with patch("src.api.routes.agent_lifecycle.list_events", mock_list):
client = _make_client()
resp = client.get("/api/agent/events?page=2&page_size=25")
assert resp.status_code == 200
assert resp.json()["page"] == 2
assert resp.json()["page_size"] == 25
def test_invalid_page_size(self):
"""Page_size outside 1-200 returns 422."""
client = _make_client()
resp = client.get("/api/agent/events?page_size=500")
assert resp.status_code == 422
# #endregion Test.Api.AgentLifecycleRoutes