""" 配置管理模块测试 """ import pytest import tempfile import shutil from pathlib import Path from src.config.settings import ( Settings, SettingsManager, AIConfig, OCRConfig, CloudStorageConfig, UIConfig, Hotkey, AdvancedConfig, AIProvider, OCRMode, CloudStorageType, Theme, ConfigError, get_config, get_settings ) class TestAIConfig: """测试 AI 配置""" def test_default_config(self): """测试默认配置""" config = AIConfig() assert config.provider == AIProvider.ANTHROPIC assert config.model == "claude-3-5-sonnet-20241022" assert config.temperature == 0.7 assert config.max_tokens == 4096 def test_validation_success(self): """测试验证成功""" config = AIConfig( provider=AIProvider.OPENAI, api_key="sk-test", temperature=1.0, max_tokens=2048 ) config.validate() # 不应抛出异常 def test_validation_missing_api_key(self): """测试缺少 API key""" config = AIConfig(provider=AIProvider.OPENAI, api_key="") with pytest.raises(ConfigError, match="API key"): config.validate() def test_validation_invalid_temperature(self): """测试无效的 temperature""" config = AIConfig(temperature=3.0) with pytest.raises(ConfigError, match="temperature"): config.validate() def test_validation_invalid_max_tokens(self): """测试无效的 max_tokens""" config = AIConfig(max_tokens=0) with pytest.raises(ConfigError, match="max_tokens"): config.validate() class TestOCRConfig: """测试 OCR 配置""" def test_default_config(self): """测试默认配置""" config = OCRConfig() assert config.mode == OCRMode.LOCAL assert config.lang == "ch" assert config.use_gpu is False def test_cloud_mode_validation(self): """测试云端模式验证""" config = OCRConfig(mode=OCRMode.CLOUD, api_endpoint="") with pytest.raises(ConfigError, match="api_endpoint"): config.validate() class TestCloudStorageConfig: """测试云存储配置""" def test_default_config(self): """测试默认配置""" config = CloudStorageConfig() assert config.type == CloudStorageType.NONE def test_no_storage_skip_validation(self): """测试不使用云存储时跳过验证""" config = CloudStorageConfig(type=CloudStorageType.NONE) config.validate() # 不应抛出异常 def test_s3_validation_success(self): """测试 S3 配置验证成功""" config = CloudStorageConfig( type=CloudStorageType.S3, endpoint="https://s3.amazonaws.com", access_key="test-key", secret_key="test-secret", bucket="test-bucket" ) config.validate() # 不应抛出异常 def test_storage_validation_missing_endpoint(self): """测试缺少 endpoint""" config = CloudStorageConfig( type=CloudStorageType.S3, endpoint="" ) with pytest.raises(ConfigError, match="endpoint"): config.validate() def test_storage_validation_missing_credentials(self): """测试缺少凭证""" config = CloudStorageConfig( type=CloudStorageType.S3, endpoint="https://s3.amazonaws.com", access_key="", secret_key="" ) with pytest.raises(ConfigError, match="access_key.*secret_key"): config.validate() class TestUIConfig: """测试界面配置""" def test_default_config(self): """测试默认配置""" config = UIConfig() assert config.theme == Theme.AUTO assert config.language == "zh_CN" assert config.window_width == 1200 assert config.window_height == 800 def test_hotkeys_default(self): """测试默认快捷键""" config = UIConfig() assert config.hotkeys.screenshot == "Ctrl+Shift+A" assert config.hotkeys.ocr == "Ctrl+Shift+O" def test_validation_invalid_size(self): """测试无效窗口大小""" config = UIConfig(window_width=300) with pytest.raises(ConfigError, match="window_width"): config.validate() class TestAdvancedConfig: """测试高级配置""" def test_default_config(self): """测试默认配置""" config = AdvancedConfig() assert config.debug_mode is False assert config.log_level == "INFO" assert config.max_log_size == 10 def test_invalid_log_level(self): """测试无效的日志级别""" config = AdvancedConfig(log_level="INVALID") with pytest.raises(ConfigError, match="log_level"): config.validate() class TestSettings: """测试主配置类""" def test_default_settings(self): """测试默认配置""" settings = Settings() assert isinstance(settings.ai, AIConfig) assert isinstance(settings.ocr, OCRConfig) assert isinstance(settings.cloud_storage, CloudStorageConfig) assert isinstance(settings.ui, UIConfig) assert isinstance(settings.advanced, AdvancedConfig) def test_validate_all(self): """测试验证所有配置""" settings = Settings() settings.validate() # 默认配置应该验证通过 def test_to_dict(self): """测试转换为字典""" settings = Settings() data = settings.to_dict() assert 'ai' in data assert 'ocr' in data assert 'cloud_storage' in data assert 'ui' in data assert 'advanced' in data def test_from_dict(self): """测试从字典创建""" data = { 'ai': {'provider': 'openai', 'api_key': 'sk-test', 'model': 'gpt-4'}, 'ocr': {'mode': 'local', 'use_gpu': True}, 'cloud_storage': {'type': 'none'}, 'ui': {'theme': 'dark', 'language': 'en_US'}, 'advanced': {'debug_mode': True} } settings = Settings.from_dict(data) assert settings.ai.provider == AIProvider.OPENAI assert settings.ai.api_key == 'sk-test' assert settings.ai.model == 'gpt-4' assert settings.ocr.use_gpu is True assert settings.ui.theme == Theme.DARK assert settings.ui.language == 'en_US' assert settings.advanced.debug_mode is True class TestSettingsManager: """测试配置管理器""" @pytest.fixture def temp_config_dir(self): """创建临时配置目录""" temp_dir = Path(tempfile.mkdtemp()) yield temp_dir shutil.rmtree(temp_dir) @pytest.fixture def config_file(self, temp_config_dir): """创建临时配置文件路径""" return temp_config_dir / 'test_config.yaml' def test_create_default_config(self, config_file): """测试创建默认配置文件""" manager = SettingsManager(config_file) settings = manager.load() assert isinstance(settings, Settings) assert config_file.exists() # 读取文件内容验证 with open(config_file, 'r', encoding='utf-8') as f: content = f.read() assert 'ai:' in content assert 'ocr:' in content def test_save_and_load(self, config_file): """测试保存和加载""" manager = SettingsManager(config_file) # 创建自定义配置 settings = Settings() settings.ai.provider = AIProvider.OPENAI settings.ai.api_key = "sk-test-key" settings.ui.theme = Theme.DARK settings.ui.window_width = 1400 # 保存 manager.save(settings) # 重新加载 manager2 = SettingsManager(config_file) loaded_settings = manager2.load() assert loaded_settings.ai.provider == AIProvider.OPENAI assert loaded_settings.ai.api_key == "sk-test-key" assert loaded_settings.ui.theme == Theme.DARK assert loaded_settings.ui.window_width == 1400 def test_reset_config(self, config_file): """测试重置配置""" manager = SettingsManager(config_file) # 修改配置 settings = manager.settings settings.ai.provider = AIProvider.OPENAI settings.ai.api_key = "sk-test" manager.save() # 重置 manager.reset() assert manager.settings.ai.provider == AIProvider.ANTHROPIC def test_get_nested_value(self, config_file): """测试获取嵌套配置值""" manager = SettingsManager(config_file) assert manager.get('ai.provider') == AIProvider.ANTHROPIC assert manager.get('ui.theme') == Theme.AUTO assert manager.get('ui.hotkeys.screenshot') == "Ctrl+Shift+A" assert manager.get('nonexistent.key', 'default') == 'default' def test_set_nested_value(self, config_file): """测试设置嵌套配置值""" manager = SettingsManager(config_file) manager.set('ai.provider', AIProvider.OPENAI) manager.set('ai.temperature', 1.5) manager.set('ui.theme', Theme.DARK) manager.set('ui.window_width', 1600) assert manager.settings.ai.provider == AIProvider.OPENAI assert manager.settings.ai.temperature == 1.5 assert manager.settings.ui.theme == Theme.DARK assert manager.settings.ui.window_width == 1600 # 重新加载验证持久化 manager2 = SettingsManager(config_file) assert manager2.settings.ai.provider == AIProvider.OPENAI assert manager2.settings.ui.window_width == 1600 def test_set_invalid_path(self, config_file): """测试设置无效路径""" manager = SettingsManager(config_file) with pytest.raises(ConfigError, match="配置路径无效"): manager.set('invalid.path.value', 'test') def test_lazy_loading(self, config_file): """测试懒加载""" manager = SettingsManager(config_file) # 首次访问应该触发加载 assert manager._settings is None _ = manager.settings assert manager._settings is not None # 后续访问应使用缓存 _ = manager.settings assert manager._settings is not None def test_get_settings_singleton(temp_config_dir): """测试全局配置单例""" import tempfile import shutil config_path = temp_config_dir / 'global_config.yaml' # 首次调用 manager1 = get_config(config_path) # 加载配置 _ = manager1.settings # 第二次调用应返回同一实例 manager2 = get_config() assert manager1 is manager2 # 清理全局单例以避免影响其他测试 from src.config import settings settings._global_settings_manager = None