Skip to content

Commit efc931f

Browse files
fix(errors): name which Tunarr auth problem it is, not just "check three things"
Closes the last verification gap from the hardening sweep. The real Tunarr here has auth disabled, so the credential path could only be proven at the header level. Verified it properly against a local stand-in server that actually enforces basic auth and returns 401 — no change to the real Tunarr. Result: correct credentials index normally; absent or wrong ones fail cleanly. But both landed in the generic "check that tunarr_url is correct and Tunarr is reachable (and that Tunarr basic auth, if you enabled it, is configured)" — which asks the user to check three unrelated things when we already know precisely which one is wrong. _endpoint_status already tells us it's a 401/403, so: - credentials set -> "Tunarr rejected the username and password (401)." - no credentials set -> "This Tunarr requires a username and password (401), and none are configured." Two different problems with two different fixes; a user who set no credentials does not need to be told to re-check the ones they don't have. Verified against the enforcing stand-in (both messages fire correctly, correct credentials still index) and re-checked against the real Tunarr for regressions: still 4100 movies / 401 shows across 4 Plex sources. 348 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent e969def commit efc931f

2 files changed

Lines changed: 52 additions & 1 deletion

File tree

backend/tests/test_media_source_errors.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,40 @@ def test_get_tunarr_version_reads_the_tunarr_field(monkeypatch):
179179
def test_get_tunarr_version_tolerates_junk(monkeypatch):
180180
monkeypatch.setattr(channel_engine, "api", lambda u, m, p, **k: "not-a-dict")
181181
assert channel_engine.get_tunarr_version("http://tunarr") is None
182+
183+
184+
# ── 401 from Tunarr: say which of the two auth problems it is ─────────────────
185+
186+
def test_401_with_no_credentials_says_to_set_them(monkeypatch):
187+
monkeypatch.setattr(channel_engine, "api", lambda *a, **k: None)
188+
monkeypatch.setattr(channel_engine, "_endpoint_status", lambda u, p, timeout=10: 401)
189+
channel_engine.set_tunarr_auth()
190+
191+
with pytest.raises(channel_engine.ChannelEngineError) as e:
192+
channel_engine.build_library_index("http://tunarr")
193+
msg = str(e.value)
194+
assert "requires a username and password" in msg
195+
assert "none are configured" in msg
196+
197+
198+
def test_401_with_credentials_says_they_are_wrong(monkeypatch):
199+
"""Different advice: they HAVE credentials, so the fix is to correct them."""
200+
monkeypatch.setattr(channel_engine, "api", lambda *a, **k: None)
201+
monkeypatch.setattr(channel_engine, "_endpoint_status", lambda u, p, timeout=10: 401)
202+
channel_engine.set_tunarr_auth("admin", "wrong")
203+
try:
204+
with pytest.raises(channel_engine.ChannelEngineError) as e:
205+
channel_engine.build_library_index("http://tunarr")
206+
assert "rejected the username and password" in str(e.value)
207+
finally:
208+
channel_engine.set_tunarr_auth()
209+
210+
211+
def test_403_is_treated_the_same_as_401(monkeypatch):
212+
monkeypatch.setattr(channel_engine, "api", lambda *a, **k: None)
213+
monkeypatch.setattr(channel_engine, "_endpoint_status", lambda u, p, timeout=10: 403)
214+
channel_engine.set_tunarr_auth()
215+
216+
with pytest.raises(channel_engine.ChannelEngineError) as e:
217+
channel_engine.build_library_index("http://tunarr")
218+
assert "403" in str(e.value)

channel_engine.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,13 +182,27 @@ def _no_plex_source_error(tunarr_url):
182182
# Capability detection rather than a version gate: a missing endpoint is
183183
# the thing that actually breaks us, and it's directly observable —
184184
# guessing a minimum version number would be less accurate, not more.
185-
if _endpoint_status(tunarr_url, "/api/media-sources") == 404:
185+
status = _endpoint_status(tunarr_url, "/api/media-sources")
186+
if status == 404:
186187
return ChannelEngineError(
187188
"This Tunarr does not have the /api/media-sources endpoint, which "
188189
"Programmarr needs to read your libraries. That endpoint arrived in "
189190
"Tunarr 1.x, so this server is most likely too old — please update "
190191
"Tunarr." + _version_suffix(tunarr_url)
191192
)
193+
if status in (401, 403):
194+
# We know exactly what's wrong here, so don't make them check three
195+
# things. Distinguish "you set no credentials" from "yours are wrong".
196+
if _TUNARR_AUTH:
197+
return ChannelEngineError(
198+
f"Tunarr rejected the username and password ({status}). Check "
199+
"tunarr_username / tunarr_password in Settings -> Connections."
200+
)
201+
return ChannelEngineError(
202+
f"This Tunarr requires a username and password ({status}), and none "
203+
"are configured. Set tunarr_username / tunarr_password in "
204+
"Settings -> Connections."
205+
)
192206
return ChannelEngineError(
193207
"Could not read media sources from Tunarr — check that tunarr_url is "
194208
"correct and Tunarr is reachable (and that Tunarr basic auth, if you "

0 commit comments

Comments
 (0)