- T17 (OV8, P1): 新建 src/genesis/writer/ 包
- docx_injector.py: DocxInjector 用原生 python-docx 实现 §6.6 占位符注入
- 章节级 {{section:id}} → 内容块 docx 元素序列(heading/paragraph/table)
- 行内 {{meta}} → 元信息填充
- 残留检查: 未替换 {{...}} 抛 DocxInjectError
- 格式精度: 注入 heading 继承模板 Heading 样式,原内容不被破坏
- 新增 test_docx_injector.py(5 用例)
- 同步 design.md §6.7 渲染链路 T17 原型说明
- TDD: RED(模块缺失)→ GREEN(聚焦 5 passed)→ 全量 245 passed / 100.00%(1361 stmts/340 br)
90 lines
3.1 KiB
Python
90 lines
3.1 KiB
Python
"""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)
|