diff --git a/content/serializers.py b/content/serializers.py index 15a84ed..39c4820 100644 --- a/content/serializers.py +++ b/content/serializers.py @@ -79,6 +79,9 @@ class AnswerResponseSerializer(serializers.Serializer): correctOptionId = serializers.CharField() xpEarned = serializers.IntegerField() heartsRemaining = serializers.IntegerField() + # حالتِ انیمیشنِ ماسکوت («رُبو») برای فرانت: جوابِ درست → happy، غلط → + # encourage. هم‌خوان با RobotMood در MathApp_frontend. + mascotMood = serializers.CharField() # ---- Lesson completion ---- diff --git a/content/tests.py b/content/tests.py index 43ffb6e..e595de1 100644 --- a/content/tests.py +++ b/content/tests.py @@ -490,3 +490,54 @@ class LessonCompleteGuestThrottleBypassTests(TestCase): 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') diff --git a/content/views.py b/content/views.py index 55d9505..12de3d0 100644 --- a/content/views.py +++ b/content/views.py @@ -199,6 +199,7 @@ class QuestionAnswerView(APIView): 'correctOptionId': str(correct_choice.id) if correct_choice else '', 'xpEarned': existing.xp_earned, 'heartsRemaining': hearts.current_hearts if hearts else 5, + 'mascotMood': 'happy' if existing.correct else 'encourage', } return Response(AnswerResponseSerializer(data).data) @@ -245,6 +246,7 @@ class QuestionAnswerView(APIView): 'correctOptionId': str(correct_choice.id) if correct_choice else '', 'xpEarned': xp_earned, 'heartsRemaining': hearts_remaining if hearts_remaining is not None else 5, + 'mascotMood': 'happy' if choice.is_correct else 'encourage', } return Response(AnswerResponseSerializer(data).data)