forked from genewildish/Mainline
refactor(app): move imports to module level for better testability
Move internal imports in run_pipeline_mode() to module level to support proper mocking in integration tests. This enables more effective testing of the app's initialization and pipeline setup. Also simplifies the test suite to focus on key integration points. Changes: - Moved effects_plugins, DisplayRegistry, PerformanceMonitor, fetch functions, and pipeline adapters to module-level imports - Removed duplicate imports from run_pipeline_mode() - Simplified test_app.py to focus on core functionality All manual tests still pass (border-test preset works correctly).
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user