Skip to main content
Glama
test_config.py19.7 kB
"""Tests for configuration management.""" import os import tempfile from unittest.mock import MagicMock, patch from src.homelab_mcp.config import ( DatabaseConfig, MCPConfig, create_database_from_config, get_config, print_config_info, ) class TestDatabaseConfig: """Test DatabaseConfig class.""" def setup_method(self): """Set up test method.""" # Store original environment to restore later self.original_env = dict(os.environ) def teardown_method(self): """Clean up test method.""" # Restore original environment os.environ.clear() os.environ.update(self.original_env) def test_default_sqlite_config(self): """Test default SQLite configuration.""" # Clear relevant environment variables for key in ["DATABASE_TYPE", "SQLITE_PATH"]: os.environ.pop(key, None) config = DatabaseConfig() assert config.db_type == "sqlite" assert config.sqlite_path is not None assert ".mcp" in config.sqlite_path assert config.sqlite_path.endswith("sitemap.db") def test_custom_sqlite_path(self): """Test custom SQLite path from environment.""" custom_path = "/custom/path/test.db" os.environ["SQLITE_PATH"] = custom_path config = DatabaseConfig() assert config.db_type == "sqlite" assert config.sqlite_path == custom_path def test_postgresql_config_from_env(self): """Test PostgreSQL configuration from environment variables.""" os.environ["DATABASE_TYPE"] = "postgresql" os.environ["POSTGRES_HOST"] = "test-host" os.environ["POSTGRES_PORT"] = "5433" os.environ["POSTGRES_DB"] = "test-db" os.environ["POSTGRES_USER"] = "test-user" os.environ["POSTGRES_PASSWORD"] = "test-pass" config = DatabaseConfig() assert config.db_type == "postgresql" assert config.postgres_config["host"] == "test-host" assert config.postgres_config["port"] == 5433 assert config.postgres_config["database"] == "test-db" assert config.postgres_config["user"] == "test-user" assert config.postgres_config["password"] == "test-pass" def test_postgresql_default_values(self): """Test PostgreSQL configuration with default values.""" os.environ["DATABASE_TYPE"] = "postgresql" # Don't set specific postgres env vars to test defaults for key in [ "POSTGRES_HOST", "POSTGRES_PORT", "POSTGRES_DB", "POSTGRES_USER", "POSTGRES_PASSWORD", ]: os.environ.pop(key, None) config = DatabaseConfig() assert config.postgres_config["host"] == "localhost" assert config.postgres_config["port"] == 5432 assert config.postgres_config["database"] == "homelab_mcp" assert config.postgres_config["user"] == "postgres" assert config.postgres_config["password"] == "password" def test_get_database_params_sqlite(self): """Test getting database parameters for SQLite.""" os.environ["DATABASE_TYPE"] = "sqlite" os.environ["SQLITE_PATH"] = "/test/path.db" config = DatabaseConfig() params = config.get_database_params() assert params["db_type"] == "sqlite" assert params["db_path"] == "/test/path.db" assert "connection_params" not in params def test_get_database_params_postgresql(self): """Test getting database parameters for PostgreSQL.""" os.environ["DATABASE_TYPE"] = "postgresql" os.environ["POSTGRES_HOST"] = "pg-host" config = DatabaseConfig() params = config.get_database_params() assert params["db_type"] == "postgresql" assert "connection_params" in params assert params["connection_params"]["host"] == "pg-host" assert "db_path" not in params def test_is_postgresql_configured_true(self): """Test PostgreSQL configuration validation when properly configured.""" os.environ["DATABASE_TYPE"] = "postgresql" os.environ["POSTGRES_HOST"] = "localhost" os.environ["POSTGRES_DB"] = "test" os.environ["POSTGRES_USER"] = "user" os.environ["POSTGRES_PASSWORD"] = "pass" config = DatabaseConfig() assert config.is_postgresql_configured() is True def test_is_postgresql_configured_false_missing_vars(self): """Test PostgreSQL configuration validation with missing variables.""" os.environ["DATABASE_TYPE"] = "postgresql" os.environ["POSTGRES_HOST"] = "localhost" # Missing other required vars for key in ["POSTGRES_DB", "POSTGRES_USER", "POSTGRES_PASSWORD"]: os.environ.pop(key, None) config = DatabaseConfig() assert config.is_postgresql_configured() is False def test_is_postgresql_configured_false_sqlite(self): """Test PostgreSQL configuration validation when using SQLite.""" os.environ["DATABASE_TYPE"] = "sqlite" config = DatabaseConfig() assert config.is_postgresql_configured() is False @patch("src.homelab_mcp.config.Path.home") def test_sqlite_path_home_directory_failure(self, mock_home): """Test SQLite path fallback when home directory access fails.""" # Mock Path.home() to raise RuntimeError mock_home.side_effect = RuntimeError("Home directory not accessible") # Clear SQLITE_PATH to force home directory usage os.environ.pop("SQLITE_PATH", None) with patch("src.homelab_mcp.config.Path.cwd") as mock_cwd: # Create proper mock Path objects mock_current_dir = MagicMock() mock_mcp_dir = MagicMock() mock_db_path = MagicMock() # Set up path operations mock_current_dir.__truediv__.return_value = mock_mcp_dir mock_mcp_dir.__truediv__.return_value = mock_db_path mock_mcp_dir.mkdir = MagicMock() # Return string when converting to str mock_db_path.__str__ = MagicMock( return_value="/current/dir/.mcp/sitemap.db" ) mock_cwd.return_value = mock_current_dir config = DatabaseConfig() # Should fallback to current directory mock_cwd.assert_called_once() mock_mcp_dir.mkdir.assert_called_once_with(exist_ok=True) assert config.sqlite_path == "/current/dir/.mcp/sitemap.db" class TestMCPConfig: """Test MCPConfig class.""" def setup_method(self): """Set up test method.""" # Store original environment to restore later self.original_env = dict(os.environ) def teardown_method(self): """Clean up test method.""" # Restore original environment os.environ.clear() os.environ.update(self.original_env) def test_default_config(self): """Test default MCP configuration.""" # Clear all relevant environment variables env_vars = [ "MCP_DEBUG", "MCP_LOG_LEVEL", "SSH_TIMEOUT", "SSH_RETRIES", "DISCOVERY_BATCH_SIZE", "DISCOVERY_TIMEOUT", "ENABLE_POSTGRESQL", "ENABLE_RESOURCE_POOLS", ] for var in env_vars: os.environ.pop(var, None) config = MCPConfig() assert config.debug is False assert config.log_level == "INFO" assert config.ssh_timeout == 10 assert config.ssh_retries == 3 assert config.discovery_batch_size == 10 assert config.discovery_timeout == 300 assert config.enable_postgresql is False assert config.enable_resource_pools is False assert isinstance(config.database, DatabaseConfig) def test_config_from_environment(self): """Test MCP configuration from environment variables.""" os.environ["MCP_DEBUG"] = "true" os.environ["MCP_LOG_LEVEL"] = "DEBUG" os.environ["SSH_TIMEOUT"] = "30" os.environ["SSH_RETRIES"] = "5" os.environ["DISCOVERY_BATCH_SIZE"] = "20" os.environ["DISCOVERY_TIMEOUT"] = "600" os.environ["ENABLE_POSTGRESQL"] = "true" os.environ["ENABLE_RESOURCE_POOLS"] = "true" config = MCPConfig() assert config.debug is True assert config.log_level == "DEBUG" assert config.ssh_timeout == 30 assert config.ssh_retries == 5 assert config.discovery_batch_size == 20 assert config.discovery_timeout == 600 assert config.enable_postgresql is True assert config.enable_resource_pools is True def test_boolean_parsing_variations(self): """Test boolean parsing with different string variations.""" # Test debug flag with different values test_cases = [ ("true", True), ("True", True), ("TRUE", True), ("false", False), ("False", False), ("FALSE", False), ("1", False), # Only 'true' should be True ("yes", False), ("", False), ] for env_value, expected in test_cases: os.environ["MCP_DEBUG"] = env_value config = MCPConfig() expected_msg = ( f"Failed for '{env_value}', expected {expected}, got {config.debug}" ) assert config.debug is expected, expected_msg def test_validate_success(self): """Test successful configuration validation.""" os.environ["DATABASE_TYPE"] = "sqlite" os.environ["SSH_TIMEOUT"] = "10" os.environ["DISCOVERY_TIMEOUT"] = "300" config = MCPConfig() errors = config.validate() assert len(errors) == 0 def test_validate_postgresql_not_configured(self): """Test validation error for unconfigured PostgreSQL.""" os.environ["DATABASE_TYPE"] = "postgresql" # Don't set PostgreSQL environment variables for key in [ "POSTGRES_HOST", "POSTGRES_DB", "POSTGRES_USER", "POSTGRES_PASSWORD", ]: os.environ.pop(key, None) config = MCPConfig() errors = config.validate() assert len(errors) >= 1 assert any( "PostgreSQL is selected but not properly configured" in error for error in errors ) def test_validate_invalid_timeouts(self): """Test validation errors for invalid timeout values.""" os.environ["SSH_TIMEOUT"] = "0" os.environ["DISCOVERY_TIMEOUT"] = "-10" config = MCPConfig() errors = config.validate() assert len(errors) >= 2 assert any("SSH_TIMEOUT must be greater than 0" in error for error in errors) assert any( "DISCOVERY_TIMEOUT must be greater than 0" in error for error in errors ) def test_validate_postgresql_missing_psycopg2(self): """Test validation error when psycopg2 is not installed.""" os.environ["DATABASE_TYPE"] = "postgresql" os.environ["POSTGRES_HOST"] = "localhost" os.environ["POSTGRES_DB"] = "test" os.environ["POSTGRES_USER"] = "user" os.environ["POSTGRES_PASSWORD"] = "pass" config = MCPConfig() # Patch the import inside validate method with patch( "builtins.__import__", side_effect=ImportError("No module named 'psycopg2'") ): errors = config.validate() assert len(errors) >= 1 assert any("psycopg2 is not installed" in error for error in errors) class TestConfigHelperFunctions: """Test configuration helper functions.""" def setup_method(self): """Set up test method.""" self.original_env = dict(os.environ) def teardown_method(self): """Clean up test method.""" os.environ.clear() os.environ.update(self.original_env) def test_get_config(self): """Test get_config function.""" config = get_config() assert isinstance(config, MCPConfig) assert isinstance(config.database, DatabaseConfig) @patch("src.homelab_mcp.sitemap.NetworkSiteMap") def test_create_database_from_config_default(self, mock_sitemap): """Test creating database from default config.""" mock_sitemap_instance = MagicMock() mock_sitemap.return_value = mock_sitemap_instance result = create_database_from_config() assert result == mock_sitemap_instance mock_sitemap.assert_called_once() # Check that database parameters were passed call_args = mock_sitemap.call_args assert "db_type" in call_args.kwargs or len(call_args.args) > 0 @patch("src.homelab_mcp.sitemap.NetworkSiteMap") def test_create_database_from_config_custom(self, mock_sitemap): """Test creating database from custom config.""" mock_sitemap_instance = MagicMock() mock_sitemap.return_value = mock_sitemap_instance # Create custom config os.environ["DATABASE_TYPE"] = "postgresql" os.environ["POSTGRES_HOST"] = "custom-host" custom_config = MCPConfig() result = create_database_from_config(custom_config) assert result == mock_sitemap_instance mock_sitemap.assert_called_once() # Verify PostgreSQL parameters were used call_args = mock_sitemap.call_args if call_args.kwargs: assert call_args.kwargs.get("db_type") == "postgresql" @patch("builtins.print") def test_print_config_info_sqlite(self, mock_print): """Test printing SQLite configuration info.""" os.environ["DATABASE_TYPE"] = "sqlite" os.environ["SQLITE_PATH"] = "/test/path.db" os.environ["MCP_DEBUG"] = "true" print_config_info() # Check that print was called with config information print_calls = [call[0][0] for call in mock_print.call_args_list] printed_text = "\n".join(print_calls) assert "Homelab MCP Configuration" in printed_text assert "Database Type: sqlite" in printed_text assert "SQLite Path: /test/path.db" in printed_text assert "Debug Mode: True" in printed_text @patch("builtins.print") def test_print_config_info_postgresql(self, mock_print): """Test printing PostgreSQL configuration info.""" os.environ["DATABASE_TYPE"] = "postgresql" os.environ["POSTGRES_HOST"] = "pg-host" os.environ["POSTGRES_PORT"] = "5433" os.environ["POSTGRES_DB"] = "pg-db" os.environ["POSTGRES_USER"] = "pg-user" os.environ["POSTGRES_PASSWORD"] = "pg-pass" print_config_info() print_calls = [call[0][0] for call in mock_print.call_args_list] printed_text = "\n".join(print_calls) assert "Database Type: postgresql" in printed_text assert "PostgreSQL Host: pg-host:5433" in printed_text assert "PostgreSQL Database: pg-db" in printed_text assert "PostgreSQL User: pg-user" in printed_text assert "[CONFIGURED]" in printed_text # Password should be masked assert "pg-pass" not in printed_text # Password should not be visible @patch("builtins.print") def test_print_config_info_with_errors(self, mock_print): """Test printing configuration info with validation errors.""" os.environ["DATABASE_TYPE"] = "postgresql" os.environ["SSH_TIMEOUT"] = "0" # Invalid value # Don't set PostgreSQL vars to cause validation error for key in [ "POSTGRES_HOST", "POSTGRES_DB", "POSTGRES_USER", "POSTGRES_PASSWORD", ]: os.environ.pop(key, None) print_config_info() print_calls = [call[0][0] for call in mock_print.call_args_list] printed_text = "\n".join(print_calls) assert "Configuration Errors" in printed_text assert "ERROR:" in printed_text assert "PostgreSQL is selected but not properly configured" in printed_text @patch("builtins.print") def test_print_config_info_valid_config(self, mock_print): """Test printing valid configuration info.""" os.environ["DATABASE_TYPE"] = "sqlite" os.environ["SSH_TIMEOUT"] = "10" os.environ["DISCOVERY_TIMEOUT"] = "300" print_config_info() print_calls = [call[0][0] for call in mock_print.call_args_list] printed_text = "\n".join(print_calls) assert "✓ Configuration is valid" in printed_text class TestConfigIntegration: """Integration tests for configuration system.""" def setup_method(self): """Set up test method.""" self.original_env = dict(os.environ) # Create a temporary directory for testing self.temp_dir = tempfile.mkdtemp() def teardown_method(self): """Clean up test method.""" os.environ.clear() os.environ.update(self.original_env) import shutil shutil.rmtree(self.temp_dir, ignore_errors=True) def test_full_sqlite_config_workflow(self): """Test complete SQLite configuration workflow.""" # Set up SQLite configuration sqlite_path = os.path.join(self.temp_dir, "test.db") os.environ["DATABASE_TYPE"] = "sqlite" os.environ["SQLITE_PATH"] = sqlite_path os.environ["MCP_DEBUG"] = "true" os.environ["SSH_TIMEOUT"] = "15" # Get configuration config = get_config() # Validate configuration structure assert isinstance(config, MCPConfig) assert config.database.db_type == "sqlite" assert config.database.sqlite_path == sqlite_path assert config.debug is True assert config.ssh_timeout == 15 # Validate database parameters db_params = config.database.get_database_params() assert db_params["db_type"] == "sqlite" assert db_params["db_path"] == sqlite_path # Validate configuration errors = config.validate() assert len(errors) == 0 # Test that PostgreSQL is not configured assert not config.database.is_postgresql_configured() def test_full_postgresql_config_workflow(self): """Test complete PostgreSQL configuration workflow.""" # Set up PostgreSQL configuration os.environ["DATABASE_TYPE"] = "postgresql" os.environ["POSTGRES_HOST"] = "test-host" os.environ["POSTGRES_PORT"] = "5433" os.environ["POSTGRES_DB"] = "test-db" os.environ["POSTGRES_USER"] = "test-user" os.environ["POSTGRES_PASSWORD"] = "test-pass" os.environ["ENABLE_POSTGRESQL"] = "true" # Get configuration config = get_config() # Validate configuration structure assert isinstance(config, MCPConfig) assert config.database.db_type == "postgresql" assert config.enable_postgresql is True # Validate database parameters db_params = config.database.get_database_params() assert db_params["db_type"] == "postgresql" assert db_params["connection_params"]["host"] == "test-host" assert db_params["connection_params"]["port"] == 5433 assert db_params["connection_params"]["database"] == "test-db" assert db_params["connection_params"]["user"] == "test-user" assert db_params["connection_params"]["password"] == "test-pass" # Test that PostgreSQL is configured assert config.database.is_postgresql_configured()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/washyu/mcp_python_server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server