Three new unit-test files, +135 tests, lifting overall project coverage from 51% to 56% with the biggest gains on the modules an operator's threat model cares about. No source-code changes.
Per-module uplift
| Module | Before | After | Lines covered |
|---|---|---|---|
modules/core/dns_providers.py
| 8% | 91% | +163 |
modules/core/notifier.py
| 14% | 91% | +150 |
modules/core/auth.py
| 59% | ~80% | +85 |
What landed
- 44 tests on
DNSManager(account management surface). Covers multi-account / default-account / first-credentialed / legacy single-account / missing / exception paths. - 59 tests on
AuthManager(security boundary).domain_matches_scopetruth table fully pinned, password hashing round-trip (bcrypt + legacy SHA-256 + malformed defence), API-token verification (HMAC and SHA-256, with legacy-after-key-rotation), user lifecycle including the "cannot delete the last admin" guard, session lifecycle. - 31 tests on
Notifier(webhook + SMTP delivery). URL-scheme defence (file:///ftp:///javascript:refused), HMAC-SHA256 signed payloads, retry-with-backoff, delivery log JSONL round-trip + cap.
Overall
pytest -m 'not e2e and not ui': 767 passed (was 632), 2 skipped, 115 deselected. Coverage 51% -> 56%.
Why these three modules
The picks are deliberate, not score-chasing:
dns_providers.pyholds DNS provider credentials (the keys that authorise ACME DNS-01 challenges on the operator's behalf).auth.pyis the multi-tenant boundary: API key scoping, role normalisation, password handling, session lifecycle.notifier.pyis the operator's eyes — webhook + SMTP delivery for cert lifecycle events, including the HMAC signature path that protects generic webhook receivers.
The remaining low-coverage modules (modules/api/resources.py 32%, modules/web/cert_routes.py 16%, modules/core/client_certificates.py 15%) need Flask app context and a richer fixture story — left for a next-session pass.
Closes #203.