[music] Resolve artist identity before splitting (fd86a11a)

This commit is contained in:
2026-08-08 20:35:45 -04:00
parent 5f88d97c69
commit 5e3b0055dc
7 changed files with 453 additions and 54 deletions

View File

View File

@ -0,0 +1,173 @@
from unittest.mock import patch
import pytest
from django.contrib.auth import get_user_model
from django.core.management import call_command
from music.models import Artist, Track
from music.utils import resolve_artist_names
from scrobbles.models import Scrobble
@pytest.mark.django_db
def test_resolve_with_mbid_keeps_literal_name():
names = resolve_artist_names("Simon & Garfunkel", artist_mbid="grp-1")
assert names == ["Simon & Garfunkel"]
@pytest.mark.django_db
@patch("music.utils.lookup_artist_from_mb", return_value={"name": "Simon & Garfunkel"})
def test_resolve_mb_exact_match_keeps_literal_name(mock_lookup):
names = resolve_artist_names("Simon & Garfunkel")
assert names == ["Simon & Garfunkel"]
mock_lookup.assert_called_once_with("Simon & Garfunkel")
@pytest.mark.django_db
@patch(
"music.utils.lookup_artist_from_mb", return_value={"name": "Simon and Garfunkel"}
)
def test_resolve_mb_inexact_match_falls_back_to_split(mock_lookup):
names = resolve_artist_names("Simon & Garfunkel")
assert names == ["Simon", "Garfunkel"]
@pytest.mark.django_db
@patch("music.utils.lookup_artist_from_mb", return_value={})
def test_resolve_splits_collab_when_mb_has_no_single_artist(mock_lookup):
names = resolve_artist_names("Matt Sweeney & Bonnie Prince Billy")
assert names == ["Matt Sweeney", "Bonnie Prince Billy"]
@pytest.mark.django_db
@patch("music.utils.lookup_artist_from_mb")
def test_resolve_existing_db_artist_skips_mb_lookup(mock_lookup):
Artist.objects.create(name="Simon & Garfunkel")
names = resolve_artist_names("Simon & Garfunkel")
assert names == ["Simon & Garfunkel"]
mock_lookup.assert_not_called()
@pytest.mark.django_db
@patch("music.utils.lookup_artist_from_mb", return_value={})
def test_resolve_strips_featured_names(mock_lookup):
names = resolve_artist_names("Ariana Grande feat. Zedd")
assert names == ["Ariana Grande"]
@pytest.mark.django_db
def test_find_or_create_trust_webhook_keeps_literal_artist():
track = Track.find_or_create(
title="Bridge over Troubled Water",
artist_name="Simon & Garfunkel",
album_name="Bridge over Troubled Water",
artist_mbid="grp-1",
trust_webhook_data=True,
)
assert list(track.artists.all().values_list("name", flat=True)) == [
"Simon & Garfunkel"
]
assert not Artist.objects.filter(name="Simon").exists()
assert not Artist.objects.filter(name="Garfunkel").exists()
@pytest.mark.django_db
@patch("music.models.resolve_track", return_value=(None, ""))
def test_find_or_create_reuses_literal_artist(mock_resolve):
artist = Artist.objects.create(name="Simon & Garfunkel")
track = Track.find_or_create(
title="Bridge over Troubled Water",
artist_name="Simon & Garfunkel",
)
assert list(track.artists.all().values_list("name", flat=True)) == [
"Simon & Garfunkel"
]
assert not Artist.objects.filter(name="Simon").exists()
assert not Artist.objects.filter(name="Garfunkel").exists()
@pytest.mark.django_db
@patch("music.models.get_track_metadata_with_artist", return_value=None)
@patch("music.models.lookup_artist_from_tadb", return_value=None)
def test_reconcile_command_merges_split_artists(mock_tadb, mock_track_meta):
user = get_user_model().objects.create(email="reconcile@example.com")
simon = Artist.objects.create(name="Simon")
garfunkel = Artist.objects.create(name="Garfunkel")
track = Track.objects.create(title="Bridge over Troubled Water", artist_fk=simon)
track.artists.add(simon, garfunkel)
Scrobble.objects.create(
track=track,
media_type="Track",
user=user,
log={
"raw_data": {
"artist": "Simon & Garfunkel",
"musicbrainz_artist_id": "grp-1",
}
},
)
call_command("reconcile_split_artists", "--commit")
track.refresh_from_db()
assert [a.name for a in track.artists.all()] == ["Simon & Garfunkel"]
assert track.artist_fk.name == "Simon & Garfunkel"
assert "artist-reconciled" in track.tags.names()
assert not Artist.objects.filter(name="Simon").exists()
assert not Artist.objects.filter(name="Garfunkel").exists()
@pytest.mark.django_db
def test_reconcile_command_dry_run_makes_no_changes():
user = get_user_model().objects.create(email="dry@example.com")
simon = Artist.objects.create(name="Simon")
garfunkel = Artist.objects.create(name="Garfunkel")
track = Track.objects.create(title="Bridge over Troubled Water")
track.artists.add(simon, garfunkel)
Scrobble.objects.create(
track=track,
media_type="Track",
user=user,
log={
"raw_data": {
"artist": "Simon & Garfunkel",
"musicbrainz_artist_id": "grp-1",
}
},
)
call_command("reconcile_split_artists")
track.refresh_from_db()
assert sorted(track.artists.all().values_list("name", flat=True)) == [
"Garfunkel",
"Simon",
]
assert Artist.objects.filter(name="Simon").exists()
assert Artist.objects.filter(name="Garfunkel").exists()
@pytest.mark.django_db
@patch("music.utils.lookup_artist_from_mb", return_value={})
def test_reconcile_command_leaves_genuine_collab_alone(mock_lookup):
user = get_user_model().objects.create(email="collab@example.com")
matt = Artist.objects.create(name="Matt Sweeney")
bonnie = Artist.objects.create(name="Bonnie Prince Billy")
track = Track.objects.create(title="My Morning Song")
track.artists.add(matt, bonnie)
Scrobble.objects.create(
track=track,
media_type="Track",
user=user,
log={"raw_data": {"artist": "Matt Sweeney & Bonnie Prince Billy"}},
)
call_command("reconcile_split_artists", "--commit")
track.refresh_from_db()
assert sorted(track.artists.all().values_list("name", flat=True)) == [
"Bonnie Prince Billy",
"Matt Sweeney",
]