""" Integration tests for engine/app.py - pipeline orchestration. Tests the main entry point and pipeline mode initialization, including preset loading, display creation, and stage setup. """ import sys from unittest.mock import MagicMock, patch import pytest from engine.app import main, run_pipeline_mode from engine.display import DisplayRegistry, NullDisplay 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_uses_preset_from_config(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 message for unknown preset.""" with ( patch("engine.app.run_pipeline_mode"), patch("engine.app.list_presets", return_value=["demo", "poetry"]), pytest.raises(SystemExit) as exc_info, patch("engine.app.config") as mock_config, ): sys.argv = ["mainline.py"] mock_config.PRESET = "nonexistent" main() assert exc_info.value.code == 1 def test_main_handles_pipeline_diagram_flag(self): """main() generates pipeline diagram if PIPELINE_DIAGRAM is set.""" with ( patch("engine.app.config") as mock_config, patch( "engine.app.generate_pipeline_diagram", return_value="diagram" ) as mock_gen, ): mock_config.PIPELINE_DIAGRAM = True with patch("builtins.print") as mock_print: main() mock_gen.assert_called_once() mock_print.assert_called_with("diagram") class TestRunPipelineMode: """Test run_pipeline_mode() pipeline setup and execution.""" def setup_method(self): """Setup for each test.""" DisplayRegistry._backends = {} DisplayRegistry._initialized = False DisplayRegistry.register("null", NullDisplay) def test_run_pipeline_mode_loads_preset(self): """run_pipeline_mode() loads the specified preset.""" preset = get_preset("demo") assert preset is not None assert preset.name == "demo" def test_run_pipeline_mode_exits_on_unknown_preset(self): """run_pipeline_mode() exits if preset not found.""" with pytest.raises(SystemExit) as exc_info: run_pipeline_mode("nonexistent-preset") assert exc_info.value.code == 1 def test_run_pipeline_mode_fetches_content_for_headlines(self): """run_pipeline_mode() fetches content for headlines preset.""" with ( patch("engine.app.load_cache", return_value=None), patch( "engine.app.fetch_all", return_value=(["item1", "item2"], None, None) ), patch("engine.app.DisplayRegistry.create") as mock_create, patch("engine.app.effects_plugins"), ): mock_display = MagicMock() mock_create.return_value = mock_display with ( patch("engine.app.Pipeline") as mock_pipeline_class, patch("engine.app.time.sleep"), pytest.raises((StopIteration, AttributeError)), ): mock_pipeline = MagicMock() mock_pipeline_class.return_value = mock_pipeline # Will timeout after first iteration run_pipeline_mode("demo") def test_run_pipeline_mode_handles_empty_source(self): """run_pipeline_mode() handles empty source without fetching.""" with ( patch("engine.app.fetch_all") as mock_fetch, patch("engine.app.DisplayRegistry.create") as mock_create, patch("engine.app.effects_plugins"), ): mock_display = MagicMock() mock_create.return_value = mock_display with patch("engine.app.Pipeline") as mock_pipeline_class: mock_pipeline = MagicMock() mock_pipeline_class.return_value = mock_pipeline with patch("engine.app.time.sleep"): try: run_pipeline_mode("border-test") except (StopIteration, AttributeError): pass # Should NOT call fetch_all for empty source mock_fetch.assert_not_called() def test_run_pipeline_mode_uses_cached_content(self): """run_pipeline_mode() uses cached content if available.""" cached_items = ["cached1", "cached2"] with ( patch("engine.app.load_cache", return_value=cached_items), patch("engine.app.fetch_all") as mock_fetch, patch("engine.app.DisplayRegistry.create") as mock_create, patch("engine.app.effects_plugins"), ): mock_display = MagicMock() mock_create.return_value = mock_display with patch("engine.app.Pipeline") as mock_pipeline_class: mock_pipeline = MagicMock() mock_pipeline_class.return_value = mock_pipeline with patch("engine.app.time.sleep"): try: run_pipeline_mode("demo") except (StopIteration, AttributeError): pass # Should NOT call fetch_all when cache exists mock_fetch.assert_not_called() def test_run_pipeline_mode_fetches_poetry_for_poetry_preset(self): """run_pipeline_mode() fetches poetry for poetry source.""" with ( patch("engine.app.load_cache", return_value=None), patch("engine.app.fetch_poetry", return_value=(["poem1"], None, None)), patch("engine.app.fetch_all") as mock_fetch_all, patch("engine.app.DisplayRegistry.create") as mock_create, patch("engine.app.effects_plugins"), ): mock_display = MagicMock() mock_create.return_value = mock_display with patch("engine.app.Pipeline") as mock_pipeline_class: mock_pipeline = MagicMock() mock_pipeline_class.return_value = mock_pipeline with patch("engine.app.time.sleep"): try: run_pipeline_mode("poetry") except (StopIteration, AttributeError): pass # Should NOT call fetch_all for poetry preset mock_fetch_all.assert_not_called() def test_run_pipeline_mode_exits_when_no_content(self): """run_pipeline_mode() exits if no content available.""" with ( patch("engine.app.load_cache", return_value=None), patch("engine.app.fetch_all", return_value=([], None, None)), patch("engine.app.effects_plugins"), ): with pytest.raises(SystemExit) as exc_info: run_pipeline_mode("demo") assert exc_info.value.code == 1 def test_run_pipeline_mode_display_flag_overrides_preset(self): """run_pipeline_mode() uses CLI --display flag over preset.""" sys.argv = ["mainline.py", "--preset", "border-test", "--display", "null"] with ( patch("engine.app.load_cache", return_value=None), patch("engine.app.fetch_all", return_value=(["item"], None, None)), patch("engine.app.DisplayRegistry.create") as mock_create, patch("engine.app.effects_plugins"), ): mock_display = MagicMock() mock_create.return_value = mock_display with patch("engine.app.Pipeline") as mock_pipeline_class: mock_pipeline = MagicMock() mock_pipeline_class.return_value = mock_pipeline with patch("engine.app.time.sleep"): try: run_pipeline_mode("border-test") except (StopIteration, AttributeError): pass # Should create display with null, not terminal mock_create.assert_called_with("null") def test_run_pipeline_mode_discovers_effects_plugins(self): """run_pipeline_mode() discovers available effect plugins.""" with ( patch("engine.app.effects_plugins") as mock_effects, patch("engine.app.load_cache", return_value=["item"]), patch("engine.app.DisplayRegistry.create") as mock_create, patch("engine.app.Pipeline") as mock_pipeline_class, ): mock_display = MagicMock() mock_create.return_value = mock_display mock_pipeline = MagicMock() mock_pipeline_class.return_value = mock_pipeline with patch("engine.app.time.sleep"): try: run_pipeline_mode("demo") except (StopIteration, AttributeError): pass # Should call discover_plugins mock_effects.discover_plugins.assert_called_once() def test_run_pipeline_mode_creates_pipeline_with_preset_config(self): """run_pipeline_mode() creates pipeline with preset configuration.""" with ( patch("engine.app.load_cache", return_value=["item"]), patch("engine.app.DisplayRegistry.create") as mock_create, patch("engine.app.effects_plugins"), patch("engine.app.Pipeline") as mock_pipeline_class, ): mock_display = MagicMock() mock_create.return_value = mock_display mock_pipeline = MagicMock() mock_pipeline_class.return_value = mock_pipeline with patch("engine.app.time.sleep"): try: run_pipeline_mode("demo") except (StopIteration, AttributeError): pass # Verify Pipeline was created (call may vary, but it should be called) assert mock_pipeline_class.called def test_run_pipeline_mode_initializes_display(self): """run_pipeline_mode() initializes display with dimensions.""" with ( patch("engine.app.load_cache", return_value=["item"]), patch("engine.app.DisplayRegistry.create") as mock_create, patch("engine.app.effects_plugins"), patch("engine.app.Pipeline"), ): mock_display = MagicMock() mock_create.return_value = mock_display with patch("engine.app.time.sleep"): try: run_pipeline_mode("demo") except (StopIteration, AttributeError): pass # Display should be initialized with dimensions mock_display.init.assert_called() def test_run_pipeline_mode_builds_pipeline_before_initialize(self): """run_pipeline_mode() calls pipeline.build() before initialize().""" with ( patch("engine.app.load_cache", return_value=["item"]), patch("engine.app.DisplayRegistry.create"), patch("engine.app.effects_plugins"), patch("engine.app.Pipeline") as mock_pipeline_class, ): mock_pipeline = MagicMock() mock_pipeline_class.return_value = mock_pipeline with patch("engine.app.time.sleep"): try: run_pipeline_mode("demo") except (StopIteration, AttributeError): pass # Build should be called assert mock_pipeline.build.called