""" Tests for engine.controller module. """ from unittest.mock import MagicMock, patch from engine import config from engine.controller import StreamController, _get_display class TestGetDisplay: """Tests for _get_display function.""" @patch("engine.controller.WebSocketDisplay") @patch("engine.controller.TerminalDisplay") def test_get_display_terminal(self, mock_terminal, mock_ws): """returns TerminalDisplay for display=terminal.""" mock_terminal.return_value = MagicMock() mock_ws.return_value = MagicMock() cfg = config.Config(display="terminal") display = _get_display(cfg) mock_terminal.assert_called() assert isinstance(display, MagicMock) @patch("engine.controller.WebSocketDisplay") @patch("engine.controller.TerminalDisplay") def test_get_display_websocket(self, mock_terminal, mock_ws): """returns WebSocketDisplay for display=websocket.""" mock_ws_instance = MagicMock() mock_ws.return_value = mock_ws_instance mock_terminal.return_value = MagicMock() cfg = config.Config(display="websocket") _get_display(cfg) mock_ws.assert_called() mock_ws_instance.start_server.assert_called() mock_ws_instance.start_http_server.assert_called() @patch("engine.controller.SixelDisplay") def test_get_display_sixel(self, mock_sixel): """returns SixelDisplay for display=sixel.""" mock_sixel.return_value = MagicMock() cfg = config.Config(display="sixel") _get_display(cfg) mock_sixel.assert_called() def test_get_display_unknown_returns_null(self): """returns NullDisplay for unknown display mode.""" cfg = config.Config(display="unknown") display = _get_display(cfg) from engine.display import NullDisplay assert isinstance(display, NullDisplay) @patch("engine.controller.WebSocketDisplay") @patch("engine.controller.TerminalDisplay") @patch("engine.controller.MultiDisplay") def test_get_display_both(self, mock_multi, mock_terminal, mock_ws): """returns MultiDisplay for display=both.""" mock_terminal_instance = MagicMock() mock_ws_instance = MagicMock() mock_terminal.return_value = mock_terminal_instance mock_ws.return_value = mock_ws_instance cfg = config.Config(display="both") _get_display(cfg) mock_multi.assert_called() call_args = mock_multi.call_args[0][0] assert mock_terminal_instance in call_args assert mock_ws_instance in call_args 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 @patch("engine.controller.MicMonitor") def test_initialize_sources_cc_subscribed(self, mock_mic): """initialize_sources subscribes C&C handler.""" mock_mic_instance = MagicMock() mock_mic_instance.available = False mock_mic_instance.start.return_value = False mock_mic.return_value = mock_mic_instance with patch("engine.controller.NtfyPoller") as mock_ntfy: mock_ntfy_instance = MagicMock() mock_ntfy_instance.start.return_value = True mock_ntfy.return_value = mock_ntfy_instance controller = StreamController() controller.initialize_sources() mock_ntfy_instance.subscribe.assert_called() 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()