EAN-first workflow in interactive_rip + GnuDB DYEAR/DGENRE parsing
EAN is now asked before the album name. On MusicBrainz hit, the ripper enters an auto-rip flow (no album name prompt, no CDDB confirm, disc count from MB data). On miss/empty EAN, the previous fallback flow (album name → CDDB confirm) is preserved. GnuDB responses now parse DYEAR and DGENRE fields into a new CddbResult NamedTuple. Album model gains an optional genre field. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
90713452c2
commit
8b449493cd
5 changed files with 510 additions and 196 deletions
170
tests/test_cddb.py
Normal file
170
tests/test_cddb.py
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
"""Tests für cddb.py — CddbResult, DYEAR/DGENRE-Parsing."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import httpx
|
||||
|
||||
from musiksammlung.cddb import CddbResult, _read_gnudb, lookup_by_discid
|
||||
|
||||
|
||||
def _make_xmcd_response(
|
||||
dtitle: str = "Artist / Album Title",
|
||||
dyear: str = "",
|
||||
dgenre: str = "",
|
||||
ttitles: dict[int, str] | None = None,
|
||||
) -> str:
|
||||
"""Baut eine GnuDB-xmcd-Antwort zusammen."""
|
||||
lines = ["210 OK"]
|
||||
lines.append(f"DTITLE={dtitle}")
|
||||
if dyear:
|
||||
lines.append(f"DYEAR={dyear}")
|
||||
if dgenre:
|
||||
lines.append(f"DGENRE={dgenre}")
|
||||
if ttitles is None:
|
||||
ttitles = {0: "Track One", 1: "Track Two"}
|
||||
for idx, title in sorted(ttitles.items()):
|
||||
lines.append(f"TTITLE{idx}={title}")
|
||||
lines.append(".")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
class TestReadGnudbCddbResult:
|
||||
"""Tests für _read_gnudb mit CddbResult-Rückgabe."""
|
||||
|
||||
def _call(self, xmcd_text: str) -> CddbResult | None:
|
||||
"""Ruft _read_gnudb mit gemockter HTTP-Antwort auf."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.text = xmcd_text
|
||||
mock_response.raise_for_status = MagicMock()
|
||||
|
||||
with patch("musiksammlung.cddb.httpx.get", return_value=mock_response):
|
||||
with patch("musiksammlung.cddb.time.sleep"):
|
||||
return _read_gnudb("rock", "ab0c1d0e")
|
||||
|
||||
def test_basic_result_fields(self) -> None:
|
||||
"""CddbResult enthält Artist, Album, Tracks."""
|
||||
text = _make_xmcd_response(dtitle="Beatles / Abbey Road")
|
||||
result = self._call(text)
|
||||
|
||||
assert result is not None
|
||||
assert isinstance(result, CddbResult)
|
||||
assert result.artist == "Beatles"
|
||||
assert result.album == "Abbey Road"
|
||||
assert len(result.tracks) == 2
|
||||
assert result.tracks[0].track_number == 1
|
||||
assert result.tracks[0].title == "Track One"
|
||||
|
||||
def test_dyear_parsed(self) -> None:
|
||||
"""DYEAR wird als int geparst."""
|
||||
text = _make_xmcd_response(dyear="1969")
|
||||
result = self._call(text)
|
||||
|
||||
assert result is not None
|
||||
assert result.year == 1969
|
||||
|
||||
def test_dyear_empty(self) -> None:
|
||||
"""Kein DYEAR → year=None."""
|
||||
text = _make_xmcd_response(dyear="")
|
||||
result = self._call(text)
|
||||
|
||||
assert result is not None
|
||||
assert result.year is None
|
||||
|
||||
def test_dyear_invalid(self) -> None:
|
||||
"""Ungültiges DYEAR → year=None."""
|
||||
text = _make_xmcd_response(dyear="unknown")
|
||||
result = self._call(text)
|
||||
|
||||
assert result is not None
|
||||
assert result.year is None
|
||||
|
||||
def test_dgenre_parsed(self) -> None:
|
||||
"""DGENRE wird übernommen."""
|
||||
text = _make_xmcd_response(dgenre="Classical")
|
||||
result = self._call(text)
|
||||
|
||||
assert result is not None
|
||||
assert result.genre == "Classical"
|
||||
|
||||
def test_dgenre_empty(self) -> None:
|
||||
"""Kein DGENRE → genre=''."""
|
||||
text = _make_xmcd_response()
|
||||
result = self._call(text)
|
||||
|
||||
assert result is not None
|
||||
assert result.genre == ""
|
||||
|
||||
def test_dyear_and_dgenre_together(self) -> None:
|
||||
"""Beide Felder gleichzeitig."""
|
||||
text = _make_xmcd_response(
|
||||
dtitle="Karajan / Beethoven Sinfonien",
|
||||
dyear="1985",
|
||||
dgenre="Classical",
|
||||
)
|
||||
result = self._call(text)
|
||||
|
||||
assert result is not None
|
||||
assert result.artist == "Karajan"
|
||||
assert result.album == "Beethoven Sinfonien"
|
||||
assert result.year == 1985
|
||||
assert result.genre == "Classical"
|
||||
|
||||
def test_no_ttitles_returns_none(self) -> None:
|
||||
"""Keine TTITLEs → None."""
|
||||
text = _make_xmcd_response(ttitles={})
|
||||
result = self._call(text)
|
||||
|
||||
assert result is None
|
||||
|
||||
def test_dtitle_without_slash(self) -> None:
|
||||
"""DTITLE ohne ' / ' → artist leer, album = gesamter DTITLE."""
|
||||
text = _make_xmcd_response(dtitle="Just An Album Name")
|
||||
result = self._call(text)
|
||||
|
||||
assert result is not None
|
||||
assert result.artist == ""
|
||||
assert result.album == "Just An Album Name"
|
||||
|
||||
def test_bad_status_code_returns_none(self) -> None:
|
||||
"""Unerwarteter Statuscode → None."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.text = "401 permission denied\n"
|
||||
mock_response.raise_for_status = MagicMock()
|
||||
|
||||
with patch("musiksammlung.cddb.httpx.get", return_value=mock_response):
|
||||
with patch("musiksammlung.cddb.time.sleep"):
|
||||
result = _read_gnudb("rock", "ab0c1d0e")
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestLookupByDiscidCddbResult:
|
||||
"""Tests dass lookup_by_discid CddbResult zurückgibt."""
|
||||
|
||||
def test_returns_cddb_result(self) -> None:
|
||||
"""Erfolgreicher Lookup liefert CddbResult."""
|
||||
expected = CddbResult(
|
||||
tracks=[],
|
||||
artist="Test",
|
||||
album="Album",
|
||||
year=2000,
|
||||
genre="Pop",
|
||||
)
|
||||
with (
|
||||
patch("musiksammlung.cddb._query_gnudb", return_value=("rock", "ab0c1d0e")),
|
||||
patch("musiksammlung.cddb._read_gnudb", return_value=expected),
|
||||
):
|
||||
result = lookup_by_discid("ab0c1d0e 2 150 1000 200")
|
||||
|
||||
assert result is expected
|
||||
|
||||
def test_returns_none_on_no_match(self) -> None:
|
||||
"""Kein Treffer → None."""
|
||||
with (
|
||||
patch("musiksammlung.cddb._query_gnudb", return_value=None),
|
||||
patch("musiksammlung.cddb.time.sleep"),
|
||||
patch("musiksammlung.cddb.random.uniform", return_value=0.01),
|
||||
):
|
||||
result = lookup_by_discid("ab0c1d0e 2 150 1000 200", retries=0)
|
||||
|
||||
assert result is None
|
||||
Loading…
Add table
Add a link
Reference in a new issue