Same importlib fix as test_username.py -- load plugins.crtsh from file path since plugins/ is not a Python package. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
122 lines
3.9 KiB
Python
122 lines
3.9 KiB
Python
"""Tests for the crt.sh certificate transparency plugin."""
|
|
|
|
import importlib.util
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
# plugins/ is not a Python package -- load the module from file path
|
|
_spec = importlib.util.spec_from_file_location(
|
|
"plugins.crtsh", Path(__file__).resolve().parent.parent / "plugins" / "crtsh.py",
|
|
)
|
|
_mod = importlib.util.module_from_spec(_spec)
|
|
sys.modules[_spec.name] = _mod
|
|
_spec.loader.exec_module(_mod)
|
|
|
|
from plugins.crtsh import ( # noqa: E402
|
|
deduplicate,
|
|
format_result,
|
|
is_expired,
|
|
is_live_cert_expired,
|
|
parse_crtsh_ts,
|
|
)
|
|
|
|
|
|
class TestDeduplicate:
|
|
def test_removes_duplicate_serials(self):
|
|
certs = [
|
|
{"serial_number": "AAA", "common_name": "a.example.com"},
|
|
{"serial_number": "BBB", "common_name": "b.example.com"},
|
|
{"serial_number": "AAA", "common_name": "a.example.com (dup)"},
|
|
]
|
|
result = deduplicate(certs)
|
|
assert len(result) == 2
|
|
serials = {c["serial_number"] for c in result}
|
|
assert serials == {"AAA", "BBB"}
|
|
|
|
def test_keeps_first_occurrence(self):
|
|
certs = [
|
|
{"serial_number": "AAA", "common_name": "first"},
|
|
{"serial_number": "AAA", "common_name": "second"},
|
|
]
|
|
result = deduplicate(certs)
|
|
assert result[0]["common_name"] == "first"
|
|
|
|
def test_empty_input(self):
|
|
assert deduplicate([]) == []
|
|
|
|
def test_no_serial_field(self):
|
|
certs = [{"common_name": "no-serial"}, {"serial_number": "", "common_name": "empty"}]
|
|
result = deduplicate(certs)
|
|
assert len(result) == 0
|
|
|
|
def test_all_unique(self):
|
|
certs = [
|
|
{"serial_number": "A", "common_name": "a"},
|
|
{"serial_number": "B", "common_name": "b"},
|
|
{"serial_number": "C", "common_name": "c"},
|
|
]
|
|
assert len(deduplicate(certs)) == 3
|
|
|
|
|
|
class TestExpiredCheck:
|
|
def test_expired_cert(self):
|
|
cert = {"not_after": "2020-01-01T00:00:00"}
|
|
assert is_expired(cert) is True
|
|
|
|
def test_valid_cert(self):
|
|
cert = {"not_after": "2099-12-31T23:59:59"}
|
|
assert is_expired(cert) is False
|
|
|
|
def test_missing_not_after(self):
|
|
assert is_expired({}) is False
|
|
assert is_expired({"not_after": ""}) is False
|
|
|
|
def test_fractional_seconds(self):
|
|
cert = {"not_after": "2020-06-15T12:30:45.123"}
|
|
assert is_expired(cert) is True
|
|
|
|
def test_parse_timestamp_basic(self):
|
|
dt = parse_crtsh_ts("2024-03-15T10:30:00")
|
|
assert dt == datetime(2024, 3, 15, 10, 30, 0, tzinfo=timezone.utc)
|
|
|
|
def test_parse_timestamp_fractional(self):
|
|
dt = parse_crtsh_ts("2024-03-15T10:30:00.500")
|
|
assert dt.microsecond == 500000
|
|
|
|
|
|
class TestLiveCertExpired:
|
|
def test_expired_live_cert(self):
|
|
cert = {"notAfter": "Jan 1 00:00:00 2020 GMT"}
|
|
assert is_live_cert_expired(cert) is True
|
|
|
|
def test_valid_live_cert(self):
|
|
cert = {"notAfter": "Dec 31 23:59:59 2099 GMT"}
|
|
assert is_live_cert_expired(cert) is False
|
|
|
|
def test_missing_field(self):
|
|
assert is_live_cert_expired({}) is False
|
|
|
|
|
|
class TestFormatResult:
|
|
def test_basic(self):
|
|
line = format_result("example.com", 100, 10, 90, None)
|
|
assert line == "example.com -- 100 certs (10 expired, 90 valid)"
|
|
|
|
def test_live_expired(self):
|
|
line = format_result("bad.com", 50, 5, 45, True)
|
|
assert "live cert EXPIRED" in line
|
|
|
|
def test_live_ok_with_expired_certs(self):
|
|
line = format_result("ok.com", 50, 5, 45, False)
|
|
assert "live cert ok" in line
|
|
|
|
def test_live_ok_no_expired(self):
|
|
"""No live check annotation when zero expired certs."""
|
|
line = format_result("clean.com", 50, 0, 50, False)
|
|
assert "live cert" not in line
|
|
|
|
def test_zero_certs(self):
|
|
line = format_result("empty.com", 0, 0, 0, None)
|
|
assert line == "empty.com -- 0 certs (0 expired, 0 valid)"
|