|
| 1 | +"""Unit tests for gui_anything.analyzer — Phase 1.""" |
| 2 | + |
| 3 | +from pathlib import Path |
| 4 | +from gui_anything.analyzer import CodebaseAnalyzer, AnalysisResult |
| 5 | +from gui_anything.pipeline import PipelineConfig |
| 6 | + |
| 7 | + |
| 8 | +CALC_DIR = Path(__file__).resolve().parent.parent / "examples" / "calculator" |
| 9 | + |
| 10 | + |
| 11 | +def _make_config(source: str = str(CALC_DIR)) -> PipelineConfig: |
| 12 | + return PipelineConfig(source=source) |
| 13 | + |
| 14 | + |
| 15 | +class TestCodebaseAnalyzer: |
| 16 | + """Tests for the CodebaseAnalyzer on the example calculator.""" |
| 17 | + |
| 18 | + def test_detects_python_language(self) -> None: |
| 19 | + analyzer = CodebaseAnalyzer(_make_config()) |
| 20 | + lang = analyzer._detect_language() |
| 21 | + assert lang == "python" |
| 22 | + |
| 23 | + def test_collect_source_files(self) -> None: |
| 24 | + analyzer = CodebaseAnalyzer(_make_config()) |
| 25 | + files = analyzer._collect_source_files("python") |
| 26 | + assert len(files) >= 1 |
| 27 | + assert any("calculator.py" in str(f) for f in files) |
| 28 | + |
| 29 | + def test_analyze_extracts_functions(self) -> None: |
| 30 | + result = CodebaseAnalyzer(_make_config()).analyze() |
| 31 | + names = [f.name for f in result.functions] |
| 32 | + assert "add" in names |
| 33 | + assert "subtract" in names |
| 34 | + assert "divide" in names |
| 35 | + |
| 36 | + def test_no_dunder_methods_extracted(self) -> None: |
| 37 | + """__init__ and __post_init__ should be filtered out.""" |
| 38 | + result = CodebaseAnalyzer(_make_config()).analyze() |
| 39 | + names = [f.name for f in result.functions] |
| 40 | + assert "__init__" not in names |
| 41 | + assert "__post_init__" not in names |
| 42 | + |
| 43 | + def test_no_duplicate_methods(self) -> None: |
| 44 | + """Class methods should NOT be double-counted as top-level functions.""" |
| 45 | + result = CodebaseAnalyzer(_make_config()).analyze() |
| 46 | + # The calculator has only top-level functions, no class methods |
| 47 | + # to appear as top-level. Verify no duplicates by name+file. |
| 48 | + seen = set() |
| 49 | + for f in result.functions: |
| 50 | + key = (f.name, f.file_path) |
| 51 | + assert key not in seen, f"Duplicate function: {key}" |
| 52 | + seen.add(key) |
| 53 | + |
| 54 | + def test_data_model_detected(self) -> None: |
| 55 | + result = CodebaseAnalyzer(_make_config()).analyze() |
| 56 | + model_names = [m.name for m in result.data_models] |
| 57 | + assert "CalculatorState" in model_names |
| 58 | + |
| 59 | + def test_function_parameters_parsed(self) -> None: |
| 60 | + result = CodebaseAnalyzer(_make_config()).analyze() |
| 61 | + add_fn = next(f for f in result.functions if f.name == "add") |
| 62 | + assert len(add_fn.parameters) == 2 |
| 63 | + assert add_fn.parameters[0].name == "a" |
| 64 | + assert add_fn.parameters[0].type_hint == "float" |
| 65 | + |
| 66 | + def test_function_defaults_detected(self) -> None: |
| 67 | + result = CodebaseAnalyzer(_make_config()).analyze() |
| 68 | + export_fn = next(f for f in result.functions if f.name == "export_history") |
| 69 | + fmt_param = next(p for p in export_fn.parameters if p.name == "format") |
| 70 | + assert fmt_param.required is False |
| 71 | + assert fmt_param.default == "'txt'" |
| 72 | + |
| 73 | + def test_docstrings_extracted(self) -> None: |
| 74 | + result = CodebaseAnalyzer(_make_config()).analyze() |
| 75 | + add_fn = next(f for f in result.functions if f.name == "add") |
| 76 | + assert add_fn.docstring is not None |
| 77 | + assert "Add" in add_fn.docstring |
| 78 | + |
| 79 | + def test_categorization(self) -> None: |
| 80 | + result = CodebaseAnalyzer(_make_config()).analyze() |
| 81 | + add_fn = next(f for f in result.functions if f.name == "add") |
| 82 | + assert add_fn.category == "create" |
| 83 | + get_fn = next(f for f in result.functions if f.name == "get_history") |
| 84 | + assert get_fn.category == "read" |
| 85 | + |
| 86 | + def test_to_dict_serializable(self) -> None: |
| 87 | + result = CodebaseAnalyzer(_make_config()).analyze() |
| 88 | + d = result.to_dict() |
| 89 | + assert "functions" in d |
| 90 | + assert "classes" in d |
| 91 | + assert "data_models" in d |
| 92 | + import json |
| 93 | + json.dumps(d) # Should not raise |
| 94 | + |
| 95 | + def test_unknown_language(self, tmp_path: Path) -> None: |
| 96 | + """An empty directory should return 'unknown' language.""" |
| 97 | + config = _make_config(str(tmp_path)) |
| 98 | + analyzer = CodebaseAnalyzer(config) |
| 99 | + assert analyzer._detect_language() == "unknown" |
0 commit comments