165 lines
6.0 KiB
Python
165 lines
6.0 KiB
Python
"""
|
|
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() |