Fix and expand tests: 63 tests passing, covers all core modules
Remove tests/ from .gitignore (was accidentally excluded). - test_ripper.py: rewrite for current API (_parse_cddb_lines, _extract_tracks, _rename_files, _clean_input); fix default quality - test_organizer.py: update filename assertions (spaces→underscores); add TestSanitizeFilename, TestCheckDiscCounts, in-place mode - test_playlist.py: fix dummy filenames to underscore scheme; add multi-disc, filename sanitization, EXTINF and fallback tests Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
734bc80b79
commit
775f274d02
7 changed files with 637 additions and 1 deletions
226
tests/test_organizer.py
Normal file
226
tests/test_organizer.py
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
"""Tests für den Organizer."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from musiksammlung.models import Album, Disc, Track
|
||||
from musiksammlung.organizer import (
|
||||
_sanitize_filename,
|
||||
build_mapping,
|
||||
check_disc_counts,
|
||||
discover_audio_files,
|
||||
)
|
||||
|
||||
|
||||
class TestSanitizeFilename:
|
||||
"""Tests für _sanitize_filename."""
|
||||
|
||||
def test_replaces_spaces(self) -> None:
|
||||
assert _sanitize_filename("Hello World") == "Hello_World"
|
||||
|
||||
def test_replaces_punctuation(self) -> None:
|
||||
assert _sanitize_filename("Song (Live)") == "Song_Live"
|
||||
assert _sanitize_filename("Artist: Name") == "Artist_Name"
|
||||
|
||||
def test_collapses_multiple_underscores(self) -> None:
|
||||
assert _sanitize_filename("A B") == "A_B"
|
||||
assert _sanitize_filename("A--B") == "A_B"
|
||||
|
||||
def test_strips_leading_trailing_underscores(self) -> None:
|
||||
assert _sanitize_filename("_Test_") == "Test"
|
||||
|
||||
def test_keeps_umlauts(self) -> None:
|
||||
assert _sanitize_filename("Für Elise") == "Für_Elise"
|
||||
assert _sanitize_filename("Über den Wolken") == "Über_den_Wolken"
|
||||
|
||||
def test_keeps_digits(self) -> None:
|
||||
assert _sanitize_filename("Track 01") == "Track_01"
|
||||
|
||||
|
||||
class TestDiscoverAudioFiles:
|
||||
"""Tests für discover_audio_files."""
|
||||
|
||||
def test_finds_and_sorts_numerically(self, tmp_path: Path) -> None:
|
||||
(tmp_path / "Track_03.flac").touch()
|
||||
(tmp_path / "Track_01.flac").touch()
|
||||
(tmp_path / "Track_02.flac").touch()
|
||||
(tmp_path / "cover.jpg").touch()
|
||||
|
||||
files = discover_audio_files(tmp_path)
|
||||
assert len(files) == 3
|
||||
assert files[0].name == "Track_01.flac"
|
||||
assert files[2].name == "Track_03.flac"
|
||||
|
||||
def test_ignores_non_audio_files(self, tmp_path: Path) -> None:
|
||||
(tmp_path / "track01.flac").touch()
|
||||
(tmp_path / "album.json").touch()
|
||||
(tmp_path / "playlist.m3u").touch()
|
||||
files = discover_audio_files(tmp_path)
|
||||
assert len(files) == 1
|
||||
|
||||
def test_empty_directory(self, tmp_path: Path) -> None:
|
||||
assert discover_audio_files(tmp_path) == []
|
||||
|
||||
|
||||
class TestCheckDiscCounts:
|
||||
"""Tests für check_disc_counts."""
|
||||
|
||||
def _album_with_tracks(self, n: int) -> Album:
|
||||
return Album(
|
||||
artist="Test",
|
||||
album="Album",
|
||||
discs=[
|
||||
Disc(
|
||||
disc_number=1,
|
||||
tracks=[Track(track_number=i, title=f"Track {i}") for i in range(1, n + 1)],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
def test_matching_counts_is_ok(self, tmp_path: Path) -> None:
|
||||
(tmp_path / "track01.flac").touch()
|
||||
(tmp_path / "track02.flac").touch()
|
||||
album = self._album_with_tracks(2)
|
||||
|
||||
results = check_disc_counts(album, tmp_path)
|
||||
assert len(results) == 1
|
||||
assert results[0].ok is True
|
||||
assert results[0].surplus_files == 0
|
||||
assert results[0].surplus_json == 0
|
||||
|
||||
def test_surplus_files(self, tmp_path: Path) -> None:
|
||||
for i in range(1, 4):
|
||||
(tmp_path / f"track0{i}.flac").touch()
|
||||
album = self._album_with_tracks(2) # JSON has 2, disk has 3
|
||||
|
||||
results = check_disc_counts(album, tmp_path)
|
||||
assert results[0].ok is False
|
||||
assert results[0].surplus_files == 1
|
||||
assert results[0].surplus_json == 0
|
||||
|
||||
def test_surplus_json(self, tmp_path: Path) -> None:
|
||||
(tmp_path / "track01.flac").touch()
|
||||
album = self._album_with_tracks(3) # JSON has 3, disk has 1
|
||||
|
||||
results = check_disc_counts(album, tmp_path)
|
||||
assert results[0].ok is False
|
||||
assert results[0].surplus_json == 2
|
||||
assert results[0].surplus_files == 0
|
||||
|
||||
def test_multi_disc(self, tmp_path: Path) -> None:
|
||||
cd1 = tmp_path / "CD1"
|
||||
cd2 = tmp_path / "CD2"
|
||||
cd1.mkdir()
|
||||
cd2.mkdir()
|
||||
(cd1 / "track01.flac").touch()
|
||||
(cd2 / "track01.flac").touch()
|
||||
(cd2 / "track02.flac").touch()
|
||||
|
||||
album = Album(
|
||||
artist="A",
|
||||
album="B",
|
||||
discs=[
|
||||
Disc(disc_number=1, tracks=[Track(track_number=1, title="T1")]),
|
||||
Disc(disc_number=2, tracks=[Track(track_number=1, title="T2"), Track(track_number=2, title="T3")]),
|
||||
],
|
||||
)
|
||||
results = check_disc_counts(album, tmp_path)
|
||||
assert all(r.ok for r in results)
|
||||
|
||||
def test_missing_disc_directory(self, tmp_path: Path) -> None:
|
||||
album = Album(
|
||||
artist="A",
|
||||
album="B",
|
||||
discs=[
|
||||
Disc(disc_number=1, tracks=[Track(track_number=1, title="T")]),
|
||||
Disc(disc_number=2, tracks=[Track(track_number=1, title="T")]),
|
||||
],
|
||||
)
|
||||
# Neither CD1 nor CD2 exists
|
||||
results = check_disc_counts(album, tmp_path)
|
||||
assert all(r.audio_file_count == 0 for r in results)
|
||||
assert all(not r.ok for r in results)
|
||||
|
||||
|
||||
class TestBuildMapping:
|
||||
"""Tests für build_mapping."""
|
||||
|
||||
def test_single_disc(self, tmp_path: Path) -> None:
|
||||
(tmp_path / "Track_01.flac").touch()
|
||||
(tmp_path / "Track_02.flac").touch()
|
||||
|
||||
album = Album(
|
||||
artist="TestArtist",
|
||||
album="TestAlbum",
|
||||
year=2000,
|
||||
discs=[
|
||||
Disc(
|
||||
disc_number=1,
|
||||
tracks=[
|
||||
Track(track_number=1, title="Erster Song"),
|
||||
Track(track_number=2, title="Zweiter Song"),
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
mapping = build_mapping(album, tmp_path, tmp_path / "output")
|
||||
|
||||
targets = list(mapping.values())
|
||||
assert targets[0].name == "01_Erster_Song.flac"
|
||||
assert targets[1].name == "02_Zweiter_Song.flac"
|
||||
# Single-Disc: kein CD1-Unterordner
|
||||
assert "CD1" not in str(targets[0])
|
||||
|
||||
def test_multi_disc(self, tmp_path: Path) -> None:
|
||||
cd1 = tmp_path / "CD1"
|
||||
cd2 = tmp_path / "CD2"
|
||||
cd1.mkdir()
|
||||
cd2.mkdir()
|
||||
(cd1 / "Track_01.flac").touch()
|
||||
(cd2 / "Track_01.flac").touch()
|
||||
|
||||
album = Album(
|
||||
artist="Artist",
|
||||
album="Box Set",
|
||||
year=1999,
|
||||
discs=[
|
||||
Disc(disc_number=1, tracks=[Track(track_number=1, title="Song A")]),
|
||||
Disc(disc_number=2, tracks=[Track(track_number=1, title="Song B")]),
|
||||
],
|
||||
)
|
||||
mapping = build_mapping(album, tmp_path, tmp_path / "output")
|
||||
targets = list(mapping.values())
|
||||
assert "CD1" in str(targets[0])
|
||||
assert "CD2" in str(targets[1])
|
||||
|
||||
def test_in_place_mode(self, tmp_path: Path) -> None:
|
||||
(tmp_path / "track01.flac").touch()
|
||||
album = Album(
|
||||
artist="Artist",
|
||||
album="Album",
|
||||
discs=[Disc(disc_number=1, tracks=[Track(track_number=1, title="Song")])],
|
||||
)
|
||||
mapping = build_mapping(album, tmp_path, in_place=True)
|
||||
|
||||
src, dst = next(iter(mapping.items()))
|
||||
# Source and target are in the same directory
|
||||
assert src.parent == tmp_path
|
||||
assert dst.parent == tmp_path
|
||||
assert dst.name == "01_Song.flac"
|
||||
|
||||
def test_output_dir_structure(self, tmp_path: Path) -> None:
|
||||
(tmp_path / "track01.flac").touch()
|
||||
album = Album(
|
||||
artist="Karl Böhm",
|
||||
album="Beethoven Sinfonien",
|
||||
year=1970,
|
||||
discs=[Disc(disc_number=1, tracks=[Track(track_number=1, title="Allegro")])],
|
||||
)
|
||||
output = tmp_path / "Musik"
|
||||
mapping = build_mapping(album, tmp_path, output)
|
||||
|
||||
dst = next(iter(mapping.values()))
|
||||
# Artist and album dirs are sanitized
|
||||
assert "Karl_Böhm" in str(dst)
|
||||
assert "Beethoven_Sinfonien" in str(dst)
|
||||
Loading…
Add table
Add a link
Reference in a new issue