Files
awoooi/apps/api/tests/test_rag_provider.py
OG T 8724ed7dcf fix(mcp): P1 修復 - DI 一致性 + 測試補充 + 配置優化
首席架構師審查 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>
2026-03-29 16:23:30 +08:00

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