from genesis.data_models import ( CellValue, ExcelTable, ParsedTemplate, ChapterMarker, Provenance, SheetType, StructuredSource, ) from genesis.writer.models import ContentBlock, ChapterContent, GenerationContext, ChapterSpec def _cell(v): return CellValue(value=v, provenance=Provenance("要件.xlsx", "s1", 1, "A", "列")) def _table(name, sheet_type, headers, rows): return ExcelTable( name=name, detected_type=sheet_type, extraction_method="openpyxl", headers=headers, rows=[{h: _cell(v) for h, v in zip(headers, row)} for row in rows], ) def _mk_source(): """含 FUNCTION / DATABASE / GENERIC 三张表的 StructuredSource(模板仅占位)。""" template = ParsedTemplate( file_name="t.docx", sections=[ChapterMarker(type="heading", name="x", level=1)], placeholders={}, styles={"used": []}, ) return StructuredSource( tables=[ _table("機能一覧", SheetType.FUNCTION, ["機能ID", "機能名"], [["F001", "止损风控"], ["F002", "订单登录"]]), _table("DB定義", SheetType.DATABASE, ["テーブルID", "テーブル名"], [["T001", "trade_order"]]), _table("自由記述", SheetType.GENERIC, ["text"], [["系统采用前后端分离架构"]]), ], template=template, rule_docs=[], image_analyses=[], existing_system=None, comments=[], ) _UNSET = object() def _ctx(cid, title, source=_UNSET, report=None): return GenerationContext( chapter_id=cid, title=title, template_marker=ChapterSpec(chapter_id=cid, title=title, section_placeholder=f"section:{cid}"), structured_source=None if source is None else ( _mk_source() if source is _UNSET else source ), write_rules=[], design_rules=[], template_styles=set(), impact_report=report, ) def test_content_block_defaults(): b = ContentBlock(block_id="b1", type="paragraph", text="你好") assert b.level is None assert b.source_uris == [] def test_chapter_content_holds_blocks(): b = ContentBlock(block_id="b1", type="heading", level=2, text="标题") c = ChapterContent(chapter_id="db_design", version=1, title="DB 设计", blocks=[b]) assert c.blocks[0].type == "heading" assert c.version == 1 def test_generation_context_impact_field_default_none(): ctx = GenerationContext( chapter_id="db_design", title="DB 设计", template_marker=ChapterSpec(chapter_id="db_design", title="DB 设计", section_placeholder="{{section:db_design}}"), structured_source=None, write_rules=["规则1"], design_rules=["规则2"], template_styles={"Heading 1"}, ) assert ctx.impact_report is None # to_vars 始终提供 impact 变量;无影响调查书时为空串 assert ctx.to_vars()["impact"] == "" def test_generation_context_impact_var_formats_report(): from genesis.data_models import ChangeAnalysis, ChangeElement, ChangeType, ImpactReport ca = ChangeAnalysis( project_type="enhancement", new_elements=[ChangeElement("F001", "機能", "止损风控", ChangeType.NEW)], modified_elements=[], deleted_elements=[], unchanged_elements=[], warnings=[], ) report = ImpactReport(metadata={"version": "v1"}, change_analysis=ca, summary={"new": 1}) # function_list 章节对应 ElementType=機能 → 该要素应保留 ctx = _ctx("function_list", "機能一覧", source=None, report=report) impact = ctx.to_vars()["impact"] assert "止损风控" in impact assert "F001" in impact def _impact_ctx(report): # function_list 章节对应 ElementType=機能 → [削除] 機能要素可保留 return _ctx("function_list", "機能一覧", source=None, report=report) def test_impact_var_empty_when_report_without_analysis(): from genesis.data_models import ImpactReport report = ImpactReport(metadata={"version": "v1"}, change_analysis=None, summary={}) assert _impact_ctx(report).to_vars()["impact"] == "" def test_impact_var_formats_deleted_and_warnings(): from genesis.data_models import ChangeAnalysis, ChangeElement, ChangeType, ImpactReport, ImpactWarning ca = ChangeAnalysis( project_type="enhancement", new_elements=[], modified_elements=[], deleted_elements=[ChangeElement("F030", "機能", "旧功能", ChangeType.DELETED)], unchanged_elements=[], warnings=[ImpactWarning("F030", "缺少既存対応")], ) report = ImpactReport(metadata={}, change_analysis=ca, summary={"deleted": 1, "warnings": 1}) impact = _impact_ctx(report).to_vars()["impact"] assert "[削除]" in impact and "旧功能" in impact assert "[警告]" in impact and "缺少既存対応" in impact def test_chapter_spec_placeholder_optional(): s = ChapterSpec(chapter_id="x", title="X", section_placeholder=None) assert s.section_placeholder is None # ---------- 章节级数据注入(design.md §6.8 ①:selector=该章数据) ---------- def test_to_vars_data_scoped_by_chapter(): """db_design 章的 data 只含 DATABASE 表 + GENERIC 自由記述,不含 FUNCTION 表。""" data = _ctx("db_design", "DB設計").to_vars()["data"] assert "テーブルID" in data and "trade_order" in data # DB 表内容 assert "前后端分离" in data # GENERIC 通用背景 assert "止损风控" not in data and "機能ID" not in data # FUNCTION 表被排除 def test_to_vars_data_function_chapter_gets_function_table(): data = _ctx("function_list", "機能一覧").to_vars()["data"] assert "F001" in data and "止损风控" in data assert "trade_order" not in data # DB 表被排除 def test_to_vars_data_introduction_gets_all_tables(): """introduction 概览章注入全部类型表。""" data = _ctx("introduction", "はじめに").to_vars()["data"] assert "F001" in data and "trade_order" in data def test_to_vars_data_empty_when_no_matching_table(): """无对应类型表(如 report_list 无 REPORT 表)→ 仅 GENERIC 背景,不报错。""" data = _ctx("report_list", "帳票一覧").to_vars()["data"] assert "帳票ID" not in data assert "前后端分离" in data # GENERIC 始终保留 def test_to_vars_data_none_safe_and_source_key_removed(): """structured_source=None → data 为空串;旧的全量 source 键不再提供。""" vars_ = _ctx("db_design", "DB設計", source=None).to_vars() assert vars_["data"] == "" assert "source" not in vars_ def test_to_vars_data_renders_markdown_table_shape(): """表渲染为可读 Markdown 形态(表名行 + 表头行 + 分隔行)。""" data = _ctx("db_design", "DB設計").to_vars()["data"] assert "DB定義" in data assert "| テーブルID | テーブル名 |" in data assert "---" in data assert "| T001 | trade_order |" in data def test_impact_filtered_by_chapter_element_type(): """db_design 章 impact 只显示 DB 要素;機能要素被过滤。""" from genesis.data_models import ChangeAnalysis, ChangeElement, ChangeType, ImpactReport ca = ChangeAnalysis( project_type="enhancement", new_elements=[ ChangeElement("T001", "DB", "订单表", ChangeType.NEW), ChangeElement("F001", "機能", "止损风控", ChangeType.NEW), ], modified_elements=[], deleted_elements=[], unchanged_elements=[], warnings=[], ) report = ImpactReport(metadata={}, change_analysis=ca, summary={"new": 2}) impact = _ctx("db_design", "DB設計", source=None, report=report).to_vars()["impact"] assert "T001" in impact and "订单表" in impact assert "F001" not in impact and "止损风控" not in impact def test_impact_introduction_shows_all_elements(): """introduction 章(None 过滤)展示全部要素。""" from genesis.data_models import ChangeAnalysis, ChangeElement, ChangeType, ImpactReport ca = ChangeAnalysis( project_type="enhancement", new_elements=[ChangeElement("T001", "DB", "订单表", ChangeType.NEW), ChangeElement("F001", "機能", "止损风控", ChangeType.NEW)], modified_elements=[], deleted_elements=[], unchanged_elements=[], warnings=[], ) report = ImpactReport(metadata={}, change_analysis=ca, summary={"new": 2}) impact = _ctx("introduction", "はじめに", source=None, report=report).to_vars()["impact"] assert "T001" in impact and "F001" in impact # ---------- 子节结构注入(design.md §6.5:H2/H3 归入本章) ---------- def test_chapter_spec_sub_headings_default_empty(): s = ChapterSpec(chapter_id="x", title="X") assert s.sub_headings == [] def test_to_vars_exposes_sub_headings(): spec = ChapterSpec( chapter_id="function_list", title="2. 機能一覧", section_placeholder="section:function_list", sub_headings=["2.1 機能一覧表", "2.2 機能詳細"], ) ctx = GenerationContext( chapter_id=spec.chapter_id, title=spec.title, template_marker=spec, structured_source=None, write_rules=[], design_rules=[], template_styles=set(), ) v = ctx.to_vars() assert "2.1 機能一覧表" in v["sub_headings"] assert "2.2 機能詳細" in v["sub_headings"] def test_to_vars_sub_headings_empty_when_none(): vars_ = _ctx("db_design", "DB設計").to_vars() assert vars_["sub_headings"] == ""