fix: split HTTP connect/read timeouts (Jellyfin build + qBit stats)
Every HTTP client passed an integer timeout to requests, applying the same value to BOTH connect and read phases. A slow Jellyfin /Items page or qBit /sync/maindata blew through the 10s read budget → ReadTimeoutError. Split into a (connect=5s, read=60s default) tuple via shared http_timeout() helper. The media index build worker uses a 180s read floor. Existing services with low timeout_seconds benefit from bumping to 60+.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
"""Tests for the shared HTTP timeout helper."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from media_library_viewer_api.clients.http_timeout import (
|
||||
DEFAULT_CONNECT_TIMEOUT,
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
http_timeout,
|
||||
)
|
||||
|
||||
|
||||
class TestHttpTimeout:
|
||||
def test_http_timeout_returns_tuple(self) -> None:
|
||||
result = http_timeout(30)
|
||||
assert result == (DEFAULT_CONNECT_TIMEOUT, 30.0)
|
||||
|
||||
def test_http_timeout_default_when_none(self) -> None:
|
||||
result = http_timeout(None)
|
||||
assert result == (DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT)
|
||||
|
||||
def test_http_timeout_default_when_zero(self) -> None:
|
||||
result = http_timeout(0)
|
||||
assert result == (DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT)
|
||||
|
||||
def test_http_timeout_custom_connect(self) -> None:
|
||||
result = http_timeout(30, connect_timeout=10)
|
||||
assert result == (10.0, 30.0)
|
||||
|
||||
def test_http_timeout_default_when_negative(self) -> None:
|
||||
result = http_timeout(-5)
|
||||
assert result == (DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT)
|
||||
|
||||
def test_http_timeout_default_when_garbage_string(self) -> None:
|
||||
result = http_timeout("garbage") # type: ignore[arg-type]
|
||||
assert result == (DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT)
|
||||
|
||||
def test_http_timeout_accepts_int(self) -> None:
|
||||
result = http_timeout(45)
|
||||
assert result == (5.0, 45.0)
|
||||
@@ -129,6 +129,21 @@ class QbittorrentClientTests(unittest.TestCase):
|
||||
with self.assertRaises(requests.ConnectionError):
|
||||
client._login()
|
||||
|
||||
def test_qbittorrent_client_uses_tuple_timeout(self) -> None:
|
||||
"""The client passes a (connect, read) tuple to requests, not an int."""
|
||||
# self.client was constructed with timeout=5 in setUp() and has a mocked session.
|
||||
assert isinstance(self.client.timeout, tuple)
|
||||
assert len(self.client.timeout) == 2
|
||||
assert self.client.timeout[0] == 5.0 # connect timeout
|
||||
assert self.client.timeout[1] == 5.0 # read timeout (what we passed)
|
||||
|
||||
# Verify it's actually passed to requests as-is.
|
||||
self.session.post.return_value = self._login_response()
|
||||
self.client._login()
|
||||
call_kwargs = self.session.post.call_args.kwargs
|
||||
assert call_kwargs["timeout"] == (5.0, 5.0)
|
||||
assert not isinstance(call_kwargs["timeout"], int)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user