🐛 修復單元測試問題

This commit is contained in:
Minidoracat 2025-06-11 14:59:27 +08:00
parent 85ff83f037
commit 5150617abb
7 changed files with 134 additions and 40 deletions

View File

@ -83,25 +83,37 @@ class I18nManager:
if env_lang and env_lang in self._supported_languages: if env_lang and env_lang in self._supported_languages:
return env_lang return env_lang
# 3. 自動偵測系統語言 # 3. 檢查其他環境變數LANG, LC_ALL 等)
try: for env_var in ["LANG", "LC_ALL", "LC_MESSAGES", "LANGUAGE"]:
# 獲取系統語言 env_value = os.getenv(env_var, "").strip()
system_locale = locale.getdefaultlocale()[0] if env_value:
if system_locale: if env_value.startswith("zh_TW") or env_value.startswith("zh_Hant"):
if system_locale.startswith("zh_TW") or system_locale.startswith(
"zh_Hant"
):
return "zh-TW" return "zh-TW"
if system_locale.startswith("zh_CN") or system_locale.startswith( if env_value.startswith("zh_CN") or env_value.startswith("zh_Hans"):
"zh_Hans"
):
return "zh-CN" return "zh-CN"
if system_locale.startswith("en"): if env_value.startswith("en"):
return "en" return "en"
except Exception:
pass
# 4. 回退到默認語言 # 4. 自動偵測系統語言(僅在非測試模式下)
if not os.getenv("MCP_TEST_MODE"):
try:
# 獲取系統語言
system_locale = locale.getdefaultlocale()[0]
if system_locale:
if system_locale.startswith("zh_TW") or system_locale.startswith(
"zh_Hant"
):
return "zh-TW"
if system_locale.startswith("zh_CN") or system_locale.startswith(
"zh_Hans"
):
return "zh-CN"
if system_locale.startswith("en"):
return "en"
except Exception:
pass
# 5. 回退到默認語言
return self._fallback_language return self._fallback_language
def _load_saved_language(self) -> str | None: def _load_saved_language(self) -> str | None:

View File

@ -47,12 +47,16 @@ class WebUIManager:
if env_port: if env_port:
try: try:
custom_port = int(env_port) custom_port = int(env_port)
if 1024 <= custom_port <= 65535: if custom_port == 0:
# 特殊值 0 表示使用系統自動分配的端口
preferred_port = 0
debug_log("使用環境變數指定的自動端口分配 (0)")
elif 1024 <= custom_port <= 65535:
preferred_port = custom_port preferred_port = custom_port
debug_log(f"使用環境變數指定的端口: {preferred_port}") debug_log(f"使用環境變數指定的端口: {preferred_port}")
else: else:
debug_log( debug_log(
f"MCP_WEB_PORT 值無效 ({custom_port}),必須在 1024-65535 範圍內,使用預設端口 8765" f"MCP_WEB_PORT 值無效 ({custom_port}),必須在 1024-65535 範圍內或為 0,使用預設端口 8765"
) )
except ValueError: except ValueError:
debug_log( debug_log(
@ -63,9 +67,23 @@ class WebUIManager:
# 使用增強的端口管理,測試模式下禁用自動清理避免權限問題 # 使用增強的端口管理,測試模式下禁用自動清理避免權限問題
auto_cleanup = os.environ.get("MCP_TEST_MODE", "").lower() != "true" auto_cleanup = os.environ.get("MCP_TEST_MODE", "").lower() != "true"
self.port = port or PortManager.find_free_port_enhanced(
preferred_port=preferred_port, auto_cleanup=auto_cleanup, host=self.host if port is not None:
) # 如果明確指定了端口,使用指定的端口
self.port = port
elif preferred_port == 0:
# 如果偏好端口為 0使用系統自動分配
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((self.host, 0))
self.port = s.getsockname()[1]
debug_log(f"系統自動分配端口: {self.port}")
else:
# 使用增強的端口管理
self.port = PortManager.find_free_port_enhanced(
preferred_port=preferred_port, auto_cleanup=auto_cleanup, host=self.host
)
self.app = FastAPI(title="MCP Feedback Enhanced") self.app = FastAPI(title="MCP Feedback Enhanced")
# 設置壓縮和緩存中間件 # 設置壓縮和緩存中間件

View File

@ -50,13 +50,35 @@ def test_project_dir(temp_dir: Path) -> Path:
@pytest.fixture @pytest.fixture
def web_ui_manager() -> Generator[WebUIManager, None, None]: def web_ui_manager() -> Generator[WebUIManager, None, None]:
"""創建 WebUIManager fixture""" """創建 WebUIManager fixture"""
manager = WebUIManager(host="127.0.0.1", port=0) # 使用隨機端口 import os
yield manager
# 清理 # 設置測試模式環境變數
if manager.server_thread and manager.server_thread.is_alive(): original_test_mode = os.environ.get("MCP_TEST_MODE")
# 這裡可以添加服務器停止邏輯 original_web_port = os.environ.get("MCP_WEB_PORT")
pass
os.environ["MCP_TEST_MODE"] = "true"
# 使用動態端口範圍避免衝突
os.environ["MCP_WEB_PORT"] = "0" # 讓系統自動分配端口
try:
manager = WebUIManager(host="127.0.0.1") # 使用環境變數控制端口
yield manager
finally:
# 恢復原始環境變數
if original_test_mode is not None:
os.environ["MCP_TEST_MODE"] = original_test_mode
else:
os.environ.pop("MCP_TEST_MODE", None)
if original_web_port is not None:
os.environ["MCP_WEB_PORT"] = original_web_port
else:
os.environ.pop("MCP_WEB_PORT", None)
# 清理
if manager.server_thread and manager.server_thread.is_alive():
# 這裡可以添加服務器停止邏輯
pass
@pytest.fixture @pytest.fixture

View File

@ -26,8 +26,8 @@ class SimpleMCPClient:
async def start_server(self) -> bool: async def start_server(self) -> bool:
"""啟動 MCP 服務器""" """啟動 MCP 服務器"""
try: try:
# 使用當前專案的 MCP 服務器 # 使用正確的 uv run 命令啟動 MCP 服務器
cmd = ["python", "-m", "mcp_feedback_enhanced.server"] cmd = ["uv", "run", "python", "-m", "mcp_feedback_enhanced"]
self.server_process = subprocess.Popen( self.server_process = subprocess.Popen(
cmd, cmd,
@ -37,6 +37,8 @@ class SimpleMCPClient:
text=True, text=True,
bufsize=0, bufsize=0,
cwd=Path.cwd(), cwd=Path.cwd(),
encoding="utf-8", # 明確指定 UTF-8 編碼
errors="replace", # 處理編碼錯誤
) )
self.stdin = self.server_process.stdin self.stdin = self.server_process.stdin

View File

@ -106,9 +106,10 @@ class TestI18NFileSystemIntegration:
assert locales_dir.exists(), f"翻譯目錄不存在: {locales_dir}" assert locales_dir.exists(), f"翻譯目錄不存在: {locales_dir}"
# 檢查每種支援語言的翻譯文件 # 檢查每種支援語言的翻譯文件(使用正確的路徑結構)
for lang in TestData.SUPPORTED_LANGUAGES: for lang in TestData.SUPPORTED_LANGUAGES:
lang_file = locales_dir / f"{lang}.json" lang_dir = locales_dir / lang
lang_file = lang_dir / "translation.json"
assert lang_file.exists(), f"翻譯文件不存在: {lang_file}" assert lang_file.exists(), f"翻譯文件不存在: {lang_file}"
# 檢查文件內容 # 檢查文件內容
@ -132,7 +133,8 @@ class TestI18NFileSystemIntegration:
locales_dir = manager._locales_dir locales_dir = manager._locales_dir
for lang in TestData.SUPPORTED_LANGUAGES: for lang in TestData.SUPPORTED_LANGUAGES:
lang_file = locales_dir / f"{lang}.json" lang_dir = locales_dir / lang
lang_file = lang_dir / "translation.json"
if lang_file.exists(): if lang_file.exists():
# 測試 UTF-8 編碼 # 測試 UTF-8 編碼
@ -160,6 +162,9 @@ class TestI18NEnvironmentIntegration:
try: try:
# 測試不同的環境設置 # 測試不同的環境設置
test_cases = [ test_cases = [
{"MCP_LANGUAGE": "zh-TW", "expected": "zh-TW"},
{"MCP_LANGUAGE": "zh-CN", "expected": "zh-CN"},
{"MCP_LANGUAGE": "en", "expected": "en"},
{"LANG": "zh_TW.UTF-8", "expected": "zh-TW"}, {"LANG": "zh_TW.UTF-8", "expected": "zh-TW"},
{"LANG": "zh_CN.UTF-8", "expected": "zh-CN"}, {"LANG": "zh_CN.UTF-8", "expected": "zh-CN"},
{"LANG": "en_US.UTF-8", "expected": "en"}, {"LANG": "en_US.UTF-8", "expected": "en"},
@ -170,22 +175,34 @@ class TestI18NEnvironmentIntegration:
# 清理環境變數 # 清理環境變數
for var in env_vars: for var in env_vars:
os.environ.pop(var, None) os.environ.pop(var, None)
# 也清理 MCP_LANGUAGE
os.environ.pop("MCP_LANGUAGE", None)
# 設置測試模式,禁用系統語言檢測
os.environ["MCP_TEST_MODE"] = "true"
# 設置測試環境 # 設置測試環境
for key, value in test_case.items(): for key, value in test_case.items():
if key != "expected": if key != "expected":
os.environ[key] = value os.environ[key] = value
# 創建新的管理器實例 # 創建新的管理器實例,並清理可能的保存設定
manager = I18nManager() import tempfile
# 修復 attr-defined 錯誤 - 使用正確的方法名 from pathlib import Path
detected = manager._detect_language()
# 驗證檢測結果 with tempfile.TemporaryDirectory() as temp_dir:
expected = test_case["expected"] # 臨時修改配置文件路徑,避免使用真實的用戶配置
assert detected == expected, ( manager = I18nManager()
f"環境 {test_case} 檢測到 {detected},預期 {expected}" manager._config_file = Path(temp_dir) / "test_language.json"
)
# 修復 attr-defined 錯誤 - 使用正確的方法名
detected = manager._detect_language()
# 驗證檢測結果
expected = test_case["expected"]
assert detected == expected, (
f"環境 {test_case} 檢測到 {detected},預期 {expected}"
)
finally: finally:
# 恢復原始環境變數 # 恢復原始環境變數

View File

@ -97,6 +97,24 @@ class TestWebUIIntegration:
data = msg.json() data = msg.json()
assert data["type"] == "connection_established" assert data["type"] == "connection_established"
# 可能會收到額外的消息session_updated 或 status_update先處理掉
try:
while True:
extra_msg = await asyncio.wait_for(ws.receive(), timeout=1)
if extra_msg.type == aiohttp.WSMsgType.TEXT:
extra_data = extra_msg.json()
if extra_data["type"] in [
"session_updated",
"status_update",
]:
continue
# 如果是其他類型的消息,可能是我們要的回應,先保存
break
break
except TimeoutError:
# 沒有額外消息,繼續測試
pass
# 測試發送心跳 # 測試發送心跳
heartbeat_msg = { heartbeat_msg = {
"type": "heartbeat", "type": "heartbeat",

View File

@ -33,6 +33,11 @@ class TestResourceManager:
# 重置單例實例 # 重置單例實例
ResourceManager._instance = None ResourceManager._instance = None
# 重置全局資源管理器實例
import mcp_feedback_enhanced.utils.resource_manager as rm_module
rm_module._resource_manager = None
def test_singleton_pattern(self): def test_singleton_pattern(self):
"""測試單例模式""" """測試單例模式"""
rm1 = ResourceManager() rm1 = ResourceManager()