""" Tests for engine.controller module. """ from unittest.mock import MagicMock, patch from engine import config from engine.controller import StreamController class TestStreamController: """Tests for StreamController class.""" def test_init_default_config(self): """StreamController initializes with default config.""" controller = StreamController() assert controller.config is not None assert isinstance(controller.config, config.Config) def test_init_custom_config(self): """StreamController accepts custom config.""" custom_config = config.Config(headline_limit=500) controller = StreamController(config=custom_config) assert controller.config.headline_limit == 500 def test_init_sources_none_by_default(self): """Sources are None until initialized.""" controller = StreamController() assert controller.mic is None assert controller.ntfy is None @patch("engine.controller.MicMonitor") @patch("engine.controller.NtfyPoller") def test_initialize_sources(self, mock_ntfy, mock_mic): """initialize_sources creates mic and ntfy instances.""" mock_mic_instance = MagicMock() mock_mic_instance.available = True mock_mic_instance.start.return_value = True mock_mic.return_value = mock_mic_instance mock_ntfy_instance = MagicMock() mock_ntfy_instance.start.return_value = True mock_ntfy.return_value = mock_ntfy_instance controller = StreamController() mic_ok, ntfy_ok = controller.initialize_sources() assert mic_ok is True assert ntfy_ok is True assert controller.mic is not None assert controller.ntfy is not None @patch("engine.controller.MicMonitor") @patch("engine.controller.NtfyPoller") def test_initialize_sources_mic_unavailable(self, mock_ntfy, mock_mic): """initialize_sources handles unavailable mic.""" mock_mic_instance = MagicMock() mock_mic_instance.available = False mock_mic.return_value = mock_mic_instance mock_ntfy_instance = MagicMock() mock_ntfy_instance.start.return_value = True mock_ntfy.return_value = mock_ntfy_instance controller = StreamController() mic_ok, ntfy_ok = controller.initialize_sources() assert mic_ok is False assert ntfy_ok is True class TestStreamControllerCleanup: """Tests for StreamController cleanup.""" @patch("engine.controller.MicMonitor") def test_cleanup_stops_mic(self, mock_mic): """cleanup stops the microphone if running.""" mock_mic_instance = MagicMock() mock_mic.return_value = mock_mic_instance controller = StreamController() controller.mic = mock_mic_instance controller.cleanup() mock_mic_instance.stop.assert_called_once() class TestStreamControllerWarmup: """Tests for StreamController topic warmup.""" def test_warmup_topics_idempotent(self): """warmup_topics can be called multiple times.""" StreamController._topics_warmed = False with patch("urllib.request.urlopen") as mock_urlopen: StreamController.warmup_topics() StreamController.warmup_topics() assert mock_urlopen.call_count >= 3 def test_warmup_topics_sets_flag(self): """warmup_topics sets the warmed flag.""" StreamController._topics_warmed = False with patch("urllib.request.urlopen"): StreamController.warmup_topics() assert StreamController._topics_warmed is True def test_warmup_topics_skips_after_first(self): """warmup_topics skips after first call.""" StreamController._topics_warmed = True with patch("urllib.request.urlopen") as mock_urlopen: StreamController.warmup_topics() mock_urlopen.assert_not_called()