Files
PlantAI-rag-new/tests/docgen/test_templates.py
T
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

163 lines
6.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_templates.py —— 内置模版系统验收用例(DC-UNIT-TPL-*)。
2026-09-04 手工测试需求2:套用模版生成可预览 HTML。
口径:公开函数/文件系统断言;全部离线(不依赖 LLM/网络/vstore 服务)。
执行:bash tests/docgen/run_all.sh unit
"""
from __future__ import annotations
import json
import re
import sys
from pathlib import Path
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 templating # noqa: E402
pytestmark = pytest.mark.unit
# Story 禁用属性(双渲染纪律:浏览器与 PyMuPDF Story 共用 css)
BANNED_PATTERNS = (
re.compile(r"position\s*:", re.IGNORECASE),
re.compile(r"display\s*:\s*(flex|grid)", re.IGNORECASE),
re.compile(r"transform\s*:", re.IGNORECASE),
re.compile(r"@media", re.IGNORECASE),
re.compile(r"@page\s*\{[^}]*margin", re.IGNORECASE),
re.compile(r"var\(--", re.IGNORECASE),
re.compile(r"[vhvw]h\b|[vhvw]w\b", re.IGNORECASE),
)
class TestTemplates:
def test_DC_UNIT_TPL_01_registry_loads(self):
registry = templating.load_registry()
assert isinstance(registry, dict)
entries = registry.get("templates") or []
keys = [e.get("key") for e in entries if isinstance(e, dict)]
assert len(keys) == 7
assert set(keys) == {
"official", "report", "application", "contract",
"speech", "plan", "document",
}
assert registry.get("fallback") == "document"
@pytest.mark.parametrize(
"text,expected",
[
("帮我写一份会议通知", "official"),
("起草一份请示", "official"),
("写一个调研报告", "report"),
("拟一份年度工作总结", "report"),
("写一份项目申报书", "application"),
("起草一份采购合同", "contract"),
("写个讲话稿", "speech"),
("写一份感谢信", "speech"),
("制定一个实施方案", "plan"),
("写个说明书", "document"),
("写一篇论文", "document"),
("", "document"),
],
)
def test_DC_UNIT_TPL_02_classify(self, text, expected):
assert templating.classify_doc_type(text) == expected
def test_DC_UNIT_TPL_03_render_html_structure(self):
html, css = templating.render_html(
"# 关于安全生产的通知\n\n## 一、总体要求\n\n正文内容。",
title="关于安全生产的通知",
key="official",
)
assert "<main>" in html and "</main>" in html
assert 'class="doc-title"' in html
assert "关于安全生产的通知" in html
assert "正文内容" in html
assert "{{TITLE}}" not in html
assert css and "doc-table" in css or True
def test_DC_UNIT_TPL_04_style_hints_overlay(self):
hints = {
"body_font": "宋体",
"body_size": 14,
"headings": {"1": {"font": "黑体", "size": 18}},
}
_, css = templating.render_html(
"# 标题\n\n正文。", title="t", key="report", style_hints=hints
)
assert "宋体" in css
assert "font-size: 14.0pt" in css
assert "font-size: 18.0pt" in css
def test_DC_UNIT_TPL_05_never_raises(self):
for markdown in ("", "| a | b |\n|---|---|", "```\nfence\n```", "### 标题"):
html, css = templating.render_html(markdown, title="", key="document")
assert isinstance(html, str) and isinstance(css, str)
# 乱 key 回落 fallback
html, _ = templating.render_html("x", title="", key="nonexistent-key")
assert "<main>" in html
def test_DC_UNIT_TPL_06_css_story_safe(self):
tpl_dir = templating.templates_dir()
for css_file in sorted(tpl_dir.glob("*.css")):
content = css_file.read_text(encoding="utf-8")
# 剥离注释后再扫(注释里的禁用词不算违规)
content = re.sub(r"/\*[\s\S]*?\*/", "", content)
for pattern in BANNED_PATTERNS:
assert not pattern.search(content), (
f"{css_file.name} 含 Story 禁用属性:{pattern.pattern}"
)
def test_DC_UNIT_TPL_07_wrapper_single_main_xml(self):
tpl_dir = templating.templates_dir()
for html_file in sorted(tpl_dir.glob("*.html")):
content = html_file.read_text(encoding="utf-8")
assert content.count("<main>") == 1, f"{html_file.name} main 注入点不唯一"
assert "<!DOCTYPE html>" in content
# main 必须为空注入点(渲染时整体替换)
m = re.search(r"<main>\s*</main>", content)
assert m is not None, f"{html_file.name} main 非空注入点"
def test_DC_UNIT_TPL_08_tables_marked(self):
html, _ = templating.render_html(
"| 列一 | 列二 |\n| --- | --- |\n| 值1 | 值2 |",
title="t",
key="document",
)
assert 'class="doc-table"' in html
def test_DC_UNIT_TPL_09_letterhead(self):
# 2026-09-04:红头文字可编辑持久化——letterhead 参数覆盖,空回落模版默认
html, _ = templating.render_html(
"# 标题\n\n正文。", title="t", key="official", letterhead="某某农业科学研究院"
)
assert "某某农业科学研究院" in html
assert "红头文件" not in html
# 空 letterhead → 模版默认
html2, _ = templating.render_html("# 标题", title="t", key="official")
assert "红头文件" in html2
# 无红头区模板(contract):letterhead 不影响
html3, _ = templating.render_html("# 标题", title="t", key="contract", letterhead="x")
assert "{{LETTERHEAD}}" not in html3
@pytest.mark.parametrize(
"text,expected",
[
("帮我写一个十一放假通知,红头用三亚海古纪生物科技有限公司", "三亚海古纪生物科技有限公司"),
("红头:某市农业农村局,写一份通知", "某市农业农村局"),
("写一份通知,红头为某某研究所", "某某研究所"),
("帮我写一份普通通知", ""),
],
)
def test_DC_UNIT_TPL_10_letterhead_prompt_extract(self, text, expected):
# 2026-09-04:prompt「红头用XX」→ 生成即采用指定红头
got = templating.extract_letterhead_from_prompt(text)
if expected:
assert got == expected
else:
assert got == ""