cleanup: trim bloated multi-line comments across the project
This commit is contained in:
@@ -27,18 +27,11 @@ def apply_offer(*, user_id: int, offer: Offer, source: str) -> UserOffer | None:
|
||||
if not offer.is_active:
|
||||
return None
|
||||
|
||||
# ⚠️ اصلاح: قبلاً هر IntegrityError داخل کل بلوکِ atomic (که شاملِ
|
||||
# grant_free_subscription هم میشد) بهعنوان «duplicate UserOffer»
|
||||
# تفسیر میشد و None برمیگردوند — یعنی اگه یک IntegrityError واقعیِ
|
||||
# دیگه (مثلاً داخل grant_free_subscription) اتفاق میافتاد، خطا قورت
|
||||
# داده میشد و caller فکر میکرد «کاربر قبلاً این آفر رو گرفته»، در
|
||||
# حالی که اصلاً چنین چیزی نبود. حالا فقط IntegrityError ناشی از
|
||||
# UniqueConstraintِ خودِ UserOffer.create (duplicate واقعی) گرفته
|
||||
# میشه — با یک savepoint جداگانه (atomic تودرتو) که فقط همون create
|
||||
# رو rollback میکنه، نه کل تراکنش رو — و خطاهای دیگه (مثلاً از داخل
|
||||
# grant_free_subscription) عادی propagate میشن. atomic بودنِ کلِ
|
||||
# عملیات (UserOffer + Subscription + expires_at) هم دستنخورده باقی
|
||||
# مونده، دقیقاً طبق همون دلیلی که در docstring بالا توضیح داده شده.
|
||||
# فقط IntegrityErrorِ ناشیِ از UniqueConstraintِ خودِ UserOffer.create
|
||||
# (duplicate واقعی) اینجا گرفته میشه، با یک savepoint مجزا که فقط
|
||||
# همون create رو rollback میکنه — وگرنه یک IntegrityErrorِ دیگه (مثلاً
|
||||
# داخلِ grant_free_subscription) قورت داده میشد و caller اشتباهاً فکر
|
||||
# میکرد کاربر قبلاً این آفر رو گرفته.
|
||||
with transaction.atomic():
|
||||
try:
|
||||
with transaction.atomic(): # savepoint مجزا فقط برای create
|
||||
|
||||
@@ -102,12 +102,7 @@ class RedeemOfferCodeThrottleTests(TestCase):
|
||||
self.assertEqual(blocked_response.status_code, 429)
|
||||
|
||||
def test_throttle_is_scoped_per_user_not_shared_globally(self):
|
||||
# ⚠️ توجه: از یک کاربرِ *غیرمهمان* استفاده میشه (نه یک حسابِ مهمانِ
|
||||
# دومِ متفاوت)، چون طبقِ فیکسِ GuestAwareDiscountThrottle حسابهای
|
||||
# مهمان دیگه bucket جداگانه ندارن (نگاه کنید به
|
||||
# RedeemOfferCodeGuestThrottleBypassTests پایینتر) — این تست فقط
|
||||
# رفتارِ per-user برای کاربرهایِ ثبتنامشده رو تضمین میکنه که باید
|
||||
# دقیقاً مثلِ قبل دستنخورده بمونه.
|
||||
# کاربرِ غیرمهمان (نه یک حسابِ مهمانِ دوم، که دیگه bucket جدا نداره).
|
||||
other_user = User.objects.create(is_guest=False, name='', phone='+989120000701')
|
||||
other_token = issue_access_token(other_user.id)
|
||||
|
||||
@@ -299,12 +294,7 @@ class ValidateDiscountThrottleTests(TestCase):
|
||||
self.assertEqual(blocked_response.status_code, 429)
|
||||
|
||||
def test_throttle_is_scoped_per_user_not_shared_globally(self):
|
||||
# ⚠️ توجه: از یک کاربرِ *غیرمهمان* استفاده میشه (نه یک حسابِ مهمانِ
|
||||
# دومِ متفاوت)، چون طبقِ فیکسِ GuestAwareDiscountThrottle حسابهای
|
||||
# مهمان دیگه bucket جداگانه ندارن (نگاه کنید به
|
||||
# ValidateDiscountGuestThrottleBypassTests پایینتر) — این تست فقط
|
||||
# رفتارِ per-user برای کاربرهایِ ثبتنامشده رو تضمین میکنه که باید
|
||||
# دقیقاً مثلِ قبل دستنخورده بمونه.
|
||||
# کاربرِ غیرمهمان (نه یک حسابِ مهمانِ دوم، که دیگه bucket جدا نداره).
|
||||
other_user = User.objects.create(is_guest=False, name='', phone='+989120000731')
|
||||
other_token = issue_access_token(other_user.id)
|
||||
|
||||
@@ -387,12 +377,7 @@ class RedeemDiscountThrottleTests(TestCase):
|
||||
self.assertEqual(blocked_response.status_code, 429)
|
||||
|
||||
def test_throttle_is_scoped_per_user_not_shared_globally(self):
|
||||
# ⚠️ توجه: از یک کاربرِ *غیرمهمان* استفاده میشه (نه یک حسابِ مهمانِ
|
||||
# دومِ متفاوت)، چون طبقِ فیکسِ GuestAwareDiscountThrottle حسابهای
|
||||
# مهمان دیگه bucket جداگانه ندارن (نگاه کنید به
|
||||
# RedeemDiscountGuestThrottleBypassTests پایینتر) — این تست فقط
|
||||
# رفتارِ per-user برای کاربرهایِ ثبتنامشده رو تضمین میکنه که باید
|
||||
# دقیقاً مثلِ قبل دستنخورده بمونه.
|
||||
# کاربرِ غیرمهمان (نه یک حسابِ مهمانِ دوم، که دیگه bucket جدا نداره).
|
||||
other_user = User.objects.create(is_guest=False, name='', phone='+989120000301')
|
||||
other_token = issue_access_token(other_user.id)
|
||||
other_order = Order.objects.create(
|
||||
|
||||
@@ -69,21 +69,11 @@ class ValidateDiscountView(APIView):
|
||||
# صورت کلاینت بدون احراز هویت میتواند کدهای تخفیف را probe کند.
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
# ⚠️ اضافه شده: این endpoint خودِ type/value کد رو هم برمیگردونه (نه
|
||||
# فقط valid/invalid)، پس بدونِ throttle یک کاربرِ احرازهویتشده میتونست
|
||||
# بینهایت کدهای تخفیف رو probe کنه و جزئیاتشون رو یاد بگیره. طبقِ
|
||||
# همون الگویِ ScopedRateThrottle که برای RedeemOfferCodeView
|
||||
# (offer_redeem) اضافه شد: چون کاربر احرازهویتشده، کلید بر اساسِ pk
|
||||
# کاربره (نه IP). نرخ کمی بالاتر از offer_redeem انتخاب شده چون این
|
||||
# یک چکِ read-only پیش از خریده (ممکنه کاربر چند کد رو قبل از تصمیمِ
|
||||
# نهایی امتحان کنه)، نه خودِ مصرف کد.
|
||||
# ⚠️ اصلاح شده: چون کاربرهای مهمان (GuestSessionView) با هر deviceId
|
||||
# جدید یک pk تازه میگیرن، ScopedRateThrottleِ ساده (کلید بر اساسِ
|
||||
# user.pk) توسطِ چرخشِ حساب مهمان قابلدورزدن بود. حالا از همون
|
||||
# GuestAwareDiscountThrottleِ مشترک (که RedeemDiscountView/
|
||||
# RedeemOfferCodeView هم استفاده میکنن) استفاده میشه که برای
|
||||
# کاربرهای مهمان کلید رو بر اساسِ IP میسازه و برای کاربرهای
|
||||
# غیرمهمان رفتارِ per-user قبلی رو دستنخورده نگه میداره.
|
||||
# این endpoint type/value کد رو برمیگردونه، پس بدونِ throttle یک
|
||||
# کاربر میتونست بینهایت کد رو probe کنه؛ نرخ کمی بالاتر از
|
||||
# offer_redeem چون این یک چکِ read-only پیش از خریده. GuestAwareDiscountThrottle
|
||||
# مشترک (هم اینجا هم RedeemDiscountView/RedeemOfferCodeView) برای
|
||||
# مهمانها کلید رو بر اساسِ IP میسازه تا چرخشِ حساب bucket تازه نده.
|
||||
throttle_scope = 'discount_validate'
|
||||
throttle_classes = [GuestAwareDiscountThrottle]
|
||||
|
||||
@@ -117,23 +107,11 @@ class RedeemDiscountView(APIView):
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
# ⚠️ اضافه شده: این endpoint هم دقیقاً همون oracle ای که باعث شد
|
||||
# ValidateDiscountView و RedeemOfferCodeView throttle بگیرن رو داره —
|
||||
# کدِ ناموجود ۴۰۴ میگیره، کدِ موجود-ولی-نامعتبر ۴۰۰ میگیره، پس بدونِ
|
||||
# throttle یک کاربرِ احرازهویتشده میتونست کدهای تخفیف رو روی سفارشِ
|
||||
# خودش probe/brute-force کنه. اینجا ریسک از اون دوتا هم بیشتره، چون
|
||||
# حدسِ درست صرفاً اطلاعات لو نمیده؛ یک کدِ واقعی رو مصرف میکنه
|
||||
# (سهمِ max_uses مشترک بین همهی کاربرها رو میخوره) و باعثِ افتِ
|
||||
# واقعیِ مبلغِ سفارش میشه؛ ضمناً هر تلاش دو تا select_for_update و یک
|
||||
# تراکنشِ دیتابیسی اجرا میکنه که گرونتر از GET سبکِ validate است.
|
||||
# طبقِ همون الگو، ScopedRateThrottle با کلیدِ per-user (نه IP) اضافه شد.
|
||||
# ⚠️ اصلاح شده: چون کاربرهای مهمان (GuestSessionView) با هر deviceId
|
||||
# جدید یک pk تازه میگیرن، ScopedRateThrottleِ ساده (کلید بر اساسِ
|
||||
# user.pk) توسطِ چرخشِ حساب مهمان قابلدورزدن بود. حالا از
|
||||
# GuestAwareDiscountThrottle استفاده میشه که برای کاربرهای مهمان
|
||||
# کلید رو بر اساسِ IP میسازه (دقیقاً همون الگویِ GuestAwareAIThrottle
|
||||
# و GuestAwareFamilyThrottle) و برای کاربرهای غیرمهمان رفتارِ per-user
|
||||
# قبلی رو دستنخورده نگه میداره.
|
||||
# همون oracleِ probe مثلِ ValidateDiscountView، ولی ریسکش بیشتره: حدسِ
|
||||
# درست یه کدِ واقعی رو مصرف میکنه (سهمِ max_uses مشترک) و مبلغِ سفارش
|
||||
# رو واقعاً کم میکنه، ضمن اینکه هر تلاش دو select_for_update و یه
|
||||
# تراکنشه. GuestAwareDiscountThrottle برای مهمانها کلید رو بر اساسِ IP
|
||||
# میسازه تا چرخشِ حساب bucket تازه نده.
|
||||
throttle_scope = 'discount_redeem'
|
||||
throttle_classes = [GuestAwareDiscountThrottle]
|
||||
|
||||
@@ -200,24 +178,11 @@ class RedeemOfferCodeView(APIView):
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
# ⚠️ اضافه شده: کدهای نامعتبر فقط یک ValidationError ساده (۴۰۰) برمیگردونن
|
||||
# و هیچ throttleای اینجا نبود؛ یعنی یک کاربرِ احرازهویتشده میتونست
|
||||
# بدونِ هیچ محدودیتی کدهای آفر رو حدس بزنه (offer-code probing). طبقِ
|
||||
# همون الگویِ ScopedRateThrottle که در بقیهی اکشنهایِ کمتکرار/حساسِ
|
||||
# پروژه استفاده شده (مثلِ purchase در subscription، support_create در
|
||||
# support)، اینجا هم یک throttle_scope مجزا اضافه شد.
|
||||
# ⚠️ اصلاح شده: چون کاربرهای مهمان (GuestSessionView) با هر deviceId
|
||||
# جدید یک pk تازه میگیرن، ScopedRateThrottleِ ساده (کلید بر اساسِ
|
||||
# user.pk) توسطِ چرخشِ حساب مهمان قابلدورزدن بود — بهخصوص اینجا
|
||||
# حساستره چون Offer.code سراسری/یکتاست و redeem_offer_code() فقط
|
||||
# جلوی تکرارِ *همون* کاربر رو میگیره، نه کاربرهای مهمانِ جدید (یک
|
||||
# حسابِ مهمانِ چرخشی میتونست همون آفرِ سراسری رو دوباره از
|
||||
# apply_offer() بگیره). حالا از همون GuestAwareDiscountThrottle که
|
||||
# برای RedeemDiscountView اضافه شد استفاده میشه (دقیقاً همون الگویِ
|
||||
# GuestAwareAIThrottle، GuestAwareFamilyThrottle و
|
||||
# GuestAwarePurchaseThrottle) که برای کاربرهای مهمان کلید رو بر
|
||||
# اساسِ IP میسازه و برای کاربرهای غیرمهمان رفتارِ per-user قبلی رو
|
||||
# دستنخورده نگه میداره.
|
||||
# بدونِ throttle، کدهای آفر قابلِ probe بودن؛ اینجا حساستره چون
|
||||
# Offer.code سراسری/یکتاست و redeem_offer_code() فقط جلوی تکرارِ همون
|
||||
# کاربر رو میگیره — یک حسابِ مهمانِ چرخشی میتونست همون آفرِ سراسری رو
|
||||
# دوباره بگیره. GuestAwareDiscountThrottle برای مهمانها کلید رو بر
|
||||
# اساسِ IP میسازه تا چرخشِ حساب bucket تازه نده.
|
||||
throttle_scope = 'offer_redeem'
|
||||
throttle_classes = [GuestAwareDiscountThrottle]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user