""" Unit tests for the ConfigManager module. """ import pytest import tempfile import os import yaml from pathlib import Path from unittest.mock import patch, mock_open from src.config_manager import ConfigManager class TestConfigManager: """Test cases for ConfigManager class.""" def test_init_with_default_path(self): """Test ConfigManager initialization with default path.""" config_manager = ConfigManager() assert config_manager.config_path == Path("config/config.yaml") assert isinstance(config_manager.config, dict) assert isinstance(config_manager.default_config, dict) def test_init_with_custom_path(self): """Test ConfigManager initialization with custom path.""" custom_path = "custom/config.yaml" config_manager = ConfigManager(custom_path) assert config_manager.config_path == Path(custom_path) def test_get_default_config(self): """Test default configuration structure.""" config_manager = ConfigManager() default_config = config_manager._get_default_config() # Check required sections assert "scraper" in default_config assert "sources" in default_config assert "output" in default_config assert "database" in default_config assert "analysis" in default_config # Check some default values assert default_config["scraper"]["delay_between_requests"] == 1.0 assert default_config["scraper"]["timeout"] == 30 assert default_config["scraper"]["headless"] is True assert isinstance(default_config["sources"], list) assert len(default_config["sources"]) > 0 @patch('builtins.open', new_callable=mock_open, read_data="scraper:\n timeout: 60") @patch('pathlib.Path.exists') def test_load_config_existing_file(self, mock_exists, mock_file): """Test loading configuration from existing file.""" mock_exists.return_value = True config_manager = ConfigManager() config = config_manager.load_config() mock_file.assert_called_once() assert config["scraper"]["timeout"] == 60 @patch('builtins.open', new_callable=mock_open) @patch('pathlib.Path.exists') def test_load_config_create_default(self, mock_exists, mock_file): """Test creating default configuration when file doesn't exist.""" mock_exists.return_value = False config_manager = ConfigManager() config = config_manager.load_config() # Verify file was created mock_file.assert_called_once() # Verify config is default assert config == config_manager.default_config @patch('builtins.open', new_callable=mock_open) def test_save_config(self, mock_file): """Test saving configuration to file.""" config_manager = ConfigManager() config_manager.config = {"test": "value"} config_manager.save_config() mock_file.assert_called_once() # Verify yaml.dump was called with correct arguments with patch('yaml.dump') as mock_dump: config_manager.save_config() mock_dump.assert_called_once() def test_validate_and_merge_config(self): """Test configuration validation and merging.""" config_manager = ConfigManager() # Test with partial config partial_config = { "scraper": { "timeout": 60 } } config_manager.config = partial_config merged = config_manager._validate_and_merge_config() # Should have all sections assert "sources" in merged assert "output" in merged # Should have updated value assert merged["scraper"]["timeout"] == 60 # Should have default values for missing keys assert merged["scraper"]["delay_between_requests"] == 1.0 def test_validate_and_merge_config_missing_required(self): """Test validation fails when required sections are missing.""" config_manager = ConfigManager() config_manager.config = {"invalid": "config"} with pytest.raises(ValueError, match="Missing required configuration section"): config_manager._validate_and_merge_config() def test_validate_and_merge_config_no_sources(self): """Test validation fails when no sources are configured.""" config_manager = ConfigManager() config_manager.config = { "scraper": {}, "sources": [], "output": {} } with pytest.raises(ValueError, match="At least one data source must be configured"): config_manager._validate_and_merge_config() def test_get_with_dot_notation(self): """Test getting configuration values with dot notation.""" config_manager = ConfigManager() config_manager.config = { "scraper": { "timeout": 60, "nested": { "value": "test" } } } assert config_manager.get("scraper.timeout") == 60 assert config_manager.get("scraper.nested.value") == "test" assert config_manager.get("nonexistent", "default") == "default" def test_set_with_dot_notation(self): """Test setting configuration values with dot notation.""" config_manager = ConfigManager() config_manager.config = {"scraper": {}} config_manager.set("scraper.timeout", 60) config_manager.set("new.nested.value", "test") assert config_manager.config["scraper"]["timeout"] == 60 assert config_manager.config["new"]["nested"]["value"] == "test" @patch.object(ConfigManager, 'load_config') def test_reload(self, mock_load): """Test reloading configuration.""" config_manager = ConfigManager() config_manager.reload() mock_load.assert_called_once()