Files
musicfetch/tests/test_repair.py
zebra fdc3cc84a5 feat: --repair flag to re-tag existing downloads from source metadata
Walks <root>/<artist>/<source>/ (known yt-dlp source folders only; skips Lidarr
album dirs), re-queries each file's source by the [id] in its filename, and fixes
tags (album/year/artist/title) via mutagen. Honors --dry-run for preview. CLI-only
(not the REST API). Fixes downloads that landed with missing album / wrong year.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 19:11:43 -07:00

119 lines
5.0 KiB
Python

import server.mf # noqa: F401 — loads musicfetch, registers musicfetch_core
import musicfetch_core as mf
# ---- _is_source_dir ----
def test_is_source_dir():
assert mf._is_source_dir("youtube") is True
assert mf._is_source_dir("soundcloud") is True
assert mf._is_source_dir("downloads") is True
assert mf._is_source_dir("Discovery") is False # Lidarr album folder
assert mf._is_source_dir("Random Access Memories") is False
assert mf._is_source_dir("") is False
# ---- _parse_track_file ----
def test_parse_track_file():
assert mf._parse_track_file("Under My Skin [nGSNF2l44Zc].opus") == ("Under My Skin", "nGSNF2l44Zc")
assert mf._parse_track_file("Ignomon [2202690443].m4a") == ("Ignomon", "2202690443")
assert mf._parse_track_file("no-id-here.opus") is None
assert mf._parse_track_file("cover.jpg") is None
# ---- _repair_probe_url ----
def test_repair_probe_url():
assert mf._repair_probe_url("youtube", "vid") == "https://music.youtube.com/watch?v=vid"
assert mf._repair_probe_url("soundcloud", "123") == "https://api.soundcloud.com/tracks/123"
assert mf._repair_probe_url("bandcamp", "x") is None
# ---- _desired_tags ----
def test_desired_tags_full():
meta = {"artist": "Daft Punk", "title": "Aerodynamic", "album": "Discovery", "release_year": 2001}
assert mf._desired_tags(meta) == {"artist": "Daft Punk", "title": "Aerodynamic",
"album": "Discovery", "date": "2001"}
def test_desired_tags_year_fallbacks_and_omits_empty():
meta = {"title": "T", "uploader": "Chan", "upload_date": "20230102"} # no album, no release_year
out = mf._desired_tags(meta)
assert out["date"] == "2023"
assert out["title"] == "T"
assert out["artist"] == "Chan"
assert "album" not in out # omitted when empty
def test_desired_tags_drops_unknown_artist():
meta = {"title": "T"} # get_artist_from_metadata -> "Unknown Artist"
assert "artist" not in mf._desired_tags(meta)
# ---- repair_file (fake audio + mocked metadata) ----
class _FakeAudio(dict):
def __init__(self, initial):
super().__init__(initial)
self.saved = False
def save(self):
self.saved = True
def test_repair_file_writes_changed_fields(monkeypatch):
monkeypatch.setattr(mf, "run_yt_dlp_get_metadata",
lambda url: {"artist": "Daft Punk", "title": "Aerodynamic",
"album": "Discovery", "release_year": 2001})
audio = _FakeAudio({"artist": ["Daft Punk"], "title": ["Aerodynamic"]}) # album/date missing
monkeypatch.setattr(mf, "_open_audio", lambda path: (audio, None))
changed = mf.repair_file("X/youtube/Aerodynamic [vid].opus", "youtube", dry_run=False)
assert set(changed) == {"album=Discovery", "date=2001"}
assert audio["album"] == ["Discovery"]
assert audio["date"] == ["2001"]
assert audio.saved is True
def test_repair_file_dry_run_writes_nothing(monkeypatch):
monkeypatch.setattr(mf, "run_yt_dlp_get_metadata",
lambda url: {"artist": "A", "title": "T", "album": "Alb", "release_year": 2020})
audio = _FakeAudio({})
monkeypatch.setattr(mf, "_open_audio", lambda path: (audio, None))
changed = mf.repair_file("X/youtube/T [vid].opus", "youtube", dry_run=True)
assert changed # reports would-change
assert audio == {} # nothing written
assert audio.saved is False
def test_repair_file_skips_unparseable(monkeypatch):
called = {"meta": False}
monkeypatch.setattr(mf, "run_yt_dlp_get_metadata",
lambda url: called.update(meta=True) or {})
assert mf.repair_file("X/youtube/no-id.opus", "youtube", dry_run=False) == []
assert called["meta"] is False # never hit the network
def test_repair_file_skips_unqueryable_source(monkeypatch):
monkeypatch.setattr(mf, "run_yt_dlp_get_metadata", lambda url: {"title": "x"})
assert mf.repair_file("X/bandcamp/T [id].m4a", "bandcamp", dry_run=False) == []
# ---- repair_library (real temp tree, repair_file mocked) ----
def test_repair_library_scans_only_source_dirs(tmp_path, monkeypatch):
root = tmp_path
(root / "Daft Punk" / "youtube").mkdir(parents=True)
(root / "Daft Punk" / "youtube" / "Aerodynamic [vid].opus").write_text("x")
(root / "Daft Punk" / "Discovery").mkdir(parents=True) # Lidarr album -> skip
(root / "Daft Punk" / "Discovery" / "Aerodynamic.flac").write_text("x")
(root / "Ephixa" / "soundcloud").mkdir(parents=True)
(root / "Ephixa" / "soundcloud" / "Ignomon [123].m4a").write_text("x")
visited = []
monkeypatch.setattr(mf, "repair_file",
lambda path, source, dry_run: visited.append((source, path)) or ["album=X"])
scanned, changed = mf.repair_library(str(root), dry_run=False)
assert scanned == 2 and changed == 2
sources = sorted(s for s, _ in visited)
assert sources == ["soundcloud", "youtube"] # Discovery album folder skipped
def test_repair_library_missing_root(monkeypatch):
assert mf.repair_library("/no/such/dir", dry_run=False) == (0, 0)