Files
QMDSearch/tests/test_ingest_task_api.py
T
kplam 51dc8dc4f6 Initial commit: QMDSearch 分层信息检索服务
- FastAPI + Qdrant + Redis + Ollama 技术栈
- L1→L2→L3→chunk 四层分层检索(dense + sparse RRF 融合)
- 文档三级总结与 2.5 级回退
- query 解析路由与分类
- /admin 管理页面
2026-07-29 21:24:40 +08:00

161 lines
5.7 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.
"""入库异步任务 API 测试(TestClient + FakeManager,不真实联网)"""
from collections.abc import Iterator
from typing import Any
import pytest
from fastapi.testclient import TestClient
from app.api.v1 import document as document_module
from app.main import app
from app.models.document import DocumentInput
from app.services.qdrant import QdrantService
class FakeManager:
"""假入库任务管理器:记录 submit 调用,按 task_id 返回预置任务"""
def __init__(self, tasks: dict[str, dict[str, Any]] | None = None, task_id: str = "task-1") -> None:
self.tasks = tasks or {}
self.task_id = task_id
self.submitted: list[DocumentInput] = []
async def submit(self, doc: DocumentInput) -> str:
self.submitted.append(doc)
return self.task_id
async def get(self, task_id: str) -> dict[str, Any] | None:
return self.tasks.get(task_id)
def _task(task_id: str, status: str, **extra: Any) -> dict[str, Any]:
"""构造一条任务记录(时间字段为固定 ISO8601 字符串)"""
record: dict[str, Any] = {
"task_id": task_id,
"status": status,
"created_at": "2026-07-29T08:00:00+00:00",
"updated_at": "2026-07-29T08:00:01+00:00",
"result": None,
"error": None,
}
record.update(extra)
return record
@pytest.fixture
def client(monkeypatch: pytest.MonkeyPatch) -> Iterator[TestClient]:
"""TestClientlifespan 中的 Qdrant 集合初始化替换为空操作"""
async def _noop_ensure_collections(self: QdrantService) -> None:
return None
monkeypatch.setattr(QdrantService, "ensure_collections", _noop_ensure_collections)
with TestClient(app) as test_client:
yield test_client
def _inject_manager(monkeypatch: pytest.MonkeyPatch, manager: FakeManager) -> None:
"""将 FakeManager 注入路由的 _get_task_manager"""
monkeypatch.setattr(document_module, "_get_task_manager", lambda: manager)
def test_post_documents_returns_202(client: TestClient, monkeypatch: pytest.MonkeyPatch) -> None:
"""POST /documentsHTTP 202data.task_id 非空且 status=pending,文档已提交给管理器"""
manager = FakeManager(task_id="abc123")
_inject_manager(monkeypatch, manager)
resp = client.post("/api/v1/documents", json={"text": "正文内容", "title": "标题"})
assert resp.status_code == 202
body = resp.json()
assert body["code"] == 0
assert body["data"]["task_id"] == "abc123"
assert body["data"]["status"] == "pending"
assert len(manager.submitted) == 1
def test_get_task_pending(client: TestClient, monkeypatch: pytest.MonkeyPatch) -> None:
"""GET tasks/{id}:进行中任务 → code=0,含 status/created_at/updated_at"""
manager = FakeManager(tasks={"t-pending": _task("t-pending", "summarizing")})
_inject_manager(monkeypatch, manager)
resp = client.get("/api/v1/documents/tasks/t-pending")
body = resp.json()
assert body["code"] == 0
data = body["data"]
assert data["task_id"] == "t-pending"
assert data["status"] == "summarizing"
assert data["created_at"]
assert data["updated_at"]
assert data["result"] is None
assert data["error"] is None
def test_get_task_done(client: TestClient, monkeypatch: pytest.MonkeyPatch) -> None:
"""GET tasks/{id}done 任务 → data.result 含 document_id"""
result = {
"document_id": "doc-1",
"summary": {"l1_summary": "一句话", "l2_outline": None, "l3_content_outline": "大纲", "level": "L3"},
"category": "技术文档",
"collection": "四层集合",
"chunks_count": 3,
"tags": ["API"],
"category_confidence": 0.9,
}
manager = FakeManager(tasks={"t-done": _task("t-done", "done", result=result)})
_inject_manager(monkeypatch, manager)
resp = client.get("/api/v1/documents/tasks/t-done")
body = resp.json()
assert body["code"] == 0
data = body["data"]
assert data["status"] == "done"
assert data["result"]["document_id"] == "doc-1"
assert data["result"]["chunks_count"] == 3
assert data["error"] is None
def test_get_task_failed(client: TestClient, monkeypatch: pytest.MonkeyPatch) -> None:
"""GET tasks/{id}failed 任务 → data.error 含 stage/message/partial_summary"""
partial = {"l1_summary": "L1", "l2_outline": None, "l3_content_outline": "L3", "level": "L3"}
error = {"stage": "embed", "message": "向量化失败: boom", "partial_summary": partial}
manager = FakeManager(tasks={"t-failed": _task("t-failed", "failed", error=error)})
_inject_manager(monkeypatch, manager)
resp = client.get("/api/v1/documents/tasks/t-failed")
body = resp.json()
assert body["code"] == 0
data = body["data"]
assert data["status"] == "failed"
assert data["error"]["stage"] == "embed"
assert "boom" in data["error"]["message"]
assert data["error"]["partial_summary"] == partial
assert data["result"] is None
def test_get_task_not_found(client: TestClient, monkeypatch: pytest.MonkeyPatch) -> None:
"""GET tasks/{id}:不存在的 task_id → code=1004"""
_inject_manager(monkeypatch, FakeManager())
resp = client.get("/api/v1/documents/tasks/不存在")
body = resp.json()
assert body["code"] == 1004
assert body["message"] == "任务不存在"
assert body["data"] is None
def test_post_empty_text_creates_no_task(client: TestClient, monkeypatch: pytest.MonkeyPatch) -> None:
"""POST 空文本:code=1001,且不产生任务(submit 未被调用)"""
manager = FakeManager()
_inject_manager(monkeypatch, manager)
resp = client.post("/api/v1/documents", json={"text": " "})
body = resp.json()
assert body["code"] == 1001
assert manager.submitted == []