Files
lhl 916c5beed7 feat(web): 项目级配置 + 会话命名/历史 + 设计文档纳入影响调查
- 会话支持 name/project 字段,上传要件定义后自动命名;前端侧边栏会话历史 + localStorage 恢复,顶部只显示会话名
- 新增 ProjectsStore(SQLite)与 /api/projects CRUD;绑定项目后 _rebuild_source 合并模板/规则/代码库/设计文档,上传区仅要件定义
- StructuredSource.design_docs 与 ImpactReport.design_references;影响调查新增既有设计文档确定性交叉引用(无 LLM)
- 同步更新 docs/design.md §12.7、README、_AI_USAGE_LOG.md;全量测试 558 通过,覆盖率 99.10%
2026-08-27 12:13:28 +08:00

238 lines
7.9 KiB
Python
Raw Permalink 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.
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_design_docs_collected_as_design_category(tmp_path):
xlsx = _xlsx(tmp_path)
design = make_rule_doc(tmp_path, [("H1", "設計書:注文管理の全体方針")])
result = SourceParser().parse(requirement_paths=[xlsx], design_doc_paths=[design])
assert len(result.design_docs) == 1
assert result.design_docs[0].category == "design"
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 helperI8 ----------
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 MVPexisting_system_path2026-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",
)