""" RAG Provider 測試 ================ Phase 13.2 #84 - Runbook RAG Tool 測試項目: - Provider 初始化 (DI 注入 vs Lazy Load) - Tool 列表 - Input 驗證 - 健康檢查 P1 修復: 2026-03-29 (首席架構師審查後補充) """ import pytest from src.plugins.mcp.providers.rag_provider import RAGProvider class TestRAGProviderInit: """Provider 初始化測試""" def test_init_without_service(self): """測試無參數初始化 (Lazy Load 模式)""" provider = RAGProvider() assert provider.name == "runbooks" assert provider._rag_service is None def test_init_with_service_injection(self): """測試 DI 注入模式""" class MockRAGService: """Minimal mock for DI testing""" async def search(self, query: str, top_k: int): return [] async def get_index_stats(self): return {"status": "ok"} mock_service = MockRAGService() provider = RAGProvider(rag_service=mock_service) assert provider._rag_service is mock_service class TestRAGProviderTools: """Tool 列表測試""" @pytest.mark.asyncio async def test_list_tools_returns_three_tools(self): """確認提供 3 個工具""" provider = RAGProvider() tools = await provider.list_tools() assert len(tools) == 3 tool_names = {t.name for t in tools} assert tool_names == {"search_runbook", "index_documents", "get_index_stats"} @pytest.mark.asyncio async def test_search_runbook_tool_schema(self): """確認 search_runbook 的 schema 正確""" provider = RAGProvider() tools = await provider.list_tools() search_tool = next(t for t in tools if t.name == "search_runbook") schema = search_tool.input_schema assert "properties" in schema assert "query" in schema["properties"] assert schema["required"] == ["query"] @pytest.mark.asyncio async def test_index_documents_tool_schema(self): """確認 index_documents 的 schema 正確""" provider = RAGProvider() tools = await provider.list_tools() index_tool = next(t for t in tools if t.name == "index_documents") schema = index_tool.input_schema assert "properties" in schema assert "force" in schema["properties"] class TestRAGProviderValidation: """Input 驗證測試""" @pytest.mark.asyncio async def test_search_without_query_returns_error(self): """空 query 應該返回錯誤""" class MockRAGService: async def search(self, query: str, top_k: int): return [] provider = RAGProvider(rag_service=MockRAGService()) result = await provider.execute("search_runbook", {"query": ""}) assert result.success is True # 執行成功但結果為空 assert result.output["error"] == "Query is required" assert result.output["results"] == [] @pytest.mark.asyncio async def test_search_top_k_limited_to_10(self): """top_k 應該被限制在 10 以內""" class MockRAGService: def __init__(self): self.last_top_k = None async def search(self, query: str, top_k: int): self.last_top_k = top_k return [{"content": "test", "score": 0.9}] mock_service = MockRAGService() provider = RAGProvider(rag_service=mock_service) await provider.execute("search_runbook", {"query": "test", "top_k": 100}) assert mock_service.last_top_k == 10 # 應該被限制為 10 @pytest.mark.asyncio async def test_unknown_tool_returns_error(self): """未知工具應該返回錯誤""" provider = RAGProvider() result = await provider.execute("unknown_tool", {}) assert result.success is False assert "Unknown tool" in result.error class TestRAGProviderClose: """Close 方法測試""" @pytest.mark.asyncio async def test_close_clears_service(self): """close 應該清理 service 引用""" class MockRAGService: pass provider = RAGProvider(rag_service=MockRAGService()) assert provider._rag_service is not None await provider.close() assert provider._rag_service is None