animetix-web / tests /api /test_query_counts.py
MissawB's picture
Upload folder using huggingface_hub (part 6)
5294c4d verified
Raw
History Blame Contribute Delete
4.97 kB
"""N+1 regression locks for the list/social endpoints (2026-07-05 debt audit).
Each test captures the SQL query count on a small dataset, grows the dataset,
and asserts the count is unchanged: a fixed number of queries per request,
never a number that scales with the rows being serialized. This is the
property that ``select_related``/``prefetch_related`` must guarantee, without
pinning brittle absolute counts.
"""
import pytest
from animetix.models import AIFeedback, CreativeFusion, DiscoveryClub, VsBattle
from django.contrib.auth.models import User
from django.db import connection
from django.test import override_settings
from django.test.utils import CaptureQueriesContext
from django.urls import reverse
from rest_framework.test import APIClient
LOCMEM_CACHE = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "query-counts",
}
}
pytestmark = pytest.mark.django_db
@pytest.fixture(autouse=True)
def _locmem_cache():
"""Force LocMem cache so DRF throttling does not hit redis (CI sets REDIS_URL)."""
with override_settings(CACHES=LOCMEM_CACHE):
yield
@pytest.fixture
def api_client():
return APIClient()
@pytest.fixture
def creator(db):
return User.objects.create_user(username="alice", password="password")
def _make_fusion(creator, i):
return CreativeFusion.objects.create(
title_a=f"Titre A{i}",
title_b=f"Titre B{i}",
media_type_a="Anime",
media_type_b="Manga",
scenario_text="scenario",
creator=creator,
)
def _make_battle(creator, i):
return VsBattle.objects.create(
char_a_name=f"Goku {i}",
char_b_name=f"Naruto {i}",
char_a_data={},
char_b_data={},
winner="Goku",
verdict_summary="verdict",
debate_history=[],
creator=creator,
is_public=True,
)
def _make_club(creator, i):
return DiscoveryClub.objects.create(
name=f"Club {i}",
description="description",
creator=creator,
is_private=False,
)
def _count_queries(client, url):
# Warm-up: fills per-process caches (ContentType, prepared statements…) so
# the two measured requests only differ by the size of the dataset.
client.get(url)
with CaptureQueriesContext(connection) as ctx:
response = client.get(url)
assert response.status_code == 200, response.content
return len(ctx)
def test_profile_detail_fusion_queries_do_not_scale(api_client, creator):
"""ProfileDetailView serializes top/recent fusions: fixed query count."""
for i in range(2):
_make_fusion(creator, i)
url = reverse("api_profile_detail", args=[creator.username])
baseline = _count_queries(api_client, url)
for i in range(2, 10):
_make_fusion(creator, i)
assert _count_queries(api_client, url) == baseline
def test_vs_battle_arena_queries_do_not_scale(api_client, creator):
"""The public arena list (creator_name + likes_count): fixed query count."""
for i in range(2):
_make_battle(creator, i)
url = reverse("api_vs_battle_arena")
baseline = _count_queries(api_client, url)
for i in range(2, 10):
_make_battle(creator, i)
assert _count_queries(api_client, url) == baseline
def test_vs_battle_arena_authenticated_queries_do_not_scale(api_client, creator):
"""Authenticated arena adds is_liked per battle: still a fixed query count."""
viewer = User.objects.create_user(username="bob", password="password")
api_client.force_authenticate(user=viewer)
for i in range(2):
battle = _make_battle(creator, i)
battle.likes.add(viewer)
url = reverse("api_vs_battle_arena")
baseline = _count_queries(api_client, url)
for i in range(2, 10):
_make_battle(creator, i)
assert _count_queries(api_client, url) == baseline
def test_feedback_history_queries_do_not_scale(api_client, creator):
"""The AI feedback history (username per row): fixed query count."""
api_client.force_authenticate(user=creator)
for _ in range(2):
AIFeedback.objects.create(user=creator, feedback_type="rag", is_positive=True)
url = reverse("submit_ai_feedback")
baseline = _count_queries(api_client, url)
for _ in range(8):
AIFeedback.objects.create(user=creator, feedback_type="rag", is_positive=True)
assert _count_queries(api_client, url) == baseline
def test_club_list_queries_do_not_scale(api_client, creator):
"""The club list (members_count per club): fixed query count."""
api_client.force_authenticate(user=creator)
for i in range(2):
club = _make_club(creator, i)
club.members.add(creator)
url = reverse("api-clubs")
baseline = _count_queries(api_client, url)
for i in range(2, 10):
club = _make_club(creator, i)
club.members.add(creator)
assert _count_queries(api_client, url) == baseline