import pytest from pathlib import Path from genesis.data_models import StructuredSource from genesis.impact.code_parser import CodeParseError from genesis.parsers.source_aggregator import SourceParser from tests.docx_helpers import make_rule_doc, new_document, save_document from tests.excel_helpers import new_workbook, save_workbook def _xlsx(tmp_path, name: str = "source.xlsx") -> str: wb = new_workbook({"機能一覧": [["機能ID", "機能名"], ["A001", "社員登録"]]}) path = tmp_path / name wb.save(path) return str(path) def test_parse_full_assembly(tmp_path): xlsx = _xlsx(tmp_path) template = save_document(tmp_path, new_document()) rule = make_rule_doc(tmp_path, [("H1", "1. 機能一覧の書き方")]) instr = make_rule_doc(tmp_path, [("H1", "2. 機能一覧の作成手順")]) result = SourceParser().parse( requirement_paths=[xlsx], template_path=template, write_instruction_paths=[instr], rule_paths=[rule], ) assert isinstance(result, StructuredSource) assert len(result.tables) == 1 assert result.template is not None assert result.template.file_name == "source.docx" assert len(result.rule_docs) == 2 assert all(r.category == "write" for r in result.rule_docs) assert result.image_analyses == [] assert result.existing_system is None def test_parse_without_template_and_rules(tmp_path): xlsx = _xlsx(tmp_path) result = SourceParser().parse(requirement_paths=[xlsx]) assert len(result.tables) == 1 assert result.template is None assert result.rule_docs == [] def test_parse_missing_requirement_file(tmp_path): with pytest.raises(FileNotFoundError): SourceParser().parse(requirement_paths=[tmp_path / "missing.xlsx"]) def test_parse_missing_template_file(tmp_path): xlsx = _xlsx(tmp_path) with pytest.raises(FileNotFoundError): SourceParser().parse(requirement_paths=[xlsx], template_path=tmp_path / "missing.docx") def test_parse_unknown_extension_in_requirements(tmp_path): bad = tmp_path / "note.txt" bad.write_text("hello", encoding="utf-8") with pytest.raises(ValueError, match="不支持的文件类型"): SourceParser().parse(requirement_paths=[bad]) def test_parse_unknown_extension_in_rules(tmp_path): bad = tmp_path / "note.txt" bad.write_text("hello", encoding="utf-8") with pytest.raises(ValueError, match="不支持的文件类型"): SourceParser().parse(rule_paths=[bad]) def test_parse_unknown_extension_in_template(tmp_path): bad = tmp_path / "note.txt" bad.write_text("hello", encoding="utf-8") with pytest.raises(ValueError, match="不支持的文件类型"): SourceParser().parse(template_path=bad) def test_parse_missing_rule_file(tmp_path): with pytest.raises(FileNotFoundError): SourceParser().parse(rule_paths=[tmp_path / "missing.docx"]) def test_parse_extensionless_requirement(tmp_path): bad = tmp_path / "note" bad.write_text("hello", encoding="utf-8") with pytest.raises(ValueError, match="无扩展名"): SourceParser().parse(requirement_paths=[bad]) def test_parse_extensionless_rule(tmp_path): bad = tmp_path / "note" bad.write_text("hello", encoding="utf-8") with pytest.raises(ValueError, match="无扩展名"): SourceParser().parse(rule_paths=[bad]) # ---------- T7: DRY _validate_path helper(I8) ---------- def test_validate_path_rejects_bad_extension(tmp_path): from genesis.parsers.source_aggregator import _validate_path bad = tmp_path / "note.txt" bad.write_text("x", encoding="utf-8") with pytest.raises(ValueError, match="不支持的文件类型"): _validate_path(bad, (".xlsx",)) def test_validate_path_rejects_missing_file(tmp_path): from genesis.parsers.source_aggregator import _validate_path with pytest.raises(FileNotFoundError): _validate_path(tmp_path / "missing.xlsx", (".xlsx",)) def test_validate_path_returns_resolved_path(tmp_path): from genesis.parsers.source_aggregator import _validate_path good = tmp_path / "ok.xlsx" good.write_bytes(b"x") result = _validate_path(str(good), (".xlsx",)) assert isinstance(result, Path) assert result.suffix.lower() == ".xlsx" # ---------- Impact Agent MVP:existing_system_path(2026-08-23) ---------- JAVA_PROJECT = { "trade-order/OrderController.java": ( "package com.trade.order.controller;\n" "import org.springframework.web.bind.annotation.*;\n" "@RestController\n" "@RequestMapping(\"/api/order\")\n" "public class OrderController {\n" " @GetMapping(\"/{id}\")\n" " public String get(Long id) { return \"ok\"; }\n" "}\n" ), "trade-order/OrderServiceImpl.java": ( "package com.trade.order.service;\n" "import org.springframework.stereotype.Service;\n" "@Service\n" "public class OrderServiceImpl {\n" " public void createOrder() {}\n" "}\n" ), "trade-order/OrderDO.java": ( "package com.trade.order.entity;\n" "import com.baomidou.mybatisplus.annotation.TableName;\n" "@TableName(\"trade_order\")\n" "public class OrderDO {\n" " private Long id;\n" "}\n" ), } def _java_project(tmp_path) -> str: for rel, content in JAVA_PROJECT.items(): p = tmp_path / "existing" / rel p.parent.mkdir(parents=True, exist_ok=True) p.write_text(content, encoding="utf-8") return str(tmp_path / "existing") def test_parse_with_existing_system_path(tmp_path): xlsx = _xlsx(tmp_path) existing = _java_project(tmp_path) result = SourceParser().parse(requirement_paths=[xlsx], existing_system_path=existing) assert result.existing_system is not None names = {c.class_name for c in result.existing_system.controller_layer} assert "OrderController" in names assert {s.class_name for s in result.existing_system.service_layer} == {"OrderServiceImpl"} assert {e.class_name for e in result.existing_system.entity_layer} == {"OrderDO"} def test_parse_without_existing_system_path_keeps_none(tmp_path): xlsx = _xlsx(tmp_path) result = SourceParser().parse(requirement_paths=[xlsx]) assert result.existing_system is None def test_parse_existing_system_path_invalid_dir_raises(tmp_path): xlsx = _xlsx(tmp_path) with pytest.raises(CodeParseError): SourceParser().parse(requirement_paths=[xlsx], existing_system_path=tmp_path / "nope") def test_parse_existing_system_path_without_java_raises(tmp_path): xlsx = _xlsx(tmp_path) empty = tmp_path / "empty" empty.mkdir() with pytest.raises(CodeParseError): SourceParser().parse(requirement_paths=[xlsx], existing_system_path=empty) # ---------- 多语言预备重构:existing_system_language 透传 ---------- def test_parse_existing_system_language_explicit(tmp_path): xlsx = _xlsx(tmp_path) existing = _java_project(tmp_path) result = SourceParser().parse( requirement_paths=[xlsx], existing_system_path=existing, existing_system_language="java", ) assert result.existing_system is not None assert {c.class_name for c in result.existing_system.controller_layer} == {"OrderController"} def test_parse_existing_system_language_unsupported_raises(tmp_path): xlsx = _xlsx(tmp_path) existing = _java_project(tmp_path) with pytest.raises(CodeParseError): SourceParser().parse( requirement_paths=[xlsx], existing_system_path=existing, existing_system_language="kotlin", )