""" 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.PRESET = "border-test" sys.argv = ["mainline.py"] main() mock_run.assert_called_once_with("border-test") 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.PRESET = "nonexistent" 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), patch("engine.app.fetch_all") as mock_fetch, patch("engine.app.DisplayRegistry.create") as mock_create, patch("engine.app.Pipeline") as mock_pipeline_class, patch("engine.app.effects_plugins"), patch("engine.app.get_registry"), patch("engine.app.PerformanceMonitor"), patch("engine.app.set_monitor"), patch("engine.app.time.sleep"), ): # Setup mocks to return early mock_display = Mock() mock_display.get_dimensions = Mock(return_value=(80, 24)) mock_create.return_value = mock_display mock_pipeline = Mock() mock_pipeline.context = Mock() mock_pipeline.context.params = None mock_pipeline.execute = Mock(side_effect=KeyboardInterrupt) mock_pipeline_class.return_value = mock_pipeline with pytest.raises(KeyboardInterrupt): run_pipeline_mode("demo") # Verify fetch_all was NOT called (cache was used) mock_fetch.assert_not_called() 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, patch("engine.app.Pipeline") as mock_pipeline_class, patch("engine.app.effects_plugins"), patch("engine.app.get_registry"), patch("engine.app.PerformanceMonitor"), patch("engine.app.set_monitor"), patch("engine.app.time.sleep"), ): mock_display = Mock() mock_display.get_dimensions = Mock(return_value=(80, 24)) mock_create.return_value = mock_display mock_pipeline = Mock() mock_pipeline.context = Mock() mock_pipeline.context.params = None mock_pipeline.execute = Mock(side_effect=KeyboardInterrupt) mock_pipeline_class.return_value = mock_pipeline with pytest.raises(KeyboardInterrupt): run_pipeline_mode("demo") # 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, patch("engine.app.Pipeline") as mock_pipeline_class, patch("engine.app.effects_plugins"), patch("engine.app.get_registry"), patch("engine.app.PerformanceMonitor"), patch("engine.app.set_monitor"), patch("engine.app.time.sleep"), ): mock_display = Mock() mock_display.get_dimensions = Mock(return_value=(80, 24)) mock_create.return_value = mock_display mock_pipeline = Mock() mock_pipeline.context = Mock() mock_pipeline.context.params = None mock_pipeline.execute = Mock(side_effect=KeyboardInterrupt) mock_pipeline_class.return_value = mock_pipeline with pytest.raises(KeyboardInterrupt): run_pipeline_mode("demo") # 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, patch("engine.app.Pipeline") as mock_pipeline_class, patch("engine.app.effects_plugins"), patch("engine.app.get_registry"), patch("engine.app.PerformanceMonitor"), patch("engine.app.set_monitor"), patch("engine.app.time.sleep"), ): mock_display = Mock() mock_display.get_dimensions = Mock(return_value=(80, 24)) mock_create.return_value = mock_display mock_pipeline = Mock() mock_pipeline.context = Mock() mock_pipeline.context.params = None mock_pipeline.execute = Mock(side_effect=KeyboardInterrupt) mock_pipeline_class.return_value = mock_pipeline with pytest.raises(KeyboardInterrupt): run_pipeline_mode("poetry") # 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, patch("engine.app.Pipeline") as mock_pipeline_class, patch("engine.app.get_registry"), patch("engine.app.PerformanceMonitor"), patch("engine.app.set_monitor"), patch("engine.app.time.sleep"), ): mock_display = Mock() mock_display.get_dimensions = Mock(return_value=(80, 24)) mock_create.return_value = mock_display mock_pipeline = Mock() mock_pipeline.context = Mock() mock_pipeline.context.params = None mock_pipeline.execute = Mock(side_effect=KeyboardInterrupt) mock_pipeline_class.return_value = mock_pipeline with pytest.raises(KeyboardInterrupt): run_pipeline_mode("demo") # Verify effects_plugins.discover_plugins was called mock_effects.discover_plugins.assert_called_once()