# -*- coding: utf-8 -*- """Test the cli_helpers.config module.""" from __future__ import unicode_literals import os from unittest.mock import MagicMock import pytest from cli_helpers.compat import MAC, text_type, WIN from cli_helpers.config import ( Config, DefaultConfigValidationError, get_system_config_dirs, get_user_config_dir, _pathify, ) from .utils import with_temp_dir APP_NAME, APP_AUTHOR = "Test", "Acme" TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "config_data") DEFAULT_CONFIG = { "section": { "test_boolean_default": "True", "test_string_file": "~/myfile", "test_option": "foobar✔", }, "section2": {}, } DEFAULT_VALID_CONFIG = { "section": { "test_boolean_default": True, "test_string_file": "~/myfile", "test_option": "foobar✔", }, "section2": {}, } def _mocked_user_config(temp_dir, *args, **kwargs): config = Config(*args, **kwargs) config.user_config_file = MagicMock( return_value=os.path.join(temp_dir, config.filename) ) return config def test_user_config_dir(): """Test that the config directory is a string with the app name in it.""" if "XDG_CONFIG_HOME" in os.environ: del os.environ["XDG_CONFIG_HOME"] config_dir = get_user_config_dir(APP_NAME, APP_AUTHOR) assert isinstance(config_dir, text_type) assert config_dir.endswith(APP_NAME) or config_dir.endswith(_pathify(APP_NAME)) def test_sys_config_dirs(): """Test that the sys config directories are returned correctly.""" if "XDG_CONFIG_DIRS" in os.environ: del os.environ["XDG_CONFIG_DIRS"] config_dirs = get_system_config_dirs(APP_NAME, APP_AUTHOR) assert isinstance(config_dirs, list) assert config_dirs[0].endswith(APP_NAME) or config_dirs[0].endswith( _pathify(APP_NAME) ) @pytest.mark.skipif(not WIN, reason="requires Windows") def test_windows_user_config_dir_no_roaming(): """Test that Windows returns the user config directory without roaming.""" config_dir = get_user_config_dir(APP_NAME, APP_AUTHOR, roaming=False) assert isinstance(config_dir, text_type) assert config_dir.endswith(APP_NAME) assert "Local" in config_dir @pytest.mark.skipif(not MAC, reason="requires macOS") def test_mac_user_config_dir_no_xdg(): """Test that macOS returns the user config directory without XDG.""" config_dir = get_user_config_dir(APP_NAME, APP_AUTHOR, force_xdg=False) assert isinstance(config_dir, text_type) assert config_dir.endswith(APP_NAME) assert "Library" in config_dir @pytest.mark.skipif(not MAC, reason="requires macOS") def test_mac_system_config_dirs_no_xdg(): """Test that macOS returns the system config directories without XDG.""" config_dirs = get_system_config_dirs(APP_NAME, APP_AUTHOR, force_xdg=False) assert isinstance(config_dirs, list) assert config_dirs[0].endswith(APP_NAME) assert "Library" in config_dirs[0] def test_config_reading_raise_errors(): """Test that instantiating Config will raise errors when appropriate.""" with pytest.raises(ValueError): Config(APP_NAME, APP_AUTHOR, "test_config", write_default=True) with pytest.raises(ValueError): Config(APP_NAME, APP_AUTHOR, "test_config", validate=True) with pytest.raises(TypeError): Config(APP_NAME, APP_AUTHOR, "test_config", default=b"test") def test_config_user_file(): """Test that the Config user_config_file is appropriate.""" config = Config(APP_NAME, APP_AUTHOR, "test_config") assert get_user_config_dir(APP_NAME, APP_AUTHOR) in config.user_config_file() def test_config_reading_default_dict(): """Test that the Config constructor will read in defaults from a dict.""" default = {"main": {"foo": "bar"}} config = Config(APP_NAME, APP_AUTHOR, "test_config", default=default) assert config.data == default def test_config_reading_no_default(): """Test that the Config constructor will work without any defaults.""" config = Config(APP_NAME, APP_AUTHOR, "test_config") assert config.data == {} def test_config_reading_default_file(): """Test that the Config will work with a default file.""" config = Config( APP_NAME, APP_AUTHOR, "test_config", default=os.path.join(TEST_DATA_DIR, "configrc"), ) config.read_default_config() assert config.data == DEFAULT_CONFIG def test_config_reading_configspec(): """Test that the Config default file will work with a configspec.""" config = Config( APP_NAME, APP_AUTHOR, "test_config", validate=True, default=os.path.join(TEST_DATA_DIR, "configspecrc"), ) config.read_default_config() assert config.data == DEFAULT_VALID_CONFIG def test_config_reading_configspec_with_error(): """Test that reading an invalid configspec raises and exception.""" with pytest.raises(DefaultConfigValidationError): config = Config( APP_NAME, APP_AUTHOR, "test_config", validate=True, default=os.path.join(TEST_DATA_DIR, "invalid_configspecrc"), ) config.read_default_config() @with_temp_dir def test_write_and_read_default_config(temp_dir=None): config_file = "test_config" default_file = os.path.join(TEST_DATA_DIR, "configrc") temp_config_file = os.path.join(temp_dir, config_file) config = _mocked_user_config( temp_dir, APP_NAME, APP_AUTHOR, config_file, default=default_file ) config.read_default_config() config.write_default_config() user_config = _mocked_user_config( temp_dir, APP_NAME, APP_AUTHOR, config_file, default=default_file ) user_config.read() assert temp_config_file in user_config.config_filenames assert user_config == config with open(temp_config_file) as f: contents = f.read() assert "# Test file comment" in contents assert "# Test section comment" in contents assert "# Test field comment" in contents assert "# Test field commented out" in contents @with_temp_dir def test_write_and_read_default_config_from_configspec(temp_dir=None): config_file = "test_config" default_file = os.path.join(TEST_DATA_DIR, "configspecrc") temp_config_file = os.path.join(temp_dir, config_file) config = _mocked_user_config( temp_dir, APP_NAME, APP_AUTHOR, config_file, default=default_file, validate=True ) config.read_default_config() config.write_default_config() user_config = _mocked_user_config( temp_dir, APP_NAME, APP_AUTHOR, config_file, default=default_file, validate=True ) user_config.read() assert temp_config_file in user_config.config_filenames assert user_config == config with open(temp_config_file) as f: contents = f.read() assert "# Test file comment" in contents assert "# Test section comment" in contents assert "# Test field comment" in contents assert "# Test field commented out" in contents @with_temp_dir def test_overwrite_default_config_from_configspec(temp_dir=None): config_file = "test_config" default_file = os.path.join(TEST_DATA_DIR, "configspecrc") temp_config_file = os.path.join(temp_dir, config_file) config = _mocked_user_config( temp_dir, APP_NAME, APP_AUTHOR, config_file, default=default_file, validate=True ) config.read_default_config() config.write_default_config() with open(temp_config_file, "a") as f: f.write("--APPEND--") config.write_default_config() with open(temp_config_file) as f: assert "--APPEND--" in f.read() config.write_default_config(overwrite=True) with open(temp_config_file) as f: assert "--APPEND--" not in f.read() def test_read_invalid_config_file(): config_file = "invalid_configrc" config = _mocked_user_config(TEST_DATA_DIR, APP_NAME, APP_AUTHOR, config_file) config.read() assert "section" in config assert "test_string_file" in config["section"] assert "test_boolean_default" not in config["section"] assert "section2" in config @with_temp_dir def test_write_to_user_config(temp_dir=None): config_file = "test_config" default_file = os.path.join(TEST_DATA_DIR, "configrc") temp_config_file = os.path.join(temp_dir, config_file) config = _mocked_user_config( temp_dir, APP_NAME, APP_AUTHOR, config_file, default=default_file ) config.read_default_config() config.write_default_config() with open(temp_config_file) as f: assert "test_boolean_default = True" in f.read() config["section"]["test_boolean_default"] = False config.write() with open(temp_config_file) as f: assert "test_boolean_default = False" in f.read() @with_temp_dir def test_write_to_outfile(temp_dir=None): config_file = "test_config" outfile = os.path.join(temp_dir, "foo") default_file = os.path.join(TEST_DATA_DIR, "configrc") config = _mocked_user_config( temp_dir, APP_NAME, APP_AUTHOR, config_file, default=default_file ) config.read_default_config() config.write_default_config() config["section"]["test_boolean_default"] = False config.write(outfile=outfile) with open(outfile) as f: assert "test_boolean_default = False" in f.read()