"""docx 注入原型测试(T17,OV8)。 OV8 裁定:最难成功标准(格式精度)排关键路径末尾 → docx 注入原型提前验证。 本文件测试 DocxInjector:模板占位符替换(章节级/行内)、残留检查、格式精度 (注入 heading 继承模板 Heading 样式,原有内容样式不被破坏)。 """ from __future__ import annotations import pytest from docx import Document from genesis.writer.docx_injector import ( Block, DocxInjectError, DocxInjector, ) def _make_template(tmp_path, body: str) -> str: doc = Document() doc.add_paragraph("{{doc_title}}") # 行内占位符 doc.add_paragraph(body) # 章节占位符所在段落 doc.add_paragraph("尾部固定内容") path = tmp_path / "template.docx" doc.save(str(path)) return str(path) def _section_blocks() -> list[Block]: return [ Block(kind="heading", text="3.1 テーブル一覧", level=2), Block(kind="paragraph", text="以下がDB表定义です。"), Block(kind="table", rows=[["テーブル", "説明"], ["TB001", "社員"]]), ] # ---------- 章节级占位符替换 ---------- def test_section_placeholder_replaced(tmp_path): tpl = _make_template(tmp_path, "{{section:db_design}}") inj = DocxInjector(tpl) out = inj.inject({"db_design": _section_blocks()}, {"doc_title": "概要設計書"}) full_text = "\n".join(p.text for p in out.paragraphs) assert "{{section:db_design}}" not in full_text assert "3.1 テーブル一覧" in full_text assert "以下がDB表定义です。" in full_text # ---------- 行内占位符替换 ---------- def test_inline_meta_replaced(tmp_path): tpl = _make_template(tmp_path, "{{section:db_design}}") inj = DocxInjector(tpl) out = inj.inject({"db_design": []}, {"doc_title": "概要設計書"}) assert "{{doc_title}}" not in "\n".join(p.text for p in out.paragraphs) assert "概要設計書" in "\n".join(p.text for p in out.paragraphs) # ---------- 占位符残留检查 ---------- def test_residue_detection_raises(tmp_path): tpl = _make_template(tmp_path, "{{section:unknown_chapter}}") inj = DocxInjector(tpl) with pytest.raises(DocxInjectError, match="残留"): inj.inject({}, {"doc_title": "X"}) # ---------- 格式精度:heading 继承模板样式 ---------- def test_heading_inherits_template_style(tmp_path): tpl = _make_template(tmp_path, "{{section:db_design}}") inj = DocxInjector(tpl) out = inj.inject({"db_design": _section_blocks()}, {"doc_title": "T"}) # 注入的 heading block(level=2)应渲染为模板中存在的 Heading 2 样式段落 heading_paras = [p for p in out.paragraphs if p.style.name == "Heading 2"] assert any("3.1 テーブル一覧" in p.text for p in heading_paras) def test_original_content_style_preserved(tmp_path): tpl = _make_template(tmp_path, "{{section:db_design}}") inj = DocxInjector(tpl) out = inj.inject({"db_design": []}, {"doc_title": "T"}) # 模板原有段落(尾部固定内容)在注入后仍存在且未被破坏 assert any("尾部固定内容" in p.text for p in out.paragraphs) # ---------- Block 支持 table/list/note 等 kind(renderer 依赖) ---------- def test_block_accepts_table_list_note(): assert Block(kind="table", text="t", rows=[["x"]]).kind == "table" assert Block(kind="list", text="a\nb").kind == "list" assert Block(kind="note", text="n").kind == "note"