Files
awoooi/apps/api/tests/test_terminal_service.py
OG T 59c9eff83a fix(api): 修復 10 個 Lint 錯誤 (imports 排序 + unused imports + set comprehension)
- F401: 移除未使用的 imports (TerminalSessionStatus, AutoApproveDecision, TerminalSession)
- I001: 修正 import blocks 排序
- C401: set(generator) → {set comprehension}

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-28 18:51:52 +08:00

225 lines
7.3 KiB
Python

"""
Phase 19.6: Terminal Service Tests
==================================
Omni-Terminal SSE 架構測試
測試內容:
1. 意圖分類 (classify_intent)
2. Service 依賴注入
3. Model 驗證
@see ADR-031 Omni-Terminal SSE Architecture
@author Claude Code (首席架構師)
@version 1.0.0
@date 2026-03-28 (台北時間)
"""
import pytest
from src.models.terminal import (
SpatialContext,
TerminalIntentRequest,
TerminalSessionStatus,
)
from src.services.terminal_service import (
IntentType,
TerminalService,
classify_intent,
get_terminal_service,
)
# =============================================================================
# Intent Classification Tests
# =============================================================================
INTENT_CLASSIFICATION_TEST_CASES = [
# 查詢類 - 狀態
("status", IntentType.QUERY_STATUS),
("狀態", IntentType.QUERY_STATUS),
("pod status", IntentType.QUERY_STATUS),
("健康狀態", IntentType.QUERY_STATUS),
("health check", IntentType.QUERY_STATUS),
# 查詢類 - 指標
("metrics", IntentType.QUERY_METRICS),
("指標", IntentType.QUERY_METRICS),
("cpu usage", IntentType.QUERY_METRICS),
("記憶體使用量", IntentType.QUERY_METRICS),
("memory utilization", IntentType.QUERY_METRICS),
# 查詢類 - 日誌
("logs", IntentType.QUERY_LOGS),
("日誌", IntentType.QUERY_LOGS),
("error logs", IntentType.QUERY_LOGS),
("錯誤訊息", IntentType.QUERY_LOGS),
("exception trace", IntentType.QUERY_LOGS),
# 操作類 - 簽核
("approve", IntentType.ACTION_APPROVAL),
("簽核", IntentType.ACTION_APPROVAL),
("授權", IntentType.ACTION_APPROVAL),
("批准這個操作", IntentType.ACTION_APPROVAL),
("show pending approval", IntentType.ACTION_APPROVAL),
# 操作類 - 重啟
("restart", IntentType.ACTION_RESTART),
("重啟", IntentType.ACTION_RESTART),
("rollout", IntentType.ACTION_RESTART),
("重新部署", IntentType.ACTION_RESTART),
# 操作類 - 擴容
("scale", IntentType.ACTION_SCALE),
("擴容", IntentType.ACTION_SCALE),
("增加副本", IntentType.ACTION_SCALE),
("replica count", IntentType.ACTION_SCALE),
# 分析類 - RCA
("rca", IntentType.ANALYZE_RCA),
("root cause", IntentType.ANALYZE_RCA),
("根因", IntentType.ANALYZE_RCA),
("分析問題", IntentType.ANALYZE_RCA),
("為什麼 API 變慢", IntentType.ANALYZE_RCA),
("why is it slow", IntentType.ANALYZE_RCA),
# 分析類 - Incident
("incident", IntentType.ANALYZE_INCIDENT),
("事件", IntentType.ANALYZE_INCIDENT),
("alert 告警", IntentType.ANALYZE_INCIDENT),
("告警詳情", IntentType.ANALYZE_INCIDENT),
# 一般
("hello", IntentType.GENERAL),
("你好", IntentType.GENERAL),
("help", IntentType.GENERAL),
("random text", IntentType.GENERAL),
]
@pytest.mark.parametrize("intent,expected_type", INTENT_CLASSIFICATION_TEST_CASES)
def test_classify_intent(intent: str, expected_type: IntentType):
"""測試意圖分類準確度"""
result = classify_intent(intent)
assert result == expected_type, f"Intent '{intent}' should be classified as {expected_type}, got {result}"
def test_classify_intent_case_insensitive():
"""測試意圖分類不區分大小寫"""
assert classify_intent("STATUS") == IntentType.QUERY_STATUS
assert classify_intent("Metrics") == IntentType.QUERY_METRICS
assert classify_intent("RESTART") == IntentType.ACTION_RESTART
# =============================================================================
# Model Validation Tests
# =============================================================================
def test_spatial_context_required_fields():
"""測試 SpatialContext 必填欄位"""
context = SpatialContext(current_page="/dashboard")
assert context.current_page == "/dashboard"
assert context.focused_entity_id is None # 預設為 None
def test_spatial_context_with_entity():
"""測試 SpatialContext 帶實體 ID"""
context = SpatialContext(
current_page="/incidents",
focused_entity_id="INC-001",
)
assert context.current_page == "/incidents"
assert context.focused_entity_id == "INC-001"
def test_terminal_intent_request_validation():
"""測試 TerminalIntentRequest 驗證"""
request = TerminalIntentRequest(
intent="check system status",
context=SpatialContext(current_page="/dashboard"),
)
assert request.intent == "check system status"
assert request.context.current_page == "/dashboard"
assert request.session_id is None # 預設為 None
def test_terminal_intent_request_with_session():
"""測試帶 session_id 的 TerminalIntentRequest"""
request = TerminalIntentRequest(
intent="continue analysis",
context=SpatialContext(current_page="/"),
session_id="sess-001",
)
assert request.session_id == "sess-001"
def test_terminal_session_status_enum():
"""測試 TerminalSessionStatus 枚舉值"""
assert TerminalSessionStatus.PENDING.value == "pending"
assert TerminalSessionStatus.PROCESSING.value == "processing"
assert TerminalSessionStatus.COMPLETED.value == "completed"
assert TerminalSessionStatus.ERROR.value == "error"
assert TerminalSessionStatus.ABORTED.value == "aborted"
# =============================================================================
# Dependency Injection Tests
# =============================================================================
@pytest.mark.asyncio
async def test_get_terminal_service():
"""測試 FastAPI 依賴注入函數"""
service = await get_terminal_service()
assert isinstance(service, TerminalService)
@pytest.mark.asyncio
async def test_get_terminal_service_creates_new_instance():
"""測試每次呼叫建立新實例 (非 Singleton)"""
service1 = await get_terminal_service()
service2 = await get_terminal_service()
assert service1 is not service2, "Should create new instance each time"
# =============================================================================
# Service Unit Tests (No External Dependencies)
# =============================================================================
@pytest.mark.asyncio
async def test_terminal_service_instantiation():
"""測試 TerminalService 實例化"""
service = TerminalService()
assert service._sessions == {}
assert service._tasks == {}
@pytest.mark.asyncio
async def test_get_session_not_found():
"""測試取得不存在的 Session"""
service = TerminalService()
session = await service.get_session("nonexistent-session")
assert session is None
@pytest.mark.asyncio
async def test_abort_session_not_found():
"""測試中斷不存在的 Session"""
service = TerminalService()
result = await service.abort_session("nonexistent-session")
assert result is False
# =============================================================================
# Intent Coverage Statistics
# =============================================================================
def test_intent_type_coverage():
"""確保所有 IntentType 都有測試案例"""
tested_types = {expected for _, expected in INTENT_CLASSIFICATION_TEST_CASES}
all_types = set(IntentType)
missing = all_types - tested_types
assert not missing, f"Missing test cases for IntentType: {missing}"