"""tests/test_codegraph.py — Unit tests for CodeGraph V2.""" import sys, os sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) import pytest from codegraph.graph import CodeGraph, _naming_style from codegraph.extractor import extract_metadata class TestNamingStyle: def test_snake_case(self): assert _naming_style("get_user") == "snake_case" assert _naming_style("handle_path") == "snake_case" def test_camel_case(self): assert _naming_style("getUser") == "camelCase" assert _naming_style("handlePath") == "camelCase" def test_pascal_case(self): assert _naming_style("GetUser") == "PascalCase" assert _naming_style("UserManager") == "PascalCase" def test_all_lowercase(self): assert _naming_style("foo") == "snake_case" class TestCodeGraph: def test_empty_graph(self): g = CodeGraph(episode_seed=1) assert g.components == {} assert g.conventions == {} def test_update_adds_component(self): g = CodeGraph(episode_seed=1) meta = extract_metadata( "def get_user(uid: int) -> dict:\n \"\"\"Get user.\"\"\"\n return {}", "users.py", 0 ) g.update("users.py", meta) assert "users" in g.components def test_syntax_error_not_added(self): g = CodeGraph(episode_seed=1) bad_meta = {"status": "syntax_error", "functions": [], "imports": []} g.update("bad.py", bad_meta) assert len(g.components) == 0 def test_conventions_inferred_after_update(self): g = CodeGraph(episode_seed=1) meta = extract_metadata( "def snake_one(x: int) -> str:\n \"\"\"Doc.\"\"\"\n return str(x)\n" "def snake_two(y: int) -> str:\n \"\"\"Doc.\"\"\"\n return str(y)", "module.py", 0 ) g.update("module.py", meta) assert g.conventions.get("naming") in ("snake_case", "camelCase", "PascalCase", "mixed", "unknown") def test_mixed_style_detected(self): g = CodeGraph(episode_seed=1) # Create artificial metadata with exactly 50/50 split meta = { "status": "ok", "functions": [ {"name": "get_user"}, # snake_case {"name": "getUser"}, # camelCase {"name": "set_value"}, # snake_case {"name": "getValue"}, # camelCase ], "imports": [], "conventions": {}, "language": "py", "created_at_step": 0, } g.update("mixed.py", meta) # 50/50 split — below 60% threshold → should be "mixed" assert g.conventions.get("naming") == "mixed" def test_slim_dict_under_limit(self): g = CodeGraph(episode_seed=1) for i in range(10): meta = extract_metadata( f"def func_{i}(x: int) -> str:\n return str(x)", f"module_{i}.py", i ) g.update(f"module_{i}.py", meta) slim = g.to_slim_dict(limit=6000) assert len(slim) <= 6000 class TestExtractor: def test_extracts_functions(self): code = "def hello(x: int) -> str:\n return str(x)" meta = extract_metadata(code, "test.py", 0) assert meta["status"] == "ok" assert any(f["name"] == "hello" for f in meta["functions"]) def test_extracts_imports(self): code = "import os\nfrom pathlib import Path\ndef foo(): pass" meta = extract_metadata(code, "test.py", 0) assert meta["status"] == "ok" assert len(meta["imports"]) >= 1 def test_syntax_error_returns_structured(self): code = "def broken(:\n pass" meta = extract_metadata(code, "bad.py", 0) assert meta["status"] == "syntax_error" assert "line" in meta assert "feedback" in meta def test_conventions_detected(self): code = "try:\n pass\nexcept ValueError:\n pass\ndef f(x: int) -> str:\n return str(x)" meta = extract_metadata(code, "test.py", 0) assert meta["conventions"]["uses_try_catch"] is True assert meta["conventions"]["uses_type_hints"] is True def test_no_print_detected(self): code = "def f():\n print('hello')" meta = extract_metadata(code, "test.py", 0) assert meta["conventions"]["no_print_stmts"] is False def test_language_set_correctly(self): meta_py = extract_metadata("def f(): pass", "module.py", 0) assert meta_py["language"] == "py" if __name__ == "__main__": pytest.main([__file__, "-v"])