forked from genewildish/Mainline
refactor: remove legacy controller.py and MicMonitor
- Delete engine/controller.py (StreamController - deprecated) - Delete engine/mic.py (MicMonitor - deprecated) - Delete tests/test_controller.py (was testing removed legacy code) - Delete tests/test_mic.py (was testing removed legacy code) - Update tests/test_emitters.py to test MicSensor instead of MicMonitor - Clean up pipeline.py introspector to remove StreamController reference - Update AGENTS.md to reflect architecture changes
This commit is contained in:
@@ -1,171 +0,0 @@
|
||||
"""
|
||||
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()
|
||||
@@ -58,12 +58,12 @@ class TestProtocolCompliance:
|
||||
assert callable(poller.subscribe)
|
||||
assert callable(poller.unsubscribe)
|
||||
|
||||
def test_mic_monitor_complies_with_protocol(self):
|
||||
"""MicMonitor implements EventEmitter and Startable protocols."""
|
||||
from engine.mic import MicMonitor
|
||||
def test_mic_sensor_complies_with_protocol(self):
|
||||
"""MicSensor implements Startable and Stoppable protocols."""
|
||||
from engine.sensors.mic import MicSensor
|
||||
|
||||
monitor = MicMonitor()
|
||||
assert hasattr(monitor, "subscribe")
|
||||
assert hasattr(monitor, "unsubscribe")
|
||||
assert hasattr(monitor, "start")
|
||||
assert hasattr(monitor, "stop")
|
||||
sensor = MicSensor()
|
||||
assert hasattr(sensor, "start")
|
||||
assert hasattr(sensor, "stop")
|
||||
assert callable(sensor.start)
|
||||
assert callable(sensor.stop)
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
"""
|
||||
Tests for engine.mic module.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from unittest.mock import patch
|
||||
|
||||
from engine.events import MicLevelEvent
|
||||
|
||||
|
||||
class TestMicMonitorImport:
|
||||
"""Tests for module import behavior."""
|
||||
|
||||
def test_mic_monitor_imports_without_error(self):
|
||||
"""MicMonitor can be imported even without sounddevice."""
|
||||
from engine.mic import MicMonitor
|
||||
|
||||
assert MicMonitor is not None
|
||||
|
||||
|
||||
class TestMicMonitorInit:
|
||||
"""Tests for MicMonitor initialization."""
|
||||
|
||||
def test_init_sets_threshold(self):
|
||||
"""Threshold is set correctly."""
|
||||
from engine.mic import MicMonitor
|
||||
|
||||
monitor = MicMonitor(threshold_db=60)
|
||||
assert monitor.threshold_db == 60
|
||||
|
||||
def test_init_defaults(self):
|
||||
"""Default values are set correctly."""
|
||||
from engine.mic import MicMonitor
|
||||
|
||||
monitor = MicMonitor()
|
||||
assert monitor.threshold_db == 50
|
||||
|
||||
def test_init_db_starts_at_negative(self):
|
||||
"""_db starts at negative value."""
|
||||
from engine.mic import MicMonitor
|
||||
|
||||
monitor = MicMonitor()
|
||||
assert monitor.db == -99.0
|
||||
|
||||
|
||||
class TestMicMonitorProperties:
|
||||
"""Tests for MicMonitor properties."""
|
||||
|
||||
def test_excess_returns_positive_when_above_threshold(self):
|
||||
"""excess returns positive value when above threshold."""
|
||||
from engine.mic import MicMonitor
|
||||
|
||||
monitor = MicMonitor(threshold_db=50)
|
||||
with patch.object(monitor, "_db", 60.0):
|
||||
assert monitor.excess == 10.0
|
||||
|
||||
def test_excess_returns_zero_when_below_threshold(self):
|
||||
"""excess returns zero when below threshold."""
|
||||
from engine.mic import MicMonitor
|
||||
|
||||
monitor = MicMonitor(threshold_db=50)
|
||||
with patch.object(monitor, "_db", 40.0):
|
||||
assert monitor.excess == 0.0
|
||||
|
||||
|
||||
class TestMicMonitorAvailable:
|
||||
"""Tests for MicMonitor.available property."""
|
||||
|
||||
def test_available_is_bool(self):
|
||||
"""available returns a boolean."""
|
||||
from engine.mic import MicMonitor
|
||||
|
||||
monitor = MicMonitor()
|
||||
assert isinstance(monitor.available, bool)
|
||||
|
||||
|
||||
class TestMicMonitorStop:
|
||||
"""Tests for MicMonitor.stop method."""
|
||||
|
||||
def test_stop_does_nothing_when_no_stream(self):
|
||||
"""stop() does nothing if no stream exists."""
|
||||
from engine.mic import MicMonitor
|
||||
|
||||
monitor = MicMonitor()
|
||||
monitor.stop()
|
||||
assert monitor._stream is None
|
||||
|
||||
|
||||
class TestMicMonitorEventEmission:
|
||||
"""Tests for MicMonitor event emission."""
|
||||
|
||||
def test_subscribe_adds_callback(self):
|
||||
"""subscribe() adds a callback."""
|
||||
from engine.mic import MicMonitor
|
||||
|
||||
monitor = MicMonitor()
|
||||
def callback(e):
|
||||
return None
|
||||
|
||||
monitor.subscribe(callback)
|
||||
|
||||
assert callback in monitor._subscribers
|
||||
|
||||
def test_unsubscribe_removes_callback(self):
|
||||
"""unsubscribe() removes a callback."""
|
||||
from engine.mic import MicMonitor
|
||||
|
||||
monitor = MicMonitor()
|
||||
def callback(e):
|
||||
return None
|
||||
monitor.subscribe(callback)
|
||||
|
||||
monitor.unsubscribe(callback)
|
||||
|
||||
assert callback not in monitor._subscribers
|
||||
|
||||
def test_emit_calls_subscribers(self):
|
||||
"""_emit() calls all subscribers."""
|
||||
from engine.mic import MicMonitor
|
||||
|
||||
monitor = MicMonitor()
|
||||
received = []
|
||||
|
||||
def callback(event):
|
||||
received.append(event)
|
||||
|
||||
monitor.subscribe(callback)
|
||||
event = MicLevelEvent(
|
||||
db_level=60.0, excess_above_threshold=10.0, timestamp=datetime.now()
|
||||
)
|
||||
monitor._emit(event)
|
||||
|
||||
assert len(received) == 1
|
||||
assert received[0].db_level == 60.0
|
||||
|
||||
def test_emit_handles_subscriber_exception(self):
|
||||
"""_emit() handles exceptions in subscribers gracefully."""
|
||||
from engine.mic import MicMonitor
|
||||
|
||||
monitor = MicMonitor()
|
||||
|
||||
def bad_callback(event):
|
||||
raise RuntimeError("test")
|
||||
|
||||
monitor.subscribe(bad_callback)
|
||||
event = MicLevelEvent(
|
||||
db_level=60.0, excess_above_threshold=10.0, timestamp=datetime.now()
|
||||
)
|
||||
monitor._emit(event)
|
||||
Reference in New Issue
Block a user