fix(#3210 review): convert bare urlopen TimeoutError to ValueError in _joplin_api_get

Codex+Opus pre-release gate both flagged: TimeoutError is now in the
consolidated _CLIENT_DISCONNECT_ERRORS dispatch set, so a bare socket-connect
TimeoutError from Joplins urlopen(timeout=8) — which is NOT always URLError-
wrapped — would escape _handle_notes_search and be swallowed by the dispatch
disconnect handler as a fake client disconnect (silent empty response, no log).
Catch (URLError, TimeoutError) at the route so it surfaces as a clean
"not reachable" ValueError -> JSON error. Adds a regression test.

Co-authored-by: someaka <someaka@users.noreply.github.com>
This commit is contained in:
nesquena-hermesandsomeaka committed 2026-05-31 01:57:29 +00:00
1 parent c4d845c9d5
commit dca4a2a7af
2 files changed
+31 -1

No files matched your search

+24
View File
@@ -138,6 +138,30 @@ def test_joplin_get_note_validates_id_and_truncates_body(monkeypatch):
assert "Preview truncated" in note["body"]
def test_joplin_api_get_converts_bare_timeout_to_valueerror(monkeypatch):
"""Regression (#3210 review): a bare socket TimeoutError from urlopen must
surface as a ValueError, not propagate.
`urlopen(timeout=8)` can raise a bare ``TimeoutError`` (not URLError-wrapped)
on a slow/unreachable Joplin server. After the consolidated client-disconnect
handling landed, ``TimeoutError`` is in the request dispatch's
``_CLIENT_DISCONNECT_ERRORS`` set — so if it escaped ``_joplin_api_get`` it
would be swallowed as a fake client disconnect (silent empty response, no
log). It must be converted to a "not reachable" ValueError at the route.
"""
import pytest
from api import routes
def fake_urlopen(request, timeout):
raise TimeoutError("timed out")
monkeypatch.setattr(routes, "_joplin_connection_from_config", lambda: ("http://127.0.0.1:41184", "secret-token"))
monkeypatch.setattr("urllib.request.urlopen", fake_urlopen)
with pytest.raises(ValueError, match="not reachable"):
routes._joplin_api_get("/search", {"query": "hello world"})
def test_joplin_api_get_sends_header_and_query_token_for_clip_search_compat(monkeypatch):
from api import routes