Skip to main content
Glama
test_config_management.py15.7 kB
"""配置管理模块测试 测试 add-config-management 提案的实现 """ import os import tempfile from pathlib import Path from unittest.mock import patch import pytest from uart_mcp.config import ( BlacklistManager, ConfigManager, UartConfig, get_blacklist_manager, get_blacklist_path, get_config_dir, get_config_manager, get_config_path, ) class TestUartConfig: """测试 UartConfig 数据类""" def test_default_values(self): """测试默认配置值是否符合规格""" config = UartConfig() assert config.baudrate == 115200 assert config.bytesize == 8 assert config.parity == "N" assert config.stopbits == 1.0 assert config.read_timeout == 1000 assert config.write_timeout == 1000 assert config.xonxoff is False assert config.rtscts is False assert config.dsrdtr is False assert config.auto_reconnect is True assert config.reconnect_interval == 5000 assert config.log_level == "INFO" def test_custom_values(self): """测试自定义配置值""" config = UartConfig( baudrate=115200, bytesize=7, parity="E", stopbits=2.0, read_timeout=2000, write_timeout=3000, xonxoff=True, auto_reconnect=False, log_level="DEBUG", ) assert config.baudrate == 115200 assert config.bytesize == 7 assert config.parity == "E" assert config.stopbits == 2.0 assert config.xonxoff is True assert config.auto_reconnect is False assert config.log_level == "DEBUG" class TestConfigPaths: """测试配置路径生成""" def test_config_dir_linux(self): """测试 Linux 配置目录""" with patch("platform.system", return_value="Linux"): with patch("pathlib.Path.home", return_value=Path("/home/user")): config_dir = get_config_dir() assert str(config_dir) == "/home/user/.uart-mcp" def test_config_dir_windows(self): """测试 Windows 配置目录""" with patch("platform.system", return_value="Windows"): with patch.dict(os.environ, {"APPDATA": "C:\\Users\\user\\AppData\\Roaming"}): config_dir = get_config_dir() # Path 会自动处理路径分隔符,使用 normalize assert str(config_dir).replace("\\", "/") == "C:/Users/user/AppData/Roaming/.uart-mcp" def test_config_path(self): """测试配置文件路径""" config_path = get_config_path() assert config_path.name == "config.toml" assert "uart-mcp" in str(config_path) def test_blacklist_path(self): """测试黑名单文件路径""" blacklist_path = get_blacklist_path() assert blacklist_path.name == "blacklist.conf" assert "uart-mcp" in str(blacklist_path) class TestConfigManager: """测试 ConfigManager 类""" def test_load_default_config(self): """测试加载默认配置(无配置文件)""" with tempfile.TemporaryDirectory() as tmpdir: with patch("uart_mcp.config.get_config_path", return_value=Path(tmpdir) / "nonexistent.toml"): cm = ConfigManager() config = cm.config assert config.baudrate == 115200 # 使用默认值 def test_load_config_from_file(self): """测试从文件加载配置""" config_content = """ [serial] baudrate = 115200 bytesize = 7 [timeout] read_timeout = 2000 [reconnect] auto_reconnect = false """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_content) os.chmod(config_path, 0o600) with patch("uart_mcp.config.get_config_path", return_value=config_path): cm = ConfigManager() assert cm.config.baudrate == 115200 assert cm.config.bytesize == 7 assert cm.config.read_timeout == 2000 assert cm.config.auto_reconnect is False def test_permission_check_unix(self): """测试 Unix 系统权限校验""" with tempfile.NamedTemporaryFile(delete=False) as f: test_path = Path(f.name) try: # 设置错误权限 os.chmod(test_path, 0o644) with patch("platform.system", return_value="Linux"): cm = ConfigManager() # 测试内部方法 with pytest.raises(PermissionError) as exc: cm._check_permission(test_path) assert "1008" in str(exc.value) finally: os.unlink(test_path) def test_permission_check_windows_skipped(self): """测试 Windows 跳过权限校验""" with tempfile.NamedTemporaryFile(delete=False) as f: test_path = Path(f.name) try: os.chmod(test_path, 0o644) # 异常权限 with patch("platform.system", return_value="Windows"): cm = ConfigManager() # Windows 不应抛出异常 cm._check_permission(test_path) # 应通过 finally: os.unlink(test_path) def test_invalid_toml_handling(self): """测试无效 TOML 处理""" config_content = "invalid toml [[[[" with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_content) os.chmod(config_path, 0o600) with patch("uart_mcp.config.get_config_path", return_value=config_path): # 初始化时应捕获错误并使用默认值 cm = ConfigManager() assert cm.config.baudrate == 115200 # 使用默认值 def test_reload_config(self): """测试配置热加载""" config_content_v1 = """ [serial] baudrate = 9600 """ config_content_v2 = """ [serial] baudrate = 57600 """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_content_v1) os.chmod(config_path, 0o600) with patch("uart_mcp.config.get_config_path", return_value=config_path): cm = ConfigManager() assert cm.config.baudrate == 9600 # 更新文件 config_path.write_text(config_content_v2) os.chmod(config_path, 0o600) # 热加载 cm.reload() assert cm.config.baudrate == 57600 def test_reload_preserves_old_on_error(self): """测试热加载失败时保留旧配置""" config_content = """ [serial] baudrate = 115200 """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_content) os.chmod(config_path, 0o600) with patch("uart_mcp.config.get_config_path", return_value=config_path): cm = ConfigManager() assert cm.config.baudrate == 115200 # 破坏文件 config_path.write_text("invalid [[[[") os.chmod(config_path, 0o600) # 热加载失败,应保留旧配置 with pytest.raises(ValueError): cm.reload() assert cm.config.baudrate == 115200 def test_get_config_manager_singleton(self): """测试配置管理器单例模式""" cm1 = get_config_manager() cm2 = get_config_manager() assert cm1 is cm2 class TestBlacklistManager: """测试 BlacklistManager 类""" def test_no_blacklist_file(self): """测试无黑名单文件""" with tempfile.TemporaryDirectory() as tmpdir, \ patch("uart_mcp.config.get_blacklist_path", return_value=Path(tmpdir) / "nonexistent.conf"): bm = BlacklistManager() assert len(bm._patterns) == 0 assert len(bm._exact_matches) == 0 assert not bm.is_blacklisted("/dev/ttyUSB0") def test_exact_matching(self): """测试精确匹配""" content = "/dev/ttyUSB0\nCOM1\n" with tempfile.TemporaryDirectory() as tmpdir: blacklist_path = Path(tmpdir) / "blacklist.conf" blacklist_path.write_text(content) os.chmod(blacklist_path, 0o600) with patch("uart_mcp.config.get_blacklist_path", return_value=blacklist_path): bm = BlacklistManager() assert bm.is_blacklisted("/dev/ttyUSB0") is True assert bm.is_blacklisted("COM1") is True assert bm.is_blacklisted("/dev/ttyUSB1") is False assert bm.is_blacklisted("COM2") is False def test_regex_matching(self): """测试正则表达式匹配""" content = "COM[0-9]+\n/dev/ttyS[2-9]\n" with tempfile.TemporaryDirectory() as tmpdir: blacklist_path = Path(tmpdir) / "blacklist.conf" blacklist_path.write_text(content) os.chmod(blacklist_path, 0o600) with patch("uart_mcp.config.get_blacklist_path", return_value=blacklist_path): bm = BlacklistManager() assert bm.is_blacklisted("COM1") is True assert bm.is_blacklisted("COM55") is True assert bm.is_blacklisted("COMA") is False assert bm.is_blacklisted("/dev/ttyS2") is True assert bm.is_blacklisted("/dev/ttyS1") is False # 注意:[2-9] 是单字符,/dev/ttyS10 是 /dev/ttyS1 + 0,不匹配 # 这是符合正则语法预期的行为 assert bm.is_blacklisted("/dev/ttyS10") is False # [2-9] 匹配单个字符 def test_comments_and_empty_lines(self): """测试注释和空行处理""" content = """ # 注释行 /dev/ttyUSB0 # 另一个注释 COM[0-9]+ """ with tempfile.TemporaryDirectory() as tmpdir: blacklist_path = Path(tmpdir) / "blacklist.conf" blacklist_path.write_text(content) os.chmod(blacklist_path, 0o600) with patch("uart_mcp.config.get_blacklist_path", return_value=blacklist_path): bm = BlacklistManager() assert len(bm._patterns) == 1 assert len(bm._exact_matches) == 1 def test_invalid_regex_skipped(self): """测试无效正则表达式跳过""" content = "/dev/ttyUSB0\n[invalid(\n/dev/ttyACM0\n" with tempfile.TemporaryDirectory() as tmpdir: blacklist_path = Path(tmpdir) / "blacklist.conf" blacklist_path.write_text(content) os.chmod(blacklist_path, 0o600) with patch("uart_mcp.config.get_blacklist_path", return_value=blacklist_path): bm = BlacklistManager() # 2个精确匹配,0个正则(无效被跳过) assert len(bm._exact_matches) == 2 assert len(bm._patterns) == 0 def test_permission_check_unix(self): """测试权限校验""" with tempfile.NamedTemporaryFile(delete=False, mode='w') as f: f.write("/dev/ttyUSB0\n") test_path = Path(f.name) try: os.chmod(test_path, 0o644) # 错误权限 with patch("uart_mcp.config.get_blacklist_path", return_value=test_path), \ patch("platform.system", return_value="Linux"): with pytest.raises(PermissionError) as exc: BlacklistManager() assert "1008" in str(exc.value) finally: os.unlink(test_path) def test_reload_blacklist(self): """测试黑名单热加载""" content_v1 = "/dev/ttyUSB0\n" content_v2 = "/dev/ttyUSB1\nCOM[0-9]+\n" with tempfile.TemporaryDirectory() as tmpdir: blacklist_path = Path(tmpdir) / "blacklist.conf" blacklist_path.write_text(content_v1) os.chmod(blacklist_path, 0o600) with patch("uart_mcp.config.get_blacklist_path", return_value=blacklist_path): bm = BlacklistManager() assert bm.is_blacklisted("/dev/ttyUSB0") is True assert bm.is_blacklisted("COM1") is False # 更新文件 blacklist_path.write_text(content_v2) os.chmod(blacklist_path, 0o600) # 热加载 bm.reload() assert bm.is_blacklisted("/dev/ttyUSB1") is True assert bm.is_blacklisted("COM1") is True assert bm.is_blacklisted("/dev/ttyUSB0") is False # 已移除 def test_reload_failure_rollback(self): """测试热加载失败回滚 - 权限错误场景""" content_v1 = "/dev/ttyUSB0\n" with tempfile.TemporaryDirectory() as tmpdir: blacklist_path = Path(tmpdir) / "blacklist.conf" blacklist_path.write_text(content_v1) os.chmod(blacklist_path, 0o600) with patch("uart_mcp.config.get_blacklist_path", return_value=blacklist_path), \ patch("platform.system", return_value="Linux"): bm = BlacklistManager() assert bm.is_blacklisted("/dev/ttyUSB0") is True original_count = len(bm._patterns) + len(bm._exact_matches) # 修改权限导致加载失败 os.chmod(blacklist_path, 0o644) # 热加载应失败并抛出异常 with pytest.raises(PermissionError): bm.reload() # 规则应保持不变(已回滚) assert len(bm._patterns) + len(bm._exact_matches) == original_count assert bm.is_blacklisted("/dev/ttyUSB0") is True class TestIntegration: """测试集成""" def test_singletons(self): """测试所有单例""" cm1 = get_config_manager() cm2 = get_config_manager() assert cm1 is cm2 bm1 = get_blacklist_manager() bm2 = get_blacklist_manager() assert bm1 is bm2 def test_config_and_blacklist_coexist(self): """测试配置和黑名单管理器共存""" # 需清空之前的测试留下的文件,使用独立临时目录 with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" blacklist_path = Path(tmpdir) / "blacklist.conf" config_path.write_text("[serial]\nbaudrate = 115200\n") blacklist_path.write_text("/dev/ttyUSB0\n") os.chmod(config_path, 0o600) os.chmod(blacklist_path, 0o600) # 使用新实例而非单例 with patch("uart_mcp.config.get_config_path", return_value=config_path): cm = ConfigManager() assert cm.config.baudrate == 115200 with patch("uart_mcp.config.get_blacklist_path", return_value=blacklist_path), \ patch("platform.system", return_value="Linux"): # 确保运行权限检查 bm = BlacklistManager() assert bm.is_blacklisted("/dev/ttyUSB0") is True # 重置单例以便测试 def reset_singletons(): """重置单例(用于测试)""" global _config_manager, _blacklist_manager _config_manager = None _blacklist_manager = None # 模块级变量重置 _blacklist_manager = None _config_manager = None

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/donnel666/uart-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server