A community based topic aggregation platform built on atproto
at main 7.9 kB view raw
1""" 2Tests for Configuration Loader. 3 4Tests loading and validating aggregator configuration. 5""" 6import pytest 7import tempfile 8from pathlib import Path 9 10from src.config import ConfigLoader, ConfigError 11from src.models import AggregatorConfig, FeedConfig 12 13 14@pytest.fixture 15def valid_config_yaml(): 16 """Valid configuration YAML.""" 17 return """ 18coves_api_url: "https://api.coves.social" 19 20feeds: 21 - name: "World News" 22 url: "https://news.kagi.com/world.xml" 23 community_handle: "world-news.coves.social" 24 enabled: true 25 26 - name: "Tech News" 27 url: "https://news.kagi.com/tech.xml" 28 community_handle: "tech.coves.social" 29 enabled: true 30 31 - name: "Science News" 32 url: "https://news.kagi.com/science.xml" 33 community_handle: "science.coves.social" 34 enabled: false 35 36log_level: "info" 37""" 38 39 40@pytest.fixture 41def temp_config_file(valid_config_yaml): 42 """Create a temporary config file.""" 43 with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.yaml') as f: 44 f.write(valid_config_yaml) 45 temp_path = Path(f.name) 46 yield temp_path 47 # Cleanup 48 if temp_path.exists(): 49 temp_path.unlink() 50 51 52class TestConfigLoader: 53 """Test suite for ConfigLoader.""" 54 55 def test_load_valid_config(self, temp_config_file): 56 """Test loading valid configuration.""" 57 loader = ConfigLoader(temp_config_file) 58 config = loader.load() 59 60 assert isinstance(config, AggregatorConfig) 61 assert config.coves_api_url == "https://api.coves.social" 62 assert config.log_level == "info" 63 assert len(config.feeds) == 3 64 65 def test_parse_feed_configs(self, temp_config_file): 66 """Test parsing feed configurations.""" 67 loader = ConfigLoader(temp_config_file) 68 config = loader.load() 69 70 # Check first feed 71 feed1 = config.feeds[0] 72 assert isinstance(feed1, FeedConfig) 73 assert feed1.name == "World News" 74 assert feed1.url == "https://news.kagi.com/world.xml" 75 assert feed1.community_handle == "world-news.coves.social" 76 assert feed1.enabled is True 77 78 # Check disabled feed 79 feed3 = config.feeds[2] 80 assert feed3.name == "Science News" 81 assert feed3.enabled is False 82 83 def test_get_enabled_feeds_only(self, temp_config_file): 84 """Test getting only enabled feeds.""" 85 loader = ConfigLoader(temp_config_file) 86 config = loader.load() 87 88 enabled_feeds = [f for f in config.feeds if f.enabled] 89 assert len(enabled_feeds) == 2 90 assert all(f.enabled for f in enabled_feeds) 91 92 def test_missing_config_file_raises_error(self): 93 """Test that missing config file raises error.""" 94 with pytest.raises(ConfigError, match="not found"): 95 loader = ConfigLoader(Path("nonexistent.yaml")) 96 loader.load() 97 98 def test_invalid_yaml_raises_error(self): 99 """Test that invalid YAML raises error.""" 100 with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.yaml') as f: 101 f.write("invalid: yaml: content: [[[") 102 temp_path = Path(f.name) 103 104 try: 105 with pytest.raises(ConfigError, match="Failed to parse"): 106 loader = ConfigLoader(temp_path) 107 loader.load() 108 finally: 109 temp_path.unlink() 110 111 def test_missing_required_field_raises_error(self): 112 """Test that missing required fields raise error.""" 113 invalid_yaml = """ 114feeds: 115 - name: "Test" 116 url: "https://test.xml" 117 # Missing community_handle! 118""" 119 with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.yaml') as f: 120 f.write(invalid_yaml) 121 temp_path = Path(f.name) 122 123 try: 124 with pytest.raises(ConfigError, match="Missing required field"): 125 loader = ConfigLoader(temp_path) 126 loader.load() 127 finally: 128 temp_path.unlink() 129 130 def test_missing_coves_api_url_raises_error(self): 131 """Test that missing coves_api_url raises error.""" 132 invalid_yaml = """ 133feeds: 134 - name: "Test" 135 url: "https://test.xml" 136 community_handle: "test.coves.social" 137""" 138 with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.yaml') as f: 139 f.write(invalid_yaml) 140 temp_path = Path(f.name) 141 142 try: 143 with pytest.raises(ConfigError, match="coves_api_url"): 144 loader = ConfigLoader(temp_path) 145 loader.load() 146 finally: 147 temp_path.unlink() 148 149 def test_default_log_level(self): 150 """Test that log_level defaults to 'info' if not specified.""" 151 minimal_yaml = """ 152coves_api_url: "https://api.coves.social" 153feeds: 154 - name: "Test" 155 url: "https://test.xml" 156 community_handle: "test.coves.social" 157""" 158 with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.yaml') as f: 159 f.write(minimal_yaml) 160 temp_path = Path(f.name) 161 162 try: 163 loader = ConfigLoader(temp_path) 164 config = loader.load() 165 assert config.log_level == "info" 166 finally: 167 temp_path.unlink() 168 169 def test_default_enabled_true(self): 170 """Test that feed enabled defaults to True if not specified.""" 171 yaml_content = """ 172coves_api_url: "https://api.coves.social" 173feeds: 174 - name: "Test" 175 url: "https://test.xml" 176 community_handle: "test.coves.social" 177 # No 'enabled' field 178""" 179 with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.yaml') as f: 180 f.write(yaml_content) 181 temp_path = Path(f.name) 182 183 try: 184 loader = ConfigLoader(temp_path) 185 config = loader.load() 186 assert config.feeds[0].enabled is True 187 finally: 188 temp_path.unlink() 189 190 def test_invalid_url_format_raises_error(self): 191 """Test that invalid URLs raise error.""" 192 invalid_yaml = """ 193coves_api_url: "https://api.coves.social" 194feeds: 195 - name: "Test" 196 url: "not-a-valid-url" 197 community_handle: "test.coves.social" 198""" 199 with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.yaml') as f: 200 f.write(invalid_yaml) 201 temp_path = Path(f.name) 202 203 try: 204 with pytest.raises(ConfigError, match="Invalid URL"): 205 loader = ConfigLoader(temp_path) 206 loader.load() 207 finally: 208 temp_path.unlink() 209 210 def test_empty_feeds_list_raises_error(self): 211 """Test that empty feeds list raises error.""" 212 invalid_yaml = """ 213coves_api_url: "https://api.coves.social" 214feeds: [] 215""" 216 with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.yaml') as f: 217 f.write(invalid_yaml) 218 temp_path = Path(f.name) 219 220 try: 221 with pytest.raises(ConfigError, match="at least one feed"): 222 loader = ConfigLoader(temp_path) 223 loader.load() 224 finally: 225 temp_path.unlink() 226 227 def test_load_from_env_override(self, temp_config_file, monkeypatch): 228 """Test that environment variables can override config values.""" 229 # Set environment variable 230 monkeypatch.setenv("COVES_API_URL", "https://test.coves.social") 231 232 loader = ConfigLoader(temp_config_file) 233 config = loader.load() 234 235 # Should use env var instead of config file 236 assert config.coves_api_url == "https://test.coves.social" 237 238 def test_get_feed_by_url(self, temp_config_file): 239 """Test helper to get feed config by URL.""" 240 loader = ConfigLoader(temp_config_file) 241 config = loader.load() 242 243 feed = next((f for f in config.feeds if f.url == "https://news.kagi.com/tech.xml"), None) 244 assert feed is not None 245 assert feed.name == "Tech News" 246 assert feed.community_handle == "tech.coves.social"