"""Tests für cddb.py — CddbResult, DYEAR/DGENRE-Parsing.""" from unittest.mock import MagicMock, patch 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