"""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)