"""Tests for the presence file watcher in dashboard.app.""" import asyncio import json from unittest.mock import AsyncMock, patch import pytest # Common patches to eliminate delays and inject mock ws_manager _FAST = { "dashboard.app._PRESENCE_POLL_SECONDS": 0.01, "dashboard.app._PRESENCE_INITIAL_DELAY": 0, } def _patches(mock_ws, presence_file): """Return a combined context manager for presence watcher patches.""" from contextlib import ExitStack stack = ExitStack() stack.enter_context(patch("dashboard.app.PRESENCE_FILE", presence_file)) stack.enter_context(patch("infrastructure.ws_manager.handler.ws_manager", mock_ws)) for key, val in _FAST.items(): stack.enter_context(patch(key, val)) return stack @pytest.mark.asyncio async def test_presence_watcher_broadcasts_on_file_change(tmp_path): """Watcher reads presence.json and broadcasts via ws_manager.""" from dashboard.app import _presence_watcher presence_file = tmp_path / "presence.json" state = { "version": 1, "liveness": "2026-03-18T21:47:12Z", "current_focus": "Reviewing PR #267", "mood": "focused", } presence_file.write_text(json.dumps(state)) mock_ws = AsyncMock() with _patches(mock_ws, presence_file): task = asyncio.create_task(_presence_watcher()) await asyncio.sleep(0.15) task.cancel() try: await task except asyncio.CancelledError: pass mock_ws.broadcast.assert_called_with("timmy_state", state) @pytest.mark.asyncio async def test_presence_watcher_synthesised_state_when_missing(tmp_path): """Watcher broadcasts synthesised idle state when file is absent.""" from dashboard.app import _SYNTHESIZED_STATE, _presence_watcher missing_file = tmp_path / "no-such-file.json" mock_ws = AsyncMock() with _patches(mock_ws, missing_file): task = asyncio.create_task(_presence_watcher()) await asyncio.sleep(0.15) task.cancel() try: await task except asyncio.CancelledError: pass mock_ws.broadcast.assert_called_with("timmy_state", _SYNTHESIZED_STATE) @pytest.mark.asyncio async def test_presence_watcher_handles_bad_json(tmp_path): """Watcher logs warning on malformed JSON and doesn't crash.""" from dashboard.app import _presence_watcher presence_file = tmp_path / "presence.json" presence_file.write_text("{bad json!!!") mock_ws = AsyncMock() with _patches(mock_ws, presence_file): task = asyncio.create_task(_presence_watcher()) await asyncio.sleep(0.15) task.cancel() try: await task except asyncio.CancelledError: pass # Should not have broadcast anything on bad JSON mock_ws.broadcast.assert_not_called()