soulsync/tests/test_navidrome_self_heal.py
BoulderBadgeDad 4dd09ff48a Navidrome: self-heal the connection instead of latching disconnected (jimmydotcom)
A transient ping failure (network blip, Navidrome busy mid-scan) makes
_setup_client null out the configured creds, and _connection_attempted then
latches the client "disconnected" — so is_connected() returned False forever until
the user hit the manual Test button to re-read config. That's the reported
"disconnects every 5-10 min, reconnects instantly on Test."

Fix: ensure_connection no longer latches on a failed attempt — once a short
throttle (_RECONNECT_THROTTLE_S = 20s) elapses it re-attempts, and is_connected()
triggers that retry whenever it's currently disconnected. So a blip recovers on its
own within the next status check, no manual reconnect. The throttle prevents ping
storms when Navidrome is genuinely down.

Tests: transient failure self-heals after the throttle (and doesn't re-ping within
it); a connected client never re-pings; first connect attempts once. 115 navidrome/
media-server tests green.
2026-06-11 21:34:48 -07:00

54 lines
2.1 KiB
Python

"""Navidrome connection self-heals after a transient ping failure (jimmydotcom).
A failed ping nukes the creds in _setup_client; previously _connection_attempted
latched the client 'disconnected' until a manual Test. Now is_connected re-attempts
(throttled) so a blip recovers on its own."""
from __future__ import annotations
from core.navidrome_client import NavidromeClient
def test_self_heals_after_transient_failure(monkeypatch):
c = NavidromeClient()
calls = {'n': 0}
def fake_setup():
calls['n'] += 1
if calls['n'] == 1: # transient failure nukes creds
c.base_url = c.username = c.password = None
else: # blip passed → connects
c.base_url, c.username, c.password = 'http://nd:4533', 'u', 'p'
monkeypatch.setattr(c, '_setup_client', fake_setup)
assert c.is_connected() is False # first attempt fails
assert calls['n'] == 1
assert c.is_connected() is False # immediate recheck: throttled
assert calls['n'] == 1 # did NOT re-ping (no storm)
c._last_connect_attempt -= (c._RECONNECT_THROTTLE_S + 1) # throttle window elapses
assert c.is_connected() is True # re-attempts → recovers itself
assert calls['n'] == 2 # no manual reconnect was needed
def test_connected_client_does_not_reattempt(monkeypatch):
c = NavidromeClient()
c.base_url, c.username, c.password = 'http://nd', 'u', 'p'
c._connection_attempted = True
calls = {'n': 0}
monkeypatch.setattr(c, '_setup_client', lambda: calls.__setitem__('n', calls['n'] + 1))
assert c.is_connected() is True
assert calls['n'] == 0 # already connected → never re-pings
def test_first_connect_attempts_once(monkeypatch):
c = NavidromeClient()
calls = {'n': 0}
def fake_setup():
calls['n'] += 1
c.base_url, c.username, c.password = 'http://nd', 'u', 'p'
monkeypatch.setattr(c, '_setup_client', fake_setup)
assert c.is_connected() is True
assert calls['n'] == 1