""" Tests for the new unified pipeline architecture. """ from unittest.mock import MagicMock from engine.pipeline import ( Pipeline, PipelineConfig, PipelineContext, Stage, StageRegistry, create_default_pipeline, discover_stages, ) class TestStageRegistry: """Tests for StageRegistry.""" def setup_method(self): """Reset registry before each test.""" StageRegistry._discovered = False StageRegistry._categories.clear() StageRegistry._instances.clear() def test_discover_stages_registers_sources(self): """discover_stages registers source stages.""" discover_stages() sources = StageRegistry.list("source") assert "HeadlinesDataSource" in sources assert "PoetryDataSource" in sources assert "PipelineDataSource" in sources def test_discover_stages_registers_displays(self): """discover_stages registers display stages.""" discover_stages() displays = StageRegistry.list("display") assert "terminal" in displays assert "pygame" in displays assert "websocket" in displays assert "null" in displays assert "sixel" in displays def test_create_source_stage(self): """StageRegistry.create creates source stages.""" discover_stages() source = StageRegistry.create("source", "HeadlinesDataSource") assert source is not None assert source.name == "headlines" def test_create_display_stage(self): """StageRegistry.create creates display stages.""" discover_stages() display = StageRegistry.create("display", "terminal") assert display is not None assert hasattr(display, "_display") def test_create_display_stage_pygame(self): """StageRegistry.create creates pygame display stage.""" discover_stages() display = StageRegistry.create("display", "pygame") assert display is not None class TestPipeline: """Tests for Pipeline class.""" def setup_method(self): """Reset registry before each test.""" StageRegistry._discovered = False StageRegistry._categories.clear() StageRegistry._instances.clear() discover_stages() def test_create_pipeline(self): """Pipeline can be created with config.""" config = PipelineConfig(source="headlines", display="terminal") pipeline = Pipeline(config=config) assert pipeline.config is not None assert pipeline.config.source == "headlines" assert pipeline.config.display == "terminal" def test_add_stage(self): """Pipeline.add_stage adds a stage.""" pipeline = Pipeline() mock_stage = MagicMock(spec=Stage) mock_stage.name = "test_stage" mock_stage.category = "test" pipeline.add_stage("test", mock_stage) assert "test" in pipeline.stages def test_build_resolves_dependencies(self): """Pipeline.build resolves execution order.""" pipeline = Pipeline() mock_source = MagicMock(spec=Stage) mock_source.name = "source" mock_source.category = "source" mock_source.dependencies = set() mock_display = MagicMock(spec=Stage) mock_display.name = "display" mock_display.category = "display" mock_display.dependencies = {"source"} pipeline.add_stage("source", mock_source) pipeline.add_stage("display", mock_display) pipeline.build() assert pipeline._initialized is True assert "source" in pipeline.execution_order assert "display" in pipeline.execution_order def test_execute_runs_stages(self): """Pipeline.execute runs all stages in order.""" pipeline = Pipeline() call_order = [] mock_source = MagicMock(spec=Stage) mock_source.name = "source" mock_source.category = "source" mock_source.dependencies = set() mock_source.process = lambda data, ctx: call_order.append("source") or "data" mock_effect = MagicMock(spec=Stage) mock_effect.name = "effect" mock_effect.category = "effect" mock_effect.dependencies = {"source"} mock_effect.process = lambda data, ctx: call_order.append("effect") or data mock_display = MagicMock(spec=Stage) mock_display.name = "display" mock_display.category = "display" mock_display.dependencies = {"effect"} mock_display.process = lambda data, ctx: call_order.append("display") or data pipeline.add_stage("source", mock_source) pipeline.add_stage("effect", mock_effect) pipeline.add_stage("display", mock_display) pipeline.build() result = pipeline.execute(None) assert result.success is True assert call_order == ["source", "effect", "display"] def test_execute_handles_stage_failure(self): """Pipeline.execute handles stage failures.""" pipeline = Pipeline() mock_source = MagicMock(spec=Stage) mock_source.name = "source" mock_source.category = "source" mock_source.dependencies = set() mock_source.process = lambda data, ctx: "data" mock_failing = MagicMock(spec=Stage) mock_failing.name = "failing" mock_failing.category = "effect" mock_failing.dependencies = {"source"} mock_failing.optional = False mock_failing.process = lambda data, ctx: (_ for _ in ()).throw( Exception("fail") ) pipeline.add_stage("source", mock_source) pipeline.add_stage("failing", mock_failing) pipeline.build() result = pipeline.execute(None) assert result.success is False assert result.error is not None def test_optional_stage_failure_continues(self): """Pipeline.execute continues on optional stage failure.""" pipeline = Pipeline() mock_source = MagicMock(spec=Stage) mock_source.name = "source" mock_source.category = "source" mock_source.dependencies = set() mock_source.process = lambda data, ctx: "data" mock_optional = MagicMock(spec=Stage) mock_optional.name = "optional" mock_optional.category = "effect" mock_optional.dependencies = {"source"} mock_optional.optional = True mock_optional.process = lambda data, ctx: (_ for _ in ()).throw( Exception("fail") ) pipeline.add_stage("source", mock_source) pipeline.add_stage("optional", mock_optional) pipeline.build() result = pipeline.execute(None) assert result.success is True class TestPipelineContext: """Tests for PipelineContext.""" def test_init_empty(self): """PipelineContext initializes with empty services and state.""" ctx = PipelineContext() assert ctx.services == {} assert ctx.state == {} def test_init_with_services(self): """PipelineContext accepts initial services.""" ctx = PipelineContext(services={"display": MagicMock()}) assert "display" in ctx.services def test_init_with_state(self): """PipelineContext accepts initial state.""" ctx = PipelineContext(initial_state={"count": 42}) assert ctx.get_state("count") == 42 def test_get_set_services(self): """PipelineContext can get/set services.""" ctx = PipelineContext() mock_service = MagicMock() ctx.set("test_service", mock_service) assert ctx.get("test_service") == mock_service def test_get_set_state(self): """PipelineContext can get/set state.""" ctx = PipelineContext() ctx.set_state("counter", 100) assert ctx.get_state("counter") == 100 def test_lazy_resolver(self): """PipelineContext resolves lazy services.""" ctx = PipelineContext() config = ctx.get("config") assert config is not None def test_has_capability(self): """PipelineContext.has_capability checks for services.""" ctx = PipelineContext(services={"display.output": MagicMock()}) assert ctx.has_capability("display.output") is True assert ctx.has_capability("missing") is False class TestCreateDefaultPipeline: """Tests for create_default_pipeline function.""" def setup_method(self): """Reset registry before each test.""" StageRegistry._discovered = False StageRegistry._categories.clear() StageRegistry._instances.clear() discover_stages() def test_create_default_pipeline(self): """create_default_pipeline creates a working pipeline.""" pipeline = create_default_pipeline() assert pipeline is not None assert "display" in pipeline.stages