Files
feedBack/tests/test_albums_view.py
T
0a8c8945ea v3 library: Albums view — the client half of the album-condense work (#716)
* v3 library: Albums view — the client half of the album-condense work

Follow-up to the query_albums endpoint: the UI that consumes it, plus the
track-order plumbing the endpoint's track list needs.

- Albums view (a fourth view toggle next to grid/tree/folder): album cards
  (cover / title / artist / track count) from /api/library/albums,
  respecting the active filter drawer; clicking one opens the track list
  with per-track play and a Play-album button that feeds the play queue
  (falls back to plain playSong when the queue plugin is absent).
- Track order: the scanner now reads the feedpak `track`/`disc` fields
  (spec 1.12.0) into new nullable songs columns (idempotent ALTERs), and
  the album track list orders by the new `track` sort — disc, then track
  number, unauthored charts to the bottom by title. Charts without
  authored numbers keep working; they just sort alphabetically.
- The albums view persists like the other view choices.

3 new tests: manifest track/disc extraction (and unauthored -> None),
the disc->track->title sort order over /api/library, and the put()
round-trip. Full-suite failure set matches the known env baseline (one
tuner-config name swapped inside the suite-ordering flake family — the
file passes 25/25 in isolation on clean main).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nm7tHs1Yvjjtnnu4nzJgdN

* v3 albums: honour Genre/Match filters in album grid + detail (review fixes)

The Albums view only partially respected the filter drawer:

- /api/library/albums silently dropped the `genre` and `match` params the
  client sends via queryParams(), so with a Genre or Match filter active the
  album grid surfaced albums with zero matching tracks. Thread match_states/
  genre through the endpoint -> query_albums -> _build_where, mirroring the
  /api/library grid route. (SmartCollection/pass-through providers keep their
  existing kwarg handling.)

- The album-detail track list built its own params (provider/artist/album/
  sort only), so it ignored ALL active filters — the track list and the
  Play-album queue could include songs the user had filtered out. Reuse
  queryParams({...}, {catalog: true}) so detail honours the same filters as
  the grid while pinning this album's artist/album and track order.

+1 regression test (albums endpoint honours the Genre filter).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
2026-07-02 15:57:26 +02:00

94 lines
3.7 KiB
Python

"""Tests for the Albums-view follow-up (#689's client half): the feedpak
`track`/`disc` fields flowing scanner → songs columns → the `track` sort the
album track list orders by."""
import importlib
import sys
import pytest
from fastapi.testclient import TestClient
@pytest.fixture()
def server(tmp_path, monkeypatch, isolate_logging):
monkeypatch.setenv("CONFIG_DIR", str(tmp_path / "config"))
dlc = tmp_path / "dlc"
dlc.mkdir()
monkeypatch.setenv("DLC_DIR", str(dlc))
monkeypatch.setenv("FEEDBACK_SKIP_STARTUP_TASKS", "1")
sys.modules.pop("server", None)
srv = importlib.import_module("server")
try:
yield srv
finally:
conn = getattr(getattr(srv, "meta_db", None), "conn", None)
if conn is not None:
conn.close()
sys.modules.pop("server", None)
@pytest.fixture()
def client(server):
return TestClient(server.app)
def _put(server, fn, title, track=None, disc=None, album="The Album",
artist="Artist", genre=""):
server.meta_db.put(fn, 0, 0, {
"title": title, "artist": artist, "album": album, "year": "1990",
"duration": 100, "arrangements": [{"name": "Lead", "index": 0}],
"track_number": track, "disc": disc, "genre": genre,
})
def test_sloppak_extract_meta_reads_track_and_disc(server):
d = server.DLC_DIR / "a.sloppak"
d.mkdir(parents=True)
(d / "manifest.yaml").write_text(
"title: Song\nartist: Artist\nduration: 100\n"
"arrangements: []\nstems: []\ntrack: 7\ndisc: 2\n", encoding="utf-8")
import sloppak
meta = sloppak.extract_meta(d)
assert meta["track_number"] == 7
assert meta["disc"] == 2
# Unauthored → None (the album view falls back to title order).
(d / "manifest.yaml").write_text(
"title: Song\nartist: Artist\nduration: 100\n"
"arrangements: []\nstems: []\n", encoding="utf-8")
meta = sloppak.extract_meta(d)
assert meta["track_number"] is None
assert meta["disc"] is None
def test_track_sort_orders_by_disc_then_track_nulls_last(server, client):
_put(server, "d2t1.sloppak", "Zeta", track=1, disc=2)
_put(server, "d1t2.sloppak", "Yankee", track=2, disc=1)
_put(server, "d1t1.sloppak", "Xray", track=1, disc=1)
_put(server, "nonum-b.sloppak", "Bravo") # unauthored → bottom,
_put(server, "nonum-a.sloppak", "Alpha") # ordered by title
body = client.get("/api/library", params={
"artist": "Artist", "album": "The Album", "sort": "track", "size": 50}).json()
assert [s["filename"] for s in body["songs"]] == [
"d1t1.sloppak", "d1t2.sloppak", "d2t1.sloppak",
"nonum-a.sloppak", "nonum-b.sloppak"]
def test_track_and_disc_survive_put_roundtrip(server):
_put(server, "a.sloppak", "Song", track=3, disc=1)
row = server.meta_db.conn.execute(
"SELECT track_number, disc FROM songs WHERE filename = 'a.sloppak'").fetchone()
assert row == (3, 1)
def test_albums_endpoint_honours_genre_filter(server, client):
"""The albums grid must respect the Genre drawer filter the client sends —
without this the /api/library/albums route silently dropped `genre` and
surfaced albums with no matching tracks."""
_put(server, "rock.sloppak", "Rocker", album="Rock LP", genre="Rock")
_put(server, "jazz.sloppak", "Smooth", album="Jazz LP", genre="Jazz")
all_albums = client.get("/api/library/albums", params={"artist": "Artist"}).json()
assert {a["album"] for a in all_albums["albums"]} == {"Rock LP", "Jazz LP"}
filtered = client.get("/api/library/albums",
params={"artist": "Artist", "genre": "Rock"}).json()
assert [a["album"] for a in filtered["albums"]] == ["Rock LP"]