""" Tests for Heartbeat Monitoring Tests cover: - Heartbeat registration and updates - Timeout detection - Pause/resume functionality - Status reporting """ import pytest import asyncio from datetime import datetime, timezone, timedelta from unittest.mock import AsyncMock import sys sys.path.insert(0, str(__file__).rsplit('/tests/', 1)[0]) from sessions.heartbeat import HeartbeatMonitor, HeartbeatClient, HeartbeatEntry class TestHeartbeatMonitor: """Tests for HeartbeatMonitor""" @pytest.fixture def monitor(self): """Create a heartbeat monitor""" return HeartbeatMonitor( timeout_seconds=5, check_interval_seconds=1, max_missed_beats=2 ) def test_register_session(self, monitor): """Should register session for monitoring""" monitor.register("session-1", "tutor-agent") assert "session-1" in monitor.sessions assert monitor.sessions["session-1"].agent_type == "tutor-agent" def test_beat_updates_timestamp(self, monitor): """Beat should update last_beat timestamp""" monitor.register("session-1", "agent") original = monitor.sessions["session-1"].last_beat import time time.sleep(0.01) result = monitor.beat("session-1") assert result is True assert monitor.sessions["session-1"].last_beat > original assert monitor.sessions["session-1"].missed_beats == 0 def test_beat_nonexistent_session(self, monitor): """Beat should return False for unregistered session""" result = monitor.beat("nonexistent") assert result is False def test_unregister_session(self, monitor): """Should unregister session from monitoring""" monitor.register("session-1", "agent") result = monitor.unregister("session-1") assert result is True assert "session-1" not in monitor.sessions def test_pause_session(self, monitor): """Should pause monitoring for session""" monitor.register("session-1", "agent") result = monitor.pause("session-1") assert result is True assert "session-1" in monitor._paused_sessions def test_resume_session(self, monitor): """Should resume monitoring for paused session""" monitor.register("session-1", "agent") monitor.pause("session-1") result = monitor.resume("session-1") assert result is True assert "session-1" not in monitor._paused_sessions def test_get_status(self, monitor): """Should return session status""" monitor.register("session-1", "tutor-agent") status = monitor.get_status("session-1") assert status is not None assert status["session_id"] == "session-1" assert status["agent_type"] == "tutor-agent" assert status["is_healthy"] is True assert status["is_paused"] is False def test_get_status_nonexistent(self, monitor): """Should return None for nonexistent session""" status = monitor.get_status("nonexistent") assert status is None def test_get_all_status(self, monitor): """Should return status for all sessions""" monitor.register("session-1", "agent-1") monitor.register("session-2", "agent-2") all_status = monitor.get_all_status() assert len(all_status) == 2 assert "session-1" in all_status assert "session-2" in all_status def test_registered_count(self, monitor): """Should return correct registered count""" assert monitor.registered_count == 0 monitor.register("s1", "a") monitor.register("s2", "a") assert monitor.registered_count == 2 def test_healthy_count(self, monitor): """Should return correct healthy count""" monitor.register("s1", "a") monitor.register("s2", "a") # Both should be healthy initially assert monitor.healthy_count == 2 # Simulate missed beat monitor.sessions["s1"].missed_beats = 1 assert monitor.healthy_count == 1 class TestHeartbeatClient: """Tests for HeartbeatClient""" @pytest.fixture def monitor(self): """Create a monitor for the client""" return HeartbeatMonitor(timeout_seconds=5) def test_client_creation(self, monitor): """Client should be created with correct settings""" client = HeartbeatClient( session_id="session-1", monitor=monitor, interval_seconds=2 ) assert client.session_id == "session-1" assert client.interval == 2 assert client._running is False @pytest.mark.asyncio async def test_client_start_stop(self, monitor): """Client should start and stop correctly""" monitor.register("session-1", "agent") client = HeartbeatClient( session_id="session-1", monitor=monitor, interval_seconds=1 ) await client.start() assert client._running is True await asyncio.sleep(0.1) await client.stop() assert client._running is False @pytest.mark.asyncio async def test_client_context_manager(self, monitor): """Client should work as context manager""" monitor.register("session-1", "agent") async with HeartbeatClient("session-1", monitor, 1) as client: assert client._running is True assert client._running is False class TestHeartbeatEntry: """Tests for HeartbeatEntry dataclass""" def test_entry_creation(self): """Entry should be created with correct values""" entry = HeartbeatEntry( session_id="session-1", agent_type="tutor-agent", last_beat=datetime.now(timezone.utc) ) assert entry.session_id == "session-1" assert entry.agent_type == "tutor-agent" assert entry.missed_beats == 0