feat: add FastAPI HTTP API layer
- Implement FastAPI server with health, collections, ingest, search, and delete endpoints - Add Pydantic request models for ingest and search - Add lazy singleton initialization for DB, embedder, and services - Support environment variable override for data dir and collection name - Add integration tests with TestClient and temp directory - Set TRANSFORMERS_OFFLINE/HF_HUB_OFFLINE for offline model loading in tests
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
"""API 端点集成测试."""
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
"""创建测试客户端(使用临时目录 + 本地嵌入模型)."""
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
# 设置环境变量使 app 使用测试配置
|
||||
os.environ["MD_VECTOR_DB_DATA_DIR"] = tmpdir
|
||||
os.environ["MD_VECTOR_DB_COLLECTION"] = "test_api"
|
||||
# 离线模式: 模型已缓存, 无需联网
|
||||
os.environ["TRANSFORMERS_OFFLINE"] = "1"
|
||||
os.environ["HF_HUB_OFFLINE"] = "1"
|
||||
|
||||
from src.server.app import app
|
||||
|
||||
with TestClient(app) as c:
|
||||
yield c
|
||||
|
||||
|
||||
class TestHealthEndpoint:
|
||||
"""健康检查."""
|
||||
|
||||
def test_health_returns_ok(self, client):
|
||||
response = client.get("/api/v1/health")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "ok"
|
||||
|
||||
|
||||
class TestCollectionEndpoint:
|
||||
"""Collection 端点."""
|
||||
|
||||
def test_list_collections(self, client):
|
||||
response = client.get("/api/v1/collections")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "collections" in data
|
||||
|
||||
|
||||
class TestSearchEndpoint:
|
||||
"""搜索端点."""
|
||||
|
||||
def test_search_requires_query(self, client):
|
||||
response = client.post("/api/v1/search", json={})
|
||||
assert response.status_code == 422 # FastAPI 自动校验
|
||||
|
||||
def test_search_empty_collection(self, client):
|
||||
response = client.post(
|
||||
"/api/v1/search",
|
||||
json={"query": "test", "top_k": 5},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "results" in data
|
||||
assert data["results"] == []
|
||||
|
||||
|
||||
class TestIngestEndpoint:
|
||||
"""入库端点."""
|
||||
|
||||
def test_ingest_content(self, client):
|
||||
response = client.post(
|
||||
"/api/v1/ingest",
|
||||
json={
|
||||
"content": "# Test\nHello world.",
|
||||
"file_name": "test.md",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "ok"
|
||||
assert data["chunks"] > 0
|
||||
|
||||
def test_ingest_then_search(self, client):
|
||||
"""入库后能检索到."""
|
||||
client.post(
|
||||
"/api/v1/ingest",
|
||||
json={"content": "# 配置说明\nChromaDB 配置很简单。", "file_name": "config.md"},
|
||||
)
|
||||
response = client.post(
|
||||
"/api/v1/search",
|
||||
json={"query": "配置", "top_k": 3},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["results"]) > 0
|
||||
Reference in New Issue
Block a user