File size: 6,658 Bytes
5248e3b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | """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"
|