|
12 | 12 | )
|
13 | 13 |
|
14 | 14 | from zulip_bots.request_test_lib import (
|
15 |
| - mock_request_exception, |
| 15 | + mock_request_exception |
| 16 | +) |
| 17 | + |
| 18 | +from zulip_bots.bots.trivia_quiz.controller import ( |
| 19 | + TriviaQuizModel, |
| 20 | + NotAvailableException |
16 | 21 | )
|
17 | 22 |
|
18 | 23 | from zulip_bots.bots.trivia_quiz.trivia_quiz import (
|
19 |
| - get_quiz_from_payload, |
20 |
| - fix_quotes, |
21 |
| - get_quiz_from_id, |
22 |
| - update_quiz, |
23 |
| - handle_answer, |
| 24 | + TriviaQuizMessageHandler |
24 | 25 | )
|
25 | 26 |
|
| 27 | +from zulip_bots.game_handler import BadMoveException |
| 28 | + |
26 | 29 | class TestTriviaQuizBot(BotTestCase, DefaultTests):
|
27 |
| - bot_name = "trivia_quiz" # type: str |
| 30 | + bot_name = 'trivia_quiz' # type: str |
28 | 31 |
|
29 | 32 | new_question_response = '\nQ: Which class of animals are newts members of?\n\n' + \
|
30 | 33 | '* **A** Amphibian\n' + \
|
31 | 34 | '* **B** Fish\n' + \
|
32 | 35 | '* **C** Reptiles\n' + \
|
33 | 36 | '* **D** Mammals\n' + \
|
34 |
| - '**reply**: answer Q001 <letter>' |
35 |
| - |
36 |
| - def get_test_quiz(self) -> Tuple[Dict[str, Any], Any]: |
37 |
| - bot_handler = StubBotHandler() |
38 |
| - quiz_payload = read_bot_fixture_data('trivia_quiz', 'test_new_question')['response'] |
39 |
| - with patch('random.shuffle'): |
40 |
| - quiz = get_quiz_from_payload(quiz_payload) |
41 |
| - return quiz, bot_handler |
42 |
| - |
43 |
| - def _test(self, message: str, response: str, fixture: Optional[str]=None) -> None: |
44 |
| - if fixture: |
45 |
| - with self.mock_http_conversation(fixture): |
46 |
| - self.verify_reply(message, response) |
47 |
| - else: |
48 |
| - self.verify_reply(message, response) |
49 |
| - |
50 |
| - def test_bot_responds_to_empty_message(self) -> None: |
51 |
| - self._test('', 'type "new" for a new question') |
52 |
| - |
53 |
| - def test_bot_new_question(self) -> None: |
54 |
| - with patch('random.shuffle'): |
55 |
| - self._test('new', self.new_question_response, 'test_new_question') |
| 37 | + '**reply**: <letter>' |
| 38 | + |
| 39 | + test_question = { |
| 40 | + 'question': 'Question 1?', |
| 41 | + 'answers': { |
| 42 | + 'A': 'Correct', |
| 43 | + 'B': 'Incorrect 1', |
| 44 | + 'C': 'Incorrect 2', |
| 45 | + 'D': 'Incorrect 3' |
| 46 | + }, |
| 47 | + 'correct_letter': 'A' |
| 48 | + } |
| 49 | + |
| 50 | + test_question_message_content = ''' |
| 51 | +Q: Question 1? |
| 52 | +
|
| 53 | +* **A** Correct |
| 54 | +* **B** Incorrect 1 |
| 55 | +* **C** Incorrect 2 |
| 56 | +* **D** Incorrect 3 |
| 57 | +**reply**: <letter>''' |
| 58 | + |
| 59 | + test_question_message_widget = '{"widget_type": "zform", "extra_data": {"type": "choices", "heading": "Question 1?", "choices": [{"type": "multiple_choice", "short_name": "A", "long_name": "Correct", "reply": "A"}, {"type": "multiple_choice", "short_name": "B", "long_name": "Incorrect 1", "reply": "B"}, {"type": "multiple_choice", "short_name": "C", "long_name": "Incorrect 2", "reply": "C"}, {"type": "multiple_choice", "short_name": "D", "long_name": "Incorrect 3", "reply": "D"}]}}' |
56 | 60 |
|
57 | 61 | def test_question_not_available(self) -> None:
|
58 |
| - self._test('new', 'Uh-Oh! Trivia service is down.', 'test_status_code') |
59 |
| - |
60 |
| - with mock_request_exception(): |
61 |
| - self.verify_reply('new', 'Uh-Oh! Trivia service is down.') |
62 |
| - |
63 |
| - def test_fix_quotes(self) -> None: |
64 |
| - self.assertEqual(fix_quotes('test & test'), html.unescape('test & test')) |
65 |
| - print('f') |
66 |
| - with patch('html.unescape') as mock_html_unescape: |
67 |
| - mock_html_unescape.side_effect = Exception |
68 |
| - with self.assertRaises(Exception) as exception: |
69 |
| - fix_quotes('test') |
70 |
| - self.assertEqual(str(exception.exception), "Please use python3.4 or later for this bot.") |
71 |
| - |
72 |
| - def test_invalid_answer(self) -> None: |
73 |
| - invalid_replies = ['answer A', |
74 |
| - 'answer A Q10', |
75 |
| - 'answer Q001 K', |
76 |
| - 'answer 001 A'] |
77 |
| - for reply in invalid_replies: |
78 |
| - self._test(reply, 'Invalid answer format') |
79 |
| - |
80 |
| - def test_invalid_quiz_id(self) -> None: |
81 |
| - self._test('answer Q100 A', 'Invalid quiz id') |
82 |
| - |
83 |
| - def test_answers(self) -> None: |
84 |
| - quiz_payload = read_bot_fixture_data('trivia_quiz', 'test_new_question')['response'] |
85 |
| - with patch('random.shuffle'): |
86 |
| - quiz = get_quiz_from_payload(quiz_payload) |
87 |
| - |
88 |
| - # Test initial storage |
89 |
| - self.assertEqual(quiz['question'], 'Which class of animals are newts members of?') |
90 |
| - self.assertEqual(quiz['correct_letter'], 'A') |
91 |
| - self.assertEqual(quiz['answers']['D'], 'Mammals') |
92 |
| - self.assertEqual(quiz['answered_options'], []) |
93 |
| - self.assertEqual(quiz['pending'], True) |
94 |
| - |
95 |
| - # test incorrect answer |
96 |
| - with patch('zulip_bots.bots.trivia_quiz.trivia_quiz.get_quiz_from_id', |
97 |
| - return_value=json.dumps(quiz)): |
98 |
| - self._test('answer Q001 B', ':disappointed: WRONG, Foo Test User! B is not correct.') |
99 |
| - |
100 |
| - # test correct answer |
101 |
| - with patch('zulip_bots.bots.trivia_quiz.trivia_quiz.get_quiz_from_id', |
102 |
| - return_value=json.dumps(quiz)): |
103 |
| - with patch('zulip_bots.bots.trivia_quiz.trivia_quiz.start_new_quiz') as mock_new_quiz: |
104 |
| - self._test('answer Q001 A', ':tada: **Amphibian** is correct, Foo Test User!') |
105 |
| - |
106 |
| - def test_update_quiz(self) -> None: |
107 |
| - quiz, bot_handler = self.get_test_quiz() |
108 |
| - update_quiz(quiz, 'Q001', bot_handler) |
109 |
| - test_quiz = json.loads(bot_handler.storage.get('Q001')) |
110 |
| - self.assertEqual(test_quiz, quiz) |
111 |
| - |
112 |
| - def test_get_quiz_from_id(self) -> None: |
113 |
| - quiz, bot_handler = self.get_test_quiz() |
114 |
| - bot_handler.storage.put('Q001', quiz) |
115 |
| - self.assertEqual(get_quiz_from_id('Q001', bot_handler), quiz) |
116 |
| - |
117 |
| - def test_handle_answer(self) -> None: |
118 |
| - quiz, bot_handler = self.get_test_quiz() |
119 |
| - # create test initial storage |
120 |
| - update_quiz(quiz, 'Q001', bot_handler) |
121 |
| - |
122 |
| - # test for a correct answer |
123 |
| - start_new_question, response = handle_answer(quiz, 'A', 'Q001', bot_handler, 'Test user') |
124 |
| - self.assertTrue(start_new_question) |
125 |
| - self.assertEqual(response, ':tada: **Amphibian** is correct, Test user!') |
126 |
| - |
127 |
| - # test for an incorrect answer |
128 |
| - start_new_question, response = handle_answer(quiz, 'D', 'Q001', bot_handler, 'Test User') |
129 |
| - self.assertFalse(start_new_question) |
130 |
| - self.assertEqual(response, ':disappointed: WRONG, Test User! D is not correct.') |
131 |
| - |
132 |
| - def test_handle_answer_three_failed_attempts(self) -> None: |
133 |
| - quiz, bot_handler = self.get_test_quiz() |
134 |
| - # create test storage for a question which has been incorrectly answered twice |
135 |
| - quiz['answered_options'] = ['C', 'B'] |
136 |
| - update_quiz(quiz, 'Q001', bot_handler) |
137 |
| - |
138 |
| - # test response and storage after three failed attempts |
139 |
| - start_new_question, response = handle_answer(quiz, 'D', 'Q001', bot_handler, 'Test User') |
140 |
| - self.assertEqual(response, ':disappointed: WRONG, Test User! The correct answer is **Amphibian**.') |
141 |
| - self.assertTrue(start_new_question) |
142 |
| - quiz_reset = json.loads(bot_handler.storage.get('Q001')) |
143 |
| - self.assertEqual(quiz_reset['pending'], False) |
144 |
| - |
145 |
| - # test response after question has ended |
146 |
| - incorrect_answers = ['B', 'C', 'D'] |
147 |
| - for ans in incorrect_answers: |
148 |
| - start_new_question, response = handle_answer(quiz, ans, 'Q001', bot_handler, 'Test User') |
149 |
| - self.assertEqual(response, ':disappointed: WRONG, Test User! The correct answer is **Amphibian**.') |
150 |
| - self.assertFalse(start_new_question) |
151 |
| - start_new_question, response = handle_answer(quiz, 'A', 'Q001', bot_handler, 'Test User') |
152 |
| - self.assertEqual(response, ':tada: **Amphibian** is correct, Test User!') |
153 |
| - self.assertFalse(start_new_question) |
154 |
| - |
155 |
| - # test storage after question has ended |
156 |
| - quiz_reset = json.loads(bot_handler.storage.get('Q001')) |
157 |
| - self.assertEqual(quiz_reset['pending'], False) |
| 62 | + with self.mock_http_conversation('test_new_question'): |
| 63 | + model = TriviaQuizModel() |
| 64 | + # Exception |
| 65 | + with self.assertRaises(NotAvailableException): |
| 66 | + with mock_request_exception(): |
| 67 | + model.get_trivia_quiz() |
| 68 | + # non-ok status code |
| 69 | + with self.assertRaises(NotAvailableException): |
| 70 | + with self.mock_http_conversation("test_status_code"): |
| 71 | + model.get_trivia_quiz() |
| 72 | + |
| 73 | + def test_validate_move(self) -> None: |
| 74 | + with self.mock_http_conversation('test_new_question'): |
| 75 | + model = TriviaQuizModel() |
| 76 | + valid_moves = [ |
| 77 | + 'A', |
| 78 | + 'B', |
| 79 | + 'C', |
| 80 | + 'D' |
| 81 | + ] |
| 82 | + invalid_moves = [ |
| 83 | + 'AA', |
| 84 | + '1' |
| 85 | + ] |
| 86 | + for valid_move in valid_moves: |
| 87 | + self.assertTrue(model.validate_move(valid_move)) |
| 88 | + for invalid_move in invalid_moves: |
| 89 | + self.assertFalse(model.validate_move(invalid_move)) |
| 90 | + |
| 91 | + def test_make_move(self): |
| 92 | + with self.mock_http_conversation('test_new_question'): |
| 93 | + model = TriviaQuizModel() |
| 94 | + model.current_board = self.test_question |
| 95 | + model.scores = { |
| 96 | + 0: 0, |
| 97 | + 1: 1 |
| 98 | + } |
| 99 | + # Invalid move should raise BadMoveException |
| 100 | + with self.assertRaises(BadMoveException): |
| 101 | + model.make_move('AA', 0) |
| 102 | + # Correct move should: |
| 103 | + with self.mock_http_conversation('test_new_question'): |
| 104 | + with patch('random.shuffle'): |
| 105 | + move_data = model.make_move('A', 0) |
| 106 | + # Increment score |
| 107 | + self.assertEqual(model.scores[0], 1) |
| 108 | + # Change question |
| 109 | + self.assertEqual(model.current_board, read_bot_fixture_data("trivia_quiz", "test_new_question_dict")) |
| 110 | + # Move data correct should be true |
| 111 | + self.assertTrue(move_data['correct']) |
| 112 | + # Move data score should be the same as model.scores[player_number] |
| 113 | + self.assertEqual(move_data['score'], 1) |
| 114 | + # Incorrect move should: |
| 115 | + with self.mock_http_conversation('test_new_question'): |
| 116 | + model.current_board = self.test_question |
| 117 | + move_data = model.make_move('B', 1) |
| 118 | + # Decrement score |
| 119 | + self.assertEqual(model.scores[1], 0) |
| 120 | + # Move data correct should be false |
| 121 | + self.assertFalse(move_data['correct']) |
| 122 | + |
| 123 | + def test_determine_game_over(self): |
| 124 | + with self.mock_http_conversation('test_new_question'): |
| 125 | + model = TriviaQuizModel() |
| 126 | + model.scores = { |
| 127 | + 0: 0, |
| 128 | + 1: 5, |
| 129 | + 2: 1 |
| 130 | + } |
| 131 | + self.assertEqual(model.determine_game_over(["Test 0", "Test 1", "Test 2"]), "Test 1") |
| 132 | + model.scores = { |
| 133 | + 0: 0, |
| 134 | + 1: 4, |
| 135 | + 2: 1 |
| 136 | + } |
| 137 | + self.assertIsNone(model.determine_game_over(["Test 0", "Test 1", "Test 2"])) |
| 138 | + |
| 139 | + def test_message_handler_parse_board(self): |
| 140 | + message_handler = TriviaQuizMessageHandler() |
| 141 | + board_message_content, board_message_widget = message_handler.parse_board(self.test_question) |
| 142 | + self.assertEqual(board_message_content, self.test_question_message_content) |
| 143 | + self.assertEqual(board_message_widget, self.test_question_message_widget) |
| 144 | + |
| 145 | + def test_message_handler_alert_move_message(self): |
| 146 | + message_handler = TriviaQuizMessageHandler() |
| 147 | + correct_responses = [ |
| 148 | + (("Test User", "A", {'correct': True, 'score': 5}), ":tada: Correct Test User (5 points)!"), |
| 149 | + (("Test User", "B", {'correct': False, 'score': 1, 'correct_letter': "B"}), ":disappointed: Incorrect Test User (1 points). The correct answer was **B**") |
| 150 | + ] |
| 151 | + for args, response in correct_responses: |
| 152 | + self.assertEqual(message_handler.alert_move_message(*args), response) |
| 153 | + |
| 154 | + def test_message_handler_get_player_color(self): |
| 155 | + message_handler = TriviaQuizMessageHandler() |
| 156 | + self.assertIsNone(message_handler.get_player_color(0)) |
0 commit comments