from typing import List, Union, Dict from unittest import TestCase from unittest.mock import ANY, patch, Mock from parameterized import parameterized from samcli.lib.cookiecutter.question import Question, QuestionKind, Choice, Confirm, Info, QuestionFactory class TestQuestion(TestCase): _ANY_TEXT = "any text" _ANY_KEY = "any key" _ANY_OPTIONS = ["option1", "option2", "option3"] _ANY_ANSWER = "any answer" _ANY_NEXT_QUESTION_MAP = { "option1": "key1", "option2": "key2", "option3": "key3", } _ANY_DEFAULT_NEXT_QUESTION_KEY = "default" _ANY_KIND = QuestionKind.question def setUp(self): self.question = Question( text=self._ANY_TEXT, key=self._ANY_KEY, default=self._ANY_ANSWER, is_required=True, allow_autofill=False, next_question_map=self._ANY_NEXT_QUESTION_MAP, default_next_question_key=self._ANY_DEFAULT_NEXT_QUESTION_KEY, ) def get_question_with_default_from_cookiecutter_context_using_keypath( self, key_path: List[Union[str, Dict]] ) -> Question: return Question( text=self._ANY_TEXT, key=self._ANY_KEY, default={"keyPath": key_path}, is_required=True, next_question_map=self._ANY_NEXT_QUESTION_MAP, default_next_question_key=self._ANY_DEFAULT_NEXT_QUESTION_KEY, ) def test_creating_questions(self): q = Question(text=self._ANY_TEXT, key=self._ANY_KEY) self.assertEqual(q.text, self._ANY_TEXT) self.assertEqual(q.key, self._ANY_KEY) self.assertEqual(q.default_answer, "") self.assertFalse(q.required) self.assertEqual(q.next_question_map, {}) self.assertIsNone(q.default_next_question_key) q = self.question self.assertEqual(q.text, self._ANY_TEXT) self.assertEqual(q.key, self._ANY_KEY) self.assertEqual(q.default_answer, self._ANY_ANSWER) self.assertTrue(q.required) self.assertEqual(q.next_question_map, self._ANY_NEXT_QUESTION_MAP) self.assertEqual(q.default_next_question_key, self._ANY_DEFAULT_NEXT_QUESTION_KEY) def test_question_key_and_text_are_required(self): with self.assertRaises(TypeError): Question(text=self._ANY_TEXT) with self.assertRaises(TypeError): Question(key=self._ANY_KEY) def test_get_next_question_key(self): self.assertEqual(self.question.get_next_question_key("option1"), "key1") self.assertEqual(self.question.get_next_question_key("option2"), "key2") self.assertEqual(self.question.get_next_question_key("option3"), "key3") self.assertEqual(self.question.get_next_question_key("any-option"), self._ANY_DEFAULT_NEXT_QUESTION_KEY) self.question.set_default_next_question_key("new_default") self.assertEqual(self.question.get_next_question_key(None), "new_default") @patch("samcli.lib.cookiecutter.question.click") def test_ask(self, mock_click): mock_click.prompt.return_value = self._ANY_ANSWER answer = self.question.ask({}) self.assertEqual(answer, self._ANY_ANSWER) mock_click.prompt.assert_called_once_with(text=self.question.text, default=self.question.default_answer) @patch("samcli.lib.cookiecutter.question.click") def test_ask_resolves_from_cookiecutter_context(self, mock_click): # Setup expected_default_value = Mock() previous_question_key = "this is a question" previous_question_answer = "this is an answer" context = { "['x', 'this is an answer']": expected_default_value, previous_question_key: previous_question_answer, } question = self.get_question_with_default_from_cookiecutter_context_using_keypath( ["x", {"valueOf": previous_question_key}] ) # Trigger question.ask(context=context) # Verify mock_click.prompt.assert_called_once_with(text=self.question.text, default=expected_default_value) @patch("samcli.lib.cookiecutter.question.click") def test_ask_resolves_from_cookiecutter_context_non_exist_key_path(self, mock_click): # Setup context = {} question = self.get_question_with_default_from_cookiecutter_context_using_keypath(["y"]) # Trigger question.ask(context=context) # Verify mock_click.prompt.assert_called_once_with(text=self.question.text, default=None) def test_ask_resolves_from_cookiecutter_context_non_exist_question_key(self): # Setup expected_default_value = Mock() previous_question_key = "this is a question" previous_question_answer = "this is an answer" context = { "['x', 'this is an answer']": expected_default_value, previous_question_key: previous_question_answer, } question = self.get_question_with_default_from_cookiecutter_context_using_keypath( ["x", {"valueOf": "non_exist_question_key"}] ) # Trigger with self.assertRaises(KeyError): question.ask(context=context) @parameterized.expand([("this should have been a list"), ([1],), ({},)]) def test_ask_resolves_from_cookiecutter_context_with_key_path_not_a_list(self, key_path): # Setup context = {} question = self.get_question_with_default_from_cookiecutter_context_using_keypath(key_path) # Trigger with self.assertRaises(ValueError): question.ask(context=context) @parameterized.expand([({"keyPath123": Mock()},), ({"keyPath": [{"valueOf123": Mock()}]},)]) def test_ask_resolves_from_cookiecutter_context_with_default_object_missing_keys(self, default_object): # Setup context = {} question = self.get_question_with_default_from_cookiecutter_context_using_keypath([]) question._default_answer = default_object # Trigger with self.assertRaises(KeyError): question.ask(context=context) def test_question_allow_autofill_with_default_value(self): q = Question(text=self._ANY_TEXT, key=self._ANY_KEY, is_required=True, allow_autofill=True, default="123") self.assertEqual("123", q.ask()) @patch("samcli.lib.cookiecutter.question.click") def test_question_allow_autofill_without_default_value(self, click_mock): answer_mock = click_mock.prompt.return_value = Mock() q = Question(text=self._ANY_TEXT, key=self._ANY_KEY, is_required=True, allow_autofill=True) self.assertEqual(answer_mock, q.ask()) class TestChoice(TestCase): def setUp(self): self.question = Choice( text=TestQuestion._ANY_TEXT, key=TestQuestion._ANY_KEY, options=TestQuestion._ANY_OPTIONS, default=TestQuestion._ANY_ANSWER, is_required=True, next_question_map=TestQuestion._ANY_NEXT_QUESTION_MAP, default_next_question_key=TestQuestion._ANY_DEFAULT_NEXT_QUESTION_KEY, ) def test_create_choice_question(self): self.assertEqual(self.question.text, TestQuestion._ANY_TEXT) self.assertEqual(self.question.key, TestQuestion._ANY_KEY) self.assertEqual(self.question._options, TestQuestion._ANY_OPTIONS) with self.assertRaises(TypeError): Choice(key=TestQuestion._ANY_KEY, text=TestQuestion._ANY_TEXT) with self.assertRaises(ValueError): Choice(key=TestQuestion._ANY_KEY, text=TestQuestion._ANY_TEXT, options=None) with self.assertRaises(ValueError): Choice(key=TestQuestion._ANY_KEY, text=TestQuestion._ANY_TEXT, options=[]) def test_get_options_indexes_with_different_bases(self): indexes = self.question._get_options_indexes() self.assertEqual(indexes, [0, 1, 2]) indexes = self.question._get_options_indexes(base=1) self.assertEqual(indexes, [1, 2, 3]) @patch("samcli.lib.cookiecutter.question.click.Choice") @patch("samcli.lib.cookiecutter.question.click") def test_ask(self, mock_click, mock_choice): mock_click.prompt.return_value = 2 answer = self.question.ask({}) self.assertEqual(answer, TestQuestion._ANY_OPTIONS[1]) # we deduct one from user's choice (base 1 vs base 0) mock_click.prompt.assert_called_once_with( text="Choice", default=self.question.default_answer, show_choices=False, type=ANY, show_default=self.question.default_answer is not None, ) mock_choice.assert_called_once_with(["1", "2", "3"]) class TestInfo(TestCase): @patch("samcli.lib.cookiecutter.question.click") def test_ask(self, mock_click): q = Info(text=TestQuestion._ANY_TEXT, key=TestQuestion._ANY_KEY) mock_click.echo.return_value = None answer = q.ask({}) self.assertIsNone(answer) mock_click.echo.assert_called_once_with(message=q.text) class TestConfirm(TestCase): @patch("samcli.lib.cookiecutter.question.click") def test_ask(self, mock_click): q = Confirm(text=TestQuestion._ANY_TEXT, key=TestQuestion._ANY_KEY) mock_click.confirm.return_value = True answer = q.ask({}) self.assertTrue(answer) mock_click.confirm.assert_called_once_with(text=q.text) class TestQuestionFactory(TestCase): def test_there_is_a_handler_for_each_question_kind(self): question_json = {"key": TestQuestion._ANY_KEY, "question": TestQuestion._ANY_TEXT, "options": ["a", "b"]} for kind in QuestionKind: question_json["kind"] = kind.name q = QuestionFactory.create_question_from_json(question_json) expected_type = QuestionFactory.question_classes[kind] self.assertTrue(isinstance(q, expected_type))