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