Files
MathApp/config/tests_audit_round2.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

127 lines
6.2 KiB
Python
Raw Permalink 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.
"""
بازبینیِ دومِ ۲۰۲۶-۰۹-۲۵: ایرادهایی که بازتولید و بسته شدند.
هر تست همان سناریویی را اجرا می‌کند که پیش‌تر خراب بود. ایرادهایی که عمداً
باز ماندند (تصمیمِ محصولی یا خطرِ شکستنِ فرانت) در docs/security-before-production.md
و کامنتِ همان کدها ثبت‌اند.
"""
from unittest.mock import MagicMock, patch
from django.core.cache import cache
from django.test import TestCase, override_settings
from django.utils import timezone
from rest_framework.test import APIClient
from accounts.authentication import issue_access_token
from accounts.models import User
from content.models import Choice, Grade, Lesson, Node, Question, Unit
from discount.models import Offer
from payment.models import Order
from payment.services import apply_order_payment_result
from subscription.models import Plan, Subscription
def _client(user):
client = APIClient()
client.credentials(HTTP_AUTHORIZATION=f'Bearer {issue_access_token(user.id)}')
return client
class _Base(TestCase):
def setUp(self):
cache.clear()
self.user = User.objects.create(phone='09120008001')
self.client = _client(self.user)
self.year = Plan.objects.create(title='سالانه', price=299000, duration_days=365)
def tearDown(self):
cache.clear()
def buy(self, client=None):
response = (client or self.client).post(
'/api/subscription/purchase', {'planId': str(self.year.id)}, format='json')
self.assertEqual(response.status_code, 200, response.content)
return Order.objects.get(pk=response.json()['orderId'])
def pay(self, order, ref):
apply_order_payment_result(order=order, gateway='zarinpal', gateway_ref_id=ref,
new_status='paid', event='test')
class OfferNeverShortensSubscription(_Base):
def test_trial_code_after_a_paid_year_keeps_the_year(self):
self.pay(self.buy(), ref='R1')
before = Subscription.objects.get(user_id=self.user.id).expires_at
Offer.objects.create(name='هفته رایگان', kind='trial', duration_days=7, code='WEEK')
response = self.client.post('/api/discount/offers/redeem', {'code': 'WEEK'}, format='json')
self.assertEqual(response.status_code, 200, response.content)
self.assertEqual(Subscription.objects.get(user_id=self.user.id).expires_at, before)
def test_trial_still_works_for_a_user_without_subscription(self):
Offer.objects.create(name='هفته رایگان', kind='trial', duration_days=7, code='WEEK')
self.client.post('/api/discount/offers/redeem', {'code': 'WEEK'}, format='json')
sub = Subscription.objects.get(user_id=self.user.id)
self.assertEqual(sub.status, 'active')
self.assertIn((sub.expires_at - timezone.now()).days, (6, 7))
def test_free_full_still_upgrades_a_paid_year_to_unlimited(self):
self.pay(self.buy(), ref='R3')
Offer.objects.create(name='رایگانِ کامل', kind='free_full', code='FULL')
self.client.post('/api/discount/offers/redeem', {'code': 'FULL'}, format='json')
self.assertIsNone(Subscription.objects.get(user_id=self.user.id).expires_at)
@override_settings(ZARINPAL_MERCHANT_ID='x' * 36, ZARINPAL_REDIRECT_URL='https://app.example/')
class ZarinpalErrorBodyReturnsToApp(_Base):
def test_documented_error_body_redirects_instead_of_500(self):
order = self.buy()
Order.objects.filter(pk=order.pk).update(gateway_authority='A' * 36)
fake = MagicMock(status_code=200)
fake.json.return_value = {'data': [], 'errors': {'code': -51, 'message': 'failed'}}
with patch('payment.zarinpal.requests.post', return_value=fake):
response = APIClient().get('/api/payment/zarinpal/callback',
{'Authority': 'A' * 36, 'Status': 'OK'})
self.assertEqual(response.status_code, 302)
self.assertIn('status=failed', response['Location'])
class WeeklyReportCountsIslands(TestCase):
# تا ۲۰۲۶-۱۰-۰۱ همین سناریو گزارشِ والد را از /api/family/weekly-report می‌خواند؛
# آن اپ حذف شد و گزارش حالا در حسابِ خودِ دانش‌آموز است.
def test_island_quiz_xp_reaches_the_weekly_report(self):
cache.clear()
grade = Grade.objects.create(title='پایه نهم', icon='', color='')
node = Node.objects.create(unit=Unit.objects.create(grade=grade, title='فصل', order=1),
title='جزیره', order=1)
child = User.objects.create(phone='09120008002', grade_id=grade.id)
child_client = _client(child)
for i in range(4):
question = Question.objects.create(
lesson=Lesson.objects.create(node=node, title=f'درس {i}'),
type='multiple_choice', prompt=f'سوال {i}')
right = Choice.objects.create(question=question, value='الف', is_correct=True)
Choice.objects.create(question=question, value='ب', is_correct=False)
child_client.post(f'/api/questions/{question.id}/answer', {'selectedOptionId': right.id},
format='json')
child_client.post(f'/api/nodes/{node.id}/quiz/complete',
{'correctCount': 4, 'totalQuestions': 4}, format='json')
week = child_client.get('/api/user/weekly-report').json()['thisWeek']
self.assertEqual(week['xp'], 40)
self.assertEqual(week['islands'], 1)
self.assertEqual(week['lessons'], 0)
self.assertEqual((week['questions'], week['correct']), (4, 4))
class AskWithNonNumericQuestionId(TestCase):
def test_answer_is_saved_not_500(self):
cache.clear()
user = User.objects.create(phone='09120008004')
response = _client(user).post('/api/ai/ask',
{'userQuestion': 'کسر چیست؟', 'questionId': 'q-12'},
format='json')
self.assertEqual(response.status_code, 200, response.content)
from ai_tutor.models import Message
self.assertIsNone(Message.objects.get(pk=response.json()['messageId']).question_id)