""" Unit tests for configuration module. Tests specific examples and edge cases for configuration loading and validation. """ import pytest import tempfile import os from pathlib import Path from pydantic import ValidationError from src.core.config import Settings, LLMConfig, AgentConfig class TestSettings: """Test Settings class.""" def test_settings_with_required_fields(self): """Test that Settings can be created with required fields.""" settings = Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4" ) assert settings.LLM_PROVIDER == "openai" assert settings.LLM_MODEL == "gpt-4" assert settings.APP_NAME == "Agent Microservice" # default value assert settings.DEBUG is False # default value assert settings.CLASSIFICATION_LLM_MODEL == "bo_gptoss20b_dev" assert settings.CLASSIFICATION_LLM_TEMPERATURE == 0.3 def test_settings_classification_custom_values(self): """Test that Settings can be created with custom classification LLM values.""" settings = Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4", CLASSIFICATION_LLM_MODEL="custom_model", CLASSIFICATION_LLM_TEMPERATURE=0.5, CLASSIFICATION_LLM_MAX_TOKENS=512, CLASSIFICATION_LLM_TOP_P=0.9 ) assert settings.CLASSIFICATION_LLM_MODEL == "custom_model" assert settings.CLASSIFICATION_LLM_TEMPERATURE == 0.5 assert settings.CLASSIFICATION_LLM_MAX_TOKENS == 512 assert settings.CLASSIFICATION_LLM_TOP_P == 0.9 def test_settings_missing_required_field_llm_provider(self): """Test that missing LLM_PROVIDER raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: Settings(LLM_MODEL="gpt-4") errors = exc_info.value.errors() assert any(error['loc'] == ('LLM_PROVIDER',) for error in errors) def test_settings_missing_required_field_llm_model(self): """Test that missing LLM_MODEL raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: Settings(LLM_PROVIDER="openai") errors = exc_info.value.errors() assert any(error['loc'] == ('LLM_MODEL',) for error in errors) def test_settings_invalid_llm_provider(self): """Test that invalid LLM_PROVIDER raises ValidationError.""" with pytest.raises(ValidationError) as exc_info: Settings( LLM_PROVIDER="invalid_provider", LLM_MODEL="gpt-4" ) errors = exc_info.value.errors() assert any("LLM_PROVIDER" in str(error) for error in errors) def test_settings_valid_providers(self): """Test that valid providers are accepted.""" for provider in ["openai", "anthropic"]: settings = Settings( LLM_PROVIDER=provider, LLM_MODEL="test-model" ) assert settings.LLM_PROVIDER == provider def test_settings_temperature_validation(self): """Test temperature validation.""" # Valid temperature settings = Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4", LLM_TEMPERATURE=1.5 ) assert settings.LLM_TEMPERATURE == 1.5 # Temperature too low with pytest.raises(ValidationError): Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4", LLM_TEMPERATURE=-0.1 ) # Temperature too high with pytest.raises(ValidationError): Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4", LLM_TEMPERATURE=2.1 ) def test_settings_from_yaml_valid(self): """Test loading settings from valid YAML file.""" yaml_content = """ APP_NAME: "Test Agent" VERSION: "2.0.0" DEBUG: true LLM_PROVIDER: "openai" LLM_MODEL: "gpt-3.5-turbo" LLM_TEMPERATURE: 0.5 HOST: "127.0.0.1" PORT: 9000 LOG_LEVEL: "DEBUG" """ with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f: f.write(yaml_content) temp_path = f.name try: settings = Settings.from_yaml(temp_path) assert settings.APP_NAME == "Test Agent" assert settings.VERSION == "2.0.0" assert settings.DEBUG is True assert settings.LLM_PROVIDER == "openai" assert settings.LLM_MODEL == "gpt-3.5-turbo" assert settings.LLM_TEMPERATURE == 0.5 assert settings.HOST == "127.0.0.1" assert settings.PORT == 9000 assert settings.LOG_LEVEL == "DEBUG" finally: os.unlink(temp_path) def test_settings_from_yaml_missing_required(self): """Test that loading YAML without required fields raises error.""" yaml_content = """ APP_NAME: "Test Agent" VERSION: "2.0.0" """ with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f: f.write(yaml_content) temp_path = f.name try: with pytest.raises(ValidationError): Settings.from_yaml(temp_path) finally: os.unlink(temp_path) def test_settings_from_yaml_file_not_found(self): """Test that loading from non-existent file raises FileNotFoundError.""" with pytest.raises(FileNotFoundError): Settings.from_yaml("/nonexistent/path/config.yaml") def test_settings_from_yaml_empty_file(self): """Test loading from empty YAML file.""" with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f: f.write("") temp_path = f.name try: with pytest.raises(ValidationError): Settings.from_yaml(temp_path) finally: os.unlink(temp_path) def test_settings_log_level_validation(self): """Test log level validation.""" # Valid log levels for level in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]: settings = Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4", LOG_LEVEL=level ) assert settings.LOG_LEVEL == level # Case insensitive settings = Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4", LOG_LEVEL="debug" ) assert settings.LOG_LEVEL == "DEBUG" # Invalid log level with pytest.raises(ValidationError): Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4", LOG_LEVEL="INVALID" ) def test_settings_log_format_validation(self): """Test log format validation.""" # Valid formats for fmt in ["json", "text"]: settings = Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4", LOG_FORMAT=fmt ) assert settings.LOG_FORMAT == fmt # Case insensitive settings = Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4", LOG_FORMAT="JSON" ) assert settings.LOG_FORMAT == "json" # Invalid format with pytest.raises(ValidationError): Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4", LOG_FORMAT="xml" ) def test_settings_port_validation(self): """Test port number validation.""" # Valid port settings = Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4", PORT=8080 ) assert settings.PORT == 8080 # Port too low with pytest.raises(ValidationError): Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4", PORT=0 ) # Port too high with pytest.raises(ValidationError): Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4", PORT=65536 ) class TestLLMConfig: """Test LLMConfig model.""" def test_llm_config_valid(self): """Test creating valid LLMConfig.""" config = LLMConfig( provider="openai", model="gpt-4", temperature=0.8, max_tokens=1000 ) assert config.provider == "openai" assert config.model == "gpt-4" assert config.temperature == 0.8 assert config.max_tokens == 1000 def test_llm_config_default_temperature(self): """Test default temperature value.""" config = LLMConfig( provider="openai", model="gpt-4" ) assert config.temperature == 0.7 def test_llm_config_invalid_provider(self): """Test that invalid provider raises error.""" with pytest.raises(ValidationError): LLMConfig( provider="invalid", model="gpt-4" ) def test_llm_config_temperature_bounds(self): """Test temperature boundary validation.""" # Valid boundaries LLMConfig(provider="openai", model="gpt-4", temperature=0.0) LLMConfig(provider="openai", model="gpt-4", temperature=2.0) # Invalid boundaries with pytest.raises(ValidationError): LLMConfig(provider="openai", model="gpt-4", temperature=-0.1) with pytest.raises(ValidationError): LLMConfig(provider="openai", model="gpt-4", temperature=2.1) def test_llm_config_max_tokens_validation(self): """Test max_tokens validation.""" # Valid max_tokens config = LLMConfig( provider="openai", model="gpt-4", max_tokens=100 ) assert config.max_tokens == 100 # None is valid config = LLMConfig( provider="openai", model="gpt-4", max_tokens=None ) assert config.max_tokens is None # Zero or negative is invalid with pytest.raises(ValidationError): LLMConfig( provider="openai", model="gpt-4", max_tokens=0 ) class TestAgentConfig: """Test AgentConfig model.""" def test_agent_config_valid(self): """Test creating valid AgentConfig.""" config = AgentConfig( name="Test Agent", description="A test agent", system_prompt="You are a test assistant", tools=["tool1", "tool2"], max_iterations=5 ) assert config.name == "Test Agent" assert config.description == "A test agent" assert config.system_prompt == "You are a test assistant" assert config.tools == ["tool1", "tool2"] assert config.max_iterations == 5 def test_agent_config_default_tools(self): """Test default empty tools list.""" config = AgentConfig( name="Test Agent", description="A test agent", system_prompt="You are a test assistant" ) assert config.tools == [] def test_agent_config_default_max_iterations(self): """Test default max_iterations value.""" config = AgentConfig( name="Test Agent", description="A test agent", system_prompt="You are a test assistant" ) assert config.max_iterations == 10 def test_agent_config_max_iterations_validation(self): """Test max_iterations must be positive.""" # Valid AgentConfig( name="Test Agent", description="A test agent", system_prompt="You are a test assistant", max_iterations=1 ) # Invalid with pytest.raises(ValidationError): AgentConfig( name="Test Agent", description="A test agent", system_prompt="You are a test assistant", max_iterations=0 ) class TestSettingsConversion: """Test Settings conversion methods.""" def test_to_llm_config(self): """Test converting Settings to LLMConfig.""" settings = Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4", LLM_TEMPERATURE=0.9, LLM_MAX_TOKENS=2000 ) llm_config = settings.to_llm_config() assert isinstance(llm_config, LLMConfig) assert llm_config.provider == "openai" assert llm_config.model == "gpt-4" assert llm_config.temperature == 0.9 assert llm_config.max_tokens == 2000 def test_to_agent_config_with_all_fields(self): """Test converting Settings to AgentConfig when all fields present.""" settings = Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4", AGENT_NAME="My Agent", AGENT_DESCRIPTION="My agent description", AGENT_SYSTEM_PROMPT="You are my agent", AGENT_MAX_ITERATIONS=15 ) agent_config = settings.to_agent_config() assert agent_config is not None assert isinstance(agent_config, AgentConfig) assert agent_config.name == "My Agent" assert agent_config.description == "My agent description" assert agent_config.system_prompt == "You are my agent" assert agent_config.max_iterations == 15 def test_to_agent_config_missing_fields(self): """Test that to_agent_config returns None when fields missing.""" settings = Settings( LLM_PROVIDER="openai", LLM_MODEL="gpt-4" ) agent_config = settings.to_agent_config() assert agent_config is None