首席架構師審查 P1 修復清單: P1-1 RAG Provider DI 模式一致性: - 支援 rag_service 參數注入 - 新增 close() 方法 - TYPE_CHECKING 延遲導入 P1-3 RAG 測試補充: - test_rag_provider.py (9 tests) - DI 注入/Lazy Load/Tool Schema/驗證/Close P1-4 Grafana Config 快取優化: - URL/Key 首次查詢後快取 - 減少重複 settings 存取 P1-5 Embedding 維度配置化: - MODEL_DIMENSIONS 字典 (qwen/llama/nomic) - default_dimension 參數 - 支援更多模型 測試: 9/9 PASSED Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
146 lines
4.3 KiB
Python
146 lines
4.3 KiB
Python
"""
|
|
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
|