diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 9755af9f6dd..ebaaccb1db1 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -1458,6 +1458,12 @@ class MakeMigrationsTests(MigrationTestBase): " 3) Quit and manually define a default value in models.py." ) with self.temporary_migration_module(module='migrations.test_migrations'): + # No message appears if --dry-run. + with captured_stdout() as out: + call_command( + 'makemigrations', 'migrations', interactive=True, dry_run=True, + ) + self.assertNotIn(input_msg, out.getvalue()) # 3 - quit. with mock.patch('builtins.input', return_value='3'): with captured_stdout() as out, self.assertRaises(SystemExit): @@ -1512,6 +1518,39 @@ class MakeMigrationsTests(MigrationTestBase): self.assertIn("Remove field silly_field from sillymodel", out.getvalue()) self.assertIn("Add field silly_rename to sillymodel", out.getvalue()) + @mock.patch('builtins.input', return_value='Y') + def test_makemigrations_model_rename_interactive(self, mock_input): + class RenamedModel(models.Model): + silly_field = models.BooleanField(default=False) + + class Meta: + app_label = 'migrations' + + with self.temporary_migration_module( + module='migrations.test_migrations_no_default', + ): + with captured_stdout() as out: + call_command('makemigrations', 'migrations', interactive=True) + self.assertIn('Rename model SillyModel to RenamedModel', out.getvalue()) + + @mock.patch('builtins.input', return_value='Y') + def test_makemigrations_field_rename_interactive(self, mock_input): + class SillyModel(models.Model): + silly_rename = models.BooleanField(default=False) + + class Meta: + app_label = 'migrations' + + with self.temporary_migration_module( + module='migrations.test_migrations_no_default', + ): + with captured_stdout() as out: + call_command('makemigrations', 'migrations', interactive=True) + self.assertIn( + 'Rename field silly_field on sillymodel to silly_rename', + out.getvalue(), + ) + def test_makemigrations_handle_merge(self): """ makemigrations properly merges the conflicting migrations with --noinput. @@ -1581,6 +1620,7 @@ class MakeMigrationsTests(MigrationTestBase): class SillyModel(models.Model): silly_field = models.BooleanField(default=False) silly_date = models.DateField() # Added field without a default + silly_auto_now = models.DateTimeField(auto_now_add=True) class Meta: app_label = "migrations" diff --git a/tests/migrations/test_questioner.py b/tests/migrations/test_questioner.py index 9ab7756ab57..57749ed9125 100644 --- a/tests/migrations/test_questioner.py +++ b/tests/migrations/test_questioner.py @@ -18,12 +18,9 @@ class QuestionerTests(SimpleTestCase): questioner = MigrationQuestioner() self.assertIs(False, questioner.ask_initial('migrations')) - @mock.patch('builtins.input', return_value='datetime.timedelta(days=1)') - def test_timedelta_default(self, mock): - questioner = InteractiveMigrationQuestioner() - with captured_stdout(): - value = questioner._ask_default() - self.assertEqual(value, datetime.timedelta(days=1)) + def test_ask_not_null_alteration(self): + questioner = MigrationQuestioner() + self.assertIsNone(questioner.ask_not_null_alteration('field_name', 'model_name')) @mock.patch('builtins.input', return_value='2') def test_ask_not_null_alteration_not_provided(self, mock): @@ -31,3 +28,61 @@ class QuestionerTests(SimpleTestCase): with captured_stdout(): question = questioner.ask_not_null_alteration('field_name', 'model_name') self.assertEqual(question, NOT_PROVIDED) + + +class QuestionerHelperMethodsTests(SimpleTestCase): + questioner = InteractiveMigrationQuestioner() + + @mock.patch('builtins.input', return_value='datetime.timedelta(days=1)') + def test_questioner_default_timedelta(self, mock_input): + questioner = InteractiveMigrationQuestioner() + with captured_stdout(): + value = questioner._ask_default() + self.assertEqual(value, datetime.timedelta(days=1)) + + @mock.patch('builtins.input', return_value='') + def test_questioner_default_no_user_entry(self, mock_input): + with captured_stdout(): + value = self.questioner._ask_default(default='datetime.timedelta(days=1)') + self.assertEqual(value, datetime.timedelta(days=1)) + + @mock.patch('builtins.input', side_effect=['', 'exit']) + def test_questioner_no_default_no_user_entry(self, mock_input): + with captured_stdout() as stdout, self.assertRaises(SystemExit): + self.questioner._ask_default() + self.assertIn( + "Please enter some code, or 'exit' (without quotes) to exit.", + stdout.getvalue(), + ) + + @mock.patch('builtins.input', side_effect=['bad code', 'exit']) + def test_questioner_no_default_bad_user_entry_code(self, mock_input): + with captured_stdout() as stdout, self.assertRaises(SystemExit): + self.questioner._ask_default() + self.assertIn('Invalid input: unexpected EOF while parsing', stdout.getvalue()) + + @mock.patch('builtins.input', side_effect=['', 'n']) + def test_questioner_no_default_no_user_entry_boolean(self, mock_input): + with captured_stdout(): + value = self.questioner._boolean_input('Proceed?') + self.assertIs(value, False) + + @mock.patch('builtins.input', return_value='') + def test_questioner_default_no_user_entry_boolean(self, mock_input): + with captured_stdout(): + value = self.questioner._boolean_input('Proceed?', default=True) + self.assertIs(value, True) + + @mock.patch('builtins.input', side_effect=[10, 'garbage', 1]) + def test_questioner_bad_user_choice(self, mock_input): + question = 'Make a choice:' + with captured_stdout() as stdout: + value = self.questioner._choice_input(question, choices='abc') + expected_msg = ( + f'{question}\n' + f' 1) a\n' + f' 2) b\n' + f' 3) c\n' + ) + self.assertIn(expected_msg, stdout.getvalue()) + self.assertEqual(value, 1)