""" 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, TerminalSession, 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 = set(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}"