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:
125
content/tests.py
125
content/tests.py
@@ -20,7 +20,7 @@ class LessonCompleteImpossibleCountTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-complete-1', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000011', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
|
||||
grade = Grade.objects.create(title='پایه اول', icon='icon', color='#fff')
|
||||
@@ -79,7 +79,7 @@ class LessonCompleteRatioIgnoresClientTotalQuestionsTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-complete-2', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000012', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
|
||||
grade = Grade.objects.create(title='پایه دوم', icon='icon', color='#fff')
|
||||
@@ -130,7 +130,7 @@ class QuestionAnswerThrottleTests(TestCase):
|
||||
from django.core.cache import cache
|
||||
cache.clear()
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-answer-throttle-1', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000013', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
|
||||
grade = Grade.objects.create(title='پایه اول', icon='icon', color='#fff')
|
||||
@@ -173,13 +173,8 @@ class QuestionAnswerThrottleTests(TestCase):
|
||||
self.assertEqual(blocked_response.status_code, 429)
|
||||
|
||||
def test_changing_client_ip_does_not_bypass_the_user_limit(self):
|
||||
# ⚠️ توجه: از یک کاربرِ *غیرمهمان* استفاده میشه (نه self.user که
|
||||
# مهمان است)، چون طبقِ فیکسِ GuestAwareContentThrottle حالا کلیدِ
|
||||
# bucket برای کاربرهای مهمان عمداً بر اساسِ IP است (نگاه کنید به
|
||||
# QuestionAnswerGuestThrottleBypassTests پایینتر) — این تست فقط
|
||||
# رفتارِ per-user (نه IP) برای کاربرهایِ ثبتنامشده رو تضمین
|
||||
# میکنه که باید دقیقاً مثلِ قبل دستنخورده بمونه.
|
||||
registered_user = User.objects.create(is_guest=False, name='', phone='+989120000701')
|
||||
# کلیدِ throttle شناسهی کاربر است، نه IP؛ تغییرِ IP سطلِ تازه نمیدهد.
|
||||
registered_user = User.objects.create(name='', phone='+989120000701')
|
||||
registered_token = issue_access_token(registered_user.id)
|
||||
|
||||
def _answer_as(remote_addr):
|
||||
@@ -253,7 +248,7 @@ class LessonCompleteThrottleTests(TestCase):
|
||||
from django.core.cache import cache
|
||||
cache.clear()
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-complete-throttle-1', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000014', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
|
||||
grade = Grade.objects.create(title='پایه اول', icon='icon', color='#fff')
|
||||
@@ -294,13 +289,8 @@ class LessonCompleteThrottleTests(TestCase):
|
||||
self.assertEqual(blocked_response.status_code, 429)
|
||||
|
||||
def test_changing_client_ip_does_not_bypass_the_user_limit(self):
|
||||
# ⚠️ توجه: از یک کاربرِ *غیرمهمان* استفاده میشه (نه self.user که
|
||||
# مهمان است)، چون طبقِ فیکسِ GuestAwareContentThrottle حالا کلیدِ
|
||||
# bucket برای کاربرهای مهمان عمداً بر اساسِ IP است (نگاه کنید به
|
||||
# LessonCompleteGuestThrottleBypassTests پایینتر) — این تست فقط
|
||||
# رفتارِ per-user (نه IP) برای کاربرهایِ ثبتنامشده رو تضمین
|
||||
# میکنه که باید دقیقاً مثلِ قبل دستنخورده بمونه.
|
||||
registered_user = User.objects.create(is_guest=False, name='', phone='+989120000702')
|
||||
# کلیدِ throttle شناسهی کاربر است، نه IP؛ تغییرِ IP سطلِ تازه نمیدهد.
|
||||
registered_user = User.objects.create(name='', phone='+989120000702')
|
||||
registered_token = issue_access_token(registered_user.id)
|
||||
|
||||
def _complete_as(remote_addr):
|
||||
@@ -358,10 +348,8 @@ class LessonCompleteThrottleTests(TestCase):
|
||||
self.assertEqual(progress_after.last_activity_at, progress_snapshot['last_activity_at'])
|
||||
|
||||
|
||||
class QuestionAnswerGuestThrottleBypassTests(TestCase):
|
||||
"""اثباتِ فیکسِ throttle: چرخشِ حساب مهمان (هر deviceId یه pk تازه) نباید
|
||||
bucket تازهی question_answer بده — GuestAwareContentThrottle برای
|
||||
مهمانها کلید رو بر اساسِ IP میسازه، نه user.pk."""
|
||||
class QuestionAnswerPerUserThrottleTests(TestCase):
|
||||
"""هر کاربرِ ثبتنامشده سطلِ throttleِ جدای خودش را دارد؛ مصرفِ حسابِ دیگر به او نمیرسد."""
|
||||
|
||||
def setUp(self):
|
||||
from django.core.cache import cache
|
||||
@@ -387,58 +375,23 @@ class QuestionAnswerGuestThrottleBypassTests(TestCase):
|
||||
HTTP_AUTHORIZATION=f'Bearer {token}',
|
||||
)
|
||||
|
||||
def test_rotating_guest_accounts_share_the_same_throttle_bucket(self):
|
||||
# سه حساب مهمانِ متفاوت (شبیهسازیِ چرخشِ حساب برای دورزدنِ throttle)،
|
||||
# همه از همون کلاینت/IP — باید همه یک bucket مشترک رو مصرف کنند.
|
||||
with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'question_answer': '2/min'}):
|
||||
guest1 = User.objects.create(device_id='device-answer-guest-rot-1', is_guest=True, name='')
|
||||
response1 = self._answer(issue_access_token(guest1.id))
|
||||
self.assertEqual(response1.status_code, 200)
|
||||
|
||||
guest2 = User.objects.create(device_id='device-answer-guest-rot-2', is_guest=True, name='')
|
||||
response2 = self._answer(issue_access_token(guest2.id))
|
||||
self.assertEqual(response2.status_code, 200)
|
||||
|
||||
# سومین حسابِ مهمانِ تازه (pk جدید) باید throttle بشه، چون
|
||||
# bucket بر اساسِ IP بهاشتراک گذاشته شده، نه user.pk.
|
||||
guest3 = User.objects.create(device_id='device-answer-guest-rot-3', is_guest=True, name='')
|
||||
response3 = self._answer(issue_access_token(guest3.id))
|
||||
|
||||
self.assertEqual(response3.status_code, 429)
|
||||
|
||||
def test_registered_users_keep_independent_per_user_buckets(self):
|
||||
# عدمِ رگرسیون: کاربرهای غیرمهمان (ثبتنامشده با شماره) باید مثلِ
|
||||
# قبل هرکدوم bucket جداگانهی خودشون رو داشته باشن.
|
||||
with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'question_answer': '1/min'}):
|
||||
user_a = User.objects.create(is_guest=False, name='', phone='+989120000711')
|
||||
user_a = User.objects.create(name='', phone='+989120000711')
|
||||
response_a = self._answer(issue_access_token(user_a.id))
|
||||
self.assertEqual(response_a.status_code, 200)
|
||||
|
||||
user_b = User.objects.create(is_guest=False, name='', phone='+989120000712')
|
||||
user_b = User.objects.create(name='', phone='+989120000712')
|
||||
response_b = self._answer(issue_access_token(user_b.id))
|
||||
|
||||
self.assertEqual(response_b.status_code, 200)
|
||||
|
||||
def test_blocked_guest_request_creates_no_answer_or_hearts_change(self):
|
||||
# درخواستِ بلاکشده (از حسابِ مهمانِ چرخشی) نباید UserAnswer بسازه
|
||||
# یا هارت/progress رو تغییر بده.
|
||||
with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'question_answer': '1/min'}):
|
||||
guest1 = User.objects.create(device_id='device-answer-guest-rot-4', is_guest=True, name='')
|
||||
first_response = self._answer(issue_access_token(guest1.id))
|
||||
self.assertEqual(first_response.status_code, 200)
|
||||
|
||||
guest2 = User.objects.create(device_id='device-answer-guest-rot-5', is_guest=True, name='')
|
||||
blocked_response = self._answer(issue_access_token(guest2.id))
|
||||
|
||||
self.assertEqual(blocked_response.status_code, 429)
|
||||
self.assertEqual(UserAnswer.objects.filter(user_id=guest2.id).count(), 0)
|
||||
self.assertFalse(UserHearts.objects.filter(user_id=guest2.id).exists())
|
||||
|
||||
|
||||
class LessonCompleteGuestThrottleBypassTests(TestCase):
|
||||
"""اثباتِ فیکسِ throttle: چرخشِ حساب مهمان نباید bucket تازهی
|
||||
lesson_complete بده — GuestAwareContentThrottle برای مهمانها کلید رو
|
||||
بر اساسِ IP میسازه، نه user.pk."""
|
||||
class LessonCompletePerUserThrottleTests(TestCase):
|
||||
"""هر کاربرِ ثبتنامشده سطلِ throttleِ جدای خودش را دارد؛ مصرفِ حسابِ دیگر به او نمیرسد."""
|
||||
|
||||
def setUp(self):
|
||||
from django.core.cache import cache
|
||||
@@ -462,54 +415,20 @@ class LessonCompleteGuestThrottleBypassTests(TestCase):
|
||||
HTTP_AUTHORIZATION=f'Bearer {token}',
|
||||
)
|
||||
|
||||
def test_rotating_guest_accounts_share_the_same_throttle_bucket(self):
|
||||
# سه حساب مهمانِ متفاوت (شبیهسازیِ چرخشِ حساب برای دورزدنِ throttle)،
|
||||
# همه از همون کلاینت/IP — باید همه یک bucket مشترک رو مصرف کنند.
|
||||
with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'lesson_complete': '2/hour'}):
|
||||
guest1 = User.objects.create(device_id='device-complete-guest-rot-1', is_guest=True, name='')
|
||||
response1 = self._complete(issue_access_token(guest1.id))
|
||||
self.assertEqual(response1.status_code, 200)
|
||||
|
||||
guest2 = User.objects.create(device_id='device-complete-guest-rot-2', is_guest=True, name='')
|
||||
response2 = self._complete(issue_access_token(guest2.id))
|
||||
self.assertEqual(response2.status_code, 200)
|
||||
|
||||
# سومین حسابِ مهمانِ تازه (pk جدید) باید throttle بشه، چون
|
||||
# bucket بر اساسِ IP بهاشتراک گذاشته شده، نه user.pk.
|
||||
guest3 = User.objects.create(device_id='device-complete-guest-rot-3', is_guest=True, name='')
|
||||
response3 = self._complete(issue_access_token(guest3.id))
|
||||
|
||||
self.assertEqual(response3.status_code, 429)
|
||||
|
||||
def test_registered_users_keep_independent_per_user_buckets(self):
|
||||
# عدمِ رگرسیون: کاربرهای غیرمهمان (ثبتنامشده با شماره) باید مثلِ
|
||||
# قبل هرکدوم bucket جداگانهی خودشون رو داشته باشن.
|
||||
with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'lesson_complete': '1/hour'}):
|
||||
user_a = User.objects.create(is_guest=False, name='', phone='+989120000721')
|
||||
user_a = User.objects.create(name='', phone='+989120000721')
|
||||
response_a = self._complete(issue_access_token(user_a.id))
|
||||
self.assertEqual(response_a.status_code, 200)
|
||||
|
||||
user_b = User.objects.create(is_guest=False, name='', phone='+989120000722')
|
||||
user_b = User.objects.create(name='', phone='+989120000722')
|
||||
response_b = self._complete(issue_access_token(user_b.id))
|
||||
|
||||
self.assertEqual(response_b.status_code, 200)
|
||||
|
||||
def test_blocked_guest_request_creates_no_completion_or_reward_side_effects(self):
|
||||
# درخواستِ بلاکشده (از حسابِ مهمانِ چرخشی) نباید UserLessonCompletion
|
||||
# بسازه یا UserStats/NodeProgress رو تغییر بده.
|
||||
with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'lesson_complete': '1/hour'}):
|
||||
guest1 = User.objects.create(device_id='device-complete-guest-rot-4', is_guest=True, name='')
|
||||
first_response = self._complete(issue_access_token(guest1.id))
|
||||
self.assertEqual(first_response.status_code, 200)
|
||||
|
||||
guest2 = User.objects.create(device_id='device-complete-guest-rot-5', is_guest=True, name='')
|
||||
blocked_response = self._complete(issue_access_token(guest2.id))
|
||||
|
||||
self.assertEqual(blocked_response.status_code, 429)
|
||||
self.assertEqual(UserLessonCompletion.objects.filter(user_id=guest2.id).count(), 0)
|
||||
self.assertFalse(UserStats.objects.filter(user_id=guest2.id).exists())
|
||||
self.assertFalse(NodeProgress.objects.filter(user_id=guest2.id, node=self.node).exists())
|
||||
|
||||
|
||||
class QuestionAnswerMascotMoodTests(TestCase):
|
||||
"""پاسخِ /answer باید mascotMood بده: درست → happy، غلط → encourage
|
||||
@@ -519,7 +438,7 @@ class QuestionAnswerMascotMoodTests(TestCase):
|
||||
from django.core.cache import cache
|
||||
cache.clear()
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-mascot-1', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000015', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
grade = Grade.objects.create(title='پایه اول', icon='i', color='#fff')
|
||||
unit = Unit.objects.create(grade=grade, title='فصل ۱', order=1)
|
||||
@@ -574,7 +493,7 @@ class HomeViewScalabilityTests(TestCase):
|
||||
def setUp(self):
|
||||
_django_cache.clear()
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='dev-home-scale', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000016', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
|
||||
def _grade_with_nodes(self, n_nodes):
|
||||
@@ -661,7 +580,7 @@ class UnlimitedHeartsSubscriptionTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='dev-unlimited-hearts', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000017', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
|
||||
grade = Grade.objects.create(title='پایه اول', icon='icon', color='#fff')
|
||||
@@ -717,7 +636,7 @@ class ReadThrottleTests(TestCase):
|
||||
def setUp(self):
|
||||
_django_cache.clear()
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='dev-read-throttle', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000018', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
self.grade = Grade.objects.create(title='g', icon='i', color='#fff')
|
||||
|
||||
@@ -742,7 +661,7 @@ class HomeGradeFallbackTests(TestCase):
|
||||
def setUp(self):
|
||||
_django_cache.clear()
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='dev-home-fallback', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000019', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
|
||||
self.grade = Grade.objects.create(title='پایه نهم', icon='grade-9', color='#FF9800')
|
||||
@@ -805,7 +724,7 @@ class NodeProgressUnitParamTests(TestCase):
|
||||
from accounts.authentication import issue_access_token
|
||||
from accounts.models import User
|
||||
|
||||
user = User.objects.create(device_id='unit-param', is_guest=True)
|
||||
user = User.objects.create(phone='09000000020')
|
||||
response = APIClient().get('/api/progress/nodes?unit=abc',
|
||||
HTTP_AUTHORIZATION=f'Bearer {issue_access_token(user.id)}')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
Reference in New Issue
Block a user