Spike readiness: AI gets its own slice of threads, cheaper hot paths, classroom-friendly login
- /api/ai/ask: at most AI_MAX_CONCURRENT_PER_WORKER (12 of 32 threads) answers per worker; beyond that 503 assistant_busy + Retry-After before any quota is reserved. Without it a burst of questions (3-40 s each) took every thread and the map and quizzes queued behind. The slot is held until the stream is actually finished or closed. - Answer submission 21 -> 14 queries, 15.4 -> 11.0 ms: one query for all choices, locked get_or_create for node progress and streak, and today's streak recorded once per day via the cache instead of a transaction per answer. - /api/home 12.4 -> 8.6 ms: the user's completions are read by user_id instead of an IN list of ~600 lesson ids; the dict is returned directly (serializer stays the Swagger contract, HomeMatchesItsSerializerTests keeps them equal). - otp_login per-IP 30 -> 300/hour: a class of 30 on one school Wi-Fi filled it; guessing is bounded per code and per phone. - /api/health aiConcurrency. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -200,3 +200,35 @@ class NodeVisualDefaultsTests(TestCase):
|
||||
first = self._nodes()[0]
|
||||
self.assertEqual(first['color'], '#123456')
|
||||
self.assertEqual(first['icon'], 'Trophy')
|
||||
|
||||
|
||||
class HomeMatchesItsSerializerTests(TestCase):
|
||||
"""
|
||||
/api/home برای سرعت dict را مستقیم برمیگرداند (content/views.py، HomeView)، ولی HomeResponseSerializer
|
||||
هنوز قراردادِ Swagger است. اگر کسی کلیدی به پاسخ اضافه کند یا نوعی را عوض کند و سریالساز را نه،
|
||||
این تست میافتد: خروجیِ سریالساز روی همان پاسخ باید عینِ خودش باشد.
|
||||
"""
|
||||
|
||||
def test_view_output_is_exactly_the_documented_shape(self):
|
||||
from accounts.authentication import issue_access_token
|
||||
from content.serializers import HomeResponseSerializer
|
||||
|
||||
cache.clear()
|
||||
grade = Grade.objects.create(title='پایه نهم', icon='', color='')
|
||||
for order in (1, 2):
|
||||
unit = Unit.objects.create(grade=grade, title=f'فصل {order}', order=order, blurb='b')
|
||||
for n in (1, 2):
|
||||
node = Node.objects.create(unit=unit, title=f'جزیره {n}', order=n)
|
||||
lesson = Lesson.objects.create(node=node, title='درس')
|
||||
question = Question.objects.create(lesson=lesson, type='multiple_choice', prompt='؟')
|
||||
Choice.objects.create(question=question, value='۱', is_correct=True)
|
||||
user = User.objects.create(phone='09120000088', grade_id=grade.id)
|
||||
UserLessonCompletion.objects.create(
|
||||
user_id=user.id, lesson=Lesson.objects.first(), correct_count=1, stars=3)
|
||||
|
||||
body = self.client.get('/api/home', HTTP_AUTHORIZATION=f'Bearer {issue_access_token(user.id)}').json()
|
||||
|
||||
self.assertEqual(len(body['units']), 2)
|
||||
self.assertEqual(HomeResponseSerializer(body).data, body)
|
||||
self.assertIsInstance(body['stats']['progressPercent'], float)
|
||||
self.assertIsInstance(body['units'][0]['nodes'][0]['id'], str)
|
||||
|
||||
Reference in New Issue
Block a user