from unittest.mock import patch from django.test import TestCase from rest_framework.test import APIClient from rest_framework.throttling import ScopedRateThrottle from accounts.authentication import issue_access_token from accounts.models import User from progress.models import UserHearts, UserStats from .models import Choice, Grade, Lesson, Node, NodeProgress, Question, Unit, UserAnswer, UserLessonCompletion class LessonCompleteImpossibleCountTests(TestCase): """رگرسیون: 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 باید از actual_question_count محاسبه بشه نه از totalQuestیِ ارسالیِ کلاینت، وگرنه حتی بعدِ سقف‌زدنِ correctCount کلاینت می‌تونه با totalQuestions دروغین ratio رو مصنوعاً ۱۰۰٪ کنه.""" 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) class QuestionAnswerThrottleTests(TestCase): """محدودیتِ per-user روی QuestionAnswerView (throttle_scope='question_answer')؛ Idempotency-Key فقط retry با همون کلید رو می‌گیره، نه حجمِ درخواست‌های متفاوت.""" def setUp(self): from django.core.cache import cache cache.clear() self.client = APIClient() self.user = User.objects.create(device_id='device-answer-throttle-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) self.node = Node.objects.create(unit=unit, title='گره ۱', order=1) self.lesson = Lesson.objects.create(node=self.node, title='درس ۱', intro_text='...') self.question = Question.objects.create( lesson=self.lesson, type='multiple_choice', prompt='سوال ۱', ) self.correct_choice = Choice.objects.create( question=self.question, value='درست', is_correct=True, ) def _answer(self, idempotency_key=None, remote_addr=None): kwargs = {'HTTP_AUTHORIZATION': f'Bearer {self.token}'} if idempotency_key: kwargs['HTTP_IDEMPOTENCY_KEY'] = idempotency_key if remote_addr: kwargs['REMOTE_ADDR'] = remote_addr return self.client.post( f'/api/questions/{self.question.id}/answer', {'selectedOptionId': str(self.correct_choice.id)}, format='json', **kwargs, ) def test_requests_within_the_limit_still_succeed(self): with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'question_answer': '3/min'}): for _ in range(3): response = self._answer() self.assertEqual(response.status_code, 200) def test_excessive_requests_from_same_user_return_429(self): with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'question_answer': '3/min'}): for _ in range(3): response = self._answer() self.assertEqual(response.status_code, 200) blocked_response = self._answer() self.assertEqual(blocked_response.status_code, 429) def test_changing_client_ip_does_not_bypass_the_user_limit(self): # ⚠️ توجه: از یک کاربرِ *غیرمهمان* استفاده می‌شه (نه self.user که # مهمان است)، چون طبقِ فیکسِ GuestAwareContentThrottle حالا کلیدِ # bucket برای کاربرهای مهمان عمداً بر اساسِ IP است (نگاه کنید به # QuestionAnswerGuestThrottleBypassTests پایین‌تر) — این تست فقط # رفتارِ per-user (نه IP) برای کاربرهایِ ثبت‌نام‌شده رو تضمین # می‌کنه که باید دقیقاً مثلِ قبل دست‌نخورده بمونه. registered_user = User.objects.create(is_guest=False, name='', phone='+989120000701') registered_token = issue_access_token(registered_user.id) def _answer_as(remote_addr): return self.client.post( f'/api/questions/{self.question.id}/answer', {'selectedOptionId': str(self.correct_choice.id)}, format='json', HTTP_AUTHORIZATION=f'Bearer {registered_token}', REMOTE_ADDR=remote_addr, ) with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'question_answer': '3/min'}): for i in range(3): response = _answer_as(f'10.2.0.{i + 1}') self.assertEqual(response.status_code, 200) blocked_response = _answer_as('10.2.0.99') self.assertEqual(blocked_response.status_code, 429) def test_throttled_request_creates_no_answer_and_no_hearts_or_progress_change(self): with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'question_answer': '1/min'}): first_response = self._answer() self.assertEqual(first_response.status_code, 200) answer_count_before = UserAnswer.objects.filter(user_id=self.user.id).count() hearts_before = UserHearts.objects.filter(user_id=self.user.id).first() hearts_snapshot = hearts_before.current_hearts if hearts_before else None progress_before = NodeProgress.objects.filter(user_id=self.user.id, node=self.node).first() progress_snapshot = ( {'status': progress_before.status, 'last_activity_at': progress_before.last_activity_at} if progress_before else None ) blocked_response = self._answer() self.assertEqual(blocked_response.status_code, 429) self.assertEqual( UserAnswer.objects.filter(user_id=self.user.id).count(), answer_count_before, ) hearts_after = UserHearts.objects.filter(user_id=self.user.id).first() self.assertEqual( hearts_after.current_hearts if hearts_after else None, hearts_snapshot, ) progress_after = NodeProgress.objects.filter(user_id=self.user.id, node=self.node).first() if progress_snapshot is None: self.assertIsNone(progress_after) else: self.assertEqual(progress_after.status, progress_snapshot['status']) self.assertEqual(progress_after.last_activity_at, progress_snapshot['last_activity_at']) def test_idempotency_key_retry_still_works_within_the_limit(self): with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'question_answer': '10/min'}): first_response = self._answer(idempotency_key='retry-key-1') self.assertEqual(first_response.status_code, 200) second_response = self._answer(idempotency_key='retry-key-1') self.assertEqual(second_response.status_code, 200) self.assertEqual( UserAnswer.objects.filter( user_id=self.user.id, question=self.question, idempotency_key='retry-key-1', ).count(), 1, ) class LessonCompleteThrottleTests(TestCase): """محدودیتِ per-user روی LessonCompleteView (throttle_scope='lesson_complete')؛ Idempotency-Key فقط retry با همون کلید رو می‌گیره، نه حجمِ درخواست‌های متفاوت.""" def setUp(self): from django.core.cache import cache cache.clear() self.client = APIClient() self.user = User.objects.create(device_id='device-complete-throttle-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) self.node = Node.objects.create(unit=unit, title='گره ۱', order=1) self.lesson = Lesson.objects.create(node=self.node, title='درس ۱', intro_text='...') for i in range(3): Question.objects.create( lesson=self.lesson, type='multiple_choice', prompt=f'سوال {i}', ) def _complete(self, idempotency_key=None, remote_addr=None): kwargs = {'HTTP_AUTHORIZATION': f'Bearer {self.token}'} if idempotency_key: kwargs['HTTP_IDEMPOTENCY_KEY'] = idempotency_key if remote_addr: kwargs['REMOTE_ADDR'] = remote_addr return self.client.post( f'/api/lessons/{self.lesson.id}/complete', {'correctCount': 1, 'totalQuestions': 3}, format='json', **kwargs, ) def test_requests_within_the_limit_still_succeed(self): with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'lesson_complete': '3/min'}): for _ in range(3): response = self._complete() self.assertEqual(response.status_code, 200) def test_excessive_requests_from_same_user_return_429(self): with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'lesson_complete': '3/min'}): for _ in range(3): response = self._complete() self.assertEqual(response.status_code, 200) blocked_response = self._complete() self.assertEqual(blocked_response.status_code, 429) def test_changing_client_ip_does_not_bypass_the_user_limit(self): # ⚠️ توجه: از یک کاربرِ *غیرمهمان* استفاده می‌شه (نه self.user که # مهمان است)، چون طبقِ فیکسِ GuestAwareContentThrottle حالا کلیدِ # bucket برای کاربرهای مهمان عمداً بر اساسِ IP است (نگاه کنید به # LessonCompleteGuestThrottleBypassTests پایین‌تر) — این تست فقط # رفتارِ per-user (نه IP) برای کاربرهایِ ثبت‌نام‌شده رو تضمین # می‌کنه که باید دقیقاً مثلِ قبل دست‌نخورده بمونه. registered_user = User.objects.create(is_guest=False, name='', phone='+989120000702') registered_token = issue_access_token(registered_user.id) def _complete_as(remote_addr): return self.client.post( f'/api/lessons/{self.lesson.id}/complete', {'correctCount': 1, 'totalQuestions': 3}, format='json', HTTP_AUTHORIZATION=f'Bearer {registered_token}', REMOTE_ADDR=remote_addr, ) with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'lesson_complete': '3/min'}): for i in range(3): response = _complete_as(f'10.3.0.{i + 1}') self.assertEqual(response.status_code, 200) blocked_response = _complete_as('10.3.0.99') self.assertEqual(blocked_response.status_code, 429) def test_throttled_request_creates_no_completion_or_other_side_effects(self): with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'lesson_complete': '1/min'}): first_response = self._complete() self.assertEqual(first_response.status_code, 200) completion_count_before = UserLessonCompletion.objects.filter(user_id=self.user.id).count() stats_before = UserStats.objects.filter(user_id=self.user.id).first() stats_snapshot = ( {'xp': stats_before.xp, 'coins': stats_before.coins, 'progress_percent': stats_before.progress_percent} if stats_before else None ) progress_before = NodeProgress.objects.filter(user_id=self.user.id, node=self.node).first() progress_snapshot = ( {'status': progress_before.status, 'last_activity_at': progress_before.last_activity_at} if progress_before else None ) blocked_response = self._complete() self.assertEqual(blocked_response.status_code, 429) self.assertEqual( UserLessonCompletion.objects.filter(user_id=self.user.id).count(), completion_count_before, ) stats_after = UserStats.objects.filter(user_id=self.user.id).first() if stats_snapshot is None: self.assertIsNone(stats_after) else: self.assertEqual(stats_after.xp, stats_snapshot['xp']) self.assertEqual(stats_after.coins, stats_snapshot['coins']) self.assertEqual(stats_after.progress_percent, stats_snapshot['progress_percent']) progress_after = NodeProgress.objects.filter(user_id=self.user.id, node=self.node).first() if progress_snapshot is None: self.assertIsNone(progress_after) else: self.assertEqual(progress_after.status, progress_snapshot['status']) self.assertEqual(progress_after.last_activity_at, progress_snapshot['last_activity_at']) class QuestionAnswerGuestThrottleBypassTests(TestCase): """اثباتِ فیکسِ throttle: چرخشِ حساب مهمان (هر deviceId یه pk تازه) نباید bucket تازه‌ی question_answer بده — GuestAwareContentThrottle برای مهمان‌ها کلید رو بر اساسِ IP می‌سازه، نه user.pk.""" def setUp(self): from django.core.cache import cache cache.clear() self.client = APIClient() 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='...') self.question = Question.objects.create( lesson=self.lesson, type='multiple_choice', prompt='سوال ۱', ) self.correct_choice = Choice.objects.create( question=self.question, value='درست', is_correct=True, ) def _answer(self, token): return self.client.post( f'/api/questions/{self.question.id}/answer', {'selectedOptionId': str(self.correct_choice.id)}, format='json', HTTP_AUTHORIZATION=f'Bearer {token}', ) def test_rotating_guest_accounts_share_the_same_throttle_bucket(self): # سه حساب مهمانِ متفاوت (شبیه‌سازیِ چرخشِ حساب برای دورزدنِ throttle)، # همه از همون کلاینت/IP — باید همه یک bucket مشترک رو مصرف کنند. with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'question_answer': '2/min'}): guest1 = User.objects.create(device_id='device-answer-guest-rot-1', is_guest=True, name='') response1 = self._answer(issue_access_token(guest1.id)) self.assertEqual(response1.status_code, 200) guest2 = User.objects.create(device_id='device-answer-guest-rot-2', is_guest=True, name='') response2 = self._answer(issue_access_token(guest2.id)) self.assertEqual(response2.status_code, 200) # سومین حسابِ مهمانِ تازه (pk جدید) باید throttle بشه، چون # bucket بر اساسِ IP به‌اشتراک گذاشته شده، نه user.pk. guest3 = User.objects.create(device_id='device-answer-guest-rot-3', is_guest=True, name='') response3 = self._answer(issue_access_token(guest3.id)) self.assertEqual(response3.status_code, 429) def test_registered_users_keep_independent_per_user_buckets(self): # عدمِ رگرسیون: کاربرهای غیرمهمان (ثبت‌نام‌شده با شماره) باید مثلِ # قبل هرکدوم bucket جداگانه‌ی خودشون رو داشته باشن. with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'question_answer': '1/min'}): user_a = User.objects.create(is_guest=False, name='', phone='+989120000711') response_a = self._answer(issue_access_token(user_a.id)) self.assertEqual(response_a.status_code, 200) user_b = User.objects.create(is_guest=False, name='', phone='+989120000712') response_b = self._answer(issue_access_token(user_b.id)) self.assertEqual(response_b.status_code, 200) def test_blocked_guest_request_creates_no_answer_or_hearts_change(self): # درخواستِ بلاک‌شده (از حسابِ مهمانِ چرخشی) نباید UserAnswer بسازه # یا هارت/progress رو تغییر بده. with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'question_answer': '1/min'}): guest1 = User.objects.create(device_id='device-answer-guest-rot-4', is_guest=True, name='') first_response = self._answer(issue_access_token(guest1.id)) self.assertEqual(first_response.status_code, 200) guest2 = User.objects.create(device_id='device-answer-guest-rot-5', is_guest=True, name='') blocked_response = self._answer(issue_access_token(guest2.id)) self.assertEqual(blocked_response.status_code, 429) self.assertEqual(UserAnswer.objects.filter(user_id=guest2.id).count(), 0) self.assertFalse(UserHearts.objects.filter(user_id=guest2.id).exists()) class LessonCompleteGuestThrottleBypassTests(TestCase): """اثباتِ فیکسِ throttle: چرخشِ حساب مهمان نباید bucket تازه‌ی lesson_complete بده — GuestAwareContentThrottle برای مهمان‌ها کلید رو بر اساسِ IP می‌سازه، نه user.pk.""" def setUp(self): from django.core.cache import cache cache.clear() self.client = APIClient() grade = Grade.objects.create(title='پایه اول', icon='icon', color='#fff') unit = Unit.objects.create(grade=grade, title='فصل ۱', order=1) self.node = Node.objects.create(unit=unit, title='گره ۱', order=1) self.lesson = Lesson.objects.create(node=self.node, title='درس ۱', intro_text='...') for i in range(3): Question.objects.create( lesson=self.lesson, type='multiple_choice', prompt=f'سوال {i}', ) def _complete(self, token): return self.client.post( f'/api/lessons/{self.lesson.id}/complete', {'correctCount': 1, 'totalQuestions': 3}, format='json', HTTP_AUTHORIZATION=f'Bearer {token}', ) def test_rotating_guest_accounts_share_the_same_throttle_bucket(self): # سه حساب مهمانِ متفاوت (شبیه‌سازیِ چرخشِ حساب برای دورزدنِ throttle)، # همه از همون کلاینت/IP — باید همه یک bucket مشترک رو مصرف کنند. with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'lesson_complete': '2/hour'}): guest1 = User.objects.create(device_id='device-complete-guest-rot-1', is_guest=True, name='') response1 = self._complete(issue_access_token(guest1.id)) self.assertEqual(response1.status_code, 200) guest2 = User.objects.create(device_id='device-complete-guest-rot-2', is_guest=True, name='') response2 = self._complete(issue_access_token(guest2.id)) self.assertEqual(response2.status_code, 200) # سومین حسابِ مهمانِ تازه (pk جدید) باید throttle بشه، چون # bucket بر اساسِ IP به‌اشتراک گذاشته شده، نه user.pk. guest3 = User.objects.create(device_id='device-complete-guest-rot-3', is_guest=True, name='') response3 = self._complete(issue_access_token(guest3.id)) self.assertEqual(response3.status_code, 429) def test_registered_users_keep_independent_per_user_buckets(self): # عدمِ رگرسیون: کاربرهای غیرمهمان (ثبت‌نام‌شده با شماره) باید مثلِ # قبل هرکدوم bucket جداگانه‌ی خودشون رو داشته باشن. with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'lesson_complete': '1/hour'}): user_a = User.objects.create(is_guest=False, name='', phone='+989120000721') response_a = self._complete(issue_access_token(user_a.id)) self.assertEqual(response_a.status_code, 200) user_b = User.objects.create(is_guest=False, name='', phone='+989120000722') response_b = self._complete(issue_access_token(user_b.id)) self.assertEqual(response_b.status_code, 200) def test_blocked_guest_request_creates_no_completion_or_reward_side_effects(self): # درخواستِ بلاک‌شده (از حسابِ مهمانِ چرخشی) نباید UserLessonCompletion # بسازه یا UserStats/NodeProgress رو تغییر بده. with patch.dict(ScopedRateThrottle.THROTTLE_RATES, {'lesson_complete': '1/hour'}): guest1 = User.objects.create(device_id='device-complete-guest-rot-4', is_guest=True, name='') first_response = self._complete(issue_access_token(guest1.id)) self.assertEqual(first_response.status_code, 200) guest2 = User.objects.create(device_id='device-complete-guest-rot-5', is_guest=True, name='') blocked_response = self._complete(issue_access_token(guest2.id)) self.assertEqual(blocked_response.status_code, 429) self.assertEqual(UserLessonCompletion.objects.filter(user_id=guest2.id).count(), 0) self.assertFalse(UserStats.objects.filter(user_id=guest2.id).exists()) self.assertFalse(NodeProgress.objects.filter(user_id=guest2.id, node=self.node).exists()) class QuestionAnswerMascotMoodTests(TestCase): """پاسخِ /answer باید mascotMood بده: درست → happy، غلط → encourage (هم‌خوان با RobotMood در MathApp_frontend).""" def setUp(self): from django.core.cache import cache cache.clear() self.client = APIClient() self.user = User.objects.create(device_id='device-mascot-1', is_guest=True, name='') self.token = issue_access_token(self.user.id) grade = Grade.objects.create(title='پایه اول', icon='i', color='#fff') unit = Unit.objects.create(grade=grade, title='فصل ۱', order=1) node = Node.objects.create(unit=unit, title='گره ۱', order=1) lesson = Lesson.objects.create(node=node, title='درس ۱', intro_text='...') self.question = Question.objects.create(lesson=lesson, type='multiple_choice', prompt='۱+۱؟') self.correct = Choice.objects.create(question=self.question, value='۲', is_correct=True) self.wrong = Choice.objects.create(question=self.question, value='۳', is_correct=False) def _answer(self, choice_id): return self.client.post( f'/api/questions/{self.question.id}/answer', {'selectedOptionId': str(choice_id)}, format='json', HTTP_AUTHORIZATION=f'Bearer {self.token}', ) def test_correct_answer_mood_happy(self): r = self._answer(self.correct.id) self.assertEqual(r.status_code, 200) self.assertEqual(r.data['mascotMood'], 'happy') def test_wrong_answer_mood_encourage(self): r = self._answer(self.wrong.id) self.assertEqual(r.status_code, 200) self.assertEqual(r.data['mascotMood'], 'encourage') def test_idempotent_replay_keeps_mood(self): k = 'mascot-idem-1' first = self.client.post( f'/api/questions/{self.question.id}/answer', {'selectedOptionId': str(self.wrong.id)}, format='json', HTTP_AUTHORIZATION=f'Bearer {self.token}', HTTP_IDEMPOTENCY_KEY=k, ) replay = self.client.post( f'/api/questions/{self.question.id}/answer', {'selectedOptionId': str(self.wrong.id)}, format='json', HTTP_AUTHORIZATION=f'Bearer {self.token}', HTTP_IDEMPOTENCY_KEY=k, ) self.assertEqual(first.data['mascotMood'], 'encourage') self.assertEqual(replay.data['mascotMood'], 'encourage')