diff --git a/api/helpers.py b/api/helpers.py index 2337bb70..79a6c8f3 100644 --- a/api/helpers.py +++ b/api/helpers.py @@ -236,11 +236,16 @@ def advertise_connection_close(handler) -> None: Both attribute reads are defensive: partially-built handler stubs in the test suite carry neither ``close_connection`` nor ``_headers_buffer``, and - header emission must not depend on their presence. + header emission must not depend on their presence. The buffer is also + type-checked before it is scanned -- a stub whose ``_headers_buffer`` is a + truthy non-iterable (a bare ``Mock``) would otherwise raise ``TypeError`` + from inside ``end_headers()``, which ``_handle_write`` would swallow into a + spurious 500. """ if not getattr(handler, 'close_connection', False): return - for raw in getattr(handler, '_headers_buffer', None) or (): + buffered = getattr(handler, '_headers_buffer', None) + for raw in buffered if isinstance(buffered, (list, tuple)) else (): if raw[:11].lower() == b'connection:': return handler.send_header('Connection', 'close') diff --git a/tests/test_rejected_write_connection_close.py b/tests/test_rejected_write_connection_close.py index bd7ddb4f..a1bfc9a5 100644 --- a/tests/test_rejected_write_connection_close.py +++ b/tests/test_rejected_write_connection_close.py @@ -228,6 +228,26 @@ def test_end_headers_on_handler_stub_without_close_connection(monkeypatch): assert "Connection" not in dict(sent), "nothing was armed, so nothing to advertise" +def test_advertise_close_tolerates_a_non_iterable_header_buffer(): + """The dedup scan must be as defensive as the two attribute reads. + + A bare ``Mock`` answers every attribute, so ``_headers_buffer`` comes back + truthy but non-iterable. Iterating it unguarded raised + ``TypeError: 'Mock' object is not iterable`` from inside ``end_headers()``, + which ``_handle_write``'s generic ``except`` turns into a spurious 500 — + the same crash-inside-end_headers class as the missing + ``close_connection`` guard above. + """ + from unittest.mock import Mock + + from api.helpers import advertise_connection_close + + handler = Mock() + advertise_connection_close(handler) # must not raise + + handler.send_header.assert_called_once_with("Connection", "close") + + def test_check_auth_or_close_on_handler_stub_without_close_connection(monkeypatch): """check_auth_or_close must not invent close_connection on a stub.