feat: externe Links auf Erreichbarkeit prüfen (check-ext-links)
Neuer Befehl `python -m scanner check-ext-links`: - Liest alle externen <a>-Links aus dem letzten Snapshot - Prüft jeden per HEAD-Request (Fallback auf GET bei HTTP 405) - Meldet 4xx/5xx und Verbindungsfehler mit Quelladressen - Report unter reports/<ts>_ext-links/ - Exit-Code 1 bei kaputten Links, 0 wenn alle erreichbar Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0302d469b1
commit
ecd01abf98
3 changed files with 263 additions and 1 deletions
82
tests/test_ext_links.py
Normal file
82
tests/test_ext_links.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
"""Tests for external link reachability check."""
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from scanner.checker import check_external_links
|
||||
|
||||
|
||||
def _mock_response(status_code=200, url="https://example.com/"):
|
||||
resp = MagicMock()
|
||||
resp.status_code = status_code
|
||||
resp.url = url
|
||||
return resp
|
||||
|
||||
|
||||
class TestCheckExternalLinks:
|
||||
def test_reachable_link_recorded(self):
|
||||
with patch("scanner.checker.requests.Session") as MockSession:
|
||||
session = MockSession.return_value.__enter__.return_value
|
||||
MockSession.return_value = MagicMock()
|
||||
inst = MockSession.return_value
|
||||
inst.head.return_value = _mock_response(200)
|
||||
inst.close = MagicMock()
|
||||
|
||||
results = check_external_links(["https://example.com/"], delay=0)
|
||||
|
||||
assert results["https://example.com/"]["status"] == 200
|
||||
assert results["https://example.com/"]["error"] is None
|
||||
|
||||
def test_broken_link_404_recorded(self):
|
||||
with patch("scanner.checker.requests.Session") as MockSession:
|
||||
inst = MockSession.return_value
|
||||
inst.head.return_value = _mock_response(404, "https://example.com/gone")
|
||||
inst.close = MagicMock()
|
||||
|
||||
results = check_external_links(["https://example.com/gone"], delay=0)
|
||||
|
||||
assert results["https://example.com/gone"]["status"] == 404
|
||||
|
||||
def test_connection_error_recorded(self):
|
||||
with patch("scanner.checker.requests.Session") as MockSession:
|
||||
inst = MockSession.return_value
|
||||
inst.head.side_effect = requests.exceptions.ConnectionError("refused")
|
||||
inst.close = MagicMock()
|
||||
|
||||
results = check_external_links(["https://unreachable.example.com/"], delay=0)
|
||||
|
||||
r = results["https://unreachable.example.com/"]
|
||||
assert r["status"] is None
|
||||
assert "refused" in r["error"]
|
||||
|
||||
def test_head_405_falls_back_to_get(self):
|
||||
with patch("scanner.checker.requests.Session") as MockSession:
|
||||
inst = MockSession.return_value
|
||||
inst.head.return_value = _mock_response(405)
|
||||
get_resp = _mock_response(200)
|
||||
get_resp.close = MagicMock()
|
||||
inst.get.return_value = get_resp
|
||||
inst.close = MagicMock()
|
||||
|
||||
results = check_external_links(["https://example.com/no-head"], delay=0)
|
||||
|
||||
inst.get.assert_called_once()
|
||||
assert results["https://example.com/no-head"]["status"] == 200
|
||||
|
||||
def test_multiple_urls_all_checked(self):
|
||||
urls = [f"https://example.com/{i}" for i in range(3)]
|
||||
with patch("scanner.checker.requests.Session") as MockSession:
|
||||
inst = MockSession.return_value
|
||||
inst.head.return_value = _mock_response(200)
|
||||
inst.close = MagicMock()
|
||||
|
||||
results = check_external_links(urls, delay=0)
|
||||
|
||||
assert len(results) == 3
|
||||
assert inst.head.call_count == 3
|
||||
|
||||
def test_empty_list_returns_empty(self):
|
||||
with patch("scanner.checker.requests.Session"):
|
||||
results = check_external_links([], delay=0)
|
||||
assert results == {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue