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"