按《参赛成果物提交规范·赛道一》§6 红线: - samples/ 目录改名 sample/(git mv,保留历史) - 10 个中日文样本文件 + docs 参赛手册 PDF 重命名为 ASCII (requirements_*/template_*/rules_*/contestant-handbook.pdf) - tests/test_zh_template.py 硬编码绝对路径 D:\00_project\Genesis 改为相对路径 - 全局更新 21 个活动文件引用;历史日志/审查文档不改(追加说明记录) 全量 pytest 431 passed / 99.15%
238 lines
8.3 KiB
Python
238 lines
8.3 KiB
Python
"""CodeParser 测试(Impact Agent MVP):多语言适配器注册表 + Java 项目解析。"""
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from genesis.data_models import ControllerInfo, EndpointInfo
|
|
from genesis.impact.code_parser import (
|
|
LANGUAGE_PARSERS,
|
|
BaseLanguageParser,
|
|
CodeParseError,
|
|
CodeParser,
|
|
JavaLanguageParser,
|
|
register_language_parser,
|
|
)
|
|
|
|
FIXTURE_JAVA = {
|
|
"trade-order/src/main/java/com/trade/order/controller/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 getOrder(@PathVariable Long id) { return \"ok\"; }\n"
|
|
" @PostMapping(\"/create\")\n"
|
|
" public String createOrder() { return \"ok\"; }\n"
|
|
"}\n"
|
|
),
|
|
"trade-order/src/main/java/com/trade/order/service/OrderQueryService.java": (
|
|
"package com.trade.order.service;\n"
|
|
"import org.springframework.stereotype.Service;\n"
|
|
"@Service\n"
|
|
"public class OrderQueryService {\n"
|
|
" public void query() {}\n"
|
|
" public void update() {}\n"
|
|
"}\n"
|
|
),
|
|
"trade-order/src/main/java/com/trade/order/entity/OrderEntity.java": (
|
|
"package com.trade.order.entity;\n"
|
|
"import javax.persistence.Entity;\n"
|
|
"import javax.persistence.Table;\n"
|
|
"@Entity\n"
|
|
"@Table(name = \"t_order\")\n"
|
|
"public class OrderEntity {\n"
|
|
" private Long id;\n"
|
|
" private String orderNo;\n"
|
|
"}\n"
|
|
),
|
|
}
|
|
|
|
|
|
def _make_project(tmp_path, files: dict[str, str]) -> Path:
|
|
for rel, content in files.items():
|
|
p = tmp_path / rel
|
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
p.write_text(content, encoding="utf-8")
|
|
return tmp_path
|
|
|
|
|
|
def test_parse_controller_detected(tmp_path):
|
|
root = _make_project(tmp_path, FIXTURE_JAVA)
|
|
result = CodeParser().parse(root)
|
|
ctrls = {c.class_name: c for c in result.controllers}
|
|
oc = ctrls["OrderController"]
|
|
assert oc.base_path == "/api/order"
|
|
assert "/api/order/{id}" in oc.endpoints
|
|
assert "/api/order/create" in oc.endpoints
|
|
assert oc.source_uri.endswith("OrderController.java")
|
|
|
|
|
|
def test_parse_service_detected(tmp_path):
|
|
root = _make_project(tmp_path, FIXTURE_JAVA)
|
|
result = CodeParser().parse(root)
|
|
svcs = {s.class_name: s for s in result.services}
|
|
oq = svcs["OrderQueryService"]
|
|
assert "query" in oq.methods
|
|
assert "update" in oq.methods
|
|
|
|
|
|
def test_parse_entity_detected(tmp_path):
|
|
root = _make_project(tmp_path, FIXTURE_JAVA)
|
|
result = CodeParser().parse(root)
|
|
ents = {e.class_name: e for e in result.entities}
|
|
oe = ents["OrderEntity"]
|
|
assert oe.table_name == "t_order"
|
|
assert "id" in oe.fields
|
|
assert "orderNo" in oe.fields
|
|
|
|
|
|
def test_parse_endpoints_detected(tmp_path):
|
|
root = _make_project(tmp_path, FIXTURE_JAVA)
|
|
result = CodeParser().parse(root)
|
|
paths = {(e.method, e.path) for e in result.endpoints}
|
|
assert ("GET", "/api/order/{id}") in paths
|
|
assert ("POST", "/api/order/create") in paths
|
|
assert all(e.controller == "OrderController" for e in result.endpoints)
|
|
|
|
|
|
def test_parse_modules_top_level_dirs(tmp_path):
|
|
root = _make_project(tmp_path, FIXTURE_JAVA)
|
|
result = CodeParser().parse(root)
|
|
assert "trade-order" in result.modules
|
|
assert result.language == "java"
|
|
|
|
|
|
def test_parse_no_java_raises(tmp_path):
|
|
(tmp_path / "readme.txt").write_text("hello", encoding="utf-8")
|
|
with pytest.raises(CodeParseError):
|
|
CodeParser().parse(tmp_path)
|
|
|
|
|
|
def test_parse_missing_root_raises(tmp_path):
|
|
with pytest.raises(CodeParseError):
|
|
CodeParser().parse(tmp_path / "nope")
|
|
|
|
|
|
def test_parse_real_sample_has_order_controller():
|
|
root = Path(__file__).resolve().parents[1] / "sample" / "existing-system"
|
|
if not root.exists():
|
|
pytest.skip("样本缺失")
|
|
result = CodeParser().parse(root)
|
|
names = {c.class_name for c in result.controllers}
|
|
assert "OrderController" in names
|
|
assert any(c.base_path for c in result.controllers)
|
|
|
|
|
|
def test_parse_real_sample_has_entities_and_endpoints():
|
|
root = Path(__file__).resolve().parents[1] / "sample" / "existing-system"
|
|
if not root.exists():
|
|
pytest.skip("样本缺失")
|
|
result = CodeParser().parse(root)
|
|
assert result.entities, "既有系统样本应至少有一个实体类"
|
|
assert result.endpoints, "既有系统样本应至少有一个端点"
|
|
assert result.services, "既有系统样本应至少有一个 @Service 类"
|
|
|
|
|
|
# ---------- 多语言适配器注册表(预备性重构) ----------
|
|
|
|
class _FakeParser(BaseLanguageParser):
|
|
"""测试用假语言适配器:验证注册表/分发/多语言合并。"""
|
|
|
|
language = "fake"
|
|
extensions = (".fx",)
|
|
|
|
def parse_file(self, text: str, rel: str) -> dict:
|
|
return {
|
|
"imports": ["fake.import"],
|
|
"classes": [{"class_name": "FakeThing", "path": rel}],
|
|
"controllers": [
|
|
ControllerInfo(
|
|
name="FakeThing", class_name="FakeThing", path=rel,
|
|
base_path="/fake", endpoints=["/fake"], source_uri=rel,
|
|
)
|
|
],
|
|
"services": [],
|
|
"entities": [],
|
|
"endpoints": [
|
|
EndpointInfo(
|
|
method="GET", path="/fake", controller="FakeThing",
|
|
description="", source_uri=rel,
|
|
)
|
|
],
|
|
}
|
|
|
|
|
|
def test_language_registry_contains_java():
|
|
assert "java" in LANGUAGE_PARSERS
|
|
assert issubclass(LANGUAGE_PARSERS["java"], BaseLanguageParser)
|
|
|
|
|
|
def test_java_parser_adapter_metadata():
|
|
jp = JavaLanguageParser()
|
|
assert jp.language == "java"
|
|
assert ".java" in jp.extensions
|
|
|
|
|
|
def test_base_parser_parse_file_contract():
|
|
with pytest.raises(NotImplementedError):
|
|
BaseLanguageParser().parse_file("text", "rel")
|
|
|
|
|
|
def test_parse_explicit_language(tmp_path):
|
|
root = _make_project(tmp_path, FIXTURE_JAVA)
|
|
result = CodeParser().parse(root, language="java")
|
|
assert {c.class_name for c in result.controllers} == {"OrderController"}
|
|
assert result.language == "java"
|
|
|
|
|
|
def test_parse_unsupported_language_raises(tmp_path):
|
|
root = _make_project(tmp_path, FIXTURE_JAVA)
|
|
with pytest.raises(CodeParseError):
|
|
CodeParser().parse(root, language="kotlin")
|
|
|
|
|
|
def test_parse_explicit_language_no_files_raises(tmp_path):
|
|
(tmp_path / "readme.txt").write_text("hello", encoding="utf-8")
|
|
with pytest.raises(CodeParseError):
|
|
CodeParser().parse(tmp_path, language="java")
|
|
|
|
|
|
def test_java_parse_file_interface(tmp_path):
|
|
jp = JavaLanguageParser()
|
|
text = FIXTURE_JAVA["trade-order/src/main/java/com/trade/order/controller/OrderController.java"]
|
|
out = jp.parse_file(text, "OrderController.java")
|
|
assert len(out["controllers"]) == 1
|
|
assert out["controllers"][0].class_name == "OrderController"
|
|
assert {"/api/order/{id}", "/api/order/create"} <= {e.path for e in out["endpoints"]}
|
|
assert out["classes"] == [{"class_name": "OrderController", "path": "OrderController.java"}]
|
|
|
|
|
|
def test_register_and_dispatch_new_language(tmp_path):
|
|
register_language_parser("fake", _FakeParser)
|
|
try:
|
|
root = _make_project(tmp_path, {
|
|
"app/main.fx": "FAKE",
|
|
"trade-order/src/main/java/com/trade/order/controller/OrderController.java": (
|
|
FIXTURE_JAVA["trade-order/src/main/java/com/trade/order/controller/OrderController.java"]
|
|
),
|
|
})
|
|
result = CodeParser().parse(root)
|
|
names = {c.class_name for c in result.controllers}
|
|
assert names == {"OrderController", "FakeThing"}
|
|
assert result.language == "fake,java" # 多语言按名排序逗号连接
|
|
assert any(e.path == "/fake" for e in result.endpoints)
|
|
finally:
|
|
LANGUAGE_PARSERS.pop("fake", None)
|
|
|
|
|
|
def test_register_language_explicit_only(tmp_path):
|
|
register_language_parser("fake", _FakeParser)
|
|
try:
|
|
root = _make_project(tmp_path, {"a/main.fx": "FAKE"})
|
|
result = CodeParser().parse(root, language="fake")
|
|
assert result.language == "fake"
|
|
assert [c.class_name for c in result.controllers] == ["FakeThing"]
|
|
finally:
|
|
LANGUAGE_PARSERS.pop("fake", None)
|