Path: blob/develop/tests/unit/customizations/configure/test_configure.py
1569 views
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.1#2# Licensed under the Apache License, Version 2.0 (the "License"). You3# may not use this file except in compliance with the License. A copy of4# the License is located at5#6# http://aws.amazon.com/apache2.0/7#8# or in the "license" file accompanying this file. This file is9# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF10# ANY KIND, either express or implied. See the License for the specific11# language governing permissions and limitations under the License.12import os1314from awscli.customizations.configure import configure, ConfigValue, NOT_SET15from awscli.customizations.configure import profile_to_section16from awscli.testutils import mock, unittest17from awscli.compat import StringIO1819from . import FakeSession202122class TestConfigureCommand(unittest.TestCase):2324def setUp(self):25self.writer = mock.Mock()26self.global_args = mock.Mock()27self.global_args.profile = None28self.precanned = PrecannedPrompter(value='new_value')29self.session = FakeSession({'config_file': 'myconfigfile'})30self.configure = configure.ConfigureCommand(self.session,31prompter=self.precanned,32config_writer=self.writer)3334def assert_credentials_file_updated_with(self, new_values):35called_args = self.writer.update_config.call_args_list36credentials_file_call = called_args[0]37expected_creds_file = os.path.expanduser('~/fake_credentials_filename')38self.assertEqual(credentials_file_call,39mock.call(new_values, expected_creds_file))4041def test_configure_command_sends_values_to_writer(self):42self.configure(args=[], parsed_globals=self.global_args)43# Credentials are always written to the shared credentials file.44self.assert_credentials_file_updated_with(45{'aws_access_key_id': 'new_value',46'aws_secret_access_key': 'new_value'})4748# Non-credentials config is written to the config file.49self.writer.update_config.assert_called_with(50{'region': 'new_value',51'output': 'new_value'}, 'myconfigfile')5253def test_same_values_are_not_changed(self):54# If the user enters the same value as the current value, we don't need55# to write anything to the config.56self.configure = configure.ConfigureCommand(self.session,57prompter=EchoPrompter(),58config_writer=self.writer)59self.configure(args=[], parsed_globals=self.global_args)60self.assertFalse(self.writer.update_config.called)6162def test_none_values_are_not_changed(self):63# If a user hits enter, this will result in a None value which means64# don't change the existing values. In this case, we don't need65# to write anything out to the config.66user_presses_enter = None67precanned = PrecannedPrompter(value=user_presses_enter)68self.configure = configure.ConfigureCommand(self.session,69prompter=precanned,70config_writer=self.writer)71self.configure(args=[], parsed_globals=self.global_args)72self.assertFalse(self.writer.update_config.called)7374def test_create_configure_cmd_session_only(self):75self.configure = configure.ConfigureCommand(self.session)76self.assertIsInstance(self.configure, configure.ConfigureCommand)7778def test_some_values_changed(self):79# Test the case where the user only wants to change a single_value.80responses = {81"AWS Access Key ID": None,82"AWS Secert Access Key": None,83"Default region name": None,84"Default output format": "NEW OUTPUT FORMAT",85}86prompter = KeyValuePrompter(responses)87self.configure = configure.ConfigureCommand(self.session, prompter=prompter,88config_writer=self.writer)89self.configure(args=[], parsed_globals=self.global_args)9091# We only need to write out the default output format.92self.writer.update_config.assert_called_with(93{'output': 'NEW OUTPUT FORMAT'}, 'myconfigfile')9495def test_section_name_can_be_changed_for_profiles(self):96# If the user specifies a profile we need to write this out to97# the [profile myname] section.98self.session.profile = 'myname'99self.configure(args=[], parsed_globals=self.global_args)100# Note the __section__ key name.101self.assert_credentials_file_updated_with(102{'aws_access_key_id': 'new_value',103'aws_secret_access_key': 'new_value',104'__section__': 'myname'})105self.writer.update_config.assert_called_with(106{'__section__': 'profile myname',107'region': 'new_value',108'output': 'new_value'}, 'myconfigfile')109110def test_session_says_profile_does_not_exist(self):111# Whenever you try to get a config value from botocore,112# it will raise an exception complaining about ProfileNotFound.113# We should handle this case, and write out a new profile section114# in the config file.115session = FakeSession({'config_file': 'myconfigfile'},116profile_does_not_exist=True,117profile='profile-does-not-exist')118self.configure = configure.ConfigureCommand(session,119prompter=self.precanned,120config_writer=self.writer)121self.configure(args=[], parsed_globals=self.global_args)122self.assert_credentials_file_updated_with(123{'aws_access_key_id': 'new_value',124'aws_secret_access_key': 'new_value',125'__section__': 'profile-does-not-exist'})126self.writer.update_config.assert_called_with(127{'__section__': 'profile profile-does-not-exist',128'region': 'new_value',129'output': 'new_value'}, 'myconfigfile')130131132class TestInteractivePrompter(unittest.TestCase):133134def setUp(self):135self.input_patch = mock.patch('awscli.compat.raw_input')136self.mock_raw_input = self.input_patch.start()137self.stdout = StringIO()138self.stdout_patch = mock.patch('sys.stdout', self.stdout)139self.stdout_patch.start()140141def tearDown(self):142self.input_patch.stop()143self.stdout_patch.stop()144145def test_access_key_is_masked(self):146self.mock_raw_input.return_value = 'foo'147prompter = configure.InteractivePrompter()148response = prompter.get_value(149current_value='myaccesskey', config_name='aws_access_key_id',150prompt_text='Access key')151# First we should return the value from raw_input.152self.assertEqual(response, 'foo')153# We should also not display the entire access key.154prompt_text = self.stdout.getvalue()155self.assertNotIn('myaccesskey', prompt_text)156self.assertRegex(prompt_text, r'\[\*\*\*\*.*\]')157158def test_access_key_not_masked_when_none(self):159self.mock_raw_input.return_value = 'foo'160prompter = configure.InteractivePrompter()161response = prompter.get_value(162current_value=None, config_name='aws_access_key_id',163prompt_text='Access key')164# First we should return the value from raw_input.165self.assertEqual(response, 'foo')166prompt_text = self.stdout.getvalue()167self.assertIn('[None]', prompt_text)168169def test_secret_key_is_masked(self):170prompter = configure.InteractivePrompter()171prompter.get_value(172current_value='mysupersecretkey',173config_name='aws_secret_access_key',174prompt_text='Secret Key')175# We should also not display the entire secret key.176prompt_text = self.stdout.getvalue()177self.assertNotIn('mysupersecretkey', prompt_text)178self.assertRegex(prompt_text, r'\[\*\*\*\*.*\]')179180def test_non_secret_keys_are_not_masked(self):181prompter = configure.InteractivePrompter()182prompter.get_value(183current_value='mycurrentvalue', config_name='not_a_secret_key',184prompt_text='Enter value')185# We should also not display the entire secret key.186prompt_text = self.stdout.getvalue()187self.assertIn('mycurrentvalue', prompt_text)188self.assertRegex(prompt_text, r'\[mycurrentvalue\]')189190def test_user_hits_enter_returns_none(self):191# If a user hits enter, then raw_input returns the empty string.192self.mock_raw_input.return_value = ''193194prompter = configure.InteractivePrompter()195response = prompter.get_value(196current_value=None, config_name='aws_access_key_id',197prompt_text='Access key')198# We convert the empty string to None to indicate that there199# was no input.200self.assertIsNone(response)201202def test_compat_input_flushes_after_each_prompt(self):203# Clear out the default patch204self.stdout_patch.stop()205206# Create a mock stdout to record flush calls and replace stdout_patch207self.stdout = mock.Mock()208self.stdout_patch = mock.patch('sys.stdout', self.stdout)209self.stdout_patch.start()210211# Make sure flush called at least once212prompter = configure.InteractivePrompter()213prompter.get_value(current_value='foo', config_name='bar',214prompt_text='baz')215self.assertTrue(self.stdout.flush.called)216217# Make sure flush is called after *every* prompt218self.stdout.reset_mock()219prompter.get_value(current_value='foo2', config_name='bar2',220prompt_text='baz2')221self.assertTrue(self.stdout.flush.called)222223224class TestConfigValueMasking(unittest.TestCase):225226def test_config_value_is_masked(self):227config_value = ConfigValue(228'fake_access_key', 'config_file', 'aws_access_key_id')229self.assertEqual(config_value.value, 'fake_access_key')230config_value.mask_value()231self.assertEqual(config_value.value, '****************_key')232233def test_dont_mask_unset_value(self):234no_config = ConfigValue(NOT_SET, None, None)235self.assertEqual(no_config.value, NOT_SET)236no_config.mask_value()237self.assertEqual(no_config.value, NOT_SET)238239240class TestProfileToSection(unittest.TestCase):241242def test_normal_profile(self):243profile = 'my-profile'244section = profile_to_section(profile)245self.assertEqual('profile my-profile', section)246247def test_profile_with_spaces(self):248profile = 'my spaced profile'249section = profile_to_section(profile)250self.assertEqual('profile \'my spaced profile\'', section)251252def test_profile_with_tab(self):253profile = 'tab\ts'254section = profile_to_section(profile)255self.assertEqual('profile \'tab\ts\'', section)256257def test_profile_with_consecutive_spaces(self):258profile = ' '259section = profile_to_section(profile)260self.assertEqual('profile \' \'', section)261262263class PrecannedPrompter(object):264265def __init__(self, value):266self._value = value267268def get_value(self, current_value, logical_name, prompt_text=''):269return self._value270271272class EchoPrompter(object):273274def get_value(self, current_value, logical_name, prompt_text=''):275return current_value276277278class KeyValuePrompter(object):279280def __init__(self, mapping):281self.mapping = mapping282283def get_value(self, current_value, config_name, prompt_text=''):284return self.mapping.get(prompt_text)285286287