Harden auth, AI tutor, payments, and subscriptions
This commit is contained in:
108
content/tests.py
108
content/tests.py
@@ -1,3 +1,109 @@
|
||||
from django.test import TestCase
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
# Create your tests here.
|
||||
from accounts.authentication import issue_access_token
|
||||
from accounts.models import User
|
||||
|
||||
from .models import Grade, Lesson, Node, Question, Unit
|
||||
|
||||
|
||||
class LessonCompleteImpossibleCountTests(TestCase):
|
||||
"""
|
||||
🔒 تستِ رگرسیونِ فیکسِ امنیتی: LessonCompleteView قبلاً correctCount رو
|
||||
بدونِ مقایسه با تعدادِ واقعیِ سوالهای درس میپذیرفت، پس یک کلاینتِ
|
||||
مخرب میتونست با correctCount بزرگتر از تعدادِ واقعیِ سوالها،
|
||||
xp/coins متورم بگیره. این تست هم ردِ درخواستهای غیرممکن رو، هم
|
||||
رفتارِ صحیحِ تکمیل با مقادیرِ معتبر رو تضمین میکنه.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-complete-1', is_guest=True, name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
|
||||
grade = Grade.objects.create(title='پایه اول', icon='icon', color='#fff')
|
||||
unit = Unit.objects.create(grade=grade, title='فصل ۱', order=1)
|
||||
node = Node.objects.create(unit=unit, title='گره ۱', order=1)
|
||||
self.lesson = Lesson.objects.create(node=node, title='درس ۱', intro_text='...')
|
||||
for i in range(3):
|
||||
Question.objects.create(
|
||||
lesson=self.lesson, type='multiple_choice', prompt=f'سوال {i}',
|
||||
)
|
||||
|
||||
def _complete(self, correct_count, total_questions):
|
||||
return self.client.post(
|
||||
f'/api/lessons/{self.lesson.id}/complete',
|
||||
{'correctCount': correct_count, 'totalQuestions': total_questions},
|
||||
format='json',
|
||||
HTTP_AUTHORIZATION=f'Bearer {self.token}',
|
||||
)
|
||||
|
||||
def test_correct_count_exceeding_actual_questions_is_rejected(self):
|
||||
# فقط ۳ سوال واقعی در این درس هست؛ کلاینت ادعای ۱۰۰ تای درست از
|
||||
# ۱۰ تای کل رو میکنه.
|
||||
response = self._complete(correct_count=100, total_questions=10)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn('correctCount', response.data)
|
||||
|
||||
def test_negative_correct_count_is_rejected(self):
|
||||
response = self._complete(correct_count=-1, total_questions=3)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_zero_total_questions_is_rejected(self):
|
||||
response = self._complete(correct_count=1, total_questions=0)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_valid_completion_still_grants_expected_reward(self):
|
||||
# ۳ از ۳ سوالِ واقعیِ همین درس، درست — باید ۳ ستاره و xp کامل بده.
|
||||
response = self._complete(correct_count=3, total_questions=3)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['stars'], 3)
|
||||
self.assertEqual(response.data['xpEarned'], 30)
|
||||
self.assertEqual(response.data['coinsEarned'], 15)
|
||||
|
||||
|
||||
class LessonCompleteRatioIgnoresClientTotalQuestionsTests(TestCase):
|
||||
"""
|
||||
🔒 تستِ رگرسیونِ فیکسِ دوم: قبلاً ratio از totalQuestions ارسالیی
|
||||
خودِ کلاینت محاسبه میشد، نه از تعدادِ واقعیِ سوالهای درس. یعنی حتی
|
||||
بعد از سقفزدنِ correctCount، کلاینت میتونست با totalQuestions=1
|
||||
(بهجای ۳ سوالِ واقعی)، ratio رو مصنوعاً ۱۰۰٪ کنه و ۳ ستاره/coins
|
||||
کامل بگیره درحالیکه فقط ۱ از ۳ سوال رو درست جواب داده. حالا ratio
|
||||
همیشه بر اساسِ actual_question_count محاسبه میشه.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user = User.objects.create(device_id='device-complete-2', is_guest=True, name='')
|
||||
self.token = issue_access_token(self.user.id)
|
||||
|
||||
grade = Grade.objects.create(title='پایه دوم', icon='icon', color='#fff')
|
||||
unit = Unit.objects.create(grade=grade, title='فصل ۲', order=1)
|
||||
node = Node.objects.create(unit=unit, title='گره ۲', order=1)
|
||||
self.lesson = Lesson.objects.create(node=node, title='درس ۲', intro_text='...')
|
||||
for i in range(3):
|
||||
Question.objects.create(
|
||||
lesson=self.lesson, type='multiple_choice', prompt=f'سوال {i}',
|
||||
)
|
||||
|
||||
def _complete(self, correct_count, total_questions):
|
||||
return self.client.post(
|
||||
f'/api/lessons/{self.lesson.id}/complete',
|
||||
{'correctCount': correct_count, 'totalQuestions': total_questions},
|
||||
format='json',
|
||||
HTTP_AUTHORIZATION=f'Bearer {self.token}',
|
||||
)
|
||||
|
||||
def test_manipulated_total_questions_does_not_yield_full_stars(self):
|
||||
# درسِ ۳-سوالی؛ کلاینت فقط ۱ درست داشته ولی totalQuestions=1
|
||||
# میفرسته تا وانمود کنه ۱۰۰٪ جواب داده.
|
||||
response = self._complete(correct_count=1, total_questions=1)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotEqual(response.data['stars'], 3)
|
||||
|
||||
def test_ratio_is_based_on_actual_lesson_question_count(self):
|
||||
# ۱ از ۳ سوالِ واقعی = ratio نزدیکِ ۰.۳۳ → طبقِ آستانههای موجود
|
||||
# (>=0.9 → 3, >=0.6 → 2, >0 → 1)، باید دقیقاً ۱ ستاره بده.
|
||||
response = self._complete(correct_count=1, total_questions=1)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['stars'], 1)
|
||||
|
||||
Reference in New Issue
Block a user