Files
bouncer/tests/test_cert.py
user d13d090e8e feat: make all operational constants configurable via bouncer.toml
Replace hardcoded values across network, captcha, email, and cert
modules with BouncerConfig fields. All values have safe defaults
and are overridable in the [bouncer] section of the config file.

Configurable: probation_seconds, backoff_steps, nick_timeout,
rejoin_delay, http_timeout, captcha_poll_interval/timeout,
email_poll_interval/max_polls/request_timeout, cert_validity_days.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 16:33:08 +01:00

145 lines
5.0 KiB
Python

"""Tests for client certificate management."""
from __future__ import annotations
from pathlib import Path
import pytest
from bouncer.cert import (
cert_path,
delete_cert,
fingerprint,
generate_cert,
has_cert,
list_certs,
)
@pytest.fixture
def data_dir(tmp_path: Path) -> Path:
"""Provide a temporary data directory."""
return tmp_path
class TestCertPath:
def test_standard_path(self, data_dir: Path) -> None:
p = cert_path(data_dir, "libera", "fabesune")
assert p == data_dir / "certs" / "libera" / "fabesune.pem"
class TestGenerateCert:
def test_creates_pem_file(self, data_dir: Path) -> None:
pem = generate_cert(data_dir, "libera", "testnick")
assert pem.is_file()
assert pem == cert_path(data_dir, "libera", "testnick")
def test_pem_contains_cert_and_key(self, data_dir: Path) -> None:
pem = generate_cert(data_dir, "libera", "testnick")
content = pem.read_text()
assert "BEGIN CERTIFICATE" in content
assert "BEGIN PRIVATE KEY" in content
def test_file_permissions(self, data_dir: Path) -> None:
pem = generate_cert(data_dir, "libera", "testnick")
mode = pem.stat().st_mode & 0o777
assert mode == 0o600
def test_overwrites_existing(self, data_dir: Path) -> None:
pem1 = generate_cert(data_dir, "libera", "testnick")
fp1 = fingerprint(pem1)
pem2 = generate_cert(data_dir, "libera", "testnick")
fp2 = fingerprint(pem2)
assert pem1 == pem2
assert fp1 != fp2 # New cert = new fingerprint
def test_custom_validity_days(self, data_dir: Path) -> None:
import datetime
from cryptography import x509 as x509_mod
pem = generate_cert(data_dir, "libera", "testnick", validity_days=365)
cert_data = pem.read_bytes()
cert_obj = x509_mod.load_pem_x509_certificate(cert_data)
delta = cert_obj.not_valid_after_utc - cert_obj.not_valid_before_utc
assert 364 <= delta.days <= 366
class TestFingerprint:
def test_format(self, data_dir: Path) -> None:
pem = generate_cert(data_dir, "libera", "testnick")
fp = fingerprint(pem)
parts = fp.split(":")
assert len(parts) == 32 # SHA-256 = 32 bytes
for part in parts:
assert len(part) == 2
int(part, 16) # Must be valid hex
def test_uppercase_hex(self, data_dir: Path) -> None:
pem = generate_cert(data_dir, "libera", "testnick")
fp = fingerprint(pem)
assert fp == fp.upper()
def test_deterministic_for_same_cert(self, data_dir: Path) -> None:
pem = generate_cert(data_dir, "libera", "testnick")
assert fingerprint(pem) == fingerprint(pem)
class TestHasCert:
def test_exists(self, data_dir: Path) -> None:
generate_cert(data_dir, "libera", "testnick")
assert has_cert(data_dir, "libera", "testnick") is True
def test_not_exists(self, data_dir: Path) -> None:
assert has_cert(data_dir, "libera", "testnick") is False
class TestDeleteCert:
def test_delete_existing(self, data_dir: Path) -> None:
generate_cert(data_dir, "libera", "testnick")
assert delete_cert(data_dir, "libera", "testnick") is True
assert has_cert(data_dir, "libera", "testnick") is False
def test_delete_nonexistent(self, data_dir: Path) -> None:
assert delete_cert(data_dir, "libera", "testnick") is False
def test_cleans_empty_dir(self, data_dir: Path) -> None:
generate_cert(data_dir, "libera", "testnick")
delete_cert(data_dir, "libera", "testnick")
assert not (data_dir / "certs" / "libera").exists()
class TestListCerts:
def test_empty(self, data_dir: Path) -> None:
assert list_certs(data_dir) == []
def test_list_all(self, data_dir: Path) -> None:
generate_cert(data_dir, "libera", "nick1")
generate_cert(data_dir, "oftc", "nick2")
certs = list_certs(data_dir)
assert len(certs) == 2
networks = {c[0] for c in certs}
assert networks == {"libera", "oftc"}
def test_list_by_network(self, data_dir: Path) -> None:
generate_cert(data_dir, "libera", "nick1")
generate_cert(data_dir, "oftc", "nick2")
certs = list_certs(data_dir, network="libera")
assert len(certs) == 1
assert certs[0][0] == "libera"
assert certs[0][1] == "nick1"
def test_list_multiple_per_network(self, data_dir: Path) -> None:
generate_cert(data_dir, "libera", "nick1")
generate_cert(data_dir, "libera", "nick2")
certs = list_certs(data_dir, network="libera")
assert len(certs) == 2
nicks = {c[1] for c in certs}
assert nicks == {"nick1", "nick2"}
def test_fingerprints_present(self, data_dir: Path) -> None:
generate_cert(data_dir, "libera", "testnick")
certs = list_certs(data_dir)
assert len(certs) == 1
_, _, fp = certs[0]
assert ":" in fp
assert len(fp.split(":")) == 32