Files
MathApp/payment/tests_bazaar_pack.py
Zareian ab5e53730a Audit fixes 2026-10-03: seed keeps admin edits, plan cache, refresh limit, payment and tutor guards
- seed_content no longer undoes admin work: a SeedRecord ledger (content/seed_sync.py)
  stops deleted/renamed/re-worded content from being recreated and keeps admin-edited
  lesson text; new rows in the seed files still arrive (migration content.0010)
- plan list cache is invalidated on Plan save (receiver used a stale 'v1' key)
- token refresh per-IP cap 30/hour -> 600/hour (TOKEN_REFRESH_IP_RATE)
- a failed gateway result only changes a pending order (forged NOK no longer
  overwrites refunded/canceled)
- tutor gets the correct answer only after the student has answered
- support reply notification moved to a TicketMessage post_save signal
- global daily cap on paid AI questions (AI_GLOBAL_DAILY_MAX, default 3000)
- Bazaar verify uses only the order's SKU; bazaar orders require a SKU
- /api/health warns on the committed signing key; deploy.sh saves local commits;
  400 for bad ?unit= / ?period=; account delete keeps the referrer's Conversion
- earlier today: no stub answers outside DEBUG, ?grade= removed, logged fallbacks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 16:05:27 +03:30

97 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
خریدِ بسته‌ی سوال از راهِ کافه‌بازار (بازبینیِ ۲۰۲۶-۱۰-۰۲).
سفارشِ بازار فقط از `POST /api/payment/orders` (کلیدِ داخلی) ساخته می‌شود. آن endpoint
`planId` نمی‌گرفت، پس سفارشِ بسته بی‌`plan_id` ساخته می‌شد و بعد از تأییدِ بازار «paid» می‌شد
بی‌آن‌که `grant_pack` چیزی بدهد: پول گرفته می‌شد و اعتباری نمی‌آمد. پاسخِ تأیید هم وضعیتِ
پیش از پرداخت («pending») را برمی‌گرداند. بازار با `bazaar.verify_purchase`ِ ساختگی آزموده
می‌شود؛ خریدِ واقعیِ بازار هنوز هیچ‌جا آزموده نشده.
"""
from unittest.mock import patch
from django.core.cache import cache
from django.test import TestCase, override_settings
from rest_framework.test import APIClient
from accounts.authentication import issue_access_token
from accounts.models import User
from subscription.models import Plan
from subscription.services import credit_balance
from .bazaar import BazaarPurchaseResult
from .models import Order
@override_settings(INTERNAL_SERVICE_KEY='internal-test-key')
class BazaarPackTests(TestCase):
def setUp(self):
cache.clear()
self.user = User.objects.create(phone='09120007101', is_guest=False)
self.client = APIClient()
self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {issue_access_token(self.user.id)}')
self.pack = Plan.objects.get(kind='pack', question_credits=20)
def tearDown(self):
cache.clear()
def create_bazaar_order(self, **extra):
response = APIClient().post('/api/payment/orders', {
'userId': str(self.user.id), 'amount': self.pack.price, 'provider': 'bazaar',
'bazaarProductSku': 'pack20', **extra,
}, format='json', HTTP_X_INTERNAL_KEY='internal-test-key')
self.assertEqual(response.status_code, 200, response.content)
return response.json()['orderId']
def verify(self, order_id, valid=True):
with patch('payment.bazaar.verify_purchase',
return_value=BazaarPurchaseResult(is_valid=valid, purchase_state=0 if valid else None)):
return self.client.post('/api/payment/bazaar/verify', {
'orderId': order_id, 'productSku': 'pack20', 'purchaseToken': 'token-1'}, format='json')
def test_a_pack_bought_through_bazaar_gives_its_credits(self):
order_id = self.create_bazaar_order(planId=str(self.pack.id))
order = Order.objects.get(pk=order_id)
self.assertEqual((order.plan_id, order.plan_title, order.list_price), (self.pack.id, self.pack.title, self.pack.price))
self.verify(order_id)
self.assertEqual(credit_balance(self.user.id), 20)
def test_verify_reports_the_status_the_order_actually_has(self):
order_id = self.create_bazaar_order(planId=str(self.pack.id))
response = self.verify(order_id)
self.assertEqual(Order.objects.get(pk=order_id).status, 'paid')
self.assertEqual(response.json()['status'], 'paid')
def test_unknown_plan_is_rejected(self):
response = APIClient().post('/api/payment/orders', {
'userId': str(self.user.id), 'amount': 1, 'provider': 'bazaar', 'planId': '999999',
}, format='json', HTTP_X_INTERNAL_KEY='internal-test-key')
self.assertEqual(response.status_code, 400)
@override_settings(INTERNAL_SERVICE_KEY='internal-test-key')
class BazaarSkuComesFromTheOrderTests(TestCase):
"""تأییدِ بازار SKU را فقط از سفارش می‌گیرد، نه از کلاینت (۲۰۲۶-۱۰-۰۳)."""
def setUp(self):
cache.clear()
self.user = User.objects.create(phone='09120007102', is_guest=False)
self.client = APIClient()
self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {issue_access_token(self.user.id)}')
def test_a_bazaar_order_needs_a_sku(self):
response = APIClient().post('/api/payment/orders', {
'userId': str(self.user.id), 'amount': 1000, 'provider': 'bazaar',
}, format='json', HTTP_X_INTERNAL_KEY='internal-test-key')
self.assertEqual(response.status_code, 400)
def test_verify_never_uses_the_clients_sku(self):
order = Order.objects.create(user_id=self.user.id, amount=259000, provider='bazaar',
idempotency_key='no-sku-1')
with patch('payment.bazaar.verify_purchase') as verify:
response = self.client.post('/api/payment/bazaar/verify', {
'orderId': str(order.id), 'productSku': 'cheapest', 'purchaseToken': 't'}, format='json')
self.assertEqual(response.status_code, 400)
verify.assert_not_called()
order.refresh_from_db()
self.assertEqual(order.status, 'pending')