integrity_scanner_fuer_stat.../tests/test_assets.py

185 lines
7.7 KiB
Python
Raw Permalink Normal View History

"""Tests for asset hashing and comparison."""
from unittest.mock import MagicMock, patch
import pytest
import requests
from scanner.baseline import BaselineManager
from scanner.checker import fetch_asset_hashes
from scanner.differ import compare_asset_hashes, score_asset_diff
CFG = {"scoring": {}, "thresholds": {}}
# ---------------------------------------------------------------------------
# fetch_asset_hashes
# ---------------------------------------------------------------------------
class TestFetchAssetHashes:
def _mock_session(self, status=200, content=b"data"):
resp = MagicMock()
resp.status_code = status
resp.iter_content.return_value = [content]
resp.close = MagicMock()
inst = MagicMock()
inst.get.return_value = resp
inst.close = MagicMock()
return inst
def test_successful_fetch_returns_sha256(self):
import hashlib
data = b"hello asset"
expected = hashlib.sha256(data).hexdigest()
with patch("scanner.checker.requests.Session") as MockSession:
MockSession.return_value = self._mock_session(content=data)
results = fetch_asset_hashes(["https://x.de/app.js"], delay=0)
assert results["https://x.de/app.js"]["sha256"] == expected
assert results["https://x.de/app.js"]["error"] is None
def test_http_error_recorded(self):
with patch("scanner.checker.requests.Session") as MockSession:
MockSession.return_value = self._mock_session(status=404)
results = fetch_asset_hashes(["https://x.de/missing.js"], delay=0)
assert results["https://x.de/missing.js"]["sha256"] is None
assert "HTTP 404" in results["https://x.de/missing.js"]["error"]
def test_connection_error_recorded(self):
with patch("scanner.checker.requests.Session") as MockSession:
inst = MockSession.return_value
inst.get.side_effect = requests.exceptions.ConnectionError("refused")
inst.close = MagicMock()
results = fetch_asset_hashes(["https://unreachable.de/x.js"], delay=0)
assert results["https://unreachable.de/x.js"]["sha256"] is None
assert results["https://unreachable.de/x.js"]["error"] is not None
def test_empty_list_returns_empty(self):
with patch("scanner.checker.requests.Session"):
results = fetch_asset_hashes([], delay=0)
assert results == {}
# ---------------------------------------------------------------------------
# compare_asset_hashes
# ---------------------------------------------------------------------------
class TestCompareAssetHashes:
def _h(self, sha, type_="img", size=100):
return {"sha256": sha, "size": size, "type": type_, "error": None}
def test_no_changes_empty_diff(self):
h = {"https://x.de/img.jpg": self._h("aaa")}
result = compare_asset_hashes(h, h)
assert result["changed"] == []
assert result["new"] == []
assert result["missing"] == []
def test_changed_hash_detected(self):
baseline = {"https://x.de/img.jpg": self._h("aaa111")}
current = {"https://x.de/img.jpg": self._h("bbb222")}
result = compare_asset_hashes(baseline, current)
assert len(result["changed"]) == 1
assert result["changed"][0]["url"] == "https://x.de/img.jpg"
def test_new_asset_detected(self):
baseline = {}
current = {"https://x.de/new.jpg": self._h("abc")}
result = compare_asset_hashes(baseline, current)
assert "https://x.de/new.jpg" in result["new"]
def test_missing_asset_detected(self):
baseline = {"https://x.de/old.jpg": self._h("abc")}
current = {}
result = compare_asset_hashes(baseline, current)
assert "https://x.de/old.jpg" in result["missing"]
def test_fetch_error_not_in_changed(self):
baseline = {"https://x.de/img.jpg": self._h("aaa")}
current = {"https://x.de/img.jpg": {"sha256": None, "size": None, "type": "img", "error": "timeout"}}
result = compare_asset_hashes(baseline, current)
assert result["changed"] == []
assert len(result["fetch_errors"]) == 1
# ---------------------------------------------------------------------------
# score_asset_diff
# ---------------------------------------------------------------------------
class TestScoreAssetDiff:
def test_no_changes_green(self):
result = score_asset_diff({"changed": [], "new": [], "missing": [], "fetch_errors": [], "total_checked": 5}, CFG)
assert result["level"] == "green"
assert result["score"] == 0
def test_changed_script_scores_40(self):
diff = {"changed": [{"url": "https://x.de/app.js", "type": "script", "old_sha256": "a…", "new_sha256": "b…", "size_diff": 0}],
"new": [], "missing": [], "fetch_errors": [], "total_checked": 1}
result = score_asset_diff(diff, CFG)
assert result["score"] == 40
assert result["level"] == "yellow"
def test_changed_stylesheet_scores_30(self):
diff = {"changed": [{"url": "https://x.de/style.css", "type": "link_rel", "old_sha256": "a…", "new_sha256": "b…", "size_diff": 0}],
"new": [], "missing": [], "fetch_errors": [], "total_checked": 1}
result = score_asset_diff(diff, CFG)
assert result["score"] == 30
assert result["level"] == "yellow"
def test_changed_image_scores_20(self):
diff = {"changed": [{"url": "https://x.de/logo.png", "type": "img", "old_sha256": "a…", "new_sha256": "b…", "size_diff": 100}],
"new": [], "missing": [], "fetch_errors": [], "total_checked": 1}
result = score_asset_diff(diff, CFG)
assert result["score"] == 20
assert result["level"] == "yellow"
def test_two_changed_scripts_red(self):
entry = {"url": "https://x.de/x.js", "type": "script", "old_sha256": "a…", "new_sha256": "b…", "size_diff": 0}
diff = {"changed": [entry, {**entry, "url": "https://x.de/y.js"}],
"new": [], "missing": [], "fetch_errors": [], "total_checked": 2}
result = score_asset_diff(diff, CFG)
assert result["score"] == 80
assert result["level"] == "red"
assert result["exit_code"] == 2
class TestAutoAssetBaselineOnApprove:
"""approve --all soll die Datei-Baseline automatisch mit anlegen."""
def test_establish_asset_baseline_saves_hashes(self, tmp_path):
from scanner.__main__ import _establish_asset_baseline
bm = BaselineManager(tmp_path)
page = {
"url": "https://x.de/",
"status": 200,
"links": {"script": [{"url": "https://x.de/app.js", "class": "internal"}],
"link_rel": [], "img": []},
}
snap_dir = bm.save_snapshot([page])
with patch("scanner.__main__.fetch_asset_hashes") as mock_fetch:
mock_fetch.return_value = {
"https://x.de/app.js": {"sha256": "abc123", "size": 10, "error": None}
}
_establish_asset_baseline({"request_timeout": 15}, bm, snap_dir)
saved = bm.load_asset_hashes()
assert "https://x.de/app.js" in saved
assert saved["https://x.de/app.js"]["sha256"] == "abc123"
def test_no_assets_writes_nothing(self, tmp_path):
from scanner.__main__ import _establish_asset_baseline
bm = BaselineManager(tmp_path)
page = {"url": "https://x.de/", "status": 200,
"links": {"script": [], "link_rel": [], "img": []}}
snap_dir = bm.save_snapshot([page])
with patch("scanner.__main__.fetch_asset_hashes") as mock_fetch:
_establish_asset_baseline({"request_timeout": 15}, bm, snap_dir)
mock_fetch.assert_not_called()
assert bm.load_asset_hashes() == {}