soulsync/tests/discovery
Broque Thomas a38bfcba55 PR5h: lift _run_quality_scanner to core/discovery/quality_scanner.py
Final lift in the PR5 discovery-workers series. Pulls the 328-line
library quality scanner out of `web_server.py` into its own focused
module under `core/discovery/`. Pure 1:1 lift — wrapper keeps the
original entry-point name.

What the quality scanner does:

1. Reset scanner state (counters, results), load quality profile +
   minimum acceptable tier from QUALITY_TIERS.
2. Load tracks from DB based on scope:
   - 'watchlist' → tracks for watchlisted artists only.
   - other → all library tracks.
3. For each track:
   - Stop-request gate (state['status'] != 'running').
   - Quality-tier check via _get_quality_tier_from_extension(file_path).
   - Skip tracks meeting standards (tier_num <= min_acceptable_tier).
   - For low-quality tracks: matching_engine search query gen, score
     candidates against Spotify (artist + title similarity, album-type
     bonus), pick best match >= 0.7 confidence.
   - On match: add full Spotify track to wishlist via
     `wishlist_service.add_spotify_track_to_wishlist` with
     source_type='quality_scanner' and a source_context that captures
     original file_path, format tier, bitrate, and match confidence.
4. After all tracks: status='finished', progress=100, activity feed
   entry, emit `quality_scan_completed` event for automation engine.
5. On critical exception: status='error', error message captured.

Wishlist service interaction is via the public
`add_spotify_track_to_wishlist` API only — no overlap with kettui's
planned `core/wishlist/` package extraction (the import lives inside
the function, exactly as in the original, and will follow whatever
path that package takes).

Dependencies injected via `QualityScannerDeps` (8 fields) —
quality_scanner_state dict, quality_scanner_lock, QUALITY_TIERS
constant, spotify_client, matching_engine, automation_engine, plus 2
callable helpers (get_quality_tier_from_extension, add_activity_item).

Diff vs original after `deps.X` → global X normalization is **zero
differences** — 328 lines orig = 328 lines lifted, byte-identical body
(including all whitespace, comments, log strings, and the inline
`from core.wishlist_service import get_wishlist_service` /
`from database.music_database import MusicDatabase` imports at the
top of the function).

Tests: 11 new under tests/discovery/test_discovery_quality_scanner.py
covering state init/reset, no-watchlist-artists short-circuit,
unauthenticated Spotify error, high-quality skip, low-quality search
trigger, match → wishlist add (with full source_context payload),
no-match no-add, mid-loop stop request, completion phase + progress,
automation engine event emission, all-library scope load.

Full suite: 1152 passing (was 1141). Ruff clean.

End of the PR5 series — `web_server.py` lost ~328 lines on this commit
alone; total trim across PR5a–PR5h is ~2,400 lines of discovery worker
code moved into focused `core/discovery/*.py` modules. The remaining
discovery-adjacent worker `_process_watchlist_scan_automatically` was
deliberately deferred to avoid overlap with kettui's planned wishlist
extraction.
2026-04-28 18:41:29 -07:00
..
__init__.py PR5a: lift _run_sync_task to core/discovery/sync.py 2026-04-28 11:20:47 -07:00
test_discovery_beatport.py PR5f: lift _run_beatport_discovery_worker to core/discovery/beatport.py 2026-04-28 15:36:15 -07:00
test_discovery_deezer.py PR5d: lift _run_deezer_discovery_worker to core/discovery/deezer.py 2026-04-28 12:45:18 -07:00
test_discovery_listenbrainz.py PR5g: lift _run_listenbrainz_discovery_worker to core/discovery/listenbrainz.py 2026-04-28 16:09:02 -07:00
test_discovery_playlist.py PR5c: lift _run_playlist_discovery_worker to core/discovery/playlist.py 2026-04-28 12:20:48 -07:00
test_discovery_quality_scanner.py PR5h: lift _run_quality_scanner to core/discovery/quality_scanner.py 2026-04-28 18:41:29 -07:00
test_discovery_spotify_public.py PR5e: lift _run_spotify_public_discovery_worker to core/discovery/spotify_public.py 2026-04-28 13:13:41 -07:00
test_discovery_sync.py PR5a: lift _run_sync_task to core/discovery/sync.py 2026-04-28 11:20:47 -07:00
test_discovery_youtube.py fix: cast duration_ms to int before :02d format in discovery workers 2026-04-28 12:07:54 -07:00