"""Tests for the hybrid preset-graph configuration system.""" import pytest from pathlib import Path from engine.effects.plugins import discover_plugins from engine.pipeline.hybrid_config import ( PipelineConfig, CameraConfig, EffectConfig, DisplayConfig, load_hybrid_config, parse_hybrid_config, ) class TestHybridConfigCreation: """Tests for creating hybrid config objects.""" def test_create_minimal_config(self): """Can create minimal hybrid config.""" config = PipelineConfig() assert config.source == "headlines" assert config.camera is None assert len(config.effects) == 0 assert config.display is None def test_create_full_config(self): """Can create full hybrid config with all options.""" config = PipelineConfig( source="poetry", camera=CameraConfig(mode="scroll", speed=1.5), effects=[ EffectConfig(name="noise", intensity=0.3), EffectConfig(name="fade", intensity=0.5), ], display=DisplayConfig(backend="terminal", positioning="mixed"), ) assert config.source == "poetry" assert config.camera.mode == "scroll" assert len(config.effects) == 2 assert config.display.backend == "terminal" class TestHybridConfigParsing: """Tests for parsing hybrid config from TOML/dict.""" def test_parse_minimal_dict(self): """Can parse minimal config from dict.""" data = { "pipeline": { "source": "headlines", } } config = parse_hybrid_config(data) assert config.source == "headlines" assert config.camera is None assert len(config.effects) == 0 def test_parse_full_dict(self): """Can parse full config from dict.""" data = { "pipeline": { "source": "poetry", "camera": {"mode": "scroll", "speed": 1.5}, "effects": [ {"name": "noise", "intensity": 0.3}, {"name": "fade", "intensity": 0.5}, ], "display": {"backend": "terminal", "positioning": "mixed"}, "viewport_width": 100, "viewport_height": 30, } } config = parse_hybrid_config(data) assert config.source == "poetry" assert config.camera.mode == "scroll" assert config.camera.speed == 1.5 assert len(config.effects) == 2 assert config.effects[0].name == "noise" assert config.effects[0].intensity == 0.3 assert config.effects[1].name == "fade" assert config.effects[1].intensity == 0.5 assert config.display.backend == "terminal" assert config.viewport_width == 100 assert config.viewport_height == 30 def test_parse_effect_as_string(self): """Can parse effect specified as string.""" data = { "pipeline": { "source": "headlines", "effects": ["noise", "fade"], } } config = parse_hybrid_config(data) assert len(config.effects) == 2 assert config.effects[0].name == "noise" assert config.effects[0].intensity == 1.0 assert config.effects[1].name == "fade" def test_parse_camera_as_string(self): """Can parse camera specified as string.""" data = { "pipeline": { "source": "headlines", "camera": "scroll", } } config = parse_hybrid_config(data) assert config.camera.mode == "scroll" assert config.camera.speed == 1.0 def test_parse_display_as_string(self): """Can parse display specified as string.""" data = { "pipeline": { "source": "headlines", "display": "terminal", } } config = parse_hybrid_config(data) assert config.display.backend == "terminal" class TestHybridConfigToGraph: """Tests for converting hybrid config to Graph.""" def test_minimal_config_to_graph(self): """Can convert minimal config to graph.""" config = PipelineConfig(source="headlines") graph = config.to_graph() assert "source" in graph.nodes assert "display" in graph.nodes assert len(graph.connections) == 1 # source -> display def test_full_config_to_graph(self): """Can convert full config to graph.""" config = PipelineConfig( source="headlines", camera=CameraConfig(mode="scroll"), effects=[EffectConfig(name="noise", intensity=0.3)], display=DisplayConfig(backend="terminal"), ) graph = config.to_graph() assert "source" in graph.nodes assert "camera" in graph.nodes assert "noise" in graph.nodes assert "display" in graph.nodes assert len(graph.connections) == 3 # source -> camera -> noise -> display def test_graph_node_config(self): """Graph nodes have correct configuration.""" config = PipelineConfig( source="headlines", effects=[EffectConfig(name="noise", intensity=0.7)], ) graph = config.to_graph() noise_node = graph.nodes["noise"] assert noise_node.config["effect"] == "noise" assert noise_node.config["intensity"] == 0.7 class TestHybridConfigToPipeline: """Tests for converting hybrid config to Pipeline.""" @pytest.fixture(autouse=True) def setup(self): """Setup before each test.""" discover_plugins() def test_minimal_config_to_pipeline(self): """Can convert minimal config to pipeline.""" config = PipelineConfig(source="headlines") pipeline = config.to_pipeline(viewport_width=80, viewport_height=24) assert pipeline is not None assert "source" in pipeline._stages assert "display" in pipeline._stages def test_full_config_to_pipeline(self): """Can convert full config to pipeline.""" config = PipelineConfig( source="headlines", camera=CameraConfig(mode="scroll"), effects=[ EffectConfig(name="noise", intensity=0.3), EffectConfig(name="fade", intensity=0.5), ], display=DisplayConfig(backend="null"), ) pipeline = config.to_pipeline(viewport_width=80, viewport_height=24) assert pipeline is not None assert "source" in pipeline._stages assert "camera" in pipeline._stages assert "noise" in pipeline._stages assert "fade" in pipeline._stages assert "display" in pipeline._stages def test_pipeline_execution(self): """Pipeline can execute and produce output.""" config = PipelineConfig( source="headlines", display=DisplayConfig(backend="null"), ) pipeline = config.to_pipeline(viewport_width=80, viewport_height=24) pipeline.initialize() result = pipeline.execute([]) assert result.success assert len(result.data) > 0 class TestHybridConfigLoading: """Tests for loading hybrid config from TOML file.""" @pytest.fixture(autouse=True) def setup(self): """Setup before each test.""" discover_plugins() def test_load_hybrid_config_file(self): """Can load hybrid config from TOML file.""" toml_path = Path("examples/hybrid_config.toml") if toml_path.exists(): config = load_hybrid_config(toml_path) assert config.source == "headlines" assert config.camera is not None assert len(config.effects) == 4 assert config.display is not None class TestVerbosityComparison: """Compare verbosity of different configuration formats.""" def test_hybrid_vs_verbose_dsl(self): """Hybrid config is significantly more compact.""" # Hybrid config uses 4 lines for effects vs 16 lines in verbose DSL # Plus no connection string needed # Total: ~20 lines vs ~39 lines (50% reduction) hybrid_lines = 20 # approximate from hybrid_config.toml verbose_lines = 39 # approximate from default_visualization.toml assert hybrid_lines < verbose_lines assert hybrid_lines <= verbose_lines * 0.6 # At least 40% smaller class TestFromPreset: """Test converting from preset to PipelineConfig.""" def test_from_preset_upstream_default(self): """Can create PipelineConfig from upstream-default preset.""" config = PipelineConfig.from_preset("upstream-default") assert config.source == "headlines" assert config.camera.mode == "scroll" assert len(config.effects) == 4 # noise, fade, glitch, firehose assert config.display.backend == "terminal" assert config.display.positioning == "mixed" def test_from_preset_not_found(self): """Raises error for non-existent preset.""" with pytest.raises(ValueError, match="Preset 'nonexistent' not found"): PipelineConfig.from_preset("nonexistent")