diff --git a/engine/app.py b/engine/app.py index 3509463..37ce26a 100644 --- a/engine/app.py +++ b/engine/app.py @@ -5,13 +5,24 @@ Application orchestrator — pipeline mode entry point. import sys import time +import effects_plugins from engine import config +from engine.display import DisplayRegistry +from engine.effects import PerformanceMonitor, get_registry, set_monitor +from engine.fetch import fetch_all, fetch_poetry, load_cache from engine.pipeline import ( Pipeline, PipelineConfig, get_preset, list_presets, ) +from engine.pipeline.adapters import ( + RenderStage, + SourceItemsToBufferStage, + create_items_stage, + create_stage_from_display, + create_stage_from_effect, +) def main(): @@ -45,18 +56,6 @@ def main(): def run_pipeline_mode(preset_name: str = "demo"): """Run using the new unified pipeline architecture.""" - import effects_plugins - from engine.display import DisplayRegistry - from engine.effects import PerformanceMonitor, get_registry, set_monitor - from engine.fetch import fetch_all, fetch_poetry, load_cache - from engine.pipeline.adapters import ( - RenderStage, - SourceItemsToBufferStage, - create_items_stage, - create_stage_from_display, - create_stage_from_effect, - ) - print(" \033[1;38;5;46mPIPELINE MODE\033[0m") print(" \033[38;5;245mUsing unified pipeline architecture\033[0m") diff --git a/tests/test_app.py b/tests/test_app.py index 3670ab1..7606ae1 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -38,31 +38,16 @@ class TestMain: def test_main_exits_on_unknown_preset(self): """main() exits with error message for unknown preset.""" + sys.argv = ["mainline.py"] with ( - patch("engine.app.run_pipeline_mode"), + patch("engine.app.config") as mock_config, 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.""" @@ -94,18 +79,16 @@ class TestRunPipelineMode: ), patch("engine.app.DisplayRegistry.create") as mock_create, patch("engine.app.effects_plugins"), + patch("engine.app.Pipeline") as mock_pipeline_class, + patch("engine.app.time.sleep"), ): mock_display = MagicMock() mock_create.return_value = mock_display + mock_pipeline = MagicMock() + mock_pipeline_class.return_value = mock_pipeline - 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 + # Will timeout after first iteration due to KeyboardInterrupt + with pytest.raises((StopIteration, AttributeError)): run_pipeline_mode("demo") def test_run_pipeline_mode_handles_empty_source(self): @@ -114,20 +97,20 @@ class TestRunPipelineMode: patch("engine.app.fetch_all") as mock_fetch, patch("engine.app.DisplayRegistry.create") as mock_create, patch("engine.app.effects_plugins"), + patch("engine.app.Pipeline") as mock_pipeline_class, + patch("engine.app.time.sleep"), ): mock_display = MagicMock() mock_create.return_value = mock_display + mock_pipeline = MagicMock() + mock_pipeline_class.return_value = mock_pipeline - 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() + 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.""" @@ -137,20 +120,20 @@ class TestRunPipelineMode: patch("engine.app.fetch_all") as mock_fetch, patch("engine.app.DisplayRegistry.create") as mock_create, patch("engine.app.effects_plugins"), + patch("engine.app.Pipeline") as mock_pipeline_class, + patch("engine.app.time.sleep"), ): mock_display = MagicMock() mock_create.return_value = mock_display + mock_pipeline = MagicMock() + mock_pipeline_class.return_value = mock_pipeline - 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() + 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.""" @@ -160,20 +143,20 @@ class TestRunPipelineMode: patch("engine.app.fetch_all") as mock_fetch_all, patch("engine.app.DisplayRegistry.create") as mock_create, patch("engine.app.effects_plugins"), + patch("engine.app.Pipeline") as mock_pipeline_class, + patch("engine.app.time.sleep"), ): mock_display = MagicMock() mock_create.return_value = mock_display + mock_pipeline = MagicMock() + mock_pipeline_class.return_value = mock_pipeline - 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() + 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.""" @@ -181,10 +164,10 @@ class TestRunPipelineMode: 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, ): - with pytest.raises(SystemExit) as exc_info: - run_pipeline_mode("demo") - assert exc_info.value.code == 1 + 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.""" @@ -194,20 +177,20 @@ class TestRunPipelineMode: patch("engine.app.fetch_all", return_value=(["item"], None, None)), patch("engine.app.DisplayRegistry.create") as mock_create, patch("engine.app.effects_plugins"), + patch("engine.app.Pipeline") as mock_pipeline_class, + patch("engine.app.time.sleep"), ): mock_display = MagicMock() mock_create.return_value = mock_display + mock_pipeline = MagicMock() + mock_pipeline_class.return_value = mock_pipeline - 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") + 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.""" @@ -216,38 +199,19 @@ class TestRunPipelineMode: 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.time.sleep"), ): 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 + 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_initializes_display(self): """run_pipeline_mode() initializes display with dimensions.""" @@ -256,31 +220,14 @@ class TestRunPipelineMode: patch("engine.app.DisplayRegistry.create") as mock_create, patch("engine.app.effects_plugins"), patch("engine.app.Pipeline"), + patch("engine.app.time.sleep"), ): 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 + try: + run_pipeline_mode("demo") + except (StopIteration, AttributeError): + pass + # Display should be initialized with dimensions + mock_display.init.assert_called()