From 5150617abb19e70181cc324d657b5f562cf6e0f3 Mon Sep 17 00:00:00 2001 From: Minidoracat Date: Wed, 11 Jun 2025 14:59:27 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20=E4=BF=AE=E5=BE=A9=E5=96=AE?= =?UTF-8?q?=E5=85=83=E6=B8=AC=E8=A9=A6=E5=95=8F=E9=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mcp_feedback_enhanced/i18n.py | 42 ++++++++++++++-------- src/mcp_feedback_enhanced/web/main.py | 28 ++++++++++++--- tests/conftest.py | 34 ++++++++++++++---- tests/helpers/mcp_client.py | 6 ++-- tests/integration/test_i18n_integration.py | 41 ++++++++++++++------- tests/integration/test_web_integration.py | 18 ++++++++++ tests/unit/test_resource_manager.py | 5 +++ 7 files changed, 134 insertions(+), 40 deletions(-) diff --git a/src/mcp_feedback_enhanced/i18n.py b/src/mcp_feedback_enhanced/i18n.py index c25a6c8..b6ac67a 100644 --- a/src/mcp_feedback_enhanced/i18n.py +++ b/src/mcp_feedback_enhanced/i18n.py @@ -83,25 +83,37 @@ class I18nManager: if env_lang and env_lang in self._supported_languages: return env_lang - # 3. 自動偵測系統語言 - try: - # 獲取系統語言 - system_locale = locale.getdefaultlocale()[0] - if system_locale: - if system_locale.startswith("zh_TW") or system_locale.startswith( - "zh_Hant" - ): + # 3. 檢查其他環境變數(LANG, LC_ALL 等) + for env_var in ["LANG", "LC_ALL", "LC_MESSAGES", "LANGUAGE"]: + env_value = os.getenv(env_var, "").strip() + if env_value: + if env_value.startswith("zh_TW") or env_value.startswith("zh_Hant"): return "zh-TW" - if system_locale.startswith("zh_CN") or system_locale.startswith( - "zh_Hans" - ): + if env_value.startswith("zh_CN") or env_value.startswith("zh_Hans"): return "zh-CN" - if system_locale.startswith("en"): + if env_value.startswith("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 def _load_saved_language(self) -> str | None: diff --git a/src/mcp_feedback_enhanced/web/main.py b/src/mcp_feedback_enhanced/web/main.py index 33963c4..ea8d595 100644 --- a/src/mcp_feedback_enhanced/web/main.py +++ b/src/mcp_feedback_enhanced/web/main.py @@ -47,12 +47,16 @@ class WebUIManager: if env_port: try: 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 debug_log(f"使用環境變數指定的端口: {preferred_port}") else: debug_log( - f"MCP_WEB_PORT 值無效 ({custom_port}),必須在 1024-65535 範圍內,使用預設端口 8765" + f"MCP_WEB_PORT 值無效 ({custom_port}),必須在 1024-65535 範圍內或為 0,使用預設端口 8765" ) except ValueError: debug_log( @@ -63,9 +67,23 @@ class WebUIManager: # 使用增強的端口管理,測試模式下禁用自動清理避免權限問題 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") # 設置壓縮和緩存中間件 diff --git a/tests/conftest.py b/tests/conftest.py index c051c0a..5644da8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -50,13 +50,35 @@ def test_project_dir(temp_dir: Path) -> Path: @pytest.fixture def web_ui_manager() -> Generator[WebUIManager, None, None]: """創建 WebUIManager fixture""" - manager = WebUIManager(host="127.0.0.1", port=0) # 使用隨機端口 - yield manager + import os - # 清理 - if manager.server_thread and manager.server_thread.is_alive(): - # 這裡可以添加服務器停止邏輯 - pass + # 設置測試模式環境變數 + original_test_mode = os.environ.get("MCP_TEST_MODE") + original_web_port = os.environ.get("MCP_WEB_PORT") + + 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 diff --git a/tests/helpers/mcp_client.py b/tests/helpers/mcp_client.py index 611609f..3068f79 100644 --- a/tests/helpers/mcp_client.py +++ b/tests/helpers/mcp_client.py @@ -26,8 +26,8 @@ class SimpleMCPClient: async def start_server(self) -> bool: """啟動 MCP 服務器""" try: - # 使用當前專案的 MCP 服務器 - cmd = ["python", "-m", "mcp_feedback_enhanced.server"] + # 使用正確的 uv run 命令啟動 MCP 服務器 + cmd = ["uv", "run", "python", "-m", "mcp_feedback_enhanced"] self.server_process = subprocess.Popen( cmd, @@ -37,6 +37,8 @@ class SimpleMCPClient: text=True, bufsize=0, cwd=Path.cwd(), + encoding="utf-8", # 明確指定 UTF-8 編碼 + errors="replace", # 處理編碼錯誤 ) self.stdin = self.server_process.stdin diff --git a/tests/integration/test_i18n_integration.py b/tests/integration/test_i18n_integration.py index b5309b5..d1d636c 100644 --- a/tests/integration/test_i18n_integration.py +++ b/tests/integration/test_i18n_integration.py @@ -106,9 +106,10 @@ class TestI18NFileSystemIntegration: assert locales_dir.exists(), f"翻譯目錄不存在: {locales_dir}" - # 檢查每種支援語言的翻譯文件 + # 檢查每種支援語言的翻譯文件(使用正確的路徑結構) 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}" # 檢查文件內容 @@ -132,7 +133,8 @@ class TestI18NFileSystemIntegration: locales_dir = manager._locales_dir 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(): # 測試 UTF-8 編碼 @@ -160,6 +162,9 @@ class TestI18NEnvironmentIntegration: try: # 測試不同的環境設置 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_CN.UTF-8", "expected": "zh-CN"}, {"LANG": "en_US.UTF-8", "expected": "en"}, @@ -170,22 +175,34 @@ class TestI18NEnvironmentIntegration: # 清理環境變數 for var in env_vars: 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(): if key != "expected": os.environ[key] = value - # 創建新的管理器實例 - manager = I18nManager() - # 修復 attr-defined 錯誤 - 使用正確的方法名 - detected = manager._detect_language() + # 創建新的管理器實例,並清理可能的保存設定 + import tempfile + from pathlib import Path - # 驗證檢測結果 - expected = test_case["expected"] - assert detected == expected, ( - f"環境 {test_case} 檢測到 {detected},預期 {expected}" - ) + with tempfile.TemporaryDirectory() as temp_dir: + # 臨時修改配置文件路徑,避免使用真實的用戶配置 + manager = I18nManager() + 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: # 恢復原始環境變數 diff --git a/tests/integration/test_web_integration.py b/tests/integration/test_web_integration.py index 883e65e..eb9ddb2 100644 --- a/tests/integration/test_web_integration.py +++ b/tests/integration/test_web_integration.py @@ -97,6 +97,24 @@ class TestWebUIIntegration: data = msg.json() 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 = { "type": "heartbeat", diff --git a/tests/unit/test_resource_manager.py b/tests/unit/test_resource_manager.py index b56ba5e..db40af8 100644 --- a/tests/unit/test_resource_manager.py +++ b/tests/unit/test_resource_manager.py @@ -33,6 +33,11 @@ class TestResourceManager: # 重置單例實例 ResourceManager._instance = None + # 重置全局資源管理器實例 + import mcp_feedback_enhanced.utils.resource_manager as rm_module + + rm_module._resource_manager = None + def test_singleton_pattern(self): """測試單例模式""" rm1 = ResourceManager()