diff --git a/tests/unit/test_analyzers.py b/tests/unit/test_analyzers.py new file mode 100644 index 0000000..0462ddf --- /dev/null +++ b/tests/unit/test_analyzers.py @@ -0,0 +1,174 @@ +import pytest +from pathlib import Path +from src.analyzers.complexity import ComplexityCalculator +from src.analyzers.dependencies import DependencyAnalyzer +from src.graph.builder import GraphBuilder, GraphType, GraphNode, NodeType, GraphEdge +from src.parsers.base import Entity, EntityType + + +class TestComplexityCalculator: + def setup_method(self): + self.calculator = ComplexityCalculator() + + def test_simple_function_complexity(self): + entity = Entity( + name="simple_func", + entity_type=EntityType.FUNCTION, + file_path=Path("/test.py"), + start_line=1, + end_line=5, + code="def simple_func():\n pass", + ) + result = self.calculator.calculate_for_entity(entity) + assert result["complexity_score"] == 1 + assert result["name"] == "simple_func" + + def test_function_with_if_statement(self): + entity = Entity( + name="func_with_if", + entity_type=EntityType.FUNCTION, + file_path=Path("/test.py"), + start_line=1, + end_line=10, + code="def func_with_if(x):\n if x > 0:\n return True\n return False", + ) + result = self.calculator.calculate_for_entity(entity) + assert result["complexity_score"] == 2 + assert result["decision_points"] == 1 + + def test_function_with_multiple_decision_points(self): + entity = Entity( + name="complex_func", + entity_type=EntityType.FUNCTION, + file_path=Path("/test.py"), + start_line=1, + end_line=20, + code="""def complex_func(x): + if x > 0: + if x > 10: + return "big" + else: + return "small" + elif x < 0: + return "negative" + else: + return "zero" +""", + ) + result = self.calculator.calculate_for_entity(entity) + assert result["complexity_score"] >= 4 + + def test_function_with_loop(self): + entity = Entity( + name="func_with_loop", + entity_type=EntityType.FUNCTION, + file_path=Path("/test.py"), + start_line=1, + end_line=10, + code="def func_with_loop(items):\n for item in items:\n print(item)", + ) + result = self.calculator.calculate_for_entity(entity) + assert result["complexity_score"] == 2 + + def test_method_complexity(self): + entity = Entity( + name="method", + entity_type=EntityType.METHOD, + file_path=Path("/test.py"), + start_line=1, + end_line=8, + code="def process(self, data):\n if data:\n for item in data:\n if item.valid:\n pass", + ) + result = self.calculator.calculate_for_entity(entity) + assert result["entity_type"] == "method" + + def test_complexity_threshold(self): + assert self.calculator.complexity_threshold == 10 + + def test_set_complexity_threshold(self): + self.calculator.set_complexity_threshold(15) + assert self.calculator.complexity_threshold == 15 + + def test_is_complex_flag(self): + entity = Entity( + name="simple", + entity_type=EntityType.FUNCTION, + file_path=Path("/test.py"), + start_line=1, + end_line=5, + code="def simple():\n pass", + ) + result = self.calculator.calculate_for_entity(entity) + assert result["is_complex"] is False + + def test_high_complexity_flag(self): + entity = Entity( + name="complex", + entity_type=EntityType.FUNCTION, + file_path=Path("/test.py"), + start_line=1, + end_line=30, + code="def complex():\n if True:\n if True:\n if True:\n if True:\n if True:\n if True:\n if True:\n if True:\n if True:\n if True:\n if True:\n pass", + ) + result = self.calculator.calculate_for_entity(entity) + assert result["is_complex"] is True + + def test_calculate_project_complexity(self): + entities = [ + Entity( + name="func1", + entity_type=EntityType.FUNCTION, + file_path=Path("/test.py"), + start_line=1, + end_line=5, + code="def func1():\n pass", + ), + Entity( + name="func2", + entity_type=EntityType.FUNCTION, + file_path=Path("/test.py"), + start_line=10, + end_line=20, + code="def func2():\n if True:\n pass", + ), + ] + report = self.calculator.calculate_project_complexity(entities) + assert report["total_functions"] == 2 + assert report["total_cyclomatic_complexity"] >= 2 + assert "complexity_distribution" in report + + +class TestDependencyAnalyzer: + def setup_method(self): + self.builder = GraphBuilder(GraphType.DIRECTED) + self.analyzer = DependencyAnalyzer(self.builder) + + def test_analyze_empty_graph(self): + report = self.analyzer.analyze() + assert report.total_files == 0 + assert report.total_functions == 0 + assert report.total_classes == 0 + + def test_count_entities(self): + file_node = GraphNode(node_id="file1", node_type=NodeType.FILE, name="test.py") + func_node = GraphNode(node_id="func1", node_type=NodeType.FUNCTION, name="test_func") + class_node = GraphNode(node_id="class1", node_type=NodeType.CLASS, name="TestClass") + self.builder.add_node(file_node) + self.builder.add_node(func_node) + self.builder.add_node(class_node) + + report = self.analyzer.analyze() + assert report.total_files == 1 + assert report.total_functions == 1 + assert report.total_classes == 1 + + def test_get_architecture_layers(self): + file_node = GraphNode( + node_id="controller", + node_type=NodeType.FILE, + name="controller.py", + file_path=Path("/src/ui/controller.py"), + ) + self.builder.add_node(file_node) + layers = self.analyzer.get_architecture_layers() + assert "presentation" in layers or "other" in layers