diff --git a/tests/test_confidence.py b/tests/test_confidence.py new file mode 100644 index 0000000..8bb8398 --- /dev/null +++ b/tests/test_confidence.py @@ -0,0 +1,287 @@ +"""Tests for confidence scoring module.""" + +import pytest + +from src.core.models import ScanResult, Issue, IssueCategory, SeverityLevel +from src.reporting.confidence import ConfidenceScorer + + +class TestConfidenceScorer: + """Tests for ConfidenceScorer.""" + + def test_calculate_perfect_score(self, clean_python_code): + """Test that clean code gets a high score.""" + scorer = ConfidenceScorer() + result = ScanResult(files_scanned=1, target_path="/test") + score = scorer.calculate(result) + assert score == 100 + + def test_calculate_with_issues(self, mock_scan_result): + """Test score calculation with issues.""" + scorer = ConfidenceScorer() + score = scorer.calculate(mock_scan_result) + assert score < 100 + assert score >= 0 + + def test_security_issues_reduce_score_more(self): + """Test that security issues reduce score more than other issues.""" + scorer = ConfidenceScorer() + + result_security = ScanResult(files_scanned=1, target_path="/test") + result_security.add_issue(Issue( + severity=SeverityLevel.HIGH, + category=IssueCategory.SECURITY, + file_path="/test.py", + line_number=1, + message="Security issue", + scanner_name="test", + )) + + result_quality = ScanResult(files_scanned=1, target_path="/test") + result_quality.add_issue(Issue( + severity=SeverityLevel.HIGH, + category=IssueCategory.CODE_QUALITY, + file_path="/test.py", + line_number=1, + message="Quality issue", + scanner_name="test", + )) + + security_score = scorer.calculate(result_security) + quality_score = scorer.calculate(result_quality) + + assert security_score < quality_score + + def test_critical_issues_reduce_score_more(self): + """Test that critical issues reduce score more.""" + scorer = ConfidenceScorer() + + result_critical = ScanResult(files_scanned=1, target_path="/test") + result_critical.add_issue(Issue( + severity=SeverityLevel.CRITICAL, + category=IssueCategory.SECURITY, + file_path="/test.py", + line_number=1, + message="Critical issue", + scanner_name="test", + )) + + result_high = ScanResult(files_scanned=1, target_path="/test") + result_high.add_issue(Issue( + severity=SeverityLevel.HIGH, + category=IssueCategory.SECURITY, + file_path="/test.py", + line_number=1, + message="High issue", + scanner_name="test", + )) + + critical_score = scorer.calculate(result_critical) + high_score = scorer.calculate(result_high) + + assert critical_score < high_score + + def test_get_score_breakdown(self, mock_scan_result): + """Test score breakdown generation.""" + scorer = ConfidenceScorer() + breakdown = scorer.get_score_breakdown(mock_scan_result) + + assert "base_score" in breakdown + assert "total_deductions" in breakdown + assert "final_score" in breakdown + assert "issues_by_category" in breakdown + assert "issues_by_severity" in breakdown + + def test_get_score_grade(self): + """Test score grade calculation.""" + scorer = ConfidenceScorer() + + assert scorer.get_score_grade(95) == "A+" + assert scorer.get_score_grade(90) == "A" + assert scorer.get_score_grade(85) == "A-" + assert scorer.get_score_grade(80) == "B+" + assert scorer.get_score_grade(75) == "B" + assert scorer.get_score_grade(70) == "B-" + assert scorer.get_score_grade(65) == "C+" + assert scorer.get_score_grade(60) == "C" + assert scorer.get_score_grade(55) == "C-" + assert scorer.get_score_grade(50) == "D+" + assert scorer.get_score_grade(45) == "D" + assert scorer.get_score_grade(40) == "D-" + assert scorer.get_score_grade(30) == "F" + + def test_get_score_description(self): + """Test score description generation.""" + scorer = ConfidenceScorer() + + desc_90 = scorer.get_score_description(90) + assert "Excellent" in desc_90 + + desc_75 = scorer.get_score_description(75) + assert "Good" in desc_75 + + desc_50 = scorer.get_score_description(50) + assert "Poor" in desc_50 + + desc_25 = scorer.get_score_description(25) + assert "Critical" in desc_25 + + def test_empty_result_gets_100(self): + """Test that empty scan result gets 100 score.""" + scorer = ConfidenceScorer() + result = ScanResult(files_scanned=0, target_path="/test") + score = scorer.calculate(result) + assert score == 100 + + def test_score_never_negative(self): + """Test that score never goes below 0.""" + scorer = ConfidenceScorer() + + result = ScanResult(files_scanned=1, target_path="/test") + for _ in range(100): + result.add_issue(Issue( + severity=SeverityLevel.CRITICAL, + category=IssueCategory.SECURITY, + file_path="/test.py", + line_number=1, + message="Critical issue", + scanner_name="test", + )) + + score = scorer.calculate(result) + assert score >= 0 + + def test_score_never_exceeds_100(self): + """Test that score never goes above 100.""" + scorer = ConfidenceScorer() + result = ScanResult(files_scanned=10, target_path="/test") + score = scorer.calculate(result) + assert score <= 100 + + +class TestIssueModel: + """Tests for Issue data model.""" + + def test_issue_to_dict(self): + """Test issue serialization to dictionary.""" + issue = Issue( + severity=SeverityLevel.HIGH, + category=IssueCategory.SECURITY, + file_path="/test.py", + line_number=10, + message="Test issue", + suggestion="Fix this", + scanner_name="test", + ) + + data = issue.to_dict() + + assert data["severity"] == "high" + assert data["category"] == "security" + assert data["file_path"] == "/test.py" + assert data["line_number"] == 10 + assert data["message"] == "Test issue" + assert data["suggestion"] == "Fix this" + assert data["scanner_name"] == "test" + + +class TestScanResultModel: + """Tests for ScanResult data model.""" + + def test_add_issue(self): + """Test adding issues to scan result.""" + result = ScanResult(files_scanned=1, target_path="/test") + issue = Issue( + severity=SeverityLevel.LOW, + category=IssueCategory.STYLE, + file_path="/test.py", + line_number=1, + message="Style issue", + scanner_name="test", + ) + + result.add_issue(issue) + + assert len(result.issues) == 1 + assert result.issues[0] == issue + + def test_add_warning(self): + """Test adding warnings to scan result.""" + result = ScanResult(files_scanned=1, target_path="/test") + result.add_warning("Test warning") + + assert len(result.warnings) == 1 + assert result.warnings[0] == "Test warning" + + def test_filter_by_severity(self): + """Test filtering issues by severity.""" + result = ScanResult(files_scanned=1, target_path="/test") + result.add_issue(Issue( + severity=SeverityLevel.LOW, + category=IssueCategory.STYLE, + file_path="/test.py", + line_number=1, + message="Low issue", + scanner_name="test", + )) + result.add_issue(Issue( + severity=SeverityLevel.CRITICAL, + category=IssueCategory.SECURITY, + file_path="/test.py", + line_number=2, + message="Critical issue", + scanner_name="test", + )) + + filtered = result.filter_by_severity(SeverityLevel.HIGH) + + assert len(filtered.issues) == 1 + assert filtered.issues[0].severity == SeverityLevel.CRITICAL + + def test_get_summary(self): + """Test getting scan summary.""" + result = ScanResult(files_scanned=2, target_path="/test") + result.add_issue(Issue( + severity=SeverityLevel.HIGH, + category=IssueCategory.SECURITY, + file_path="/test.py", + line_number=1, + message="Issue 1", + scanner_name="test", + )) + result.add_issue(Issue( + severity=SeverityLevel.LOW, + category=IssueCategory.STYLE, + file_path="/test.py", + line_number=2, + message="Issue 2", + scanner_name="test", + )) + + summary = result.get_summary() + + assert summary["files_scanned"] == 2 + assert summary["total_issues"] == 2 + assert "high" in summary["issues_by_severity"] + assert "low" in summary["issues_by_severity"] + assert "security" in summary["issues_by_category"] + assert "style" in summary["issues_by_category"] + + def test_to_dict(self): + """Test scan result serialization to dictionary.""" + result = ScanResult(files_scanned=1, target_path="/test") + result.add_issue(Issue( + severity=SeverityLevel.MEDIUM, + category=IssueCategory.CODE_QUALITY, + file_path="/test.py", + line_number=5, + message="Test issue", + scanner_name="test", + )) + + data = result.to_dict() + + assert data["files_scanned"] == 1 + assert data["target_path"] == "/test" + assert len(data["issues"]) == 1 + assert data["issues"][0]["severity"] == "medium"