accounts.0008 drops the columns and refuses to run while guest rows exist. Docs describe the current state; tests that only proved guest behaviour are gone. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
74 lines
3.1 KiB
Python
74 lines
3.1 KiB
Python
"""
|
||
اعتبارِ بسته زیرِ درخواستِ همزمان (بازبینیِ ۲۰۲۶-۱۰-۰۲).
|
||
|
||
پیش از این فقط ادعا بود که قفلِ ردیفِ کاربر و `transaction_mode: IMMEDIATE`ِ SQLite جلوی
|
||
خرجِ دوبارهی یک اعتبار را میگیرند. اینجا دو thread واقعی، با دو اتصالِ جدا به دیتابیسِ فایلیِ
|
||
تست، همزمان میپرسند. جوابِ دستیار ساختگی است و ۰٫۴ ثانیه طول میکشد تا دو درخواست واقعاً روی
|
||
هم بیفتند. فقط روی SQLite سنجیده شده؛ بعد از مهاجرت به Postgres دوباره باید سبز شود.
|
||
"""
|
||
import threading
|
||
import time
|
||
from unittest.mock import patch
|
||
|
||
from django.core.cache import cache
|
||
from django.db import connection
|
||
from django.test import TransactionTestCase, override_settings
|
||
from rest_framework.test import APIClient
|
||
|
||
from accounts.authentication import issue_access_token
|
||
from accounts.models import User
|
||
from subscription.models import QuestionCredit
|
||
from subscription.services import credit_balance
|
||
|
||
from .models import Message
|
||
|
||
|
||
def _slow_answer(*args, **kwargs):
|
||
time.sleep(0.4)
|
||
return 'جواب', ['بیشتر توضیح بده']
|
||
|
||
|
||
@override_settings(AI_DAILY_QUOTA_FREE=0)
|
||
class ConcurrentCreditTests(TransactionTestCase):
|
||
def setUp(self):
|
||
cache.clear()
|
||
self.user = User.objects.create(phone='09120007201')
|
||
|
||
def tearDown(self):
|
||
cache.clear()
|
||
|
||
def race(self, *headers):
|
||
codes = []
|
||
barrier = threading.Barrier(len(headers))
|
||
|
||
def ask(extra):
|
||
try:
|
||
client = APIClient()
|
||
client.credentials(HTTP_AUTHORIZATION=f'Bearer {issue_access_token(self.user.id)}')
|
||
barrier.wait()
|
||
codes.append(client.post('/api/ai/ask', {'userQuestion': '۲ + ۲؟'}, format='json', **extra).status_code)
|
||
finally:
|
||
connection.close()
|
||
|
||
with patch('rag.services.config.rag_is_configured', return_value=False), \
|
||
patch('ai_tutor.views.generate_tutor_answer', side_effect=_slow_answer):
|
||
threads = [threading.Thread(target=ask, args=(h,)) for h in headers]
|
||
for t in threads:
|
||
t.start()
|
||
for t in threads:
|
||
t.join()
|
||
return sorted(codes)
|
||
|
||
def test_two_questions_at_once_with_one_credit_get_one_answer(self):
|
||
QuestionCredit.objects.create(user_id=self.user.id, delta=1, reason='grant')
|
||
self.assertEqual(self.race({}, {}), [200, 429])
|
||
self.assertEqual(credit_balance(self.user.id), 0)
|
||
self.assertEqual(Message.objects.filter(role='assistant').count(), 1)
|
||
|
||
def test_the_same_idempotency_key_twice_at_once_costs_one_credit(self):
|
||
QuestionCredit.objects.create(user_id=self.user.id, delta=2, reason='grant')
|
||
key = {'HTTP_IDEMPOTENCY_KEY': 'same-key'}
|
||
self.assertEqual(self.race(key, key), [200, 200])
|
||
self.assertEqual(credit_balance(self.user.id), 1)
|
||
self.assertEqual(Message.objects.filter(role='user').count(), 1)
|