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:
@@ -20,7 +20,7 @@ class PurchaseIdempotencyRetryTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-purchase-1', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000042', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
self.plan = Plan.objects.create(
|
||||
title='ماهانه', price=100000, currency='IRT', duration_days=30,
|
||||
@@ -77,7 +77,7 @@ class PurchaseExternalCallNotUnderLockTests(TransactionTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-purchase-lock-1', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000043', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
self.plan = Plan.objects.create(
|
||||
title='ماهانه', price=100000, currency='IRT', duration_days=30,
|
||||
@@ -115,7 +115,7 @@ class PurchaseOrderCreationFailureCleanupTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-purchase-cleanup-1', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000044', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
self.plan = Plan.objects.create(
|
||||
title='ماهانه', price=100000, currency='IRT', duration_days=30,
|
||||
@@ -171,7 +171,7 @@ class PurchaseFailureCleanupConcurrencyRegressionTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-purchase-race-1', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000045', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
self.plan = Plan.objects.create(
|
||||
title='ماهانه', price=100000, currency='IRT', duration_days=30,
|
||||
@@ -257,7 +257,7 @@ class PurchaseIdenticalIntermediateStateRaceRegressionTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-purchase-race-identical-1', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000046', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
self.plan = Plan.objects.create(
|
||||
title='ماهانه', price=100000, currency='IRT', duration_days=30,
|
||||
@@ -314,7 +314,7 @@ class PurchaseThrottleTests(TestCase):
|
||||
from django.core.cache import cache
|
||||
cache.clear()
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-purchase-throttle-1', is_guest=True, name='')
|
||||
self.user = User.objects.create(phone='09000000047', name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
self.plan = Plan.objects.create(
|
||||
title='ماهانه', price=100000, currency='IRT', duration_days=30,
|
||||
@@ -343,7 +343,7 @@ class PurchaseThrottleTests(TestCase):
|
||||
}
|
||||
)
|
||||
def test_requests_within_the_limit_still_succeed(self):
|
||||
# ⚠️ مثلِ GuestSessionThrottleTests: override_settings بهتنهایی روی
|
||||
# override_settings بهتنهایی روی
|
||||
# ScopedRateThrottle.THROTTLE_RATES (که یکبار موقعِ import گرفته
|
||||
# شده) اثر نداره؛ برای اینکه نرخِ واقعاً محدودشده اعمال بشه، مستقیماً
|
||||
# دیکشنریِ زندهی THROTTLE_RATES رو patch میکنیم.
|
||||
@@ -362,10 +362,8 @@ class PurchaseThrottleTests(TestCase):
|
||||
self.assertEqual(blocked_response.status_code, 429)
|
||||
|
||||
def test_changing_client_ip_does_not_bypass_the_user_limit(self):
|
||||
# کاربرِ غیرمهمان لازمه، نه self.user، چون کلیدِ bucketِ مهمانها
|
||||
# عمداً بر اساسِ IP است (نگاه کنید به PurchaseGuestThrottleBypassTests)
|
||||
# و این تست رفتارِ per-user برای ثبتنامشدهها را میسنجد.
|
||||
registered_user = User.objects.create(is_guest=False, name='', phone='+989120000501')
|
||||
# کلیدِ throttle شناسهی کاربر است، نه IP؛ تغییرِ IP سطلِ تازه نمیدهد.
|
||||
registered_user = User.objects.create(name='', phone='+989120000501')
|
||||
registered_token = issue_access_token(registered_user.id)
|
||||
|
||||
def _purchase_as(idempotency_key, remote_addr):
|
||||
@@ -413,19 +411,8 @@ class PurchaseThrottleTests(TestCase):
|
||||
self.assertEqual(subscription.version, snapshot['version'])
|
||||
|
||||
|
||||
class PurchaseGuestThrottleBypassTests(TestCase):
|
||||
"""
|
||||
🔒 اثبات فیکسِ امنیتی: قبلاً کلیدِ throttle_scope='purchase' برای هر
|
||||
کاربرِ احرازهویتشده (مهمان یا نه) user.pk بود. چون GuestSessionView
|
||||
با هر deviceId جدید یک کاربرِ مهمانِ تازه (pk جدید) میسازه، یک مهاجم
|
||||
میتونست با ساختنِ پیدرپیِ حسابهای مهمان، هر بار یک bucket throttle
|
||||
تازه برای سقفِ ۱۰/ساعتِ purchase بگیره. این endpoint حساسِ مالیه: هر
|
||||
درخواستِ پذیرفتهشده Subscription میسازه/آپدیت میکنه و create_order()
|
||||
رو صدا میزنه. حالا GuestAwarePurchaseThrottle برای کاربرهای مهمان
|
||||
کلید رو بر اساسِ IP (همون get_ident که guest_session هم استفاده
|
||||
میکنه) میسازه، پس چرخشِ حساب مهمان دیگه bucket تازه نمیده. رفتارِ
|
||||
throttle برای کاربرِ غیرمهمان (per-user، مثلِ قبل) دستنخورده مونده.
|
||||
"""
|
||||
class PurchasePerUserThrottleTests(TestCase):
|
||||
"""هر کاربرِ ثبتنامشده سطلِ throttleِ جدای خودش را دارد؛ مصرفِ حسابِ دیگر به او نمیرسد."""
|
||||
|
||||
def setUp(self):
|
||||
from django.core.cache import cache
|
||||
@@ -444,79 +431,21 @@ class PurchaseGuestThrottleBypassTests(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, {'purchase': '2/hour'}):
|
||||
guest1 = User.objects.create(device_id='device-purchase-guest-rot-1', is_guest=True, name='')
|
||||
response1 = self._purchase('key-guest-rot-1', issue_access_token(guest1.id))
|
||||
self.assertEqual(response1.status_code, 200)
|
||||
|
||||
guest2 = User.objects.create(device_id='device-purchase-guest-rot-2', is_guest=True, name='')
|
||||
response2 = self._purchase('key-guest-rot-2', issue_access_token(guest2.id))
|
||||
self.assertEqual(response2.status_code, 200)
|
||||
|
||||
# سومین حسابِ مهمانِ تازه (pk جدید) باید throttle بشه، چون
|
||||
# bucket بر اساسِ IP بهاشتراک گذاشته شده، نه user.pk — یعنی
|
||||
# سقفِ ۲/ساعت هنوز اعمال میشه، صرفنظر از تعدادِ حسابهای
|
||||
# مهمانِ ساختهشده.
|
||||
guest3 = User.objects.create(device_id='device-purchase-guest-rot-3', is_guest=True, name='')
|
||||
response3 = self._purchase('key-guest-rot-3', issue_access_token(guest3.id))
|
||||
|
||||
self.assertEqual(response3.status_code, 429)
|
||||
|
||||
def test_rotated_guest_account_cannot_get_a_fresh_bucket_after_limit(self):
|
||||
# حتی وقتی حسابِ مهمانِ جدید به سقف رسیده، یک حسابِ مهمانِ دیگر
|
||||
# (چهارمین pk) هم نباید bucket تازهای بگیره — سقف هنوز به IP
|
||||
# مشترک متصل است.
|
||||
with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'purchase': '1/hour'}):
|
||||
guest1 = User.objects.create(device_id='device-purchase-guest-rot-4', is_guest=True, name='')
|
||||
response1 = self._purchase('key-guest-rot-4', issue_access_token(guest1.id))
|
||||
self.assertEqual(response1.status_code, 200)
|
||||
|
||||
guest2 = User.objects.create(device_id='device-purchase-guest-rot-5', is_guest=True, name='')
|
||||
blocked = self._purchase('key-guest-rot-5', issue_access_token(guest2.id))
|
||||
self.assertEqual(blocked.status_code, 429)
|
||||
|
||||
guest3 = User.objects.create(device_id='device-purchase-guest-rot-6', is_guest=True, name='')
|
||||
still_blocked = self._purchase('key-guest-rot-6', issue_access_token(guest3.id))
|
||||
|
||||
self.assertEqual(still_blocked.status_code, 429)
|
||||
|
||||
def test_registered_users_keep_independent_per_user_buckets(self):
|
||||
# عدمِ رگرسیون: کاربرهای غیرمهمان (ثبتنامشده با شماره) باید مثلِ
|
||||
# قبل هرکدوم bucket جداگانهی خودشون رو داشته باشن — throttle
|
||||
# برای اونا هنوز بر اساسِ user.pk است، نه IP.
|
||||
with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'purchase': '1/hour'}):
|
||||
user_a = User.objects.create(is_guest=False, name='', phone='+989120000601')
|
||||
user_a = User.objects.create(name='', phone='+989120000601')
|
||||
response_a = self._purchase('key-reg-a', issue_access_token(user_a.id))
|
||||
self.assertEqual(response_a.status_code, 200)
|
||||
|
||||
user_b = User.objects.create(is_guest=False, name='', phone='+989120000602')
|
||||
user_b = User.objects.create(name='', phone='+989120000602')
|
||||
response_b = self._purchase('key-reg-b', issue_access_token(user_b.id))
|
||||
|
||||
self.assertEqual(response_b.status_code, 200)
|
||||
|
||||
def test_blocked_guest_request_does_not_mutate_subscription_or_call_purchase_logic(self):
|
||||
# درخواستِ بلاکشده (از حسابِ مهمانِ چرخشی) نباید Subscription/Order
|
||||
# بسازه یا create_order() رو صدا بزنه — throttle باید *قبل* از
|
||||
# رسیدن به منطقِ purchase رد کنه، حتی با Idempotency-Key معتبر.
|
||||
with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'purchase': '1/hour'}):
|
||||
guest1 = User.objects.create(device_id='device-purchase-guest-rot-7', is_guest=True, name='')
|
||||
first_response = self._purchase('key-guest-rot-7', issue_access_token(guest1.id))
|
||||
self.assertEqual(first_response.status_code, 200)
|
||||
|
||||
# حسابِ مهمانِ تازهی دوم باید بلاک بشه (bucket مشترکِ IP از
|
||||
# قبل مصرف شده) و نباید Subscription/Order برای آن بسازد.
|
||||
guest2 = User.objects.create(device_id='device-purchase-guest-rot-8', is_guest=True, name='')
|
||||
with patch('subscription.views.create_order') as mock_create_order:
|
||||
blocked_response = self._purchase('key-guest-rot-8', issue_access_token(guest2.id))
|
||||
self.assertEqual(blocked_response.status_code, 429)
|
||||
mock_create_order.assert_not_called()
|
||||
|
||||
self.assertFalse(Subscription.objects.filter(user_id=guest2.id).exists())
|
||||
self.assertEqual(Order.objects.filter(user_id=guest2.id).count(), 0)
|
||||
|
||||
|
||||
class PlanCardContractTests(TestCase):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user