diff --git a/zulip_bots/zulip_bots/bots/trivia_quiz/test_trivia_quiz.py b/zulip_bots/zulip_bots/bots/trivia_quiz/test_trivia_quiz.py index 3cbf24b..a0593d9 100644 --- a/zulip_bots/zulip_bots/bots/trivia_quiz/test_trivia_quiz.py +++ b/zulip_bots/zulip_bots/bots/trivia_quiz/test_trivia_quiz.py @@ -2,12 +2,13 @@ import json import html from unittest.mock import patch -from typing import Optional +from typing import Optional, Tuple, Any, Dict from zulip_bots.test_lib import ( BotTestCase, DefaultTests, read_bot_fixture_data, + StubBotHandler, ) from zulip_bots.request_test_lib import ( @@ -17,6 +18,9 @@ from zulip_bots.request_test_lib import ( from zulip_bots.bots.trivia_quiz.trivia_quiz import ( get_quiz_from_payload, fix_quotes, + get_quiz_from_id, + update_quiz, + handle_answer, ) class TestTriviaQuizBot(BotTestCase, DefaultTests): @@ -29,6 +33,13 @@ class TestTriviaQuizBot(BotTestCase, DefaultTests): '* **D** Mammals\n' + \ '**reply**: answer Q001 ' + def get_test_quiz(self) -> Tuple[Dict[str, Any], Any]: + bot_handler = StubBotHandler() + quiz_payload = read_bot_fixture_data('trivia_quiz', 'test_new_question')['response'] + with patch('random.shuffle'): + quiz = get_quiz_from_payload(quiz_payload) + return quiz, bot_handler + def _test(self, message: str, response: str, fixture: Optional[str]=None) -> None: if fixture: with self.mock_http_conversation(fixture): @@ -74,9 +85,12 @@ class TestTriviaQuizBot(BotTestCase, DefaultTests): with patch('random.shuffle'): quiz = get_quiz_from_payload(quiz_payload) + # Test initial storage self.assertEqual(quiz['question'], 'Which class of animals are newts members of?') self.assertEqual(quiz['correct_letter'], 'A') self.assertEqual(quiz['answers']['D'], 'Mammals') + self.assertEqual(quiz['answered_options'], []) + self.assertEqual(quiz['pending'], True) # test incorrect answer with patch('zulip_bots.bots.trivia_quiz.trivia_quiz.get_quiz_from_id', @@ -88,3 +102,56 @@ class TestTriviaQuizBot(BotTestCase, DefaultTests): return_value=json.dumps(quiz)): with patch('zulip_bots.bots.trivia_quiz.trivia_quiz.start_new_quiz') as mock_new_quiz: self._test('answer Q001 A', '**CORRECT!** Amphibian :tada:') + + def test_update_quiz(self) -> None: + quiz, bot_handler = self.get_test_quiz() + update_quiz(quiz, 'Q001', bot_handler) + test_quiz = json.loads(bot_handler.storage.get('Q001')) + self.assertEqual(test_quiz, quiz) + + def test_get_quiz_from_id(self) -> None: + quiz, bot_handler = self.get_test_quiz() + bot_handler.storage.put('Q001', quiz) + self.assertEqual(get_quiz_from_id('Q001', bot_handler), quiz) + + def test_handle_answer(self) -> None: + quiz, bot_handler = self.get_test_quiz() + # create test initial storage + update_quiz(quiz, 'Q001', bot_handler) + + # test for a correct answer + start_new_question, response = handle_answer(quiz, 'A', 'Q001', bot_handler) + self.assertTrue(start_new_question) + self.assertEqual(response, '**CORRECT!** Amphibian :tada:') + + # test for an incorrect answer + start_new_question, response = handle_answer(quiz, 'D', 'Q001', bot_handler) + self.assertFalse(start_new_question) + self.assertEqual(response, '**WRONG!** D is not correct :disappointed:') + + def test_handle_answer_three_failed_attempts(self) -> None: + quiz, bot_handler = self.get_test_quiz() + # create test storage for a question which has been incorrectly answered twice + quiz['answered_options'] = ['C', 'B'] + update_quiz(quiz, 'Q001', bot_handler) + + # test response and storage after three failed attempts + start_new_question, response = handle_answer(quiz, 'D', 'Q001', bot_handler) + self.assertEqual(response, '**WRONG!** :disappointed: The correct answer is Amphibian.') + self.assertTrue(start_new_question) + quiz_reset = json.loads(bot_handler.storage.get('Q001')) + self.assertEqual(quiz_reset['pending'], False) + + # test response after question has ended + incorrect_answers = ['B', 'C', 'D'] + for ans in incorrect_answers: + start_new_question, response = handle_answer(quiz, ans, 'Q001', bot_handler) + self.assertEqual(response, '**WRONG!** :disappointed: The correct answer is Amphibian.') + self.assertFalse(start_new_question) + start_new_question, response = handle_answer(quiz, 'A', 'Q001', bot_handler) + self.assertEqual(response, '**CORRECT!** Amphibian :tada:') + self.assertFalse(start_new_question) + + # test storage after question has ended + quiz_reset = json.loads(bot_handler.storage.get('Q001')) + self.assertEqual(quiz_reset['pending'], False) diff --git a/zulip_bots/zulip_bots/bots/trivia_quiz/trivia_quiz.py b/zulip_bots/zulip_bots/bots/trivia_quiz/trivia_quiz.py index cec22be..8c95c21 100644 --- a/zulip_bots/zulip_bots/bots/trivia_quiz/trivia_quiz.py +++ b/zulip_bots/zulip_bots/bots/trivia_quiz/trivia_quiz.py @@ -43,9 +43,9 @@ class TriviaQuizHandler: bot_handler.send_reply(message, bot_response) return quiz = json.loads(quiz_payload) - correct, bot_response = grade_question(quiz, answer) + start_new_question, bot_response = handle_answer(quiz, answer, quiz_id, bot_handler) bot_handler.send_reply(message, bot_response) - if correct: + if start_new_question: start_new_quiz(message, bot_handler) return else: @@ -125,6 +125,8 @@ def get_quiz_from_payload(payload: Dict[str, Any]) -> Dict[str, Any]: quiz = dict( question=fix_quotes(question), answers=answers, + answered_options=[], + pending=True, correct_letter=correct_letter, ) return quiz @@ -196,16 +198,39 @@ Q: {question} ) return content -def grade_question(quiz: Dict[str, Any], answer: str) -> Tuple[bool, str]: - correct = (answer == quiz['correct_letter']) +def update_quiz(quiz: Dict[str, Any], quiz_id: str, bot_handler: Any) -> None: + bot_handler.storage.put(quiz_id, json.dumps(quiz)) - if correct: - long_answer = quiz['answers'][answer] - response = '**CORRECT!** {long_answer} :tada:'.format(long_answer=long_answer) - return correct, response +def build_response(is_correct: bool, num_answers: int) -> str: + if is_correct: + response = '**CORRECT!** {answer} :tada:' + else: + if num_answers >= 3: + response = '**WRONG!** :disappointed: The correct answer is {answer}.' + else: + response = '**WRONG!** {option} is not correct :disappointed:' + return response - response = '**WRONG!** {answer} is not correct :disappointed:'.format(answer=answer) - return correct, response +def handle_answer(quiz: Dict[str, Any], option: str, quiz_id: str, + bot_handler: Any) -> Tuple[bool, str]: + answer = quiz['answers'][quiz['correct_letter']] + is_new_answer = (option not in quiz['answered_options']) + if is_new_answer: + quiz['answered_options'].append(option) + + num_answers = len(quiz['answered_options']) + is_correct = (option == quiz['correct_letter']) + + start_new_question = quiz['pending'] and (is_correct or num_answers >= 3) + if start_new_question or is_correct: + quiz['pending'] = False + + if is_new_answer or start_new_question: + update_quiz(quiz, quiz_id, bot_handler) + + response = build_response(is_correct, num_answers).format( + option=option, answer=answer, id=quiz_id) + return start_new_question, response handler_class = TriviaQuizHandler