1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
|
# type: ignore
"""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("[main]\nweather='cloudy with a chance of meatballs'\n")
config = read_config_file(f)
assert config["main"]["weather"] == "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("[main]\nweather='cloudy with a chance of meatballs'\n")
config = read_config_file(f, list_values=False)
assert config["main"]["weather"] == "'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(f'"{s}"')
assert s == strip_matching_quotes(f"'{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(f'"{s}')
assert s + "'" == strip_matching_quotes(f"{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)
|