Files
jerryandClaude Fable 5 4749f77544 fix(docgen): 两批手工测试修复——上传/预览/检索 + 护栏/导出/命名/编辑还原/二次预览
第一批:上传去限制+进度展示、模板HTML预览+按需导出、检索TAB实时更新
第二批:edit护栏移除、导出按钮纯化+file_name落库、编辑失焦还原、
       二次预览白底根治(防竞态+渲染兜底)
模板体系:7套公文模板(templates/)+templating渲染器、websearch联网

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 15:49:54 +08:00

135 lines
4.7 KiB
Python

# -*- coding: utf-8 -*-
"""tests/docgen/test_websearch.py —— 联网模版参考验收用例(DC-UNIT-WEB-*)。
2026-09-04 手工测试需求2:库内无匹配模版时联网搜索相似内容结构。
口径:monkeypatch websearch._make_client 注入 MockTransport,全程离线。
执行:bash tests/docgen/run_all.sh unit
"""
from __future__ import annotations
import asyncio
import sys
from pathlib import Path
import httpx
import pytest
REPO = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO))
sys.path.insert(0, str(REPO / "sdk"))
from app.services.docgen import websearch # noqa: E402
pytestmark = pytest.mark.unit
BING_PAGE = (
'<html><body><ol id="b_results">'
'<li class="b_algo"><h2><a href="https://example.com/fanwen/1">范文一</a></h2></li>'
'<li class="b_algo"><h2><a href="https://example.com/fanwen/2">范文二</a></h2></li>'
"</ol></body></html>"
)
RESULT_PAGE = (
"<html><head><title>安全生产通知范文</title></head><body>"
"<h1>关于安全生产的通知</h1><p>正文……</p>"
"<h2>一、总体要求</h2><h2>二、重点任务</h2><h3>2.1 隐患排查</h3>"
"</body></html>"
)
def _install(monkeypatch, handler, *, force_enabled=True):
"""注入 MockTransport 客户端工厂;force_enabled 绕过环境闸。"""
calls = []
async def wrapped(request):
calls.append(str(request.url))
return await handler(request)
def factory(timeout):
return httpx.AsyncClient(transport=httpx.MockTransport(wrapped))
monkeypatch.setattr(websearch, "_make_client", factory)
if force_enabled:
monkeypatch.setattr(websearch, "web_search_enabled", lambda: True)
return calls
def _run(description):
return asyncio.run(websearch.search_template_outline(description))
async def _handler_page(request):
if "bing.com" in request.url.host:
return httpx.Response(200, text=BING_PAGE)
return httpx.Response(200, text=RESULT_PAGE)
class TestWebsearch:
def test_DC_UNIT_WEB_01_outline_extracted(self, monkeypatch):
calls = _install(monkeypatch, _handler_page)
result = _run("帮我写一份关于安全生产的通知")
assert result is not None
assert result.source_url == "https://example.com/fanwen/1"
assert result.title == "安全生产通知范文"
assert result.outline == [
"关于安全生产的通知",
"一、总体要求",
"二、重点任务",
"2.1 隐患排查",
]
assert len(calls) == 2 # bing 一次 + 抓取页一次
def test_DC_UNIT_WEB_02_network_failure_none(self, monkeypatch):
async def handler(request):
raise httpx.ConnectError("boom", request=request)
_install(monkeypatch, handler)
assert _run("通知") is None
def test_DC_UNIT_WEB_03_oversize_page_none(self, monkeypatch):
big = "<h1>一</h1>" + "x" * (websearch.MAX_RESULT_BYTES + 10)
async def handler(request):
if "bing.com" in request.url.host:
return httpx.Response(200, text=BING_PAGE)
return httpx.Response(200, text=big)
_install(monkeypatch, handler)
# 截断后标题数 < MIN_HEADINGS → None
assert _run("通知") is None
def test_DC_UNIT_WEB_04_too_few_headings_none(self, monkeypatch):
async def handler(request):
if "bing.com" in request.url.host:
return httpx.Response(200, text=BING_PAGE)
return httpx.Response(200, text="<html><body><p>没有标题的页面</p></body></html>")
_install(monkeypatch, handler)
assert _run("通知") is None
def test_DC_UNIT_WEB_05_gb18030_decodes(self, monkeypatch):
page = (
"<html><head><title>通知范文</title></head><body>"
"<h1>关于开展检查的通知</h1><h2>一、检查范围</h2></body></html>"
).encode("gb18030")
async def handler(request):
if "bing.com" in request.url.host:
return httpx.Response(200, text=BING_PAGE)
return httpx.Response(200, content=page, headers={"content-type": "text/html"})
_install(monkeypatch, handler)
result = _run("通知")
assert result is not None
assert result.outline == ["关于开展检查的通知", "一、检查范围"]
def test_DC_UNIT_WEB_06_env_gate_off(self, monkeypatch):
async def handler(request):
return httpx.Response(200, text=BING_PAGE)
calls = _install(monkeypatch, handler, force_enabled=False)
monkeypatch.setattr(websearch, "web_search_enabled", lambda: False)
assert _run("通知") is None
assert not calls # 关闸后不发任何网络请求