项目初始化 - 创建完整项目结构(src/, data/, docs/, examples/, tests/) - 配置requirements.txt依赖 - 创建.gitignore P0基础框架 - 数据库模型:Record模型,6种分类类型 - 配置管理:YAML配置,支持AI/OCR/云存储/UI配置 - OCR模块:PaddleOCR本地识别,支持云端扩展 - AI模块:支持OpenAI/Claude/通义/Ollama,6种分类 - 存储模块:完整CRUD,搜索,统计,导入导出 - 主窗口框架:侧边栏导航,米白配色方案 - 图片处理:截图/剪贴板/文件选择/图片预览 - 处理流程整合:OCR→AI→存储串联,Markdown展示,剪贴板复制 - 分类浏览:卡片网格展示,分类筛选,搜索,详情查看 技术栈 - PyQt6 + SQLAlchemy + PaddleOCR + OpenAI/Claude SDK - 共47个Python文件,4000+行代码 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
353 lines
11 KiB
Python
353 lines
11 KiB
Python
"""
|
|
配置管理模块测试
|
|
"""
|
|
|
|
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
|