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: try:
return ssl.create_default_context(cafile=path), path return ssl.create_default_context(cafile=path), path
except (OSError, ssl.SSLError) as exc: except (OSError, ssl.SSLError) as exc:
logger.warning( logger.warning("CA bundle could not be loaded from %s: %s — trying the next bundle", path, exc)
"CA bundle could not be loaded from %s: %s — falling back to default certificates", path, exc if candidates:
) logger.warning("No configured CA bundle could be loaded — falling back to default certificates")
return None, None return None, None

View File

@@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import json import json
import logging
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import ssl import ssl
from threading import Thread 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)] 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( @pytest.mark.parametrize(
("candidates", "first_load_fails_for", "expected_load_sequence"), ("candidates", "first_load_fails_for", "expected_load_sequence"),
[ [