| """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):
|
|
|
| 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)
|
| 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"
|
|
|