diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..d9ef386 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,223 @@ +"""Unit tests for configuration loading and validation.""" + +import os +import tempfile +from pathlib import Path +from unittest.mock import patch + +import pytest + +from dev_env_sync.core.config import ConfigLoader, ConfigSchema, ConfigParseError + + +class TestConfigLoader: + """Tests for ConfigLoader class.""" + + @pytest.fixture + def sample_yaml_content(self): + """Sample YAML configuration content.""" + return ''' +version: "1.0" +name: "Test Environment" +description: "Test configuration" + +dotfiles: + bashrc: + source: ./dotfiles/.bashrc + target: ~/.bashrc + backup: true + vimrc: + source: ./dotfiles/.vimrc + target: ~/.vimrc + +shell: + shell: bash + merge_strategy: replace + +editors: + vscode: + settings: + settings_file: ./editors/vscode/settings.json + extensions: + - name: ms-python.python + +packages: + - name: brew + packages: + - git + - neovim + +backup: + enabled: true + directory: ~/.dev-env-sync-backups + timestamp_format: "%Y%m%d_%H%M%S" +''' + + def test_load_valid_config(self, sample_yaml_content, temp_dir): + """Test loading a valid configuration file.""" + config_file = temp_dir / "config.yml" + config_file.write_text(sample_yaml_content) + + loader = ConfigLoader(str(config_file)) + config = loader.load() + + assert config is not None + assert config.version == "1.0" + assert config.name == "Test Environment" + assert len(config.dotfiles) == 2 + assert "bashrc" in config.dotfiles + assert "vimrc" in config.dotfiles + + def test_load_minimal_config(self, temp_dir): + """Test loading a minimal configuration file.""" + config_file = temp_dir / "minimal.yml" + config_file.write_text('version: "1.0"\nname: "Minimal"') + + loader = ConfigLoader(str(config_file)) + config = loader.load() + + assert config is not None + assert config.version == "1.0" + assert config.name == "Minimal" + assert len(config.dotfiles) == 0 + + def test_dotfiles_parsing(self, sample_yaml_content, temp_dir): + """Test parsing dotfiles configuration.""" + config_file = temp_dir / "config.yml" + config_file.write_text(sample_yaml_content) + + loader = ConfigLoader(str(config_file)) + config = loader.load() + + assert "bashrc" in config.dotfiles + bashrc = config.dotfiles["bashrc"] + assert bashrc.source == "./dotfiles/.bashrc" + assert bashrc.target == "~/.bashrc" + assert bashrc.backup == True + + def test_packages_parsing(self, sample_yaml_content, temp_dir): + """Test parsing packages configuration.""" + config_file = temp_dir / "config.yml" + config_file.write_text(sample_yaml_content) + + loader = ConfigLoader(str(config_file)) + config = loader.load() + + assert len(config.packages) == 1 + brew_pkg = config.packages[0] + assert brew_pkg.name == "brew" + assert len(brew_pkg.packages) == 2 + assert "git" in brew_pkg.packages + assert "neovim" in brew_pkg.packages + + def test_editors_parsing(self, sample_yaml_content, temp_dir): + """Test parsing editors configuration.""" + config_file = temp_dir / "config.yml" + config_file.write_text(sample_yaml_content) + + loader = ConfigLoader(str(config_file)) + config = loader.load() + + assert "vscode" in config.editors + vscode_config = config.editors["vscode"] + assert "extensions" in vscode_config + assert len(vscode_config["extensions"]) == 1 + + def test_backup_config_parsing(self, sample_yaml_content, temp_dir): + """Test parsing backup configuration.""" + config_file = temp_dir / "config.yml" + config_file.write_text(sample_yaml_content) + + loader = ConfigLoader(str(config_file)) + config = loader.load() + + assert config.backup.enabled == True + assert config.backup.directory == "~/.dev-env-sync-backups" + assert config.backup.timestamp_format == "%Y%m%d_%H%M%S" + + def test_invalid_yaml(self, temp_dir): + """Test handling of invalid YAML.""" + config_file = temp_dir / "invalid.yml" + config_file.write_text("invalid: yaml: content: [") + + loader = ConfigLoader(str(config_file)) + + with pytest.raises(ConfigParseError): + loader.load() + + def test_nonexistent_file(self): + """Test handling of nonexistent configuration file.""" + loader = ConfigLoader("/nonexistent/path/config.yml") + + with pytest.raises(ConfigParseError): + loader.load() + + def test_create_default_config(self): + """Test creating default configuration.""" + config = ConfigLoader.create_default_config() + + assert config is not None + assert config.version == "1.0" + assert config.name == "My Dev Environment" + assert len(config.dotfiles) > 0 + assert len(config.packages) > 0 + + def test_config_to_dict(self, temp_dir): + """Test converting config to dictionary.""" + config_file = temp_dir / "config.yml" + config_file.write_text('version: "1.0"\nname: "Test"') + + loader = ConfigLoader(str(config_file)) + config = loader.load() + + config_dict = config.to_dict() + + assert isinstance(config_dict, dict) + assert config_dict["version"] == "1.0" + assert config_dict["name"] == "Test" + + +class TestConfigValidation: + """Tests for configuration validation.""" + + def test_valid_config_schema(self, temp_dir): + """Test that valid config matches schema.""" + config_file = temp_dir / "valid.yml" + config_file.write_text(''' +version: "1.0" +name: "Valid Test" +description: "Testing validation" + +dotfiles: + test: + source: ./test + target: ~/.test + backup: true + +backup: + enabled: true + directory: ~/.test-backups +''') + + loader = ConfigLoader(str(config_file)) + config = loader.load() + + assert config.version == "1.0" + assert config.backup.enabled == True + + def test_string_dotfile_shorthand(self, temp_dir): + """Test string shorthand for dotfiles.""" + config_file = temp_dir / "shorthand.yml" + config_file.write_text(''' +version: "1.0" + +dotfiles: + bashrc: ./bashrc +''') + + loader = ConfigLoader(str(config_file)) + config = loader.load() + + assert "bashrc" in config.dotfiles + assert config.dotfiles["bashrc"].source == "./bashrc" + assert config.dotfiles["bashrc"].target == "~/.bashrc"