""" پول: ایرادهایی که بازبینیِ ۲۰۲۶-۰۹-۲۵ بازتولید کرد و حالا بسته شده‌اند. هر تست همان سناریوی واقعی را اجرا می‌کند که پیش‌تر خراب بود. """ 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', is_guest=False) 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', is_guest=False) 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)) def test_guests_take_no_part(self): guest = User.objects.create(device_id='dev-aff', is_guest=True) client = APIClient() client.credentials(HTTP_AUTHORIZATION=f'Bearer {issue_access_token(guest.id)}') self.assertEqual(client.post('/api/affiliate/track', {'referralCode': 'SARA10'}, format='json').status_code, 403) self.assertEqual(client.get('/api/affiliate/me').status_code, 403) self.assertFalse(Conversion.objects.exists())