summaryrefslogtreecommitdiffstats
path: root/test/test_config.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/test_config.py')
-rw-r--r--test/test_config.py196
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)