Initial commit: QMDSearch 分层信息检索服务
- FastAPI + Qdrant + Redis + Ollama 技术栈 - L1→L2→L3→chunk 四层分层检索(dense + sparse RRF 融合) - 文档三级总结与 2.5 级回退 - query 解析路由与分类 - /admin 管理页面
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
"""OllamaClient 单元测试(mock httpx,不发起真实网络请求)"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from app.services.ollama import OllamaClient
|
||||
|
||||
|
||||
def _make_response(status_code: int, data: dict | None = None) -> httpx.Response:
|
||||
"""构造带 request 上下文的 httpx.Response(raise_for_status 依赖 request)"""
|
||||
request = httpx.Request("POST", "http://localhost:11434/api/generate")
|
||||
if data is None:
|
||||
return httpx.Response(status_code, request=request)
|
||||
return httpx.Response(status_code, json=data, request=request)
|
||||
|
||||
|
||||
class _FakeAsyncClient:
|
||||
"""httpx.AsyncClient 替代品:记录请求参数,返回预设响应或抛出预设异常"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
captured: dict,
|
||||
response: httpx.Response | None = None,
|
||||
error: Exception | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
self._captured = captured
|
||||
self._response = response
|
||||
self._error = error
|
||||
captured["timeout"] = kwargs.get("timeout")
|
||||
|
||||
async def __aenter__(self) -> "_FakeAsyncClient":
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *args: object) -> bool:
|
||||
return False
|
||||
|
||||
async def post(self, url: str, json: dict | None = None) -> httpx.Response:
|
||||
self._captured["url"] = url
|
||||
self._captured["json"] = json
|
||||
if self._error is not None:
|
||||
raise self._error
|
||||
assert self._response is not None
|
||||
return self._response
|
||||
|
||||
async def get(self, url: str) -> httpx.Response:
|
||||
self._captured["url"] = url
|
||||
if self._error is not None:
|
||||
raise self._error
|
||||
assert self._response is not None
|
||||
return self._response
|
||||
|
||||
|
||||
class TestGenerate:
|
||||
async def test_returns_response_field(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
captured: dict = {}
|
||||
monkeypatch.setattr(
|
||||
httpx,
|
||||
"AsyncClient",
|
||||
lambda **kw: _FakeAsyncClient(captured, response=_make_response(200, {"response": "生成结果"}), **kw),
|
||||
)
|
||||
client = OllamaClient(base_url="http://localhost:11434/", model="qwen2.5:1.5b")
|
||||
|
||||
result = await client.generate("你好")
|
||||
|
||||
assert result == "生成结果"
|
||||
# base_url 尾部斜杠被去除
|
||||
assert captured["url"] == "http://localhost:11434/api/generate"
|
||||
assert captured["json"] == {"model": "qwen2.5:1.5b", "prompt": "你好", "stream": False}
|
||||
assert "format" not in captured["json"]
|
||||
assert captured["timeout"] == 120.0
|
||||
|
||||
async def test_json_mode_adds_format(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
captured: dict = {}
|
||||
monkeypatch.setattr(
|
||||
httpx,
|
||||
"AsyncClient",
|
||||
lambda **kw: _FakeAsyncClient(captured, response=_make_response(200, {"response": "{}"}), **kw),
|
||||
)
|
||||
client = OllamaClient(base_url="http://localhost:11434", model="qwen2.5:1.5b")
|
||||
|
||||
await client.generate("你好", json_mode=True)
|
||||
|
||||
assert captured["json"]["format"] == "json"
|
||||
|
||||
async def test_http_error_status_raises(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
httpx, "AsyncClient", lambda **kw: _FakeAsyncClient({}, response=_make_response(500), **kw)
|
||||
)
|
||||
client = OllamaClient(base_url="http://localhost:11434", model="qwen2.5:1.5b")
|
||||
|
||||
with pytest.raises(httpx.HTTPStatusError):
|
||||
await client.generate("你好")
|
||||
|
||||
|
||||
class TestIsAvailable:
|
||||
async def test_200_returns_true(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
captured: dict = {}
|
||||
monkeypatch.setattr(
|
||||
httpx,
|
||||
"AsyncClient",
|
||||
lambda **kw: _FakeAsyncClient(captured, response=_make_response(200, {"models": []}), **kw),
|
||||
)
|
||||
client = OllamaClient(base_url="http://localhost:11434", model="qwen2.5:1.5b")
|
||||
|
||||
assert await client.is_available() is True
|
||||
assert captured["url"] == "http://localhost:11434/api/tags"
|
||||
assert captured["timeout"] == 5.0
|
||||
|
||||
async def test_non_200_returns_false(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
httpx, "AsyncClient", lambda **kw: _FakeAsyncClient({}, response=_make_response(503), **kw)
|
||||
)
|
||||
client = OllamaClient(base_url="http://localhost:11434", model="qwen2.5:1.5b")
|
||||
|
||||
assert await client.is_available() is False
|
||||
|
||||
async def test_connect_error_returns_false(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
httpx, "AsyncClient", lambda **kw: _FakeAsyncClient({}, error=httpx.ConnectError("连接被拒绝"), **kw)
|
||||
)
|
||||
client = OllamaClient(base_url="http://localhost:11434", model="qwen2.5:1.5b")
|
||||
|
||||
assert await client.is_available() is False
|
||||
Reference in New Issue
Block a user