"""Tests for analyzer modules.""" from repohealth.analyzers.bus_factor import BusFactorCalculator from repohealth.analyzers.risk_analyzer import RiskAnalyzer from repohealth.models.file_stats import FileAnalysis class TestBusFactorCalculator: """Tests for BusFactorCalculator.""" def setup_method(self): """Set up test fixtures.""" self.calculator = BusFactorCalculator() def test_calculate_gini_equal_distribution(self): """Test Gini coefficient with equal distribution.""" values = [10, 10, 10, 10] gini = self.calculator.calculate_gini(values) assert gini == 0.0 def test_calculate_gini_unequal_distribution(self): """Test Gini coefficient with unequal distribution.""" values = [100, 0, 0, 0] gini = self.calculator.calculate_gini(values) assert gini > 0.5 assert gini <= 1.0 def test_calculate_gini_single_value(self): """Test Gini coefficient with single value.""" values = [100] gini = self.calculator.calculate_gini(values) assert gini == 0.0 def test_calculate_gini_empty_list(self): """Test Gini coefficient with empty list.""" gini = self.calculator.calculate_gini([]) assert gini == 0.0 def test_calculate_file_bus_factor_single_author(self): """Test bus factor with single author.""" analysis = FileAnalysis( path="test.py", total_commits=10, author_commits={"author@example.com": 10} ) bus_factor = self.calculator.calculate_file_bus_factor(analysis) assert bus_factor == 1.0 def test_calculate_file_bus_factor_multiple_authors(self): """Test bus factor with multiple authors.""" analysis = FileAnalysis( path="test.py", total_commits=10, author_commits={"a@x.com": 5, "b@x.com": 5} ) bus_factor = self.calculator.calculate_file_bus_factor(analysis) assert bus_factor > 1.0 def test_calculate_file_bus_factor_no_commits(self): """Test bus factor with no commits.""" analysis = FileAnalysis( path="test.py", total_commits=0, author_commits={} ) bus_factor = self.calculator.calculate_file_bus_factor(analysis) assert bus_factor == 1.0 def test_calculate_repository_bus_factor(self): """Test repository-level bus factor calculation.""" files = [ FileAnalysis( path="file1.py", total_commits=10, author_commits={"a@x.com": 10} ), FileAnalysis( path="file2.py", total_commits=10, author_commits={"a@x.com": 5, "b@x.com": 5} ) ] bus_factor = self.calculator.calculate_repository_bus_factor(files) assert bus_factor > 1.0 def test_assign_risk_levels(self): """Test risk level assignment.""" files = [ FileAnalysis( path="critical.py", total_commits=10, author_commits={"a@x.com": 10} ), FileAnalysis( path="low_risk.py", total_commits=10, author_commits={"a@x.com": 3, "b@x.com": 3, "c@x.com": 4} ) ] assigned = self.calculator.assign_risk_levels(files) assert assigned[0].risk_level == "critical" assert assigned[1].risk_level == "low" def test_calculate_repository_gini(self): """Test repository-wide Gini coefficient.""" files = [ FileAnalysis( path="file1.py", total_commits=10, author_commits={"a@x.com": 10} ), FileAnalysis( path="file2.py", total_commits=10, author_commits={"b@x.com": 10} ) ] gini = self.calculator.calculate_repository_gini(files) assert gini > 0 class TestRiskAnalyzer: """Tests for RiskAnalyzer.""" def setup_method(self): """Set up test fixtures.""" self.analyzer = RiskAnalyzer() def test_identify_hotspots_critical(self): """Test hotspot identification for critical files.""" files = [ FileAnalysis( path="critical.py", total_commits=10, author_commits={"a@x.com": 9, "b@x.com": 1}, bus_factor=1.1 ), FileAnalysis( path="safe.py", total_commits=10, author_commits={"a@x.com": 4, "b@x.com": 6}, bus_factor=2.0 ) ] hotspots = self.analyzer.identify_hotspots(files) assert len(hotspots) >= 1 assert any(h.risk_level == "critical" for h in hotspots) def test_identify_hotspots_limit(self): """Test hotspot limit parameter.""" files = [ FileAnalysis( path=f"file{i}.py", total_commits=10, author_commits={"a@x.com": 9, "b@x.com": 1}, bus_factor=1.1 ) for i in range(25) ] hotspots = self.analyzer.identify_hotspots(files, limit=10) assert len(hotspots) == 10 def test_generate_suggestions(self): """Test diversification suggestions generation.""" files = [ FileAnalysis( path="file1.py", total_commits=10, author_commits={"a@x.com": 9, "b@x.com": 1} ), FileAnalysis( path="file2.py", total_commits=10, author_commits={"a@x.com": 5, "b@x.com": 5} ) ] suggestions = self.analyzer.generate_suggestions(files) assert len(suggestions) > 0 def test_calculate_risk_summary(self): """Test risk summary calculation.""" files = [ FileAnalysis( path="f1.py", total_commits=10, author_commits={"a@x.com": 10}, risk_level="critical" ), FileAnalysis( path="f2.py", total_commits=10, author_commits={"a@x.com": 8, "b@x.com": 2}, risk_level="high" ), FileAnalysis( path="f3.py", total_commits=10, author_commits={"a@x.com": 4, "b@x.com": 6}, risk_level="medium" ) ] summary = self.analyzer.calculate_risk_summary(files) assert summary["critical"] == 1 assert summary["high"] == 1 assert summary["medium"] == 1 assert "overall_risk" in summary def test_calculate_risk_summary_empty(self): """Test risk summary with empty files.""" summary = self.analyzer.calculate_risk_summary([]) assert summary["overall_risk"] == "unknown" def test_analyze_module_risk(self): """Test module-level risk analysis.""" files = [ FileAnalysis( path="core/main.py", total_commits=10, author_commits={"a@x.com": 10}, module="core", risk_level="critical" ), FileAnalysis( path="core/utils.py", total_commits=10, author_commits={"a@x.com": 10}, module="core", risk_level="critical" ), FileAnalysis( path="tests/test.py", total_commits=10, author_commits={"a@x.com": 5, "b@x.com": 5}, module="tests", risk_level="medium" ) ] module_risk = self.analyzer.analyze_module_risk(files) assert "core" in module_risk assert "tests" in module_risk