mirror of
https://github.com/Minidoracat/mcp-feedback-enhanced.git
synced 2025-07-27 02:22:26 +08:00
🐛 修復單元測試問題
This commit is contained in:
parent
85ff83f037
commit
5150617abb
@ -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:
|
||||||
|
@ -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")
|
||||||
|
|
||||||
# 設置壓縮和緩存中間件
|
# 設置壓縮和緩存中間件
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
# 恢復原始環境變數
|
# 恢復原始環境變數
|
||||||
|
@ -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",
|
||||||
|
@ -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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user