Skip to main content
Glama

Codebase MCP Server

by Ravenight13
test_settings.py15.2 kB
""" Unit tests for settings configuration module. Tests cover: - Valid configuration loading from environment variables - Required field validation - Field type coercion and constraints - Database URL asyncpg driver validation - Custom validators for URLs and ranges - Singleton pattern behavior - Error messages for common misconfigurations Constitutional Compliance: - Principle V: Production quality with comprehensive test coverage - Principle VIII: Type safety validation (mypy --strict) """ from __future__ import annotations import os from typing import Any import pytest from pydantic import ValidationError from src.config.settings import LogLevel, Settings, get_settings class TestSettingsValidation: """Test settings validation and field constraints.""" def test_valid_minimal_configuration(self, monkeypatch: pytest.MonkeyPatch) -> None: """Test settings load with minimal valid configuration.""" # Set required environment variable monkeypatch.setenv( "DATABASE_URL", "postgresql+asyncpg://user:password@localhost:5432/test_db", ) settings = Settings() # Required field should be set assert str(settings.database_url).startswith("postgresql+asyncpg://") # Optional fields should have defaults assert settings.ollama_base_url == "http://localhost:11434" assert settings.ollama_embedding_model == "nomic-embed-text" assert settings.embedding_batch_size == 50 assert settings.max_concurrent_requests == 10 assert settings.db_pool_size == 20 assert settings.db_max_overflow == 10 assert settings.log_level == LogLevel.INFO assert settings.log_file == "/tmp/codebase-mcp.log" def test_valid_full_configuration(self, monkeypatch: pytest.MonkeyPatch) -> None: """Test settings load with all fields customized.""" # Set all environment variables env_vars = { "DATABASE_URL": "postgresql+asyncpg://custom:pass@db.example.com:5432/prod_db", "OLLAMA_BASE_URL": "http://ollama.example.com:11434", "OLLAMA_EMBEDDING_MODEL": "custom-model", "EMBEDDING_BATCH_SIZE": "100", "MAX_CONCURRENT_REQUESTS": "50", "DB_POOL_SIZE": "30", "DB_MAX_OVERFLOW": "15", "LOG_LEVEL": "DEBUG", "LOG_FILE": "/var/log/codebase-mcp.log", } for key, value in env_vars.items(): monkeypatch.setenv(key, value) settings = Settings() # Verify all values loaded correctly assert "postgresql+asyncpg://custom:pass@db.example.com" in str( settings.database_url ) assert str(settings.ollama_base_url) == "http://ollama.example.com:11434/" assert settings.ollama_embedding_model == "custom-model" assert settings.embedding_batch_size == 100 assert settings.max_concurrent_requests == 50 assert settings.db_pool_size == 30 assert settings.db_max_overflow == 15 assert settings.log_level == LogLevel.DEBUG assert settings.log_file == "/var/log/codebase-mcp.log" def test_missing_required_database_url(self, monkeypatch: pytest.MonkeyPatch) -> None: """Test validation fails when required DATABASE_URL is missing.""" # Ensure DATABASE_URL is not set monkeypatch.delenv("DATABASE_URL", raising=False) with pytest.raises(ValidationError) as exc_info: Settings() # Verify error message mentions the required field error = exc_info.value assert "database_url" in str(error).lower() assert "field required" in str(error).lower() def test_invalid_database_url_driver(self, monkeypatch: pytest.MonkeyPatch) -> None: """Test validation fails when DATABASE_URL uses wrong driver.""" # Use psycopg2 instead of asyncpg monkeypatch.setenv( "DATABASE_URL", "postgresql+psycopg2://user:password@localhost:5432/test_db", ) with pytest.raises(ValidationError) as exc_info: Settings() # Verify error message explains asyncpg requirement error_msg = str(exc_info.value) assert "asyncpg" in error_msg.lower() assert "driver" in error_msg.lower() def test_invalid_database_url_sync_driver( self, monkeypatch: pytest.MonkeyPatch ) -> None: """Test validation fails when DATABASE_URL uses sync PostgreSQL driver.""" # Use standard postgresql:// scheme (sync driver) monkeypatch.setenv( "DATABASE_URL", "postgresql://user:password@localhost:5432/test_db", ) with pytest.raises(ValidationError) as exc_info: Settings() # Verify error message explains asyncpg requirement error_msg = str(exc_info.value) assert "asyncpg" in error_msg.lower() def test_invalid_ollama_url(self, monkeypatch: pytest.MonkeyPatch) -> None: """Test validation fails for malformed Ollama URL.""" monkeypatch.setenv( "DATABASE_URL", "postgresql+asyncpg://user:password@localhost:5432/test_db", ) monkeypatch.setenv("OLLAMA_BASE_URL", "not-a-valid-url") with pytest.raises(ValidationError) as exc_info: Settings() # Verify error mentions URL validation error_msg = str(exc_info.value) assert "ollama_base_url" in error_msg.lower() def test_embedding_batch_size_constraints( self, monkeypatch: pytest.MonkeyPatch ) -> None: """Test embedding_batch_size respects min/max constraints.""" monkeypatch.setenv( "DATABASE_URL", "postgresql+asyncpg://user:password@localhost:5432/test_db", ) # Test minimum boundary (below range) monkeypatch.setenv("EMBEDDING_BATCH_SIZE", "0") with pytest.raises(ValidationError) as exc_info: Settings() assert "embedding_batch_size" in str(exc_info.value).lower() # Test maximum boundary (above range) monkeypatch.setenv("EMBEDDING_BATCH_SIZE", "1001") with pytest.raises(ValidationError) as exc_info: Settings() assert "embedding_batch_size" in str(exc_info.value).lower() # Test valid boundary values monkeypatch.setenv("EMBEDDING_BATCH_SIZE", "1") settings_min = Settings() assert settings_min.embedding_batch_size == 1 monkeypatch.setenv("EMBEDDING_BATCH_SIZE", "1000") settings_max = Settings() assert settings_max.embedding_batch_size == 1000 def test_max_concurrent_requests_constraints( self, monkeypatch: pytest.MonkeyPatch ) -> None: """Test max_concurrent_requests respects min/max constraints.""" monkeypatch.setenv( "DATABASE_URL", "postgresql+asyncpg://user:password@localhost:5432/test_db", ) # Test below minimum monkeypatch.setenv("MAX_CONCURRENT_REQUESTS", "0") with pytest.raises(ValidationError) as exc_info: Settings() assert "max_concurrent_requests" in str(exc_info.value).lower() # Test above maximum monkeypatch.setenv("MAX_CONCURRENT_REQUESTS", "101") with pytest.raises(ValidationError) as exc_info: Settings() assert "max_concurrent_requests" in str(exc_info.value).lower() # Test valid range monkeypatch.setenv("MAX_CONCURRENT_REQUESTS", "50") settings = Settings() assert settings.max_concurrent_requests == 50 def test_db_pool_size_constraints(self, monkeypatch: pytest.MonkeyPatch) -> None: """Test db_pool_size respects min/max constraints.""" monkeypatch.setenv( "DATABASE_URL", "postgresql+asyncpg://user:password@localhost:5432/test_db", ) # Test below minimum monkeypatch.setenv("DB_POOL_SIZE", "4") with pytest.raises(ValidationError) as exc_info: Settings() assert "db_pool_size" in str(exc_info.value).lower() # Test above maximum monkeypatch.setenv("DB_POOL_SIZE", "51") with pytest.raises(ValidationError) as exc_info: Settings() assert "db_pool_size" in str(exc_info.value).lower() # Test valid range monkeypatch.setenv("DB_POOL_SIZE", "25") settings = Settings() assert settings.db_pool_size == 25 def test_db_max_overflow_constraints(self, monkeypatch: pytest.MonkeyPatch) -> None: """Test db_max_overflow respects min/max constraints.""" monkeypatch.setenv( "DATABASE_URL", "postgresql+asyncpg://user:password@localhost:5432/test_db", ) # Test below minimum (negative not allowed) monkeypatch.setenv("DB_MAX_OVERFLOW", "-1") with pytest.raises(ValidationError) as exc_info: Settings() assert "db_max_overflow" in str(exc_info.value).lower() # Test above maximum monkeypatch.setenv("DB_MAX_OVERFLOW", "21") with pytest.raises(ValidationError) as exc_info: Settings() assert "db_max_overflow" in str(exc_info.value).lower() # Test valid range monkeypatch.setenv("DB_MAX_OVERFLOW", "5") settings = Settings() assert settings.db_max_overflow == 5 def test_log_level_enum_validation(self, monkeypatch: pytest.MonkeyPatch) -> None: """Test log_level accepts valid enum values and rejects invalid ones.""" monkeypatch.setenv( "DATABASE_URL", "postgresql+asyncpg://user:password@localhost:5432/test_db", ) # Test invalid log level monkeypatch.setenv("LOG_LEVEL", "INVALID") with pytest.raises(ValidationError) as exc_info: Settings() assert "log_level" in str(exc_info.value).lower() # Test all valid log levels for level in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]: monkeypatch.setenv("LOG_LEVEL", level) settings = Settings() assert settings.log_level == LogLevel[level] def test_case_insensitive_env_vars(self, monkeypatch: pytest.MonkeyPatch) -> None: """Test environment variables are case-insensitive.""" # Use lowercase env vars monkeypatch.setenv( "database_url", "postgresql+asyncpg://user:password@localhost:5432/test_db", ) monkeypatch.setenv("log_level", "debug") settings = Settings() # Should parse successfully despite lowercase assert str(settings.database_url).startswith("postgresql+asyncpg://") assert settings.log_level == LogLevel.DEBUG def test_type_coercion(self, monkeypatch: pytest.MonkeyPatch) -> None: """Test automatic type coercion from string env vars.""" monkeypatch.setenv( "DATABASE_URL", "postgresql+asyncpg://user:password@localhost:5432/test_db", ) monkeypatch.setenv("EMBEDDING_BATCH_SIZE", "75") # String representation monkeypatch.setenv("DB_POOL_SIZE", "15") settings = Settings() # Should coerce strings to integers assert isinstance(settings.embedding_batch_size, int) assert settings.embedding_batch_size == 75 assert isinstance(settings.db_pool_size, int) assert settings.db_pool_size == 15 def test_extra_env_vars_forbidden(self, monkeypatch: pytest.MonkeyPatch) -> None: """Test that extra/unknown environment variables cause validation error.""" monkeypatch.setenv( "DATABASE_URL", "postgresql+asyncpg://user:password@localhost:5432/test_db", ) monkeypatch.setenv("UNKNOWN_CONFIG_VALUE", "should_fail") # This will fail due to extra='forbid' in model_config with pytest.raises(ValidationError) as exc_info: Settings() error_msg = str(exc_info.value) assert "extra" in error_msg.lower() or "forbidden" in error_msg.lower() class TestSingletonPattern: """Test settings singleton instance behavior.""" def test_get_settings_returns_same_instance( self, monkeypatch: pytest.MonkeyPatch ) -> None: """Test get_settings() returns singleton instance.""" monkeypatch.setenv( "DATABASE_URL", "postgresql+asyncpg://user:password@localhost:5432/test_db", ) # Reset singleton for test isolation by setting the module-level variable import src.config.settings as settings_module # Access private variable for test isolation only settings_module._settings_instance = None # pyright: ignore[reportPrivateUsage] # Get settings multiple times settings1 = get_settings() settings2 = get_settings() # Should be same instance assert settings1 is settings2 def test_module_level_settings_export( self, monkeypatch: pytest.MonkeyPatch ) -> None: """Test module-level settings export is available.""" monkeypatch.setenv( "DATABASE_URL", "postgresql+asyncpg://user:password@localhost:5432/test_db", ) # Import module-level settings from src.config.settings import settings # Should be Settings instance assert isinstance(settings, Settings) assert str(settings.database_url).startswith("postgresql+asyncpg://") class TestPerformanceWarnings: """Test performance-related warnings and validations.""" def test_small_batch_size_warning( self, monkeypatch: pytest.MonkeyPatch ) -> None: """Test warning is issued for very small embedding batch sizes.""" monkeypatch.setenv( "DATABASE_URL", "postgresql+asyncpg://user:password@localhost:5432/test_db", ) monkeypatch.setenv("EMBEDDING_BATCH_SIZE", "5") # Should emit warning but still succeed with pytest.warns(UserWarning, match="EMBEDDING_BATCH_SIZE.*performance"): settings = Settings() assert settings.embedding_batch_size == 5 class TestEnvFileSupport: """Test .env file loading support.""" def test_env_file_configuration( self, monkeypatch: pytest.MonkeyPatch, tmp_path: Any ) -> None: """Test settings can load from .env file.""" # Create temporary .env file env_file = tmp_path / ".env" env_file.write_text( "DATABASE_URL=postgresql+asyncpg://test:test@localhost:5432/test_db\n" "LOG_LEVEL=WARNING\n" ) # Change to temp directory monkeypatch.chdir(tmp_path) settings = Settings() # Should load from .env file assert str(settings.database_url).startswith("postgresql+asyncpg://test:test@") assert settings.log_level == LogLevel.WARNING # ============================================================================ # Test Markers # ============================================================================ pytestmark = pytest.mark.unit

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/Ravenight13/codebase-mcp'

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