fix(urllib): log the default-certificates fallback once, after every bundle failed

Each failed candidate used to claim "falling back to default certificates"
even when the next candidate (certifi on macOS) loaded fine. Per-failure
warnings now say "trying the next bundle"; the default-certificates warning
is emitted once at the final (None, None) return. Level unchanged (WARNING).

PROOF: test_default_certificates_fallback_is_logged_once_after_all_bundles_fail
fails on the previous per-candidate wording and passes with this change;
tests/hermes_cli/test_urllib_security.py 22 passed.
This commit is contained in:
kshitijk4poor
2026-09-22 15:23:54 +05:30
committed by kshitij
parent 7a0c8287ce
commit c33be87aad
2 changed files with 28 additions and 3 deletions

View File

@@ -167,9 +167,9 @@ def _build_https_context(candidates: tuple[str, ...]) -> tuple[ssl.SSLContext |
try:
return ssl.create_default_context(cafile=path), path
except (OSError, ssl.SSLError) as exc:
logger.warning(
"CA bundle could not be loaded from %s: %s — falling back to default certificates", path, exc
)
logger.warning("CA bundle could not be loaded from %s: %s — trying the next bundle", path, exc)
if candidates:
logger.warning("No configured CA bundle could be loaded — falling back to default certificates")
return None, None

View File

@@ -3,6 +3,7 @@
from __future__ import annotations
import json
import logging
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import ssl
from threading import Thread
@@ -600,6 +601,30 @@ def test_fallback_bundle_change_does_not_invalidate_the_memo(monkeypatch, tmp_pa
assert calls == [str(preferred)]
def test_default_certificates_fallback_is_logged_once_after_all_bundles_fail(monkeypatch, caplog):
"""A failed candidate says "trying the next bundle"; the default-certificates line is emitted once."""
import hermes_cli.urllib_security as urllib_security
def create_default_context(*, cafile=None):
raise ssl.SSLError(f"bad bundle {cafile}")
monkeypatch.setattr(ssl, "create_default_context", create_default_context)
with caplog.at_level(logging.WARNING, logger=urllib_security.logger.name):
assert urllib_security._build_https_context(("/a.pem", "/b.pem")) == (None, None)
messages = [record.getMessage() for record in caplog.records]
per_failure = [m for m in messages if "trying the next bundle" in m]
assert [m.split(":")[0] for m in per_failure] == [
"CA bundle could not be loaded from /a.pem",
"CA bundle could not be loaded from /b.pem",
]
assert [m for m in messages if "falling back to default certificates" in m] == [
"No configured CA bundle could be loaded — falling back to default certificates"
]
assert all(record.levelno == logging.WARNING for record in caplog.records)
@pytest.mark.parametrize(
("candidates", "first_load_fails_for", "expected_load_sequence"),
[