Files
group_xinghuo_jinrong/tests/test_embedding.py
T

133 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""T21-1 embedding 服务单测:embed_texts / embed_text(全部 mock HTTP,不依赖 Ollama)。
覆盖四类:
1. 成功语义:批量顺序一一对应、维度校验通过、空列表短路不发请求;
2. 失败语义:连接拒绝 / 非 200(模型未拉取 404)/ 响应结构异常 → EmbeddingError;
3. 维度防线:维度不符(8 维假数据)必须报错——防静默污染检索(拍板口径);
4. 便捷方法:embed_text 等价 embed_texts([text])[0]。
httpx.MockTransport 注入:_client() 是 monkeypatch 点(settings 超时透传)。
"""
from __future__ import annotations
import httpx
import pytest
from app.config.settings import settings
from app.service import embedding as emb
def _mock_client(handler) -> httpx.Client:
"""构造 mock 传输层的 httpx.Client(替换真实网络)。"""
return httpx.Client(transport=httpx.MockTransport(handler), timeout=5)
@pytest.fixture(autouse=True)
def _patch_client(monkeypatch):
"""默认给一个「合法 1024 维响应」的 mock 客户端,各用例按需覆盖。"""
def handler(request: httpx.Request) -> httpx.Response:
body = request.read()
import json
payload = json.loads(body)
vectors = [[0.1] * settings.embed_dim for _ in payload["input"]]
return httpx.Response(200, json={"model": payload["model"], "embeddings": vectors})
monkeypatch.setattr(emb, "_client", lambda: _mock_client(handler))
class TestSuccess:
"""成功语义。"""
def test_batch_order_preserved(self, monkeypatch):
def handler(request: httpx.Request) -> httpx.Response:
import json
payload = json.loads(request.read())
# 每条向量以首字符标记,断言顺序一一对应
vectors = [[float(ord(t[0]))] + [0.0] * (settings.embed_dim - 1) for t in payload["input"]]
return httpx.Response(200, json={"embeddings": vectors})
monkeypatch.setattr(emb, "_client", lambda: _mock_client(handler))
out = emb.embed_texts(["甲文本", "乙文本", "丙文本"])
assert len(out) == 3
assert out[0][0] == float(ord("甲"))
assert out[2][0] == float(ord("丙"))
def test_empty_input_short_circuits(self, monkeypatch):
# 空列表不发请求:handler 一旦被调用就 fail(返回 500 触发错误)
def handler(request: httpx.Request) -> httpx.Response:
raise AssertionError("空输入不应发起 HTTP 请求")
monkeypatch.setattr(emb, "_client", lambda: _mock_client(handler))
assert emb.embed_texts([]) == []
def test_request_payload_uses_settings_model(self, monkeypatch):
captured: dict = {}
def handler(request: httpx.Request) -> httpx.Response:
import json
captured.update(json.loads(request.read()))
payload = json.loads(request.read())
vectors = [[0.0] * settings.embed_dim for _ in payload["input"]]
return httpx.Response(200, json={"embeddings": vectors})
monkeypatch.setattr(emb, "_client", lambda: _mock_client(handler))
emb.embed_texts(["测试"])
assert captured["model"] == settings.embed_model
assert captured["input"] == ["测试"]
class TestFailure:
"""失败语义:一律 EmbeddingError,不静默降级。"""
def test_connection_refused(self, monkeypatch):
def handler(request: httpx.Request) -> httpx.Response:
raise httpx.ConnectError("connection refused")
monkeypatch.setattr(emb, "_client", lambda: _mock_client(handler))
with pytest.raises(emb.EmbeddingError, match="连接失败"):
emb.embed_texts(["任意"])
def test_model_not_found_404(self, monkeypatch):
def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(404, json={"error": "model 'bge-m3' not found"})
monkeypatch.setattr(emb, "_client", lambda: _mock_client(handler))
with pytest.raises(emb.EmbeddingError, match="HTTP 404"):
emb.embed_texts(["任意"])
def test_malformed_response(self, monkeypatch):
def handler(request: httpx.Request) -> httpx.Response:
# embeddings 缺失 / 数量不匹配均属结构异常
return httpx.Response(200, json={"embeddings": [[0.0] * settings.embed_dim]})
monkeypatch.setattr(emb, "_client", lambda: _mock_client(handler))
with pytest.raises(emb.EmbeddingError, match="数量与输入不匹配"):
emb.embed_texts(["一", "二"])
class TestDimensionGuard:
"""维度防线(拍板:维度不符必须报错)。"""
def test_wrong_dimension_rejected(self, monkeypatch):
def handler(request: httpx.Request) -> httpx.Response:
import json
payload = json.loads(request.read())
vectors = [[0.1] * 8 for _ in payload["input"]] # 假 8 维
return httpx.Response(200, json={"embeddings": vectors})
monkeypatch.setattr(emb, "_client", lambda: _mock_client(handler))
with pytest.raises(emb.EmbeddingError, match="维度不符"):
emb.embed_texts(["任意"])
class TestConvenience:
def test_embed_text_single(self):
out = emb.embed_text("单条")
assert len(out) == settings.embed_dim