""" Tests for the configuration management module. This module contains unit tests for the Config class, validating configuration loading, saving, and management functionality. """ import tempfile import json from pathlib import Path from unittest.mock import patch, mock_open from hosts.core.config import Config class TestConfig: """Test cases for the Config class.""" def test_config_initialization(self): """Test basic config initialization with defaults.""" with patch.object(Config, "load"): config = Config() # Check default settings assert config.get("show_default_entries") is False assert len(config.get("default_entries", [])) == 3 assert config.get("window_settings", {}).get("last_sort_column") == "" assert config.get("window_settings", {}).get("last_sort_ascending") is True def test_default_settings_structure(self): """Test that default settings have the expected structure.""" with patch.object(Config, "load"): config = Config() default_entries = config.get("default_entries", []) assert len(default_entries) == 3 # Check localhost entries localhost_entries = [ e for e in default_entries if e["hostname"] == "localhost" ] assert len(localhost_entries) == 2 # IPv4 and IPv6 # Check broadcasthost entry broadcast_entries = [ e for e in default_entries if e["hostname"] == "broadcasthost" ] assert len(broadcast_entries) == 1 assert broadcast_entries[0]["ip"] == "255.255.255.255" def test_config_paths(self): """Test that config paths are set correctly.""" with patch.object(Config, "load"): config = Config() expected_dir = Path.home() / ".config" / "hosts-manager" expected_file = expected_dir / "config.json" assert config.config_dir == expected_dir assert config.config_file == expected_file def test_get_existing_key(self): """Test getting an existing configuration key.""" with patch.object(Config, "load"): config = Config() result = config.get("show_default_entries") assert result is False def test_get_nonexistent_key_with_default(self): """Test getting a nonexistent key with default value.""" with patch.object(Config, "load"): config = Config() result = config.get("nonexistent_key", "default_value") assert result == "default_value" def test_get_nonexistent_key_without_default(self): """Test getting a nonexistent key without default value.""" with patch.object(Config, "load"): config = Config() result = config.get("nonexistent_key") assert result is None def test_set_configuration_value(self): """Test setting a configuration value.""" with patch.object(Config, "load"): config = Config() config.set("test_key", "test_value") assert config.get("test_key") == "test_value" def test_set_overwrites_existing_value(self): """Test that setting overwrites existing values.""" with patch.object(Config, "load"): config = Config() # Set initial value config.set("show_default_entries", True) assert config.get("show_default_entries") is True # Overwrite with new value config.set("show_default_entries", False) assert config.get("show_default_entries") is False def test_is_default_entry_true(self): """Test identifying default entries correctly.""" with patch.object(Config, "load"): config = Config() # Test localhost IPv4 assert config.is_default_entry("127.0.0.1", "localhost") is True # Test localhost IPv6 assert config.is_default_entry("::1", "localhost") is True # Test broadcasthost assert config.is_default_entry("255.255.255.255", "broadcasthost") is True def test_is_default_entry_false(self): """Test that non-default entries are not identified as default.""" with patch.object(Config, "load"): config = Config() # Test custom entries assert config.is_default_entry("192.168.1.1", "router") is False assert config.is_default_entry("10.0.0.1", "test.local") is False assert config.is_default_entry("127.0.0.1", "custom") is False def test_should_show_default_entries_default(self): """Test default value for show_default_entries.""" with patch.object(Config, "load"): config = Config() assert config.should_show_default_entries() is False def test_should_show_default_entries_configured(self): """Test configured value for show_default_entries.""" with patch.object(Config, "load"): config = Config() config.set("show_default_entries", True) assert config.should_show_default_entries() is True def test_toggle_show_default_entries(self): """Test toggling the show_default_entries setting.""" with patch.object(Config, "load"), patch.object(Config, "save") as mock_save: config = Config() # Initial state should be False assert config.should_show_default_entries() is False # Toggle to True config.toggle_show_default_entries() assert config.should_show_default_entries() is True mock_save.assert_called_once() # Toggle back to False mock_save.reset_mock() config.toggle_show_default_entries() assert config.should_show_default_entries() is False mock_save.assert_called_once() def test_load_nonexistent_file(self): """Test loading config when file doesn't exist.""" with patch("pathlib.Path.exists", return_value=False): config = Config() # Should use defaults when file doesn't exist assert config.get("show_default_entries") is False def test_load_existing_file(self): """Test loading config from existing file.""" test_config = {"show_default_entries": True, "custom_setting": "custom_value"} with ( patch("pathlib.Path.exists", return_value=True), patch("builtins.open", mock_open(read_data=json.dumps(test_config))), ): config = Config() # Should load values from file assert config.get("show_default_entries") is True assert config.get("custom_setting") == "custom_value" # Should still have defaults for missing keys assert len(config.get("default_entries", [])) == 3 def test_load_invalid_json(self): """Test loading config with invalid JSON falls back to defaults.""" with ( patch("pathlib.Path.exists", return_value=True), patch("builtins.open", mock_open(read_data="invalid json")), ): config = Config() # Should use defaults when JSON is invalid assert config.get("show_default_entries") is False def test_load_file_io_error(self): """Test loading config with file I/O error falls back to defaults.""" with ( patch("pathlib.Path.exists", return_value=True), patch("builtins.open", side_effect=IOError("File error")), ): config = Config() # Should use defaults when file can't be read assert config.get("show_default_entries") is False def test_save_creates_directory(self): """Test that save creates config directory if it doesn't exist.""" with ( patch.object(Config, "load"), patch("pathlib.Path.mkdir") as mock_mkdir, patch("builtins.open", mock_open()) as mock_file, ): config = Config() config.save() # Should create directory with parents=True, exist_ok=True mock_mkdir.assert_called_once_with(parents=True, exist_ok=True) mock_file.assert_called_once() def test_save_writes_json(self): """Test that save writes configuration as JSON.""" with ( patch.object(Config, "load"), patch("pathlib.Path.mkdir"), patch("builtins.open", mock_open()) as mock_file, ): config = Config() config.set("test_key", "test_value") config.save() # Check that file was opened for writing mock_file.assert_called_once_with(config.config_file, "w") # Check that JSON was written handle = mock_file() written_data = "".join(call.args[0] for call in handle.write.call_args_list) # Should be valid JSON containing our test data parsed_data = json.loads(written_data) assert parsed_data["test_key"] == "test_value" def test_save_io_error_silent_fail(self): """Test that save silently fails on I/O error.""" with ( patch.object(Config, "load"), patch("pathlib.Path.mkdir"), patch("builtins.open", side_effect=IOError("Write error")), ): config = Config() # Should not raise exception config.save() def test_save_directory_creation_error_silent_fail(self): """Test that save silently fails on directory creation error.""" with ( patch.object(Config, "load"), patch("pathlib.Path.mkdir", side_effect=OSError("Permission denied")), ): config = Config() # Should not raise exception config.save() def test_integration_load_save_roundtrip(self): """Test complete load/save cycle with temporary file.""" with tempfile.TemporaryDirectory() as temp_dir: config_dir = Path(temp_dir) / "hosts-manager" config_file = config_dir / "config.json" with patch.object(Config, "__init__", lambda self: None): config = Config() config.config_dir = config_dir config.config_file = config_file config._settings = config._load_default_settings() # Modify some settings config.set("show_default_entries", True) config.set("custom_setting", "test_value") # Save configuration config.save() # Verify file was created assert config_file.exists() # Create new config instance and load config2 = Config() config2.config_dir = config_dir config2.config_file = config_file config2._settings = config2._load_default_settings() config2.load() # Verify settings were loaded correctly assert config2.get("show_default_entries") is True assert config2.get("custom_setting") == "test_value" # Verify defaults are still present assert len(config2.get("default_entries", [])) == 3