"""Unit tests for configuration system.""" import os from pathlib import Path from tempfile import TemporaryDirectory from unittest.mock import patch import pytest from webdrop_bridge.config import Config, ConfigurationError @pytest.fixture(autouse=True) def clear_env(): """Clear environment variables before each test to avoid persistence.""" # Save current env saved_env = os.environ.copy() # Clear relevant variables for key in list(os.environ.keys()): if key.startswith(('APP_', 'LOG_', 'ALLOWED_', 'WEBAPP_', 'WINDOW_', 'ENABLE_')): del os.environ[key] yield # Restore env (cleanup) os.environ.clear() os.environ.update(saved_env) class TestConfigFromEnv: """Test Config.from_env() loading from environment.""" def test_from_env_with_all_values(self, tmp_path): """Test loading config with all environment variables set.""" # Create .env file env_file = tmp_path / ".env" root1 = tmp_path / "root1" root2 = tmp_path / "root2" root1.mkdir() root2.mkdir() env_file.write_text( f"APP_NAME=TestApp\n" f"APP_VERSION=2.0.0\n" f"LOG_LEVEL=DEBUG\n" f"LOG_FILE={tmp_path / 'test.log'}\n" f"ALLOWED_ROOTS={root1},{root2}\n" f"ALLOWED_URLS=example.com,*.test.org\n" f"WEBAPP_URL=http://localhost:8000\n" f"WINDOW_WIDTH=1200\n" f"WINDOW_HEIGHT=800\n" ) # Load config (env vars from file, not system) config = Config.from_env(str(env_file)) assert config.app_name == "TestApp" assert config.app_version == "2.0.0" assert config.log_level == "DEBUG" assert config.allowed_roots == [root1.resolve(), root2.resolve()] assert config.allowed_urls == ["example.com", "*.test.org"] assert config.webapp_url == "http://localhost:8000" assert config.window_width == 1200 assert config.window_height == 800 def test_from_env_with_defaults(self, tmp_path): """Test loading config uses defaults when env vars not set.""" # Create empty .env file env_file = tmp_path / ".env" env_file.write_text("") config = Config.from_env(str(env_file)) assert config.app_name == "WebDrop Bridge" assert config.app_version == "1.0.0" assert config.log_level == "INFO" assert config.window_width == 1024 assert config.window_height == 768 def test_from_env_invalid_log_level(self, tmp_path): """Test that invalid log level raises ConfigurationError.""" env_file = tmp_path / ".env" root1 = tmp_path / "root1" root1.mkdir() env_file.write_text(f"LOG_LEVEL=INVALID\nALLOWED_ROOTS={root1}\n") with pytest.raises(ConfigurationError, match="Invalid LOG_LEVEL"): Config.from_env(str(env_file)) def test_from_env_invalid_window_dimension(self, tmp_path): """Test that negative window dimensions raise ConfigurationError.""" env_file = tmp_path / ".env" root1 = tmp_path / "root1" root1.mkdir() env_file.write_text(f"WINDOW_WIDTH=-100\nALLOWED_ROOTS={root1}\n") with pytest.raises(ConfigurationError, match="Window dimensions"): Config.from_env(str(env_file)) def test_from_env_invalid_root_path(self, tmp_path): """Test that non-existent root paths raise ConfigurationError.""" env_file = tmp_path / ".env" env_file.write_text("ALLOWED_ROOTS=/nonexistent/path/that/does/not/exist\n") with pytest.raises(ConfigurationError, match="does not exist"): Config.from_env(str(env_file)) def test_from_env_empty_webapp_url(self, tmp_path): """Test that empty webapp URL raises ConfigurationError.""" env_file = tmp_path / ".env" root1 = tmp_path / "root1" root1.mkdir() env_file.write_text(f"WEBAPP_URL=\nALLOWED_ROOTS={root1}\n") with pytest.raises(ConfigurationError, match="WEBAPP_URL"): Config.from_env(str(env_file)) class TestConfigValidation: """Test Config field validation.""" def test_root_path_resolution(self, tmp_path): """Test that root paths are resolved to absolute paths.""" env_file = tmp_path / ".env" root_dir = tmp_path / "allowed" root_dir.mkdir() env_file.write_text(f"ALLOWED_ROOTS={root_dir}\n") config = Config.from_env(str(env_file)) # Should be resolved to absolute path assert config.allowed_roots[0].is_absolute() def test_multiple_root_paths(self, tmp_path): """Test loading multiple root paths.""" dir1 = tmp_path / "dir1" dir2 = tmp_path / "dir2" dir1.mkdir() dir2.mkdir() env_file = tmp_path / ".env" env_file.write_text(f"ALLOWED_ROOTS={dir1},{dir2}\n") config = Config.from_env(str(env_file)) assert len(config.allowed_roots) == 2 assert config.allowed_roots[0] == dir1.resolve() assert config.allowed_roots[1] == dir2.resolve() def test_allowed_urls_empty(self, tmp_path): """Test that empty ALLOWED_URLS means no URL restriction.""" env_file = tmp_path / ".env" env_file.write_text("ALLOWED_URLS=\n") config = Config.from_env(str(env_file)) assert config.allowed_urls == [] def test_allowed_urls_single(self, tmp_path): """Test loading single allowed URL.""" env_file = tmp_path / ".env" env_file.write_text("ALLOWED_URLS=example.com\n") config = Config.from_env(str(env_file)) assert config.allowed_urls == ["example.com"] def test_allowed_urls_multiple(self, tmp_path): """Test loading multiple allowed URLs.""" env_file = tmp_path / ".env" env_file.write_text("ALLOWED_URLS=example.com,*.test.org,localhost\n") config = Config.from_env(str(env_file)) assert config.allowed_urls == ["example.com", "*.test.org", "localhost"] def test_allowed_urls_with_whitespace(self, tmp_path): """Test that whitespace is trimmed from allowed URLs.""" env_file = tmp_path / ".env" env_file.write_text("ALLOWED_URLS= example.com , test.org \n") config = Config.from_env(str(env_file)) assert config.allowed_urls == ["example.com", "test.org"]