"""tests/paper/conftest.py —— 论文服务测试共享夹具(tmp db + 假 LLM + 假检索)。 全部 mock LLM/vstore,不依赖生产服务(8090/8094 等绝不探测)。 镜像 bioflow_service/tests/conftest.py 的 _run(新建事件循环)+ 临时库模式。 """ from __future__ import annotations import asyncio import sys from pathlib import Path import pytest REPO_ROOT = Path(__file__).resolve().parent.parent.parent for _p in (str(REPO_ROOT), str(REPO_ROOT / "sdk")): if _p not in sys.path: sys.path.insert(0, _p) from paper_writer_service.app import db as dbmod # noqa: E402 from paper_writer_service.app.services.paper import llm as llmmod # noqa: E402 from paper_writer_service.app.services.paper import search as searchmod # noqa: E402 def _run(coro): """同步跑一个协程(新建 event loop;bioflow/docgen 测试同款可靠模式)。""" return asyncio.new_event_loop().run_until_complete(coro) class FakeLLM: """可编程假 LLM:按顺序返回预设 dict;或抛 LLMError。记录全部调用。""" def __init__(self, responses=None, error=None): self.responses = list(responses or []) self.error = error self.calls: list[dict] = [] async def complete_json(self, system, user, **kwargs): self.calls.append({"system": system, "user": user}) if self.error: raise llmmod.LLMError(self.error) return self.responses.pop(0) if self.responses else {} async def complete_text(self, system, user, **kwargs): self.calls.append({"system": system, "user": user}) if self.error: raise llmmod.LLMError(self.error) return self.responses.pop(0) if self.responses else "" # --------------------------------------------------------------------------- # DB:单例改路径重连(服务模块以 from ...db import db 持有单例引用) # --------------------------------------------------------------------------- @pytest.fixture def paper_db(tmp_path): dbmod.db._path = Path(tmp_path) / "paper.db" _run(dbmod.db.connect()) yield dbmod.db _run(dbmod.db.close()) @pytest.fixture def fake_llm(monkeypatch): fake = FakeLLM() monkeypatch.setattr(llmmod, "complete_json", fake.complete_json) monkeypatch.setattr(llmmod, "complete_text", fake.complete_text) return fake @pytest.fixture def fake_search(monkeypatch): async def _search_refs(paper_id="", query="", top_k=8): return {"ok": True, "items": [ {"doc_id": "d1", "title": "水稻SPL基因家族研究进展", "snippet": "SPL 是植物特有转录因子家族。", "score": 0.9, "meta": {}}, {"doc_id": "d2", "title": "植物SPL转录因子功能", "snippet": "SPL 调控开花与株型。", "score": 0.8, "meta": {}}, ]} monkeypatch.setattr(searchmod, "search_refs", _search_refs) return _search_refs