feat(tests): improve coverage to 56%, add benchmark regression tests

- Add EffectPlugin ABC with @abstractmethod decorators for interface enforcement
- Add runtime interface checking in discover_plugins() with issubclass()
- Add EffectContext factory with sensible defaults
- Standardize Display __init__ (remove redundant init in TerminalDisplay)
- Document effect behavior when ticker_height=0
- Evaluate legacy effects: document coexistence, no deprecation needed
- Research plugin patterns (VST, Python entry points)
- Fix pysixel dependency (removed broken dependency)

Test coverage improvements:
- Add DisplayRegistry tests
- Add MultiDisplay tests
- Add SixelDisplay tests
- Add controller._get_display tests
- Add effects controller command handling tests
- Add benchmark regression tests (@pytest.mark.benchmark)
- Add pytest marker for benchmark tests in pyproject.toml

Documentation updates:
- Update AGENTS.md with 56% coverage stats and effect plugin docs
- Update README.md with Sixel display mode and benchmark commands
- Add new modules to architecture section
This commit is contained in:
2026-03-15 23:26:10 -07:00
parent dcd31469a5
commit 9e4d54a82e
17 changed files with 768 additions and 45 deletions

View File

@@ -5,7 +5,75 @@ Tests for engine.controller module.
from unittest.mock import MagicMock, patch
from engine import config
from engine.controller import StreamController
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:
@@ -68,6 +136,24 @@ class TestStreamController:
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."""