Files
musicfetch/tests/test_lidarr_library.py

269 lines
12 KiB
Python

"""Library-aware Lidarr behaviour: never add a duplicate artist, never grab a
random album, and describe a release in words a person understands."""
import server.mf # noqa: F401 — loads musicfetch, registers musicfetch_core
import musicfetch_core as mf
JACKO = {"id": 3, "artistName": "Michael Jackson", "foreignArtistId": "mj-mbid"}
def _no_add(*_a, **_kw):
raise AssertionError("add_artist must not be called for an existing artist")
# --- find_library_artist / ensure_artist -----------------------------------
def test_find_library_artist_matches_name_case_insensitively(monkeypatch):
monkeypatch.setattr(mf, "library_artists", lambda: [JACKO])
assert mf.find_library_artist(name="michael jackson") == JACKO
def test_find_library_artist_matches_mbid_when_name_differs(monkeypatch):
monkeypatch.setattr(mf, "library_artists", lambda: [JACKO])
assert mf.find_library_artist(name="Jacko", foreign_id="mj-mbid") == JACKO
def test_find_library_artist_returns_none_when_absent(monkeypatch):
monkeypatch.setattr(mf, "library_artists", lambda: [JACKO])
assert mf.find_library_artist(name="Overgrown") is None
def test_ensure_artist_never_adds_a_duplicate(monkeypatch):
monkeypatch.setattr(mf, "library_artists", lambda: [JACKO])
monkeypatch.setattr(mf, "add_artist", _no_add)
res = mf.ensure_artist({"artistName": "Michael Jackson"}, "/media/music", False, False)
assert res["state"] == "existing"
assert res["artist"] == JACKO
assert res["name"] == "Michael Jackson"
def test_ensure_artist_adds_when_missing(monkeypatch):
monkeypatch.setattr(mf, "library_artists", lambda: [])
monkeypatch.setattr(mf, "add_artist",
lambda meta, root, search_all, dry_run: {"id": 9, "artistName": "Overgrown"})
res = mf.ensure_artist({"artistName": "Overgrown", "foreignArtistId": "og"},
"/media/music", False, False)
assert res["state"] == "added"
assert res["artist"]["id"] == 9
def test_ensure_artist_reports_failure(monkeypatch):
monkeypatch.setattr(mf, "library_artists", lambda: [])
monkeypatch.setattr(mf, "add_artist", lambda *a, **kw: None)
res = mf.ensure_artist({"artistName": "Overgrown"}, "/media/music", False, False)
assert res["state"] == "failed"
assert res["artist"] is None
def test_act_lidarr_artist_does_not_add_existing(monkeypatch, capsys):
monkeypatch.setattr(mf, "library_artists", lambda: [JACKO])
monkeypatch.setattr(mf, "add_artist", _no_add)
hit = mf.Hit(source="lidarr", kind="artist", title="Michael Jackson",
artist="Michael Jackson", payload={"artist": {"artistName": "Michael Jackson"}})
assert mf.act_lidarr_artist(hit, "/media/music", False, False) is True
assert "already in Lidarr" in capsys.readouterr().out
# --- album matching --------------------------------------------------------
LIB_ALBUMS = [
{"id": 11, "title": "Off the Wall", "foreignAlbumId": "otw"},
{"id": 12, "title": "Thriller (Deluxe Edition)", "foreignAlbumId": "thr-deluxe"},
]
def test_match_album_prefers_the_musicbrainz_id():
got = mf._match_album(LIB_ALBUMS, {"title": "Something Else", "foreignAlbumId": "otw"})
assert got["id"] == 11
def test_match_album_matches_title_ignoring_case_and_punctuation():
got = mf._match_album(LIB_ALBUMS, {"title": "off the wall!"})
assert got["id"] == 11
def test_match_album_ignores_an_edition_suffix():
got = mf._match_album(LIB_ALBUMS, {"title": "Thriller"})
assert got["id"] == 12
def test_match_album_returns_none_instead_of_a_random_album():
assert mf._match_album(LIB_ALBUMS, {"title": "Bad"}) is None
def test_ensure_album_reports_missing_album_rather_than_guessing(monkeypatch):
monkeypatch.setattr(mf, "library_artists", lambda: [JACKO])
monkeypatch.setattr(mf, "_artist_albums", lambda artist_id: LIB_ALBUMS)
res = mf.ensure_album({"title": "Bad", "artist": {"artistName": "Michael Jackson"}},
"/media/music", False, False)
assert res["album"] is None
assert res["album_state"] == "missing"
assert res["artist_state"] == "existing"
def test_ensure_album_monitors_an_unmonitored_album(monkeypatch):
album = {"id": 11, "title": "Off the Wall", "monitored": False}
puts = []
monkeypatch.setattr(mf, "library_artists", lambda: [JACKO])
monkeypatch.setattr(mf, "_artist_albums", lambda artist_id: [album])
monkeypatch.setattr(mf, "lidarr_put", lambda path, payload, **kw: puts.append(payload) or {})
res = mf.ensure_album({"title": "Off the Wall", "artist": {"artistName": "Michael Jackson"}},
"/media/music", False, False)
assert res["album_state"] == "monitored"
assert res["album_id"] == 11
assert puts == [{"albumIds": [11], "monitored": True}]
def test_ensure_album_leaves_a_monitored_album_alone(monkeypatch):
album = {"id": 11, "title": "Off the Wall", "monitored": True}
monkeypatch.setattr(mf, "library_artists", lambda: [JACKO])
monkeypatch.setattr(mf, "_artist_albums", lambda artist_id: [album])
monkeypatch.setattr(mf, "lidarr_put", lambda *a, **kw: (_ for _ in ()).throw(
AssertionError("must not re-monitor")))
res = mf.ensure_album({"title": "Off the Wall", "artist": {"artistName": "Michael Jackson"}},
"/media/music", False, False)
assert res["album_state"] == "existing"
# --- releases --------------------------------------------------------------
def _release(**kw):
base = {"guid": "g1", "indexerId": 2, "title": "Overgrown-Lay_Cold-WEB-2023",
"size": 312 * 1024 * 1024, "seeders": 24, "indexer": "Redacted",
"quality": {"quality": {"name": "FLAC"}}}
base.update(kw)
return base
def test_release_label_is_readable():
label = mf.release_label(_release())
assert label == "FLAC, 312 MB, 24 seeders, Redacted"
def test_release_label_uses_gigabytes_for_big_files():
assert "2.0 GB" in mf.release_label(_release(size=2 * 1024 * 1024 * 1024))
def test_release_label_flags_a_rejected_release():
label = mf.release_label(_release(rejected=True, rejections=["Not a preferred word"]))
assert "not recommended" in label
assert "Not a preferred word" in label
def test_release_label_falls_back_to_the_raw_title():
assert mf.release_label({"title": "Weird.Release"}) == "Weird.Release"
def test_sort_releases_puts_rejected_last_and_best_seeded_first():
low = _release(guid="low", seeders=2)
high = _release(guid="high", seeders=90)
bad = _release(guid="bad", seeders=500, rejected=True)
order = [r["guid"] for r in mf.sort_releases([low, bad, high])]
assert order == ["high", "low", "bad"]
def test_list_releases_skips_a_call_without_a_real_album_id():
assert mf.list_releases(0) == []
assert mf.list_releases(None) == []
def test_grab_release_posts_the_exact_guid_and_indexer(monkeypatch):
posted = {}
monkeypatch.setattr(mf, "lidarr_post",
lambda path, payload, **kw: posted.update(path=path, payload=payload) or {})
assert mf.grab_release(_release()) is True
assert posted["path"] == "/api/v1/release"
assert posted["payload"] == {"guid": "g1", "indexerId": 2}
def test_grab_release_refuses_an_incomplete_release(monkeypatch):
monkeypatch.setattr(mf, "lidarr_post", lambda *a, **kw: (_ for _ in ()).throw(
AssertionError("must not post")))
assert mf.grab_release({"title": "no guid"}) is False
# --- album dispatch --------------------------------------------------------
def _album_hit():
return mf.Hit(source="lidarr", kind="album", title="Lay Cold", album="Lay Cold",
artist="Overgrown",
payload={"album": {"title": "Lay Cold",
"artist": {"artistName": "Overgrown"}}})
def test_act_lidarr_album_grabs_the_best_release(monkeypatch, capsys):
grabbed = {}
monkeypatch.setattr(mf, "ensure_album", lambda *a, **kw: {
"album": {"id": 5}, "album_id": 5, "artist_name": "Overgrown",
"album_title": "Lay Cold", "artist_state": "existing", "album_state": "existing"})
monkeypatch.setattr(mf, "list_releases", lambda album_id, **kw: [_release(guid="best")])
monkeypatch.setattr(mf, "grab_release",
lambda rel, dry_run=False: grabbed.update(guid=rel["guid"]) or True)
assert mf.act_lidarr_album(_album_hit(), "/media/music", False, False) is True
assert grabbed["guid"] == "best"
out = capsys.readouterr().out
assert "Overgrown is already in Lidarr." in out
assert "download client" in out
def test_act_lidarr_album_falls_through_when_no_indexer_has_it(monkeypatch, capsys):
monkeypatch.setattr(mf, "ensure_album", lambda *a, **kw: {
"album": {"id": 5}, "album_id": 5, "artist_name": "Overgrown",
"album_title": "Lay Cold", "artist_state": "added", "album_state": "monitored"})
monkeypatch.setattr(mf, "list_releases", lambda album_id, **kw: [])
assert mf.act_lidarr_album(_album_hit(), "/media/music", False, False) is False
out = capsys.readouterr().out
assert "Added Overgrown to Lidarr." in out
assert "Now monitoring" in out
def test_act_lidarr_album_uses_the_picked_release(monkeypatch):
grabbed = {}
monkeypatch.setattr(mf, "ensure_album", lambda *a, **kw: {
"album": {"id": 5}, "album_id": 5, "artist_name": "Overgrown",
"album_title": "Lay Cold", "artist_state": "existing", "album_state": "existing"})
monkeypatch.setattr(mf, "list_releases",
lambda album_id, **kw: [_release(guid="a"), _release(guid="b")])
monkeypatch.setattr(mf, "pick_release", lambda releases: releases[1])
monkeypatch.setattr(mf, "grab_release",
lambda rel, dry_run=False: grabbed.update(guid=rel["guid"]) or True)
assert mf.act_lidarr_album(_album_hit(), "/media/music", False, False,
choose_release=True) is True
assert grabbed["guid"] == "b"
def test_act_lidarr_album_sends_nothing_when_the_user_quits(monkeypatch):
monkeypatch.setattr(mf, "ensure_album", lambda *a, **kw: {
"album": {"id": 5}, "album_id": 5, "artist_name": "Overgrown",
"album_title": "Lay Cold", "artist_state": "existing", "album_state": "existing"})
monkeypatch.setattr(mf, "list_releases", lambda album_id, **kw: [_release()])
monkeypatch.setattr(mf, "pick_release", lambda releases: None)
monkeypatch.setattr(mf, "grab_release", lambda *a, **kw: (_ for _ in ()).throw(
AssertionError("must not grab")))
assert mf.act_lidarr_album(_album_hit(), "/media/music", False, False,
choose_release=True) is True
# --- guards ----------------------------------------------------------------
def test_norm_title_keeps_accented_letters():
assert mf._norm_title("Björk") == "björk"
assert mf._norm_title("AC/DC") == "ac dc"
def test_base_title_of_an_all_bracket_title_matches_nothing():
assert mf._base_title("(Untitled)") == ""
assert mf._match_album([{"id": 1, "title": "(Whatever)"}], {"title": "(Untitled)"}) is None
def test_set_album_monitored_refuses_an_album_without_a_library_id(monkeypatch):
monkeypatch.setattr(mf, "lidarr_put", lambda *a, **kw: (_ for _ in ()).throw(
AssertionError("must not PUT")))
assert mf.set_album_monitored({"title": "Lay Cold"}, False) is False
def test_ensure_album_stops_when_lidarr_gives_no_artist_id(monkeypatch):
monkeypatch.setattr(mf, "library_artists", lambda: [])
monkeypatch.setattr(mf, "add_artist", lambda *a, **kw: {"artistName": "Overgrown"})
monkeypatch.setattr(mf, "_artist_albums", lambda artist_id: (_ for _ in ()).throw(
AssertionError("must not list every album")))
res = mf.ensure_album({"title": "Lay Cold", "artist": {"artistName": "Overgrown"}},
"/media/music", False, False)
assert res["album"] is None
assert res["album_state"] == "missing"