hosts/tests/test_config.py

292 lines
12 KiB
Python

"""
Tests for the configuration management module.
This module contains unit tests for the Config class,
validating configuration loading, saving, and management functionality.
"""
import pytest
import tempfile
import json
import os
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