Remove the guest system entirely: no is_guest/device_id, no guest throttles, phone login is the only way in
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>
This commit is contained in:
@@ -29,7 +29,7 @@ class AskIdempotencyFollowUpsTests(TestCase):
|
||||
# بینِ تستها پاک نمیشه) میتونه از یک تستِ قبلی باقی بمونه.
|
||||
cache.clear()
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-ask-1', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000021', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
|
||||
def _ask(self, idempotency_key):
|
||||
@@ -83,7 +83,7 @@ class AskAIGenerationFailureTests(TestCase):
|
||||
# ⚠️ افزوده شده: نگاه کنید به توضیحِ مشابه در AskIdempotencyFollowUpsTests.setUp
|
||||
cache.clear()
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-ask-fail-1', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000022', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
|
||||
def _ask(self, idempotency_key=None):
|
||||
@@ -177,7 +177,7 @@ class AskAIConcurrentIdempotencyKeyTests(TestCase):
|
||||
# ⚠️ افزوده شده: نگاه کنید به توضیحِ مشابه در AskIdempotencyFollowUpsTests.setUp
|
||||
cache.clear()
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-ask-race-1', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000023', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
|
||||
def test_losing_request_cleans_up_and_returns_winners_result(self):
|
||||
@@ -254,9 +254,9 @@ class AskAIConversationApiContractTests(TestCase):
|
||||
# ⚠️ افزوده شده: نگاه کنید به توضیحِ مشابه در AskIdempotencyFollowUpsTests.setUp
|
||||
cache.clear()
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-contract-1', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000024', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
self.other_user = User.objects.create(device_id='device-contract-2', is_guest=True, name='')
|
||||
self.other_user = User.objects.create(phone='09000000025', name='')
|
||||
self.other_token = issue_access_token(self.other_user.id)
|
||||
|
||||
def test_no_conversation_id_creates_new_conversation(self):
|
||||
@@ -348,7 +348,7 @@ class GenerationBoundaryContextTests(TestCase):
|
||||
# فرستاده شدن به مرزِ تولید برسن — این metadataی لازم برای
|
||||
# RAG/Qdrantِ آیندهست، نه تاریخچه.
|
||||
client = APIClient()
|
||||
user = User.objects.create(device_id='device-boundary-grade-1', is_guest=True, name='')
|
||||
user = User.objects.create(phone='09000000026', name='')
|
||||
token = issue_access_token(user.id)
|
||||
|
||||
with patch(
|
||||
@@ -376,7 +376,7 @@ class GenerationBoundaryContextTests(TestCase):
|
||||
# پیاده نشده، context باید None بمونه (فقط gradeId/lessonId/سوال
|
||||
# مجازن).
|
||||
client = APIClient()
|
||||
user = User.objects.create(device_id='device-boundary-1', is_guest=True, name='')
|
||||
user = User.objects.create(phone='09000000027', name='')
|
||||
token = issue_access_token(user.id)
|
||||
|
||||
conversation = Conversation.objects.create(user_id=user.id)
|
||||
@@ -432,7 +432,7 @@ class AskAIViewThrottleTests(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
cache.clear()
|
||||
self.user = User.objects.create(device_id='device-ai-throttle-1', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000028', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
|
||||
def _ask(self, idempotency_key):
|
||||
@@ -456,18 +456,8 @@ class AskAIViewThrottleTests(TestCase):
|
||||
self.assertEqual(throttled_response.status_code, 429)
|
||||
|
||||
|
||||
class AskAIViewGuestThrottleBypassTests(TestCase):
|
||||
"""
|
||||
🔒 اثبات فیکسِ امنیتی: قبلاً کلیدِ throttle_scope='ai_ask' برای هر
|
||||
کاربرِ احرازهویتشده (مهمان یا نه) user.pk بود. چون GuestSessionView
|
||||
با هر deviceId جدید یک کاربرِ مهمانِ تازه (pk جدید) میسازه، یک
|
||||
مهاجم میتونست با ساختنِ پیدرپیِ حسابهای مهمان، هر بار یک bucket
|
||||
throttle تازه برای AskAIView بگیره. حالا GuestAwareAIThrottle برای
|
||||
کاربرهای مهمان کلید رو بر اساسِ IP (همون get_ident که guest_session
|
||||
هم استفاده میکنه) میسازه، پس چرخشِ حساب مهمان دیگه bucket تازه
|
||||
نمیده. رفتارِ throttle برای کاربرِ غیرمهمان (per-user، مثلِ قبل)
|
||||
دستنخورده مونده.
|
||||
"""
|
||||
class AskAIViewPerUserThrottleTests(TestCase):
|
||||
"""هر کاربرِ ثبتنامشده سطلِ throttleِ جدای خودش را دارد؛ مصرفِ حسابِ دیگر به او نمیرسد."""
|
||||
|
||||
def setUp(self):
|
||||
cache.clear()
|
||||
@@ -482,35 +472,17 @@ class AskAIViewGuestThrottleBypassTests(TestCase):
|
||||
HTTP_IDEMPOTENCY_KEY=idempotency_key,
|
||||
)
|
||||
|
||||
def test_rotating_guest_accounts_share_the_same_throttle_bucket(self):
|
||||
# سه حساب مهمانِ متفاوت (شبیهسازیِ چرخشِ حساب برای دورزدنِ throttle)،
|
||||
# همه از همون کلاینت/IP — باید همه یک bucket مشترک رو مصرف کنند.
|
||||
with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'ai_ask': '2/hour'}):
|
||||
guest1 = User.objects.create(device_id='device-guest-rot-1', is_guest=True, name='')
|
||||
response1 = self._ask(issue_access_token(guest1.id), 'guest-rot-key-1')
|
||||
self.assertEqual(response1.status_code, 200)
|
||||
|
||||
guest2 = User.objects.create(device_id='device-guest-rot-2', is_guest=True, name='')
|
||||
response2 = self._ask(issue_access_token(guest2.id), 'guest-rot-key-2')
|
||||
self.assertEqual(response2.status_code, 200)
|
||||
|
||||
# سومین حسابِ مهمانِ تازه (pk جدید) باید throttle بشه، چون
|
||||
# bucket بر اساسِ IP بهاشتراک گذاشته شده، نه user.pk.
|
||||
guest3 = User.objects.create(device_id='device-guest-rot-3', is_guest=True, name='')
|
||||
response3 = self._ask(issue_access_token(guest3.id), 'guest-rot-key-3')
|
||||
|
||||
self.assertEqual(response3.status_code, 429)
|
||||
|
||||
def test_registered_users_keep_independent_per_user_buckets(self):
|
||||
# عدمِ رگرسیون: کاربرهای غیرمهمان (ثبتنامشده با شماره) باید مثلِ
|
||||
# قبل هرکدوم bucket جداگانهی خودشون رو داشته باشن — throttle
|
||||
# برای اونا هنوز بر اساسِ user.pk است، نه IP.
|
||||
with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'ai_ask': '1/hour'}):
|
||||
user_a = User.objects.create(is_guest=False, name='', phone='+989120000101')
|
||||
user_a = User.objects.create(name='', phone='+989120000101')
|
||||
response_a = self._ask(issue_access_token(user_a.id), 'user-a-key-1')
|
||||
self.assertEqual(response_a.status_code, 200)
|
||||
|
||||
user_b = User.objects.create(is_guest=False, name='', phone='+989120000102')
|
||||
user_b = User.objects.create(name='', phone='+989120000102')
|
||||
response_b = self._ask(issue_access_token(user_b.id), 'user-b-key-1')
|
||||
|
||||
self.assertEqual(response_b.status_code, 200)
|
||||
@@ -522,7 +494,7 @@ class StubNeverReachesProductionTests(TestCase):
|
||||
@override_settings(DEBUG=False, OPENAI_API_KEY='', QDRANT_API_KEY='', RAG_FORCE_MOCK=False)
|
||||
def test_missing_keys_are_a_503_and_cost_nothing(self):
|
||||
cache.clear()
|
||||
user = User.objects.create(device_id='device-no-keys', is_guest=True, name='')
|
||||
user = User.objects.create(phone='09000000029', name='')
|
||||
response = APIClient().post(
|
||||
'/api/ai/ask', {'userQuestion': '۲+۲ چند است؟'}, format='json',
|
||||
HTTP_AUTHORIZATION=f'Bearer {issue_access_token(user.id)}',
|
||||
@@ -539,7 +511,7 @@ class GlobalDailyCapTests(TestCase):
|
||||
cache.clear()
|
||||
codes = []
|
||||
for i in range(3):
|
||||
user = User.objects.create(device_id=f'cap-{i}', is_guest=True, name='')
|
||||
user = User.objects.create(phone=f'0900{i:07d}', name='')
|
||||
codes.append(APIClient().post(
|
||||
'/api/ai/ask', {'userQuestion': f'۲+{i} چند است؟'}, format='json',
|
||||
HTTP_AUTHORIZATION=f'Bearer {issue_access_token(user.id)}',
|
||||
|
||||
Reference in New Issue
Block a user