diff --git a/tests/test_track_search_single_query.py b/tests/test_track_search_single_query.py new file mode 100644 index 00000000..061e3f3e --- /dev/null +++ b/tests/test_track_search_single_query.py @@ -0,0 +1,99 @@ +"""Unit tests for api_search_tracks (fix 2.2). + +The track search endpoint previously called search_tracks() then +api_get_tracks_by_ids() to re-hydrate the same rows. api_search_tracks +now returns the full dict rows in a single query. +""" + +import pytest + +from database.music_database import MusicDatabase + + +@pytest.fixture +def db(tmp_path): + return MusicDatabase(str(tmp_path / "music.db")) + + +def _insert_track(db, track_id, title, artist_id, artist_name, album_id, album_title): + with db._get_connection() as conn: + conn.execute( + "INSERT OR IGNORE INTO artists (id, name) VALUES (?, ?)", + (artist_id, artist_name), + ) + conn.execute( + "INSERT OR IGNORE INTO albums (id, title, artist_id) VALUES (?, ?, ?)", + (album_id, album_title, artist_id), + ) + conn.execute( + """INSERT INTO tracks (id, album_id, artist_id, title, track_number, duration, file_path) + VALUES (?, ?, ?, ?, 1, 180, ?)""", + (track_id, album_id, artist_id, title, f"/music/{title}.mp3"), + ) + conn.commit() + + +def test_api_search_tracks_returns_dict_rows_with_full_columns(db): + _insert_track(db, 100, "Great Song", 1, "Band One", 10, "Album X") + + results = db.api_search_tracks(title="great") + + assert len(results) == 1 + row = results[0] + assert isinstance(row, dict) + assert str(row["id"]) == "100" + assert row["title"] == "Great Song" + assert row["artist_name"] == "Band One" + assert row["album_title"] == "Album X" + # file_path is a tracks.* column that must be present + assert row["file_path"] == "/music/Great Song.mp3" + + +def test_api_search_tracks_empty_query_returns_empty(db): + _insert_track(db, 1, "Track", 1, "Artist", 1, "Album") + assert db.api_search_tracks() == [] + + +def test_api_search_tracks_no_matches_returns_empty(db): + _insert_track(db, 1, "Alpha", 1, "Artist A", 1, "Album A") + assert db.api_search_tracks(title="nonexistent") == [] + + +def test_api_search_tracks_matches_by_artist(db): + _insert_track(db, 1, "Song A", 1, "Mystery Band", 1, "Album A") + _insert_track(db, 2, "Song B", 2, "Other Artist", 2, "Album B") + + results = db.api_search_tracks(artist="mystery") + assert len(results) == 1 + assert str(results[0]["id"]) == "1" + + +def test_api_search_tracks_respects_limit(db): + for i in range(10): + _insert_track(db, i + 1, f"Common Title {i}", 1, "Artist", 1, "Album") + + results = db.api_search_tracks(title="common", limit=3) + assert len(results) == 3 + + +def test_api_search_tracks_falls_back_to_fuzzy(db): + """When basic LIKE returns nothing, fuzzy fallback should still find matches.""" + _insert_track(db, 1, "Bohemian Rhapsody", 1, "Queen", 1, "A Night at the Opera") + + # Basic search "bohemian" matches directly; the fuzzy path exists for + # cases where basic fails. Verify at least that the method surfaces a + # result for a direct query. + results = db.api_search_tracks(title="bohemian rhapsody") + assert any(str(r["id"]) == "1" for r in results) + + +def test_search_tracks_still_returns_database_track_objects(db): + """Existing callers rely on the DatabaseTrack return shape of search_tracks().""" + _insert_track(db, 1, "Legacy Track", 1, "Legacy Artist", 1, "Legacy Album") + + results = db.search_tracks(title="legacy") + assert len(results) == 1 + # DatabaseTrack exposes attributes, not dict keys. + assert str(results[0].id) == "1" + assert results[0].title == "Legacy Track" + assert results[0].artist_name == "Legacy Artist"