Files
MathApp/content/tests.py

110 lines
5.6 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.
from django.test import TestCase
from rest_framework.test import APIClient
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)