# -*- 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 = ( '
    ' '
  1. 范文一

  2. ' '
  3. 范文二

  4. ' "
" ) RESULT_PAGE = ( "安全生产通知范文" "

关于安全生产的通知

正文……

" "

一、总体要求

二、重点任务

2.1 隐患排查

" "" ) 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 = "

一

" + "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="

没有标题的页面

") _install(monkeypatch, handler) assert _run("通知") is None def test_DC_UNIT_WEB_05_gb18030_decodes(self, monkeypatch): page = ( "通知范文" "

关于开展检查的通知

一、检查范围

" ).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 # 关闸后不发任何网络请求