diff options
Diffstat (limited to 'test/test_config.py')
-rw-r--r-- | test/test_config.py | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/test/test_config.py b/test/test_config.py new file mode 100644 index 0000000..7f2b244 --- /dev/null +++ b/test/test_config.py @@ -0,0 +1,196 @@ +"""Unit tests for the mycli.config module.""" +from io import BytesIO, StringIO, TextIOWrapper +import os +import struct +import sys +import tempfile +import pytest + +from mycli.config import (get_mylogin_cnf_path, open_mylogin_cnf, + read_and_decrypt_mylogin_cnf, read_config_file, + str_to_bool, strip_matching_quotes) + +LOGIN_PATH_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), + 'mylogin.cnf')) + + +def open_bmylogin_cnf(name): + """Open contents of *name* in a BytesIO buffer.""" + with open(name, 'rb') as f: + buf = BytesIO() + buf.write(f.read()) + return buf + +def test_read_mylogin_cnf(): + """Tests that a login path file can be read and decrypted.""" + mylogin_cnf = open_mylogin_cnf(LOGIN_PATH_FILE) + + assert isinstance(mylogin_cnf, TextIOWrapper) + + contents = mylogin_cnf.read() + for word in ('[test]', 'user', 'password', 'host', 'port'): + assert word in contents + + +def test_decrypt_blank_mylogin_cnf(): + """Test that a blank login path file is handled correctly.""" + mylogin_cnf = read_and_decrypt_mylogin_cnf(BytesIO()) + assert mylogin_cnf is None + + +def test_corrupted_login_key(): + """Test that a corrupted login path key is handled correctly.""" + buf = open_bmylogin_cnf(LOGIN_PATH_FILE) + + # Skip past the unused bytes + buf.seek(4) + + # Write null bytes over half the login key + buf.write(b'\0\0\0\0\0\0\0\0\0\0') + + buf.seek(0) + mylogin_cnf = read_and_decrypt_mylogin_cnf(buf) + + assert mylogin_cnf is None + + +def test_corrupted_pad(): + """Tests that a login path file with a corrupted pad is partially read.""" + buf = open_bmylogin_cnf(LOGIN_PATH_FILE) + + # Skip past the login key + buf.seek(24) + + # Skip option group + len_buf = buf.read(4) + cipher_len, = struct.unpack("<i", len_buf) + buf.read(cipher_len) + + # Corrupt the pad for the user line + len_buf = buf.read(4) + cipher_len, = struct.unpack("<i", len_buf) + buf.read(cipher_len - 1) + buf.write(b'\0') + + buf.seek(0) + mylogin_cnf = TextIOWrapper(read_and_decrypt_mylogin_cnf(buf)) + contents = mylogin_cnf.read() + for word in ('[test]', 'password', 'host', 'port'): + assert word in contents + assert 'user' not in contents + + +def test_get_mylogin_cnf_path(): + """Tests that the path for .mylogin.cnf is detected.""" + original_env = None + if 'MYSQL_TEST_LOGIN_FILE' in os.environ: + original_env = os.environ.pop('MYSQL_TEST_LOGIN_FILE') + is_windows = sys.platform == 'win32' + + login_cnf_path = get_mylogin_cnf_path() + + if original_env is not None: + os.environ['MYSQL_TEST_LOGIN_FILE'] = original_env + + if login_cnf_path is not None: + assert login_cnf_path.endswith('.mylogin.cnf') + + if is_windows is True: + assert 'MySQL' in login_cnf_path + else: + home_dir = os.path.expanduser('~') + assert login_cnf_path.startswith(home_dir) + + +def test_alternate_get_mylogin_cnf_path(): + """Tests that the alternate path for .mylogin.cnf is detected.""" + original_env = None + if 'MYSQL_TEST_LOGIN_FILE' in os.environ: + original_env = os.environ.pop('MYSQL_TEST_LOGIN_FILE') + + _, temp_path = tempfile.mkstemp() + os.environ['MYSQL_TEST_LOGIN_FILE'] = temp_path + + login_cnf_path = get_mylogin_cnf_path() + + if original_env is not None: + os.environ['MYSQL_TEST_LOGIN_FILE'] = original_env + + assert temp_path == login_cnf_path + + +def test_str_to_bool(): + """Tests that str_to_bool function converts values correctly.""" + + assert str_to_bool(False) is False + assert str_to_bool(True) is True + assert str_to_bool('False') is False + assert str_to_bool('True') is True + assert str_to_bool('TRUE') is True + assert str_to_bool('1') is True + assert str_to_bool('0') is False + assert str_to_bool('on') is True + assert str_to_bool('off') is False + assert str_to_bool('off') is False + + with pytest.raises(ValueError): + str_to_bool('foo') + + with pytest.raises(TypeError): + str_to_bool(None) + + +def test_read_config_file_list_values_default(): + """Test that reading a config file uses list_values by default.""" + + f = StringIO(u"[main]\nweather='cloudy with a chance of meatballs'\n") + config = read_config_file(f) + + assert config['main']['weather'] == u"cloudy with a chance of meatballs" + + +def test_read_config_file_list_values_off(): + """Test that you can disable list_values when reading a config file.""" + + f = StringIO(u"[main]\nweather='cloudy with a chance of meatballs'\n") + config = read_config_file(f, list_values=False) + + assert config['main']['weather'] == u"'cloudy with a chance of meatballs'" + + +def test_strip_quotes_with_matching_quotes(): + """Test that a string with matching quotes is unquoted.""" + + s = "May the force be with you." + assert s == strip_matching_quotes('"{}"'.format(s)) + assert s == strip_matching_quotes("'{}'".format(s)) + + +def test_strip_quotes_with_unmatching_quotes(): + """Test that a string with unmatching quotes is not unquoted.""" + + s = "May the force be with you." + assert '"' + s == strip_matching_quotes('"{}'.format(s)) + assert s + "'" == strip_matching_quotes("{}'".format(s)) + + +def test_strip_quotes_with_empty_string(): + """Test that an empty string is handled during unquoting.""" + + assert '' == strip_matching_quotes('') + + +def test_strip_quotes_with_none(): + """Test that None is handled during unquoting.""" + + assert None is strip_matching_quotes(None) + + +def test_strip_quotes_with_quotes(): + """Test that strings with quotes in them are handled during unquoting.""" + + s1 = 'Darth Vader said, "Luke, I am your father."' + assert s1 == strip_matching_quotes(s1) + + s2 = '"Darth Vader said, "Luke, I am your father.""' + assert s2[1:-1] == strip_matching_quotes(s2) |