mcp-feedback-enhanced/tests/integration/test_web_integration.py
2025-06-11 14:59:27 +08:00

324 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Web UI 集成測試
"""
import asyncio
import time
import pytest
from tests.fixtures.test_data import TestData
from tests.helpers.test_utils import TestUtils
class TestWebUIIntegration:
"""Web UI 集成測試"""
@pytest.mark.asyncio
async def test_web_server_startup_and_routes(self, web_ui_manager):
"""測試 Web 服務器啟動和基本路由"""
# 啟動服務器
web_ui_manager.start_server()
# 等待服務器啟動
await asyncio.sleep(3)
# 驗證服務器正在運行
assert web_ui_manager.server_thread is not None
assert web_ui_manager.server_thread.is_alive()
# 測試基本路由可訪問性
import aiohttp
base_url = f"http://{web_ui_manager.host}:{web_ui_manager.port}"
async with aiohttp.ClientSession() as session:
# 測試主頁
async with session.get(f"{base_url}/") as response:
assert response.status == 200
text = await response.text()
assert "MCP Feedback Enhanced" in text
# 測試靜態文件
async with session.get(f"{base_url}/static/css/style.css") as response:
# 可能返回 200 或 404但不應該是服務器錯誤
assert response.status in [200, 404]
@pytest.mark.asyncio
async def test_session_api_integration(self, web_ui_manager, test_project_dir):
"""測試會話 API 集成"""
import aiohttp
# 創建會話
session_id = web_ui_manager.create_session(
str(test_project_dir), TestData.SAMPLE_SESSION["summary"]
)
# 啟動服務器
web_ui_manager.start_server()
await asyncio.sleep(3)
base_url = f"http://{web_ui_manager.host}:{web_ui_manager.port}"
async with aiohttp.ClientSession() as session:
# 測試當前會話 API
async with session.get(f"{base_url}/api/current-session") as response:
assert response.status == 200
data = await response.json()
assert data["session_id"] == session_id
assert data["project_directory"] == str(test_project_dir)
assert data["summary"] == TestData.SAMPLE_SESSION["summary"]
@pytest.mark.asyncio
async def test_websocket_connection(self, web_ui_manager, test_project_dir):
"""測試 WebSocket 連接"""
import aiohttp
# 創建會話
web_ui_manager.create_session(
str(test_project_dir), TestData.SAMPLE_SESSION["summary"]
)
# 啟動服務器
web_ui_manager.start_server()
await asyncio.sleep(3)
ws_url = f"ws://{web_ui_manager.host}:{web_ui_manager.port}/ws"
async with aiohttp.ClientSession() as session:
try:
async with session.ws_connect(ws_url) as ws:
# 應該收到連接確認消息
msg = await asyncio.wait_for(ws.receive(), timeout=5)
assert msg.type == aiohttp.WSMsgType.TEXT
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",
"tabId": "test-tab-123",
"timestamp": time.time(),
}
await ws.send_str(str(heartbeat_msg).replace("'", '"'))
# 應該收到心跳回應
response = await asyncio.wait_for(ws.receive(), timeout=5)
if response.type == aiohttp.WSMsgType.TEXT:
response_data = response.json()
assert response_data["type"] == "heartbeat_response"
except TimeoutError:
pytest.fail("WebSocket 連接或通信超時")
except Exception as e:
pytest.fail(f"WebSocket 測試失敗: {e}")
class TestWebUISessionManagement:
"""Web UI 會話管理集成測試"""
@pytest.mark.asyncio
async def test_session_lifecycle(self, web_ui_manager, test_project_dir):
"""測試會話生命週期"""
# 1. 創建會話
session_id = web_ui_manager.create_session(str(test_project_dir), "第一個會話")
current_session = web_ui_manager.get_current_session()
assert current_session is not None
assert current_session.session_id == session_id
# 2. 創建第二個會話(模擬第二次 MCP 調用)
session_id_2 = web_ui_manager.create_session(
str(test_project_dir), "第二個會話"
)
# 當前會話應該切換到新會話
current_session = web_ui_manager.get_current_session()
assert current_session.session_id == session_id_2
assert current_session.summary == "第二個會話"
# 3. 測試會話狀態更新
from mcp_feedback_enhanced.web.models import SessionStatus
current_session.update_status(SessionStatus.FEEDBACK_SUBMITTED, "已提交回饋")
assert current_session.status == SessionStatus.FEEDBACK_SUBMITTED
@pytest.mark.asyncio
async def test_session_feedback_flow(self, web_ui_manager, test_project_dir):
"""測試會話回饋流程"""
# 創建會話
web_ui_manager.create_session(
str(test_project_dir), TestData.SAMPLE_SESSION["summary"]
)
session = web_ui_manager.get_current_session()
# 模擬提交回饋
await session.submit_feedback(
TestData.SAMPLE_FEEDBACK["feedback"],
TestData.SAMPLE_FEEDBACK["images"],
TestData.SAMPLE_FEEDBACK["settings"],
)
# 驗證回饋已保存
assert session.feedback_result == TestData.SAMPLE_FEEDBACK["feedback"]
assert session.images == TestData.SAMPLE_FEEDBACK["images"]
assert session.settings == TestData.SAMPLE_FEEDBACK["settings"]
# 驗證狀態已更新
from mcp_feedback_enhanced.web.models import SessionStatus
assert session.status == SessionStatus.FEEDBACK_SUBMITTED
@pytest.mark.asyncio
async def test_session_timeout_handling(self, web_ui_manager, test_project_dir):
"""測試會話超時處理"""
# 創建會話,設置短超時
web_ui_manager.create_session(
str(test_project_dir), TestData.SAMPLE_SESSION["summary"]
)
session = web_ui_manager.get_current_session()
# 測試超時等待
try:
result = await asyncio.wait_for(
session.wait_for_feedback(timeout=1), # 1秒超時
timeout=2, # 外部超時保護
)
# 如果沒有超時,應該返回默認結果
assert TestUtils.validate_web_response(result)
except TimeoutError:
# 超時是預期的行為
pass
class TestWebUIErrorHandling:
"""Web UI 錯誤處理集成測試"""
@pytest.mark.asyncio
async def test_no_session_handling(self, web_ui_manager):
"""測試無會話時的處理"""
import aiohttp
# 確保沒有活躍會話
web_ui_manager.clear_current_session()
# 啟動服務器
web_ui_manager.start_server()
await asyncio.sleep(3)
base_url = f"http://{web_ui_manager.host}:{web_ui_manager.port}"
async with aiohttp.ClientSession() as session:
# 測試主頁應該顯示等待頁面
async with session.get(f"{base_url}/") as response:
assert response.status == 200
text = await response.text()
assert "MCP Feedback Enhanced" in text
# 測試當前會話 API 應該返回無會話狀態
async with session.get(f"{base_url}/api/current-session") as response:
assert response.status == 404 # 或其他適當的狀態碼
@pytest.mark.asyncio
async def test_websocket_without_session(self, web_ui_manager):
"""測試無會話時的 WebSocket 連接"""
import aiohttp
# 確保沒有活躍會話
web_ui_manager.clear_current_session()
# 啟動服務器
web_ui_manager.start_server()
await asyncio.sleep(3)
ws_url = f"ws://{web_ui_manager.host}:{web_ui_manager.port}/ws"
async with aiohttp.ClientSession() as session:
try:
async with session.ws_connect(ws_url) as ws:
# 連接應該被拒絕或立即關閉
msg = await asyncio.wait_for(ws.receive(), timeout=5)
if msg.type == aiohttp.WSMsgType.CLOSE:
# 連接被關閉是預期的
assert True
# 如果收到消息,應該是錯誤消息
elif msg.type == aiohttp.WSMsgType.TEXT:
data = msg.json()
assert "error" in data or data.get("type") == "error"
except aiohttp.WSServerHandshakeError:
# WebSocket 握手失敗也是預期的
assert True
except TimeoutError:
# 超時也可能是預期的行為
assert True
class TestWebUIPerformance:
"""Web UI 性能集成測試"""
@pytest.mark.asyncio
async def test_server_startup_time(self, web_ui_manager):
"""測試服務器啟動時間"""
from tests.helpers.test_utils import PerformanceTimer
with PerformanceTimer() as timer:
web_ui_manager.start_server()
await asyncio.sleep(3) # 等待啟動完成
# 啟動時間應該在合理範圍內
assert timer.duration < 10, f"Web 服務器啟動時間過長: {timer.duration:.2f}"
# 驗證服務器確實在運行
assert web_ui_manager.server_thread is not None
assert web_ui_manager.server_thread.is_alive()
@pytest.mark.asyncio
async def test_multiple_session_performance(self, web_ui_manager, test_project_dir):
"""測試多會話性能"""
from tests.helpers.test_utils import PerformanceTimer
session_ids = []
with PerformanceTimer() as timer:
# 創建多個會話
for i in range(10):
session_id = web_ui_manager.create_session(
str(test_project_dir), f"測試會話 {i + 1}"
)
session_ids.append(session_id)
# 創建會話的時間應該是線性的,不應該有明顯的性能下降
avg_time_per_session = timer.duration / 10
assert avg_time_per_session < 0.1, (
f"每個會話創建時間過長: {avg_time_per_session:.3f}"
)
# 驗證最後一個會話是當前活躍會話
current_session = web_ui_manager.get_current_session()
assert current_session.session_id == session_ids[-1]