forked from genewildish/Mainline
- Add ~20 gallery presets covering sources, effects, cameras, displays - Add MultiDisplay support with --display multi:terminal,pygame syntax - Fix ViewportFilterStage to recompute layout on viewport_width change - Add benchmark.py module for hook-based performance testing - Add viewport resize tests to test_viewport_filter_performance.py
206 lines
7.9 KiB
Python
206 lines
7.9 KiB
Python
"""
|
|
Integration tests for engine/app.py - pipeline orchestration.
|
|
|
|
Tests the main entry point and pipeline mode initialization.
|
|
"""
|
|
|
|
import sys
|
|
from unittest.mock import Mock, patch
|
|
|
|
import pytest
|
|
|
|
from engine.app import main, run_pipeline_mode
|
|
from engine.pipeline import get_preset
|
|
|
|
|
|
class TestMain:
|
|
"""Test main() entry point."""
|
|
|
|
def test_main_calls_run_pipeline_mode_with_default_preset(self):
|
|
"""main() runs default preset (demo) when no args provided."""
|
|
with patch("engine.app.run_pipeline_mode") as mock_run:
|
|
sys.argv = ["mainline.py"]
|
|
main()
|
|
mock_run.assert_called_once_with("demo")
|
|
|
|
def test_main_calls_run_pipeline_mode_with_config_preset(self):
|
|
"""main() uses PRESET from config if set."""
|
|
with (
|
|
patch("engine.app.config") as mock_config,
|
|
patch("engine.app.run_pipeline_mode") as mock_run,
|
|
):
|
|
mock_config.PIPELINE_DIAGRAM = False
|
|
mock_config.PRESET = "gallery-sources"
|
|
mock_config.PIPELINE_MODE = False
|
|
sys.argv = ["mainline.py"]
|
|
main()
|
|
mock_run.assert_called_once_with("gallery-sources")
|
|
|
|
def test_main_exits_on_unknown_preset(self):
|
|
"""main() exits with error for unknown preset."""
|
|
with (
|
|
patch("engine.app.config") as mock_config,
|
|
patch("engine.app.list_presets", return_value=["demo", "poetry"]),
|
|
):
|
|
mock_config.PIPELINE_DIAGRAM = False
|
|
mock_config.PRESET = "nonexistent"
|
|
mock_config.PIPELINE_MODE = False
|
|
sys.argv = ["mainline.py"]
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main()
|
|
assert exc_info.value.code == 1
|
|
|
|
|
|
class TestRunPipelineMode:
|
|
"""Test run_pipeline_mode() initialization."""
|
|
|
|
def test_run_pipeline_mode_loads_valid_preset(self):
|
|
"""run_pipeline_mode() loads a valid preset."""
|
|
preset = get_preset("demo")
|
|
assert preset is not None
|
|
assert preset.name == "demo"
|
|
assert preset.source == "headlines"
|
|
|
|
def test_run_pipeline_mode_exits_on_invalid_preset(self):
|
|
"""run_pipeline_mode() exits if preset not found."""
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
run_pipeline_mode("invalid-preset-xyz")
|
|
assert exc_info.value.code == 1
|
|
|
|
def test_run_pipeline_mode_exits_when_no_content_available(self):
|
|
"""run_pipeline_mode() exits if no content can be fetched."""
|
|
with (
|
|
patch("engine.app.load_cache", return_value=None),
|
|
patch("engine.app.fetch_all", return_value=([], None, None)),
|
|
patch("engine.app.effects_plugins"),
|
|
pytest.raises(SystemExit) as exc_info,
|
|
):
|
|
run_pipeline_mode("demo")
|
|
assert exc_info.value.code == 1
|
|
|
|
def test_run_pipeline_mode_uses_cache_over_fetch(self):
|
|
"""run_pipeline_mode() uses cached content if available."""
|
|
cached = ["cached_item"]
|
|
with (
|
|
patch("engine.app.load_cache", return_value=cached) as mock_load,
|
|
patch("engine.app.fetch_all") as mock_fetch,
|
|
patch("engine.app.DisplayRegistry.create") as mock_create,
|
|
):
|
|
mock_display = Mock()
|
|
mock_display.init = Mock()
|
|
mock_display.get_dimensions = Mock(return_value=(80, 24))
|
|
mock_display.is_quit_requested = Mock(return_value=True)
|
|
mock_display.clear_quit_request = Mock()
|
|
mock_display.show = Mock()
|
|
mock_display.cleanup = Mock()
|
|
mock_create.return_value = mock_display
|
|
|
|
try:
|
|
run_pipeline_mode("demo")
|
|
except (KeyboardInterrupt, SystemExit):
|
|
pass
|
|
|
|
# Verify fetch_all was NOT called (cache was used)
|
|
mock_fetch.assert_not_called()
|
|
mock_load.assert_called_once()
|
|
|
|
def test_run_pipeline_mode_creates_display(self):
|
|
"""run_pipeline_mode() creates a display backend."""
|
|
with (
|
|
patch("engine.app.load_cache", return_value=["item"]),
|
|
patch("engine.app.DisplayRegistry.create") as mock_create,
|
|
):
|
|
mock_display = Mock()
|
|
mock_display.init = Mock()
|
|
mock_display.get_dimensions = Mock(return_value=(80, 24))
|
|
mock_display.is_quit_requested = Mock(return_value=True)
|
|
mock_display.clear_quit_request = Mock()
|
|
mock_display.show = Mock()
|
|
mock_display.cleanup = Mock()
|
|
mock_create.return_value = mock_display
|
|
|
|
try:
|
|
run_pipeline_mode("gallery-display-terminal")
|
|
except (KeyboardInterrupt, SystemExit):
|
|
pass
|
|
|
|
# Verify display was created with 'terminal' (preset display)
|
|
mock_create.assert_called_once_with("terminal")
|
|
|
|
def test_run_pipeline_mode_respects_display_cli_flag(self):
|
|
"""run_pipeline_mode() uses --display CLI flag if provided."""
|
|
sys.argv = ["mainline.py", "--display", "websocket"]
|
|
|
|
with (
|
|
patch("engine.app.load_cache", return_value=["item"]),
|
|
patch("engine.app.DisplayRegistry.create") as mock_create,
|
|
):
|
|
mock_display = Mock()
|
|
mock_display.init = Mock()
|
|
mock_display.get_dimensions = Mock(return_value=(80, 24))
|
|
mock_display.is_quit_requested = Mock(return_value=True)
|
|
mock_display.clear_quit_request = Mock()
|
|
mock_display.show = Mock()
|
|
mock_display.cleanup = Mock()
|
|
mock_create.return_value = mock_display
|
|
|
|
try:
|
|
run_pipeline_mode("demo")
|
|
except (KeyboardInterrupt, SystemExit):
|
|
pass
|
|
|
|
# Verify display was created with CLI override
|
|
mock_create.assert_called_once_with("websocket")
|
|
|
|
def test_run_pipeline_mode_fetches_poetry_for_poetry_source(self):
|
|
"""run_pipeline_mode() fetches poetry for poetry preset."""
|
|
with (
|
|
patch("engine.app.load_cache", return_value=None),
|
|
patch(
|
|
"engine.app.fetch_poetry", return_value=(["poem"], None, None)
|
|
) as mock_fetch_poetry,
|
|
patch("engine.app.fetch_all") as mock_fetch_all,
|
|
patch("engine.app.DisplayRegistry.create") as mock_create,
|
|
):
|
|
mock_display = Mock()
|
|
mock_display.init = Mock()
|
|
mock_display.get_dimensions = Mock(return_value=(80, 24))
|
|
mock_display.is_quit_requested = Mock(return_value=True)
|
|
mock_display.clear_quit_request = Mock()
|
|
mock_display.show = Mock()
|
|
mock_display.cleanup = Mock()
|
|
mock_create.return_value = mock_display
|
|
|
|
try:
|
|
run_pipeline_mode("poetry")
|
|
except (KeyboardInterrupt, SystemExit):
|
|
pass
|
|
|
|
# Verify fetch_poetry was called, not fetch_all
|
|
mock_fetch_poetry.assert_called_once()
|
|
mock_fetch_all.assert_not_called()
|
|
|
|
def test_run_pipeline_mode_discovers_effect_plugins(self):
|
|
"""run_pipeline_mode() discovers available effect plugins."""
|
|
with (
|
|
patch("engine.app.load_cache", return_value=["item"]),
|
|
patch("engine.app.effects_plugins") as mock_effects,
|
|
patch("engine.app.DisplayRegistry.create") as mock_create,
|
|
):
|
|
mock_display = Mock()
|
|
mock_display.init = Mock()
|
|
mock_display.get_dimensions = Mock(return_value=(80, 24))
|
|
mock_display.is_quit_requested = Mock(return_value=True)
|
|
mock_display.clear_quit_request = Mock()
|
|
mock_display.show = Mock()
|
|
mock_display.cleanup = Mock()
|
|
mock_create.return_value = mock_display
|
|
|
|
try:
|
|
run_pipeline_mode("demo")
|
|
except (KeyboardInterrupt, SystemExit):
|
|
pass
|
|
|
|
# Verify effects_plugins.discover_plugins was called
|
|
mock_effects.discover_plugins.assert_called_once()
|