autoscan / tests /test_cve_data_schema.py
Chris4K's picture
Upload 384 files
a2a5bfd verified
Raw
History Blame Contribute Delete
6.69 kB
"""Tests for scanners/cve_data_schema.py — CVEEntry dataclass + load/save I/O."""
import json
from pathlib import Path
import pytest
from scanners.cve_data_schema import CVEEntry, load_cve_data, save_cve_data
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
def _sample_entry(**overrides) -> CVEEntry:
base = dict(
cve_id="TEST-2026-001",
package="torch",
title="Test RCE via torch.load",
triggers=["torch.load("],
safe_variants=["torch.load(f, weights_only=True)"],
severity="ERROR",
confidence="confirmed",
owasp="A06:2021-Vulnerable_and_Outdated_Components",
category="ml-security",
remediation="Upgrade torch >= 2.6",
affected_versions="<2.6.0",
fix_version="2.6.0",
references=["https://osv.dev/TEST-2026-001"],
)
base.update(overrides)
return CVEEntry(**base)
# ---------------------------------------------------------------------------
# CVEEntry dataclass
# ---------------------------------------------------------------------------
class TestCVEEntryDataclass:
def test_all_fields_accessible(self):
e = _sample_entry()
assert e.cve_id == "TEST-2026-001"
assert e.package == "torch"
assert e.triggers == ["torch.load("]
assert e.severity == "ERROR"
assert e.confidence == "confirmed"
def test_references_defaults_to_empty_list(self):
e = CVEEntry(
cve_id="X", package="p", title="t", triggers=[], safe_variants=[],
severity="ERROR", confidence="confirmed", owasp="A06", category="security",
remediation="fix", affected_versions="*", fix_version="N/A",
)
assert e.references == []
def test_triggers_is_mutable_list(self):
e = _sample_entry()
e.triggers.append("torch.jit.load(")
assert len(e.triggers) == 2
# ---------------------------------------------------------------------------
# load_cve_data
# ---------------------------------------------------------------------------
class TestLoadCVEData:
def test_loads_real_data_file(self):
entries = load_cve_data()
assert len(entries) >= 22
for e in entries:
assert isinstance(e, CVEEntry)
assert e.cve_id
assert e.package
assert e.severity in ("ERROR", "WARNING", "INFO")
assert e.confidence in ("confirmed", "likely", "possible")
def test_returns_empty_for_missing_file(self, tmp_path):
result = load_cve_data(tmp_path / "nonexistent.json")
assert result == []
def test_loads_from_custom_path(self, tmp_path):
p = tmp_path / "cve.json"
data = {
"version": "1.0",
"updated": "2026-01-01T00:00:00Z",
"entries": [
{
"cve_id": "CUSTOM-001",
"package": "numpy",
"title": "Custom test",
"triggers": ["np.load("],
"safe_variants": [],
"severity": "WARNING",
"confidence": "likely",
"owasp": "A06",
"category": "security",
"remediation": "fix",
"affected_versions": "<2.0",
"fix_version": "2.0",
"references": [],
}
],
}
p.write_text(json.dumps(data), encoding="utf-8")
entries = load_cve_data(p)
assert len(entries) == 1
assert entries[0].cve_id == "CUSTOM-001"
assert entries[0].package == "numpy"
def test_ignores_empty_entries_list(self, tmp_path):
p = tmp_path / "empty.json"
p.write_text(json.dumps({"version": "1.0", "updated": "2026-01-01T00:00:00Z", "entries": []}))
assert load_cve_data(p) == []
def test_all_real_entries_have_triggers_list(self):
for e in load_cve_data():
assert isinstance(e.triggers, list)
def test_all_real_entries_have_references_list(self):
for e in load_cve_data():
assert isinstance(e.references, list)
# ---------------------------------------------------------------------------
# save_cve_data
# ---------------------------------------------------------------------------
class TestSaveCVEData:
def test_roundtrip(self, tmp_path):
p = tmp_path / "out.json"
entries = [_sample_entry()]
save_cve_data(entries, path=p)
loaded = load_cve_data(p)
assert len(loaded) == 1
assert loaded[0].cve_id == "TEST-2026-001"
assert loaded[0].triggers == ["torch.load("]
def test_saves_multiple_entries(self, tmp_path):
p = tmp_path / "multi.json"
entries = [_sample_entry(cve_id=f"TEST-{i:03d}") for i in range(5)]
save_cve_data(entries, path=p)
loaded = load_cve_data(p)
assert len(loaded) == 5
def test_output_is_valid_json(self, tmp_path):
p = tmp_path / "valid.json"
save_cve_data([_sample_entry()], path=p)
raw = json.loads(p.read_text(encoding="utf-8"))
assert raw["version"] == "1.0"
assert "updated" in raw
assert isinstance(raw["entries"], list)
def test_custom_updated_timestamp(self, tmp_path):
p = tmp_path / "ts.json"
save_cve_data([_sample_entry()], path=p, updated="2026-05-21T00:00:00Z")
raw = json.loads(p.read_text(encoding="utf-8"))
assert raw["updated"] == "2026-05-21T00:00:00Z"
def test_auto_updated_timestamp_format(self, tmp_path):
p = tmp_path / "auto_ts.json"
save_cve_data([_sample_entry()], path=p)
raw = json.loads(p.read_text(encoding="utf-8"))
# Should be ISO 8601 UTC string ending in Z
assert raw["updated"].endswith("Z")
def test_overwrites_existing_file(self, tmp_path):
p = tmp_path / "overwrite.json"
save_cve_data([_sample_entry(cve_id="FIRST")], path=p)
save_cve_data([_sample_entry(cve_id="SECOND"), _sample_entry(cve_id="THIRD")], path=p)
loaded = load_cve_data(p)
assert len(loaded) == 2
assert loaded[0].cve_id == "SECOND"
def test_empty_entries_saves_valid_file(self, tmp_path):
p = tmp_path / "empty.json"
save_cve_data([], path=p)
loaded = load_cve_data(p)
assert loaded == []