#887: Spotify enrichment shows 'Running (Spotify Free)' for no-auth users, not 'Not Authenticated'
radoslav-orlov: with no Spotify auth, enrichment runs on the no-creds Spotify Free source (prefer-free is on by default) and IS working — pending drains, the modal shows RUNNING — but the dashboard header tooltip said 'Not Authenticated / Connect Spotify in Settings'. Two causes: - get_stats() only set using_free for the rate-limit / spent-budget bridges, not the plain no-auth-default-free case. The loop already computes the right signal (free_serving = _free_active(), True here) but it was a local var. Cache it on self each iteration and report it as using_free (no auth API call in the 2s status loop). - The dashboard's Spotify updater checked notAuthenticated BEFORE bridgingFree, so even with using_free it showed Not Authenticated. notAuthenticated now excludes the bridging-free case; the LastFM/Genius/Tidal/Qobuz updaters (no free path) are unchanged. 5 seam tests for get_stats free/auth reporting; 67 enrichment/free tests pass.
This commit is contained in:
parent
a5267ee8cf
commit
56da2e105c
3 changed files with 90 additions and 4 deletions
|
|
@ -47,6 +47,12 @@ class SpotifyWorker:
|
|||
# Current item being processed (for UI tooltip)
|
||||
self.current_item = None
|
||||
|
||||
# Whether the worker is serving via the no-creds Spotify Free source as of
|
||||
# its last loop iteration. Cached from the loop's _free_active() probe so
|
||||
# get_stats() can report it without an auth API call (#887: a no-auth user
|
||||
# whose enrichment runs on Free was shown "Not Authenticated").
|
||||
self._serving_via_free = False
|
||||
|
||||
# Statistics
|
||||
self.stats = {
|
||||
'matched': 0,
|
||||
|
|
@ -136,8 +142,14 @@ class SpotifyWorker:
|
|||
# real-API daily budget (the worker set _budget_exhausted_use_free —
|
||||
# a cheap attribute read). Lets the UI show "Running (Spotify Free)"
|
||||
# instead of a misleading "rate limited" / "daily limit reached".
|
||||
# #887: the loop's cached _free_active() result is the comprehensive
|
||||
# signal — it's True for a no-auth user enriching via Spotify Free by
|
||||
# default (prefer-free is on unless disabled), not just the rate-limit
|
||||
# / budget bridges. The extra terms stay as a fallback for the brief
|
||||
# window before the loop's first iteration sets the cache.
|
||||
using_free = bool(
|
||||
(rate_limited and self.client.is_spotify_metadata_available())
|
||||
getattr(self, '_serving_via_free', False)
|
||||
or (rate_limited and self.client.is_spotify_metadata_available())
|
||||
or getattr(self.client, '_budget_exhausted_use_free', False)
|
||||
)
|
||||
except Exception:
|
||||
|
|
@ -264,6 +276,9 @@ class SpotifyWorker:
|
|||
free_serving = self.client._free_active()
|
||||
except Exception:
|
||||
free_serving = False
|
||||
# Cache for get_stats() so the dashboard status reflects that the
|
||||
# worker IS enriching via Free even with no official auth (#887).
|
||||
self._serving_via_free = free_serving
|
||||
|
||||
# Daily budget guard — pause ONLY when the budget is spent AND we
|
||||
# can't serve via free (no free available). Otherwise free took over.
|
||||
|
|
|
|||
68
tests/test_spotify_worker_status.py
Normal file
68
tests/test_spotify_worker_status.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
"""Issue #887: the Spotify enrichment worker's get_stats() must report
|
||||
``using_free`` when it's enriching via the no-creds Spotify Free source — even
|
||||
with no official auth — so the dashboard shows "Running (Spotify Free)" instead
|
||||
of a misleading "Not Authenticated".
|
||||
|
||||
Builds the worker via __new__ (bypassing the real SpotifyClient()) and stubs the
|
||||
db-querying helpers, so the get_stats() free/auth logic is tested in isolation.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import types
|
||||
|
||||
from core.spotify_worker import SpotifyWorker
|
||||
|
||||
|
||||
def _worker(*, serving_via_free, sp=None, rate_limited=False, budget_free=False):
|
||||
w = SpotifyWorker.__new__(SpotifyWorker)
|
||||
w.running = True
|
||||
w.paused = False
|
||||
w.thread = types.SimpleNamespace(is_alive=lambda: True)
|
||||
w.current_item = None
|
||||
w.stats = {'pending': 0, 'processed': 0}
|
||||
w._serving_via_free = serving_via_free
|
||||
w.client = types.SimpleNamespace(
|
||||
sp=sp,
|
||||
is_rate_limited=lambda: rate_limited,
|
||||
get_rate_limit_info=lambda: None,
|
||||
get_post_ban_cooldown_remaining=lambda: 0,
|
||||
is_spotify_metadata_available=lambda: True,
|
||||
_budget_exhausted_use_free=budget_free,
|
||||
)
|
||||
# db-backed helpers stubbed — we only exercise the auth/free reporting.
|
||||
w._count_pending_items = lambda: 100
|
||||
w._get_progress_breakdown = lambda: {}
|
||||
w._get_daily_budget_info = lambda: {'exhausted': False}
|
||||
return w
|
||||
|
||||
|
||||
def test_no_auth_but_serving_via_free_reports_using_free():
|
||||
# #887: no official auth (sp is None), worker enriching via Spotify Free.
|
||||
stats = _worker(serving_via_free=True, sp=None).get_stats()
|
||||
assert stats['authenticated'] is False # no official auth
|
||||
assert stats['using_free'] is True # ...but Free is carrying it
|
||||
|
||||
|
||||
def test_no_auth_and_not_serving_free_is_not_using_free():
|
||||
# Genuinely can't enrich (no auth, free not active) -> Not Authenticated stands.
|
||||
stats = _worker(serving_via_free=False, sp=None).get_stats()
|
||||
assert stats['authenticated'] is False
|
||||
assert stats['using_free'] is False
|
||||
|
||||
|
||||
def test_rate_limit_bridge_still_reports_using_free():
|
||||
# Pre-existing bridge path must still work (cache False, but rate-limited).
|
||||
stats = _worker(serving_via_free=False, sp=object(), rate_limited=True).get_stats()
|
||||
assert stats['using_free'] is True
|
||||
|
||||
|
||||
def test_budget_bridge_still_reports_using_free():
|
||||
stats = _worker(serving_via_free=False, sp=object(), budget_free=True).get_stats()
|
||||
assert stats['using_free'] is True
|
||||
|
||||
|
||||
def test_authed_and_not_on_free_reports_authenticated_not_free():
|
||||
stats = _worker(serving_via_free=False, sp=object()).get_stats()
|
||||
assert stats['authenticated'] is True
|
||||
assert stats['using_free'] is False
|
||||
|
|
@ -455,11 +455,14 @@ function updateSpotifyEnrichmentStatusFromData(data) {
|
|||
const button = document.getElementById('spotify-enrich-button');
|
||||
if (!button) return;
|
||||
|
||||
const notAuthenticated = data.authenticated === false;
|
||||
const isRateLimited = data.rate_limited === true;
|
||||
// The real API is banned but the worker is still matching via the no-creds
|
||||
// Spotify Free source — treat it as running, not stuck (#798 bridge).
|
||||
// The real API is unauthed/banned but the worker is still matching via the
|
||||
// no-creds Spotify Free source — treat it as running, not stuck (#798/#887).
|
||||
const bridgingFree = data.using_free === true;
|
||||
// #887: a no-auth user whose enrichment runs on Spotify Free is NOT "not
|
||||
// authenticated" for status purposes — the worker IS enriching. Only flag
|
||||
// Not Authenticated when Free isn't carrying it.
|
||||
const notAuthenticated = data.authenticated === false && !bridgingFree;
|
||||
const rateLimitedStuck = isRateLimited && !bridgingFree;
|
||||
// Budget is a real-API cap; when bridging to free it no longer applies, so
|
||||
// only treat the budget as a stop when we're NOT serving via free (#798).
|
||||
|
|
|
|||
Loading…
Reference in a new issue