[scrobbles] Trust webhook POST data and enrich tracks async (db6b05f8)
This commit is contained in:
@ -11,8 +11,8 @@ from scrobbles.models import Scrobble
|
||||
|
||||
|
||||
def build_scrobbles(client, request_json, num=7, spacing=2, auth_token=None):
|
||||
from rest_framework.authtoken.models import Token
|
||||
import pytz
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
url = reverse("scrobbles:mopidy-webhook")
|
||||
user = get_user_model().objects.create(username="Test User")
|
||||
@ -43,14 +43,12 @@ def build_scrobbles(client, request_json, num=7, spacing=2, auth_token=None):
|
||||
@pytest.mark.django_db
|
||||
@patch("music.models.get_album_metadata_with_artist", return_value={})
|
||||
@patch("music.models.get_track_metadata_with_artist", return_value={})
|
||||
@patch("music.models.get_recording_mbid_exact", return_value=(None, None))
|
||||
@patch("music.models.lookup_artist_from_tadb", return_value={})
|
||||
@patch("music.models.lookup_album_from_tadb", return_value={})
|
||||
@time_machine.travel(datetime(2022, 3, 4, 1, 24))
|
||||
def test_scrobble_counts_data(
|
||||
mock_lookup_album_tadb,
|
||||
mock_lookup_artist_tadb,
|
||||
mock_get_recording,
|
||||
mock_get_track,
|
||||
mock_get_album,
|
||||
client,
|
||||
@ -73,14 +71,12 @@ def test_scrobble_counts_data(
|
||||
@pytest.mark.django_db
|
||||
@patch("music.models.get_album_metadata_with_artist", return_value={})
|
||||
@patch("music.models.get_track_metadata_with_artist", return_value={})
|
||||
@patch("music.models.get_recording_mbid_exact", return_value=(None, None))
|
||||
@patch("music.models.lookup_artist_from_tadb", return_value={})
|
||||
@patch("music.models.lookup_album_from_tadb", return_value={})
|
||||
@time_machine.travel(datetime(2022, 3, 4, 1, 24))
|
||||
def test_live_charts(
|
||||
mock_lookup_album_tadb,
|
||||
mock_lookup_artist_tadb,
|
||||
mock_get_recording,
|
||||
mock_get_track,
|
||||
mock_get_album,
|
||||
client,
|
||||
|
||||
@ -56,4 +56,8 @@ def test_mopidy_scrobble_handles_missing_mopidy_uri():
|
||||
artist_name="Test Artist",
|
||||
album_name="Test Album",
|
||||
run_time_seconds=180000,
|
||||
mbid=None,
|
||||
artist_mbid=None,
|
||||
album_mbid=None,
|
||||
trust_webhook_data=True,
|
||||
)
|
||||
|
||||
202
tests/scrobbles_tests/test_webhook_enrichment.py
Normal file
202
tests/scrobbles_tests/test_webhook_enrichment.py
Normal file
@ -0,0 +1,202 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.urls import reverse
|
||||
from music.models import Album, Artist, Track
|
||||
from scrobbles.models import FavoriteMedia, Scrobble
|
||||
from scrobbles.tasks import enrich_media_from_webhook
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@patch("music.models.resolve_track")
|
||||
def test_find_or_create_trust_webhook_data_skips_musicbrainz(mock_resolve):
|
||||
track = Track.find_or_create(
|
||||
title="TNT",
|
||||
artist_name="AC/DC",
|
||||
album_name="TNT",
|
||||
run_time_seconds=210,
|
||||
mbid="rec-123",
|
||||
artist_mbid="art-1",
|
||||
album_mbid="alb-1",
|
||||
trust_webhook_data=True,
|
||||
)
|
||||
|
||||
assert track.musicbrainz_id == "rec-123"
|
||||
assert track.base_run_time_seconds == 210
|
||||
assert "musicbrainz-provider" in track.tags.names()
|
||||
assert "musicbrainz-enriched" not in track.tags.names()
|
||||
mock_resolve.assert_not_called()
|
||||
assert Artist.objects.filter(name="AC/DC", musicbrainz_id="art-1").exists()
|
||||
assert Album.objects.filter(name="TNT", musicbrainz_id="alb-1").exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@patch("music.models.resolve_track")
|
||||
def test_find_or_create_reuses_existing_track_by_mbid(mock_resolve):
|
||||
existing = Track.objects.create(title="TNT", musicbrainz_id="rec-123")
|
||||
|
||||
track = Track.find_or_create(
|
||||
title="TNT",
|
||||
artist_name="AC/DC",
|
||||
mbid="rec-123",
|
||||
trust_webhook_data=True,
|
||||
)
|
||||
|
||||
assert track.id == existing.id
|
||||
mock_resolve.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@patch("music.models.resolve_track")
|
||||
def test_fix_metadata_enriches_and_tags(mock_resolve):
|
||||
track = Track.objects.create(title="TNT")
|
||||
mock_resolve.return_value = (
|
||||
{"recording_mbid": "rec-123", "length_ms": 210000},
|
||||
"exact",
|
||||
)
|
||||
|
||||
assert track.fix_metadata() is True
|
||||
|
||||
track.refresh_from_db()
|
||||
assert track.musicbrainz_id == "rec-123"
|
||||
assert track.base_run_time_seconds == 210
|
||||
assert "musicbrainz-exact" in track.tags.names()
|
||||
assert "musicbrainz-enriched" in track.tags.names()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@patch("music.models.resolve_track", return_value=(None, ""))
|
||||
def test_fix_metadata_notfound_returns_false(mock_resolve):
|
||||
track = Track.objects.create(title="Unknown")
|
||||
|
||||
assert track.fix_metadata() is False
|
||||
assert "musicbrainz-notfound" in track.tags.names()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_track_merge_repoints_scrobbles_and_favorites():
|
||||
user = get_user_model().objects.create(email="merge@example.com")
|
||||
duplicate = Track.objects.create(title="TNT", musicbrainz_id="rec-123")
|
||||
canonical = Track.objects.create(title="TNT", musicbrainz_id="rec-123")
|
||||
duplicate.tags.add("cool")
|
||||
scrobble = Scrobble.objects.create(
|
||||
track=duplicate, media_type="Track", user=user, in_progress=False
|
||||
)
|
||||
fav = FavoriteMedia.objects.create(user=user, track=duplicate, media_type="Track")
|
||||
|
||||
repointed = Track.merge(duplicate, canonical)
|
||||
|
||||
assert repointed == 1
|
||||
scrobble.refresh_from_db()
|
||||
assert scrobble.track == canonical
|
||||
fav.refresh_from_db()
|
||||
assert fav.track == canonical
|
||||
assert "cool" in canonical.tags.names()
|
||||
assert not Track.objects.filter(id=duplicate.id).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_track_merge_skips_in_progress_conflict():
|
||||
user = get_user_model().objects.create(email="conflict@example.com")
|
||||
duplicate = Track.objects.create(title="TNT", musicbrainz_id="rec-123")
|
||||
canonical = Track.objects.create(title="TNT", musicbrainz_id="rec-123")
|
||||
Scrobble.objects.create(
|
||||
track=canonical, media_type="Track", user=user, in_progress=True
|
||||
)
|
||||
scrobble = Scrobble.objects.create(
|
||||
track=duplicate, media_type="Track", user=user, in_progress=True
|
||||
)
|
||||
|
||||
repointed = Track.merge(duplicate, canonical)
|
||||
|
||||
assert repointed == 0
|
||||
scrobble.refresh_from_db()
|
||||
assert scrobble.track == duplicate
|
||||
assert Track.objects.filter(id=duplicate.id).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@patch("scrobbles.tasks.enrich_media_from_webhook")
|
||||
def test_mopidy_webhook_creates_track_from_post_data_and_dispatches(
|
||||
mock_task, client, mopidy_track, valid_auth_token
|
||||
):
|
||||
url = reverse("scrobbles:mopidy-webhook")
|
||||
headers = {"Authorization": f"Token {valid_auth_token}"}
|
||||
response = client.post(
|
||||
url,
|
||||
mopidy_track.request_data,
|
||||
content_type="application/json",
|
||||
headers=headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
scrobble = Scrobble.objects.get(id=response.data["scrobble_id"])
|
||||
assert scrobble.track.title == "Same in the End"
|
||||
assert scrobble.track.musicbrainz_id == mopidy_track.musicbrainz_track_id
|
||||
assert "webhook-metadata-only" in scrobble.tags.names()
|
||||
mock_task.delay.assert_called_once_with(scrobble.id)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@patch("scrobbles.tasks.enrich_media_from_webhook")
|
||||
def test_jellyfin_webhook_creates_track_from_post_data_and_dispatches(
|
||||
mock_task, client, jellyfin_track, valid_auth_token
|
||||
):
|
||||
url = reverse("scrobbles:jellyfin-webhook")
|
||||
headers = {"Authorization": f"Token {valid_auth_token}"}
|
||||
response = client.post(
|
||||
url,
|
||||
jellyfin_track.request_json,
|
||||
content_type="application/json",
|
||||
headers=headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
scrobble = Scrobble.objects.get(id=response.data["scrobble_id"])
|
||||
assert scrobble.track.title == "Emotion"
|
||||
assert scrobble.track.musicbrainz_id == jellyfin_track.musicbrainz_track_id
|
||||
assert "webhook-metadata-only" in scrobble.tags.names()
|
||||
mock_task.delay.assert_called_once_with(scrobble.id)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@patch("music.models.resolve_track")
|
||||
def test_enrich_task_merges_onto_canonical_track(mock_resolve):
|
||||
user = get_user_model().objects.create(email="task@example.com")
|
||||
artist = Artist.objects.create(name="AC-DC")
|
||||
canonical = Track.objects.create(
|
||||
title="TNT", musicbrainz_id="rec-123", base_run_time_seconds=210
|
||||
)
|
||||
canonical.artists.add(artist)
|
||||
canonical.tags.add("musicbrainz-enriched")
|
||||
|
||||
raw = Track.objects.create(title="TNT", artist_fk=artist)
|
||||
raw.artists.add(artist)
|
||||
scrobble = Scrobble.objects.create(track=raw, media_type="Track", user=user)
|
||||
|
||||
mock_resolve.return_value = (
|
||||
{"recording_mbid": "rec-123", "length_ms": 210000},
|
||||
"exact",
|
||||
)
|
||||
|
||||
enrich_media_from_webhook(scrobble.id)
|
||||
|
||||
scrobble.refresh_from_db()
|
||||
assert scrobble.track == canonical
|
||||
assert "webhook-enriched" in scrobble.tags.names()
|
||||
assert not Track.objects.filter(id=raw.id).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@patch("music.models.resolve_track", return_value=(None, ""))
|
||||
def test_enrich_task_tags_failure(mock_resolve):
|
||||
user = get_user_model().objects.create(email="fail@example.com")
|
||||
track = Track.objects.create(title="Unknown Song")
|
||||
scrobble = Scrobble.objects.create(track=track, media_type="Track", user=user)
|
||||
|
||||
enrich_media_from_webhook(scrobble.id)
|
||||
|
||||
scrobble.refresh_from_db()
|
||||
assert "enrichment-failed" in scrobble.tags.names()
|
||||
assert "musicbrainz-notfound" in track.tags.names()
|
||||
Reference in New Issue
Block a user