feat: container monitoring backend core (PR-1)
- Add instance_events and health_checks tables with Alembic migration - InstanceEventBus: typed pub/sub singleton with wildcard support - HealthMonitor: async background loop polling containers every 15s - SSE endpoint GET /events/stream with auth and connection limits - Lifecycle hooks in tool_instances.py (create/start/stop/restart/delete) - Structured JSON logging with correlation IDs - 15 new unit tests (EventBus, HealthMonitor, MonitoringModels) Quality gates: pytest 15 new passed, ruff clean
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
"""Unit tests for monitoring models and migration compatibility."""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import select
|
||||
|
||||
from src.models.health_check import HealthCheck
|
||||
from src.models.instance_event import InstanceEvent
|
||||
from src.models.tool_instance import ToolInstance
|
||||
from src.models.user import User
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
async def test_instance_event_creation(db_session) -> None:
|
||||
"""InstanceEvent model can be created and persisted."""
|
||||
user = User(
|
||||
id=uuid.uuid4(),
|
||||
email="test@example.com",
|
||||
name="Test",
|
||||
authentik_id="auth-1",
|
||||
)
|
||||
db_session.add(user)
|
||||
await db_session.commit()
|
||||
|
||||
instance = ToolInstance(
|
||||
id=uuid.uuid4(),
|
||||
name="test-instance",
|
||||
display_name="Test Instance",
|
||||
tool_type_id=uuid.uuid4(),
|
||||
repository_id=uuid.uuid4(),
|
||||
project_id=uuid.uuid4(),
|
||||
owner_id=user.id,
|
||||
status="pending",
|
||||
)
|
||||
db_session.add(instance)
|
||||
await db_session.commit()
|
||||
|
||||
event = InstanceEvent(
|
||||
instance_id=instance.id,
|
||||
event_type="started",
|
||||
status="starting",
|
||||
message="Container starting...",
|
||||
created_by=user.id,
|
||||
event_metadata={"previous_status": "pending"},
|
||||
)
|
||||
db_session.add(event)
|
||||
await db_session.commit()
|
||||
await db_session.refresh(event)
|
||||
|
||||
assert event.id is not None
|
||||
assert event.instance_id == instance.id
|
||||
assert event.event_type == "started"
|
||||
assert event.status == "starting"
|
||||
assert event.created_by == user.id
|
||||
assert event.event_metadata == {"previous_status": "pending"}
|
||||
assert isinstance(event.created_at, datetime)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
async def test_health_check_creation(db_session) -> None:
|
||||
"""HealthCheck model can be created and persisted."""
|
||||
user = User(
|
||||
id=uuid.uuid4(),
|
||||
email="test2@example.com",
|
||||
name="Test2",
|
||||
authentik_id="auth-2",
|
||||
)
|
||||
db_session.add(user)
|
||||
await db_session.commit()
|
||||
|
||||
instance = ToolInstance(
|
||||
id=uuid.uuid4(),
|
||||
name="test-instance-2",
|
||||
display_name="Test Instance 2",
|
||||
tool_type_id=uuid.uuid4(),
|
||||
repository_id=uuid.uuid4(),
|
||||
project_id=uuid.uuid4(),
|
||||
owner_id=user.id,
|
||||
status="running",
|
||||
)
|
||||
db_session.add(instance)
|
||||
await db_session.commit()
|
||||
|
||||
check = HealthCheck(
|
||||
instance_id=instance.id,
|
||||
container_status="running",
|
||||
container_healthy=True,
|
||||
tunnel_healthy=True,
|
||||
exit_code=None,
|
||||
probe_status="passed",
|
||||
probe_output="OK",
|
||||
)
|
||||
db_session.add(check)
|
||||
await db_session.commit()
|
||||
await db_session.refresh(check)
|
||||
|
||||
assert check.id is not None
|
||||
assert check.instance_id == instance.id
|
||||
assert check.container_status == "running"
|
||||
assert check.container_healthy is True
|
||||
assert check.tunnel_healthy is True
|
||||
assert isinstance(check.checked_at, datetime)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
async def test_instance_event_query_by_instance(db_session) -> None:
|
||||
"""InstanceEvent rows can be queried by instance_id."""
|
||||
user = User(
|
||||
id=uuid.uuid4(),
|
||||
email="test3@example.com",
|
||||
name="Test3",
|
||||
authentik_id="auth-3",
|
||||
)
|
||||
db_session.add(user)
|
||||
await db_session.commit()
|
||||
|
||||
instance = ToolInstance(
|
||||
id=uuid.uuid4(),
|
||||
name="test-instance-3",
|
||||
display_name="Test Instance 3",
|
||||
tool_type_id=uuid.uuid4(),
|
||||
repository_id=uuid.uuid4(),
|
||||
project_id=uuid.uuid4(),
|
||||
owner_id=user.id,
|
||||
status="pending",
|
||||
)
|
||||
db_session.add(instance)
|
||||
await db_session.commit()
|
||||
|
||||
event = InstanceEvent(
|
||||
instance_id=instance.id,
|
||||
event_type="created",
|
||||
status="pending",
|
||||
)
|
||||
db_session.add(event)
|
||||
await db_session.commit()
|
||||
|
||||
result = await db_session.execute(
|
||||
select(InstanceEvent).where(InstanceEvent.instance_id == instance.id)
|
||||
)
|
||||
assert result.scalar_one() is not None
|
||||
Reference in New Issue
Block a user