"""Tests for core.models — make_finding, sort_findings, dedup_findings, constants.""" import pytest from core.models import ( CONFIDENCE_RANK, FORBIDDEN_FILES, SEVERITY_RANK, TOOL_DEFAULT_CONFIDENCE, dedup_findings, make_finding, sort_findings, ) def _f(**kwargs) -> dict: defaults = dict( tool="t", rule="r", severity="WARNING", file="f.py", line=1, message="m", owasp=["A01"], category="security", confidence="likely", remediation="", ) defaults.update(kwargs) return defaults class TestMakeFinding: def test_basic_fields(self): f = make_finding("bandit", "B101", "WARNING", "test.py", 10, "msg", ["A01"]) assert f["tool"] == "bandit" assert f["rule"] == "B101" assert f["severity"] == "WARNING" assert f["file"] == "test.py" assert f["line"] == 10 assert f["message"] == "msg" assert f["owasp"] == ["A01"] assert f["category"] == "security" def test_string_owasp_converted_to_list(self): f = make_finding("t", "r", "INFO", "f.py", 1, "m", "A01") assert isinstance(f["owasp"], list) assert f["owasp"] == ["A01"] def test_default_confidence_from_tool_registry(self): f = make_finding("pip-audit", "CVE-123", "ERROR", "req.txt", 0, "m", []) assert f["confidence"] == "confirmed" def test_default_confidence_gitleaks(self): f = make_finding("gitleaks", "aws-token", "ERROR", "f.py", 1, "m", []) assert f["confidence"] == "confirmed" def test_default_confidence_unknown_tool_is_possible(self): f = make_finding("unknown-xyz", "r", "INFO", "f.py", 1, "m", []) assert f["confidence"] == "possible" def test_explicit_confidence_overrides_default(self): f = make_finding("pip-audit", "r", "INFO", "f.py", 1, "m", [], confidence="likely") assert f["confidence"] == "likely" def test_remediation_from_registry_for_known_rule(self): # A rule that exists in REMEDIATION should return a non-empty string from report.remediation import REMEDIATION if REMEDIATION: known = next(iter(REMEDIATION)) f = make_finding("t", known, "INFO", "f.py", 1, "m", []) assert f["remediation"] == REMEDIATION[known] def test_remediation_empty_for_unknown_rule(self): f = make_finding("t", "nonexistent-rule-xyz-999", "INFO", "f.py", 1, "m", []) assert f["remediation"] == "" def test_explicit_remediation_overrides_registry(self): f = make_finding("t", "r", "INFO", "f.py", 1, "m", [], remediation="fix it now") assert f["remediation"] == "fix it now" def test_category_default_security(self): f = make_finding("t", "r", "INFO", "f.py", 1, "m", []) assert f["category"] == "security" def test_custom_category(self): f = make_finding("t", "r", "INFO", "f.py", 1, "m", [], category="performance") assert f["category"] == "performance" class TestSortFindings: def test_sorts_error_before_warning_before_info(self): f1 = _f(severity="INFO") f2 = _f(severity="ERROR") f3 = _f(severity="WARNING") result = sort_findings([f1, f2, f3]) assert [r["severity"] for r in result] == ["ERROR", "WARNING", "INFO"] def test_sorts_confirmed_before_likely_before_possible(self): f1 = _f(severity="ERROR", confidence="possible") f2 = _f(severity="ERROR", confidence="confirmed") f3 = _f(severity="ERROR", confidence="likely") result = sort_findings([f1, f2, f3]) assert [r["confidence"] for r in result] == ["confirmed", "likely", "possible"] def test_sorts_by_file_within_same_severity(self): f1 = _f(severity="ERROR", confidence="likely", file="z.py", line=1) f2 = _f(severity="ERROR", confidence="likely", file="a.py", line=1) result = sort_findings([f1, f2]) assert result[0]["file"] == "a.py" def test_sorts_by_line_within_same_file(self): f1 = _f(file="a.py", line=20) f2 = _f(file="a.py", line=5) result = sort_findings([f1, f2]) assert result[0]["line"] == 5 def test_empty_list(self): assert sort_findings([]) == [] def test_single_item(self): f = _f() assert sort_findings([f]) == [f] class TestDedupFindings: def test_removes_exact_duplicate(self): f = _f() result = dedup_findings([f, f.copy()]) assert len(result) == 1 def test_keeps_different_lines(self): f1 = _f(line=1) f2 = _f(line=2) assert len(dedup_findings([f1, f2])) == 2 def test_keeps_different_tools(self): f1 = _f(tool="bandit", line=1) f2 = _f(tool="semgrep", line=1) assert len(dedup_findings([f1, f2])) == 2 def test_keeps_different_files(self): f1 = _f(file="a.py") f2 = _f(file="b.py") assert len(dedup_findings([f1, f2])) == 2 def test_dedup_preserves_order(self): f1 = _f(line=1) f2 = _f(line=2) f3 = _f(line=1) # duplicate of f1 result = dedup_findings([f1, f2, f3]) assert len(result) == 2 assert result[0]["line"] == 1 assert result[1]["line"] == 2 def test_empty_input(self): assert dedup_findings([]) == [] class TestConstants: def test_severity_rank_ordering(self): assert SEVERITY_RANK["ERROR"] < SEVERITY_RANK["WARNING"] assert SEVERITY_RANK["WARNING"] < SEVERITY_RANK["INFO"] assert SEVERITY_RANK["HIGH"] == SEVERITY_RANK["ERROR"] assert SEVERITY_RANK["MEDIUM"] == SEVERITY_RANK["WARNING"] assert SEVERITY_RANK["LOW"] == SEVERITY_RANK["INFO"] def test_confidence_rank_ordering(self): assert CONFIDENCE_RANK["confirmed"] < CONFIDENCE_RANK["likely"] assert CONFIDENCE_RANK["likely"] < CONFIDENCE_RANK["possible"] def test_forbidden_files_contains_critical(self): assert ".env" in FORBIDDEN_FILES assert "id_rsa" in FORBIDDEN_FILES assert "credentials.json" in FORBIDDEN_FILES def test_tool_defaults_confirmed(self): assert TOOL_DEFAULT_CONFIDENCE["pip-audit"] == "confirmed" assert TOOL_DEFAULT_CONFIDENCE["gitleaks"] == "confirmed" assert TOOL_DEFAULT_CONFIDENCE["forbidden-file"] == "confirmed" def test_tool_defaults_possible(self): assert TOOL_DEFAULT_CONFIDENCE["detect-secrets"] == "possible"