fix(qbittorrent): reuse authenticated client across widget fetches

QbittorrentWidgetSource built a brand-new QbittorrentClient on every fetch,
logging in each time. With three qBittorrent widgets polling every 5-30s and
qBittorrent verifying passwords with slow PBKDF2 hashing, the concurrent login
load saturates its web thread pool and the reverse proxy returns 504 gateway
timeouts on /api/v2/auth/login. The client was already designed for reuse
(login once, SID cookie reuse, 403 re-login) — the source just wasn't using it.

Cache one QbittorrentClient per service (lru_cache keyed by service id, URL,
credentials, timeout) so the SID cookie persists across fetches and login
happens once. Mirrors dependencies._jellyfin_client_for. A credentials/URL
change produces a new cache key, so stale clients aren't reused after
reconfiguration.

Also surface 502/503/504 from the login as a clear "reverse proxy returned
HTTP <code> ... qBittorrent may be down/starting/overloaded" RuntimeError
instead of a bare HTTPError, so future gateway issues read as infrastructure,
not auth.

Tests: autouse fixture clears the client cache between tests; new gateway-error
login test. 385/385 backend tests pass; ruff clean.
This commit is contained in:
Developer
2026-07-11 12:19:19 +00:00
parent dad2202756
commit b011d2421b
8 changed files with 78 additions and 15 deletions
+16
View File
@@ -30,6 +30,22 @@ def _encryption_key(monkeypatch):
yield
@pytest.fixture(autouse=True)
def _clear_qbittorrent_client_cache():
"""Reset the per-service qBittorrent client cache between tests.
QbittorrentWidgetSource reuses one authenticated client per service
(lru_cache) so the SID cookie persists across fetches. Without clearing,
a mock client cached by one test would leak into later tests that patch
QbittorrentClient differently.
"""
from media_library_viewer_api.widgets.sources import _qbittorrent_client
_qbittorrent_client.cache_clear()
yield
_qbittorrent_client.cache_clear()
@pytest.fixture
def client(tmp_path):
"""FastAPI test client with a fresh settings store and auth disabled."""