Files
MathApp/payment/tests_audit_fixes.py
Zareian 663252a260 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>
2026-10-05 18:20:10 +03:30

160 lines
7.2 KiB
Python

"""
پول: ایرادهایی که بازبینیِ ۲۰۲۶-۰۹-۲۵ بازتولید کرد و حالا بسته شده‌اند.
هر تست همان سناریوی واقعی را اجرا می‌کند که پیش‌تر خراب بود.
"""
from unittest.mock import patch
from django.core.cache import cache
from django.test import TestCase
from django.utils import timezone
from rest_framework.test import APIClient
from accounts.authentication import issue_access_token
from accounts.models import User
from affiliate.models import AffiliateAccount, Conversion
from discount.models import DiscountCode
from payment.models import Order
from payment.services import apply_order_payment_result, reconcile_pending_order
from payment.zarinpal import ZarinpalVerifyResult
from subscription.models import Plan, Subscription
class _Base(TestCase):
def setUp(self):
cache.clear()
self.user = User.objects.create(phone='09120007001')
self.client = APIClient()
self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {issue_access_token(self.user.id)}')
self.month = Plan.objects.create(title='ماهانه', price=49000, duration_days=30)
self.year = Plan.objects.create(title='سالانه', price=299000, duration_days=365)
def tearDown(self):
cache.clear()
def buy(self, plan):
response = self.client.post('/api/subscription/purchase', {'planId': str(plan.id)}, format='json')
self.assertEqual(response.status_code, 200, response.content)
return Order.objects.get(pk=response.json()['orderId'])
def pay(self, order, ref='R'):
return apply_order_payment_result(order=order, gateway='zarinpal', gateway_ref_id=ref,
new_status='paid', event='test')
def subscription(self):
return Subscription.objects.get(user_id=self.user.id)
class CheckoutTests(_Base):
def test_paying_the_cheap_order_gives_the_cheap_plan(self):
cheap = self.buy(self.month)
self.buy(self.year) # شروعِ خریدِ دوم، بدونِ پرداخت
self.pay(cheap)
sub = self.subscription()
self.assertEqual(sub.plan, self.month)
days = (sub.expires_at - timezone.now()).days
self.assertIn(days, (29, 30))
def test_opening_checkout_does_not_take_away_an_active_subscription(self):
self.pay(self.buy(self.month), ref='R1')
self.buy(self.year)
self.assertEqual(self.subscription().status, 'active')
self.assertTrue(self.client.get('/api/subscription/status').json()['active'])
def test_renewing_early_keeps_the_remaining_days(self):
self.pay(self.buy(self.month), ref='R1')
first_expiry = self.subscription().expires_at
self.pay(self.buy(self.month), ref='R2')
self.assertEqual((self.subscription().expires_at - first_expiry).days, 30)
def test_an_expired_subscription_is_not_reported_active(self):
self.pay(self.buy(self.month), ref='R1')
Subscription.objects.filter(user_id=self.user.id).update(
expires_at=timezone.now() - timezone.timedelta(days=1))
self.assertFalse(self.client.get('/api/subscription/status').json()['active'])
def test_the_mark_paid_webhook_is_gone(self):
order = self.buy(self.month)
response = self.client.post('/api/payment/webhook', {'orderId': order.id, 'status': 'paid'},
format='json', HTTP_X_WEBHOOK_SECRET='anything')
self.assertEqual(response.status_code, 404)
class DiscountTests(_Base):
def setUp(self):
super().setUp()
self.half = DiscountCode.objects.create(code='HALF', type='percent', value=50, max_uses=10)
self.tenth = DiscountCode.objects.create(code='TENTH', type='percent', value=10, max_uses=10)
self.free = DiscountCode.objects.create(code='FREE', type='percent', value=100, max_uses=10)
def redeem(self, order, code):
return self.client.post('/api/discount/redeem', {'orderId': str(order.id), 'code': code}, format='json')
def test_one_code_per_order(self):
order = self.buy(self.month)
self.assertEqual(self.redeem(order, 'HALF').status_code, 200)
self.assertEqual(self.redeem(order, 'TENTH').status_code, 400)
order.refresh_from_db()
self.assertEqual(order.amount, 24500)
def test_no_discount_after_going_to_the_gateway(self):
order = self.buy(self.month)
Order.objects.filter(pk=order.pk).update(gateway_authority='A0000000000000000000000000000001')
self.assertEqual(self.redeem(order, 'HALF').status_code, 400)
order.refresh_from_db()
self.assertEqual(order.amount, 49000)
def test_a_full_discount_completes_the_purchase_without_a_gateway(self):
order = self.buy(self.month)
body = self.redeem(order, 'FREE').json()
self.assertEqual(body['orderStatus'], 'paid')
self.assertEqual(body['amount'], 0)
self.assertEqual(self.subscription().status, 'active')
class ReconcileTests(_Base):
def test_a_payment_whose_callback_was_lost_is_picked_up(self):
order = self.buy(self.month)
Order.objects.filter(pk=order.pk).update(gateway_authority='A1')
order.refresh_from_db()
with patch('payment.zarinpal.verify_payment',
return_value=ZarinpalVerifyResult(is_valid=True, ref_id='REF9')):
self.assertTrue(self.client.get('/api/subscription/status').json()['active'])
order.refresh_from_db()
self.assertEqual(order.status, 'paid')
def test_an_unpaid_order_is_left_pending_not_failed(self):
order = self.buy(self.month)
Order.objects.filter(pk=order.pk).update(gateway_authority='A2')
order.refresh_from_db()
with patch('payment.zarinpal.verify_payment', return_value=ZarinpalVerifyResult(is_valid=False)):
self.assertFalse(reconcile_pending_order(order))
order.refresh_from_db()
self.assertEqual(order.status, 'pending')
class AffiliateTests(_Base):
def setUp(self):
super().setUp()
self.referrer = User.objects.create(phone='09120007002')
self.account = AffiliateAccount.objects.create(user_id=self.referrer.id, referral_code='SARA10',
commission_rate=0.1)
def test_commission_is_recorded_on_the_first_real_payment(self):
self.assertEqual(self.client.post('/api/affiliate/track', {'referralCode': 'SARA10'},
format='json').status_code, 200)
self.pay(self.buy(self.month), ref='R1')
conversion = Conversion.objects.get(referred_user_id=self.user.id)
self.assertEqual(conversion.status, 'paid')
self.assertEqual(conversion.commission_amount, 4900)
self.pay(self.buy(self.month), ref='R2') # فقط اولین پرداخت
self.assertEqual(Conversion.objects.get(pk=conversion.pk).commission_amount, 4900)
referrer = APIClient()
referrer.credentials(HTTP_AUTHORIZATION=f'Bearer {issue_access_token(self.referrer.id)}')
me = referrer.get('/api/affiliate/me').json()
self.assertEqual((me['referredCount'], me['paidCount'], me['totalCommission']), (1, 1, 4900))