第一批:上传去限制+进度展示、模板HTML预览+按需导出、检索TAB实时更新
第二批:edit护栏移除、导出按钮纯化+file_name落库、编辑失焦还原、
二次预览白底根治(防竞态+渲染兜底)
模板体系:7套公文模板(templates/)+templating渲染器、websearch联网
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
135 lines
4.7 KiB
Python
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 # 关闸后不发任何网络请求
|