Three changes folded into one perf+cleanup pass: 1. Indexed fast path for the per-artist pool fetch. The previous `search_tracks(artist=name)` call hit `unidecode_lower(artists.name) LIKE ?`, a function-in-WHERE that can't use `idx_artists_name`. New `MusicDatabase.get_artist_tracks_indexed` does a two-step lookup: exact-name match (indexed) plus a case-insensitive fallback, then `tracks WHERE artist_id IN (...)` via `idx_tracks_artist_id`. Drops per-artist fetch from seconds to milliseconds for the common case. The sync helper falls back to the old LIKE-based `search_tracks` only when the indexed lookup finds nothing, preserving diacritic recall and `tracks.track_artist` feature-artist matches with zero regression. 2. Public text-normalization helper. Lifted the body of `MusicDatabase._normalize_for_comparison` into `core/text/normalize.py:normalize_for_comparison` so callers outside the database layer (matching engine, sync pool, future import-side comparisons) don't reach across the module boundary into a leading-underscore "private" method. The DB method now delegates, so existing internal call sites stay untouched. Sync's lazy pool now imports the public helper. 3. Artist-name walker extracted. `_artist_name` at module level in `services/sync_service.py` replaces two near-identical inline str-or-dict-or-fallback walkers (one in `sync_playlist`, one in `_find_track_in_media_server`). Returns `''` for None instead of the literal string `'None'`. Plus three small tidies from the same review: - `_POOL_FETCH_LIMIT = 10000` constant in place of the literal at the pool-fetch call site. - Trimmed the verbose docstring + comment block on the pool helper. - Set-intersection predicate for the trigger-shape reset in `core/automation/api.py` instead of a two-line `or` chain. Also removed the duplicate `_get_active_media_client()` call at sync_service.py:212/214 — pre-existing wart that was sitting in the same block I was editing. Tests: 21 new tests across `tests/database/`, `tests/sync/`, and `tests/text/`, plus updates to the existing pool tests to cover the new fast/fallback split. Full suite stays green (3953 passing).
38 lines
1.4 KiB
Python
38 lines
1.4 KiB
Python
"""Tests for `core.text.normalize.normalize_for_comparison`."""
|
|
|
|
from core.text.normalize import normalize_for_comparison
|
|
|
|
|
|
def test_empty_input_returns_empty_string():
|
|
assert normalize_for_comparison("") == ""
|
|
assert normalize_for_comparison(None) == "" # type: ignore[arg-type]
|
|
|
|
|
|
def test_lowercases_ascii():
|
|
assert normalize_for_comparison("Drake") == "drake"
|
|
assert normalize_for_comparison("DRAKE") == "drake"
|
|
|
|
|
|
def test_strips_surrounding_whitespace():
|
|
assert normalize_for_comparison(" Drake ") == "drake"
|
|
assert normalize_for_comparison("\tDrake\n") == "drake"
|
|
|
|
|
|
def test_folds_accents_to_ascii():
|
|
"""Diacritic-different spellings of the same artist must collapse to
|
|
one normalized key — otherwise the pool would re-fetch the same
|
|
artist when the playlist and library disagree on casing/accents."""
|
|
assert normalize_for_comparison("Beyoncé") == "beyonce"
|
|
assert normalize_for_comparison("Björk") == "bjork"
|
|
assert normalize_for_comparison("Subcarpaţi") == "subcarpati"
|
|
|
|
|
|
def test_combines_lowercase_and_accent_folding():
|
|
assert normalize_for_comparison("BEYONCÉ") == "beyonce"
|
|
|
|
|
|
def test_preserves_internal_whitespace():
|
|
"""Multi-word artist names must keep their internal spacing — only
|
|
leading/trailing whitespace is stripped."""
|
|
assert normalize_for_comparison("Bon Iver") == "bon iver"
|
|
assert normalize_for_comparison("Tame Impala") == "tame impala"
|