"""Query-count QA checks — converted from scripts/qa_perf_checks.py (F33). The original script mutated the configured DB (created a user + 50 UserSkills, best-effort cleanup at the end) and only *printed* the query counts. Here the same paths run against the pytest test DB and the CaptureQueriesContext bounds become hard `django_assert_max_num_queries` assertions — so an accidental N+1 regression fails the suite instead of scrolling past in stdout. """ from io import StringIO import pytest from django.contrib.auth import get_user_model from django.core.management import call_command from rest_framework.test import APIClient from apps.analysis.services import compute_gap_report, compute_recommendations from apps.progress.models import UserProgress from apps.resources.models import Resource from apps.roles.models import Role, UserTargetRole from apps.skills.models import Skill, UserSkill User = get_user_model() @pytest.fixture def seeded(db): call_command('seed_initial_skills', stdout=StringIO()) call_command('seed_initial_roles', stdout=StringIO()) call_command('seed_initial_resources', stdout=StringIO()) return True @pytest.fixture def perf_user(seeded): """A user with an active target role and 50 UserSkills — enough to prove the query count stays O(1) in the number of skills.""" u = User.objects.create_user(username='qa.perf@test.xx', email='qa.perf@test.xx', password='x!9AAAAA', name='QP') role = Role.objects.filter(is_active=True).first() UserTargetRole.objects.create(user=u, role=role, is_active=True) for s in Skill.objects.all()[:50]: UserSkill.objects.create(user=u, skill=s, proficiency=70, user_level='ADVANCED') # Several progress rows so the progress-list query count is a real N+1 # guard (an empty list would pass the bound trivially). for res in Resource.objects.all()[:10]: UserProgress.objects.create(user=u, resource=res, status='IN_PROGRESS', progress=40) return u, role @pytest.mark.django_db def test_gap_report_query_count(perf_user, django_assert_max_num_queries): u, role = perf_user with django_assert_max_num_queries(5): compute_gap_report(u, role) @pytest.mark.django_db def test_recommendations_query_count(perf_user, django_assert_max_num_queries): u, role = perf_user with django_assert_max_num_queries(6): compute_recommendations(u, role) @pytest.mark.django_db def test_resource_detail_query_count(perf_user, django_assert_max_num_queries): u, _ = perf_user c = APIClient(); c.force_authenticate(user=u) res = Resource.objects.order_by('id').first() with django_assert_max_num_queries(8): c.get(f'/api/resources/{res.id}/') @pytest.mark.django_db def test_progress_list_query_count(perf_user, django_assert_max_num_queries): u, _ = perf_user c = APIClient(); c.force_authenticate(user=u) # The original only printed this count. With 10 progress rows the list is # O(1) in N (3 queries observed); bound at 6 so an accidental N+1 (which # would push it to ~13 with 10 rows) fails the guard with margin to spare. with django_assert_max_num_queries(6): c.get('/api/progress/')