|
|
|
|
@ -0,0 +1,196 @@
|
|
|
|
|
import pytest
|
|
|
|
|
from django.contrib.auth import get_user_model
|
|
|
|
|
from django.test.utils import CaptureQueriesContext
|
|
|
|
|
from django.db import connection
|
|
|
|
|
from django.utils import timezone
|
|
|
|
|
|
|
|
|
|
from music.aggregators import batch_live_charts, live_charts
|
|
|
|
|
from scrobbles.models import Scrobble, Track, Artist
|
|
|
|
|
from videos.models import Video
|
|
|
|
|
|
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_scrobble_queryset_with_related_reduces_queries():
|
|
|
|
|
"""Test that Scrobble.objects.with_related() reduces query count."""
|
|
|
|
|
user = User.objects.create_user(username="perfuser", password="testpass123")
|
|
|
|
|
|
|
|
|
|
artist = Artist.objects.create(name="Test Artist")
|
|
|
|
|
track = Track.objects.create(
|
|
|
|
|
title="Test Track", artist=artist, base_run_time_seconds=180
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
num_scrobbles = 10
|
|
|
|
|
for i in range(num_scrobbles):
|
|
|
|
|
Scrobble.objects.create(
|
|
|
|
|
user=user,
|
|
|
|
|
track=track,
|
|
|
|
|
media_type=Scrobble.MediaType.TRACK,
|
|
|
|
|
played_to_completion=True,
|
|
|
|
|
timestamp=timezone.now() - timezone.timedelta(days=i),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
with CaptureQueriesContext(connection) as context:
|
|
|
|
|
scrobbles = list(Scrobble.objects.with_related().filter(user=user))
|
|
|
|
|
for s in scrobbles:
|
|
|
|
|
_ = s.track.title
|
|
|
|
|
_ = s.track.artist.name
|
|
|
|
|
|
|
|
|
|
print(
|
|
|
|
|
f"\nwith_related() queries for {num_scrobbles} scrobbles: {len(context.captured_queries)}"
|
|
|
|
|
)
|
|
|
|
|
for q in context.captured_queries:
|
|
|
|
|
print(f" {q['sql'][:100]}...")
|
|
|
|
|
|
|
|
|
|
assert len(context.captured_queries) < num_scrobbles
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_batch_live_charts_vs_individual_calls():
|
|
|
|
|
"""Test that batch_live_charts uses fewer queries than individual live_charts calls."""
|
|
|
|
|
user = User.objects.create_user(username="batchuser", password="testpass123")
|
|
|
|
|
|
|
|
|
|
artist = Artist.objects.create(name="Test Artist")
|
|
|
|
|
track = Track.objects.create(
|
|
|
|
|
title="Test Track", artist=artist, base_run_time_seconds=180
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for i in range(5):
|
|
|
|
|
Scrobble.objects.create(
|
|
|
|
|
user=user,
|
|
|
|
|
track=track,
|
|
|
|
|
media_type=Scrobble.MediaType.TRACK,
|
|
|
|
|
played_to_completion=True,
|
|
|
|
|
timestamp=timezone.now() - timezone.timedelta(days=i),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
with CaptureQueriesContext(connection) as batch_context:
|
|
|
|
|
batch_result = batch_live_charts(user=user, media_type="Track", limit=15)
|
|
|
|
|
|
|
|
|
|
with CaptureQueriesContext(connection) as individual_context:
|
|
|
|
|
individual_result = {
|
|
|
|
|
"today": list(
|
|
|
|
|
live_charts(user=user, media_type="Track", chart_period="today")
|
|
|
|
|
),
|
|
|
|
|
"last7": list(
|
|
|
|
|
live_charts(user=user, media_type="Track", chart_period="last7")
|
|
|
|
|
),
|
|
|
|
|
"last30": list(
|
|
|
|
|
live_charts(user=user, media_type="Track", chart_period="last30")
|
|
|
|
|
),
|
|
|
|
|
"year": list(
|
|
|
|
|
live_charts(user=user, media_type="Track", chart_period="year")
|
|
|
|
|
),
|
|
|
|
|
"all": list(live_charts(user=user, media_type="Track", chart_period="all")),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
print(f"\nbatch_live_charts queries: {len(batch_context.captured_queries)}")
|
|
|
|
|
print(f"individual live_charts queries: {len(individual_context.captured_queries)}")
|
|
|
|
|
|
|
|
|
|
assert len(batch_context.captured_queries) <= len(
|
|
|
|
|
individual_context.captured_queries
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_as_dict_by_type_uses_with_related():
|
|
|
|
|
"""Test that as_dict_by_type uses with_related() for reduced queries."""
|
|
|
|
|
user = User.objects.create_user(username="dictuser", password="testpass123")
|
|
|
|
|
|
|
|
|
|
artist = Artist.objects.create(name="Test Artist")
|
|
|
|
|
track = Track.objects.create(
|
|
|
|
|
title="Test Track", artist=artist, base_run_time_seconds=180
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for i in range(5):
|
|
|
|
|
Scrobble.objects.create(
|
|
|
|
|
user=user,
|
|
|
|
|
track=track,
|
|
|
|
|
media_type=Scrobble.MediaType.TRACK,
|
|
|
|
|
played_to_completion=True,
|
|
|
|
|
timestamp=timezone.now() - timezone.timedelta(days=i),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
scrobbles = Scrobble.objects.filter(user=user)
|
|
|
|
|
|
|
|
|
|
with CaptureQueriesContext(connection) as context:
|
|
|
|
|
result = Scrobble.as_dict_by_type(scrobbles)
|
|
|
|
|
|
|
|
|
|
print(f"\nas_dict_by_type queries: {len(context.captured_queries)}")
|
|
|
|
|
for q in context.captured_queries:
|
|
|
|
|
print(f" {q['sql'][:100]}...")
|
|
|
|
|
|
|
|
|
|
assert len(context.captured_queries) < 10
|
|
|
|
|
assert "Track" in result
|
|
|
|
|
assert len(result["Track"]) == 5
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_person_scrobble_junction_efficient_count():
|
|
|
|
|
"""Test that PersonScrobble junction table enables efficient counts."""
|
|
|
|
|
from people.models import Person, PersonScrobble
|
|
|
|
|
|
|
|
|
|
creator = User.objects.create_user(username="creator2", password="testpass123")
|
|
|
|
|
other_user = User.objects.create_user(username="other2", password="testpass123")
|
|
|
|
|
|
|
|
|
|
person = Person.objects.create(name="Test Person", created_by=creator, user=creator)
|
|
|
|
|
|
|
|
|
|
artist = Artist.objects.create(name="Test Artist")
|
|
|
|
|
track = Track.objects.create(
|
|
|
|
|
title="Test Track", artist=artist, base_run_time_seconds=180
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for i in range(10):
|
|
|
|
|
scrobble = Scrobble.objects.create(
|
|
|
|
|
user=other_user,
|
|
|
|
|
track=track,
|
|
|
|
|
media_type=Scrobble.MediaType.TRACK,
|
|
|
|
|
played_to_completion=True,
|
|
|
|
|
log={"with_people_ids": [person.id]},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
count = person.get_scrobble_count(user=other_user)
|
|
|
|
|
assert count == 10
|
|
|
|
|
|
|
|
|
|
with CaptureQueriesContext(connection) as context:
|
|
|
|
|
assert person.get_scrobble_count(user=other_user) == 10
|
|
|
|
|
|
|
|
|
|
print(f"\nPersonScrobble count queries: {len(context.captured_queries)}")
|
|
|
|
|
for q in context.captured_queries:
|
|
|
|
|
print(f" {q['sql'][:100]}...")
|
|
|
|
|
|
|
|
|
|
assert len(context.captured_queries) <= 3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_video_scrobbles_with_related():
|
|
|
|
|
"""Test prefetching works for video scrobbles."""
|
|
|
|
|
user = User.objects.create_user(username="videouser", password="testpass123")
|
|
|
|
|
|
|
|
|
|
video = Video.objects.create(
|
|
|
|
|
title="Test Video",
|
|
|
|
|
video_type=Video.VideoType.MOVIE,
|
|
|
|
|
base_run_time_seconds=5400,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for i in range(3):
|
|
|
|
|
Scrobble.objects.create(
|
|
|
|
|
user=user,
|
|
|
|
|
video=video,
|
|
|
|
|
media_type=Scrobble.MediaType.VIDEO,
|
|
|
|
|
played_to_completion=True,
|
|
|
|
|
timestamp=timezone.now() - timezone.timedelta(days=i),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
with CaptureQueriesContext(connection) as context:
|
|
|
|
|
scrobbles = list(Scrobble.objects.with_related().filter(user=user))
|
|
|
|
|
for s in scrobbles:
|
|
|
|
|
_ = s.video.title
|
|
|
|
|
_ = s.video.video_type
|
|
|
|
|
|
|
|
|
|
print(f"\nVideo scrobbles with_related() queries: {len(context.captured_queries)}")
|
|
|
|
|
for q in context.captured_queries:
|
|
|
|
|
print(f" {q['sql'][:100]}...")
|
|
|
|
|
|
|
|
|
|
assert len(context.captured_queries) < 5
|