import os import subprocess import sys from pathlib import Path from django.db.backends.mysql.client import DatabaseClient from django.test import SimpleTestCase class MySqlDbshellCommandTestCase(SimpleTestCase): def settings_to_cmd_args_env(self, settings_dict, parameters=None): if parameters is None: parameters = [] return DatabaseClient.settings_to_cmd_args_env(settings_dict, parameters) def test_fails_with_keyerror_on_incomplete_config(self): with self.assertRaises(KeyError): self.settings_to_cmd_args_env({}) def test_basic_params_specified_in_settings(self): expected_args = [ "mysql", "--user=someuser", "--host=somehost", "--port=444", "somedbname", ] expected_env = {"MYSQL_PWD": "somepassword"} self.assertEqual( self.settings_to_cmd_args_env( { "NAME": "somedbname", "USER": "someuser", "PASSWORD": "somepassword", "HOST": "somehost", "PORT": 444, "OPTIONS": {}, } ), (expected_args, expected_env), ) def test_options_override_settings_proper_values(self): settings_port = 444 options_port = 555 self.assertNotEqual(settings_port, options_port, "test pre-req") expected_args = [ "mysql", "--user=optionuser", "--host=optionhost", "--port=%s" % options_port, "optiondbname", ] expected_env = {"MYSQL_PWD": "optionpassword"} for keys in [("database", "password"), ("db", "passwd")]: with self.subTest(keys=keys): database, password = keys self.assertEqual( self.settings_to_cmd_args_env( { "NAME": "settingdbname", "USER": "settinguser", "PASSWORD": "settingpassword", "HOST": "settinghost", "PORT": settings_port, "OPTIONS": { database: "optiondbname", "user": "optionuser", password: "optionpassword", "host": "optionhost", "port": options_port, }, } ), (expected_args, expected_env), ) def test_options_non_deprecated_keys_preferred(self): expected_args = [ "mysql", "--user=someuser", "--host=somehost", "--port=444", "optiondbname", ] expected_env = {"MYSQL_PWD": "optionpassword"} self.assertEqual( self.settings_to_cmd_args_env( { "NAME": "settingdbname", "USER": "someuser", "PASSWORD": "settingpassword", "HOST": "somehost", "PORT": 444, "OPTIONS": { "database": "optiondbname", "db": "deprecatedoptiondbname", "password": "optionpassword", "passwd": "deprecatedoptionpassword", }, } ), (expected_args, expected_env), ) def test_options_charset(self): expected_args = [ "mysql", "--user=someuser", "--host=somehost", "--port=444", "--default-character-set=utf8", "somedbname", ] expected_env = {"MYSQL_PWD": "somepassword"} self.assertEqual( self.settings_to_cmd_args_env( { "NAME": "somedbname", "USER": "someuser", "PASSWORD": "somepassword", "HOST": "somehost", "PORT": 444, "OPTIONS": {"charset": "utf8"}, } ), (expected_args, expected_env), ) def test_can_connect_using_sockets(self): expected_args = [ "mysql", "--user=someuser", "--socket=/path/to/mysql.socket.file", "somedbname", ] expected_env = {"MYSQL_PWD": "somepassword"} self.assertEqual( self.settings_to_cmd_args_env( { "NAME": "somedbname", "USER": "someuser", "PASSWORD": "somepassword", "HOST": "/path/to/mysql.socket.file", "PORT": None, "OPTIONS": {}, } ), (expected_args, expected_env), ) def test_ssl_certificate_is_added(self): expected_args = [ "mysql", "--user=someuser", "--host=somehost", "--port=444", "--ssl-ca=sslca", "--ssl-cert=sslcert", "--ssl-key=sslkey", "somedbname", ] expected_env = {"MYSQL_PWD": "somepassword"} self.assertEqual( self.settings_to_cmd_args_env( { "NAME": "somedbname", "USER": "someuser", "PASSWORD": "somepassword", "HOST": "somehost", "PORT": 444, "OPTIONS": { "ssl": { "ca": "sslca", "cert": "sslcert", "key": "sslkey", }, }, } ), (expected_args, expected_env), ) def test_parameters(self): self.assertEqual( self.settings_to_cmd_args_env( { "NAME": "somedbname", "USER": None, "PASSWORD": None, "HOST": None, "PORT": None, "OPTIONS": {}, }, ["--help"], ), (["mysql", "somedbname", "--help"], None), ) def test_crash_password_does_not_leak(self): # The password doesn't leak in an exception that results from a client # crash. args, env = DatabaseClient.settings_to_cmd_args_env( { "NAME": "somedbname", "USER": "someuser", "PASSWORD": "somepassword", "HOST": "somehost", "PORT": 444, "OPTIONS": {}, }, [], ) if env: env = {**os.environ, **env} fake_client = Path(__file__).with_name("fake_client.py") args[0:1] = [sys.executable, str(fake_client)] with self.assertRaises(subprocess.CalledProcessError) as ctx: subprocess.run(args, check=True, env=env) self.assertNotIn("somepassword", str(ctx.exception))