Files
PlantAI-rag-new/tests/docgen/test_docgen_conf.py
T
jerryandClaude Fable 5 5888e1d5c1 test(docgen): 测试治理资产——131 条验收用例 + 三轮留痕 + E2E/回归套件
- tests/docgen/:DC-UNIT/API/CONF/E2E/UI/REGRESSION/PERF 分组用例与可复跑脚本,报告逐条留痕(R1-R3 全记录)
- 意图 30 条前后端同表双跑(pytest + node)
- Playwright headless:docgen-ui/docgen-regression spec + b3 benchpanel 第 5 TAB 断言更新

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-03 21:13:47 +08:00

214 lines
7.6 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.
# -*- coding: utf-8 -*-
"""tests/docgen/test_docgen_conf.py —— DC-CONF-01~06(路径配置三形态 + 握手拒绝 + env + EMBED_URL)。
v1.2 签名适配(dev1 权威签名):
- parse_backend(vault_spec, index_spec) 两参;同形态才合法,混合抛 BackendError
- handshake(backend) 独立函数:health + 空检索,失败抛 BackendError
- get_fernet_key() 调用即抛(VSTORE_KEY 缺失 → RuntimeError)
- VstorePaths(root/vault_path/index_path/embed_url/embed_model) + set_paths 注入
DC-CONF-03/04 用受控假远端(tests/docgen/fake_remote_vstore.py,本机随机端口),不碰生产端口。
执行:bash tests/docgen/run_all.sh conf
"""
from __future__ import annotations
import os
import socket
import subprocess
import sys
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO))
VENV_PY = "/home/xzq/5090/teamWorkspace/zh/plantai-rag-new-venv/bin/python"
def _importable(mod):
try:
__import__(mod)
return True
except ImportError:
return False
def _free_port():
with socket.socket() as s:
s.bind(("127.0.0.1", 0))
return s.getsockname()[1]
def _start_fake_remote(port, reject=False):
proc = subprocess.Popen(
[VENV_PY, str(REPO / "tests/docgen/fake_remote_vstore.py"), "--port", str(port)]
+ (["--reject"] if reject else []),
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
import time
for _ in range(50):
time.sleep(0.1)
try:
# 就绪探测=端口可连(reject 模式 /health 本就返回 404,不能以 200 判定)
with socket.create_connection(("127.0.0.1", port), timeout=1):
return proc
except OSError:
continue
proc.terminate()
pytest.skip("假远端启动失败(本组前置环境)")
# ---------------------------------------------------------------------------
# DC-CONF-01/02:裸路径与 file:// → LocalBackend(两参)
# ---------------------------------------------------------------------------
def test_DC_CONF_01_bare_path(tmp_path):
if not _importable("vstore.backend"):
pytest.skip("PENDING: vstore.backend 未交付")
from vstore.backend import parse_backend
be = parse_backend(str(tmp_path / "va"), str(tmp_path / "ix"))
assert be.kind == "local"
assert Path(be.vault_path) == tmp_path / "va"
assert Path(be.index_path) == tmp_path / "ix"
def test_DC_CONF_02_file_url(tmp_path):
if not _importable("vstore.backend"):
pytest.skip("PENDING: vstore.backend 未交付")
from vstore.backend import parse_backend
be = parse_backend(
"file://" + str(tmp_path / "va"),
"file://" + str(tmp_path / "ix"),
)
assert be.kind == "local" # file:// 不算远程
assert str(be.vault_path) == str(tmp_path / "va") # 前缀剥掉
def test_DC_CONF_02b_mixed_forms_rejected(tmp_path):
"""混合形态(本地 + 远程)抛 BackendError(dev1 权威签名)。"""
if not _importable("vstore.backend"):
pytest.skip("PENDING: vstore.backend 未交付")
from vstore.backend import BackendError, parse_backend
with pytest.raises(BackendError):
parse_backend(str(tmp_path / "va"), "http://127.0.0.1:1/ix")
# ---------------------------------------------------------------------------
# DC-CONF-03:http(s):// URL → RemoteBackend + 真实受控握手通过
# ---------------------------------------------------------------------------
def test_DC_CONF_03_remote_backend_real_handshake():
if not _importable("vstore.backend"):
pytest.skip("PENDING: vstore.backend 未交付")
from vstore.backend import handshake, parse_backend
port = _free_port()
proc = _start_fake_remote(port)
try:
be = parse_backend(f"http://127.0.0.1:{port}", f"http://127.0.0.1:{port}")
assert be.kind == "remote"
handshake(be) # 不应抛异常(受控远端正常)
finally:
proc.terminate()
proc.wait(timeout=10)
# ---------------------------------------------------------------------------
# DC-CONF-04:远程握手失败 → BackendError(拒绝启动)
# ---------------------------------------------------------------------------
def test_DC_CONF_04_handshake_reject():
if not _importable("vstore.backend"):
pytest.skip("PENDING: vstore.backend 未交付")
from vstore.backend import BackendError, handshake, parse_backend
port = _free_port()
proc = _start_fake_remote(port, reject=True)
try:
be = parse_backend(f"http://127.0.0.1:{port}", f"http://127.0.0.1:{port}")
with pytest.raises(BackendError):
handshake(be)
finally:
proc.terminate()
proc.wait(timeout=10)
# ---------------------------------------------------------------------------
# DC-CONF-05:缺 VSTORE_KEY → get_fernet_key() 调用即抛
# ---------------------------------------------------------------------------
def test_DC_CONF_05_missing_key(monkeypatch):
if not _importable("vstore.settings"):
pytest.skip("PENDING: vstore.settings 未交付")
from vstore.settings import get_fernet_key
monkeypatch.delenv("VSTORE_KEY", raising=False)
with pytest.raises(RuntimeError) as ei:
get_fernet_key()
assert "VSTORE_KEY" in str(ei.value)
# ---------------------------------------------------------------------------
# DC-CONF-06:VSTORE_EMBED_URL 可配置(集群化预留:embed_batch 打到配置地址)
# ---------------------------------------------------------------------------
def test_DC_CONF_06_embed_url_configurable():
if not _importable("vstore.embed"):
pytest.skip("PENDING: vstore.embed 未交付")
import json
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
import vstore.embed as emod
import vstore.settings as smod
hits = []
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
hits.append(self.path)
length = int(self.headers.get("Content-Length") or 0)
if length:
self.rfile.read(length)
body = json.dumps(
{"model": "bge-m3:567m", "embeddings": [[0.1] * 1024]}
).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *args):
pass
srv = HTTPServer(("127.0.0.1", 0), Handler)
port = srv.server_address[1]
threading.Thread(target=srv.serve_forever, daemon=True).start()
try:
# dev1 权威注入:set_paths(VstorePaths 含 embed_url)
smod.set_paths(
smod.VstorePaths(
root="/tmp/docgen-conf-root",
vault_path="/tmp/docgen-conf-root/vault",
index_path="/tmp/docgen-conf-root/index",
embed_url=f"http://127.0.0.1:{port}",
embed_model="bge-m3:567m",
)
)
try:
res = emod.embed_batch(["占位文本一", "占位文本二"])
finally:
smod.set_paths(smod.VstorePaths()) # 恢复默认(env 驱动)配置
assert hits, "embed_batch 未向配置地址发出 HTTP 请求"
assert "/api/embed" in hits[0], f"请求路径异常: {hits[0]}"
assert all("11434" not in h for h in hits), "请求打到了默认 11434 而非配置地址"
assert len(res) == 2
finally:
srv.shutdown()