"""Tests for configuration module.""" import os from unittest.mock import patch import pytest from pydantic import ValidationError from llm_inference.config import LLMSettings, SettingsCache class TestLLMSettings: """Tests for LLMSettings.""" def test_required_fields(self) -> None: """Test that required fields must be provided.""" # Without required fields, should raise ValidationError with pytest.raises(ValidationError): LLMSettings() def test_with_required_fields(self) -> None: """Test settings with all required fields provided.""" settings = LLMSettings( default_backend="litellm", enable_vllm=False, enable_llamacpp=False, external_url="http://localhost:8100", ) assert settings.default_backend == "litellm" assert settings.enable_vllm is False assert settings.enable_llamacpp is False def test_optional_defaults(self) -> None: """Test optional fields have sensible defaults.""" settings = LLMSettings( default_backend="litellm", enable_vllm=False, enable_llamacpp=False, external_url="http://localhost:8100", ) assert settings.default_model == "qwen3.5" assert settings.host == "0.0.0.0" assert settings.port == 14011 assert settings.request_timeout == 120.0 assert settings.max_retries == 3 assert settings.rate_limit_rps == 10.0 def test_env_override(self) -> None: """Test environment variable overrides.""" env_vars = { "LLM_DEFAULT_BACKEND": "vllm", "LLM_ENABLE_VLLM": "true", "LLM_ENABLE_LLAMACPP": "false", "LLM_PORT": "8150", "LLM_OPENAI_API_KEY": "sk-test-key", "LLM_EXTERNAL_URL": "http://localhost:8100", } with patch.dict(os.environ, env_vars, clear=False): settings = LLMSettings() assert settings.default_backend == "vllm" assert settings.port == 8150 assert settings.enable_vllm is True assert settings.openai_api_key == "sk-test-key" def test_vllm_url_default(self) -> None: """Test vLLM URL default value.""" settings = LLMSettings( default_backend="litellm", enable_vllm=False, enable_llamacpp=False, external_url="http://localhost:8100", ) assert settings.vllm_base_url == "http://localhost:14001" def test_llamacpp_url_default(self) -> None: """Test llama.cpp URL default value.""" settings = LLMSettings( default_backend="litellm", enable_vllm=False, enable_llamacpp=False, external_url="http://localhost:8100", ) assert settings.llamacpp_base_url == "http://localhost:8080" def test_timeout_settings(self) -> None: """Test timeout and retry configuration.""" settings = LLMSettings( default_backend="litellm", enable_vllm=False, enable_llamacpp=False, external_url="http://localhost:8100", request_timeout=60.0, connect_timeout=5.0, max_retries=5, ) assert settings.request_timeout == 60.0 assert settings.connect_timeout == 5.0 assert settings.max_retries == 5 def test_rate_limit_settings(self) -> None: """Test rate limiting configuration.""" settings = LLMSettings( default_backend="litellm", enable_vllm=False, enable_llamacpp=False, external_url="http://localhost:8100", rate_limit_rps=20.0, rate_limit_burst=50, ) assert settings.rate_limit_rps == 20.0 assert settings.rate_limit_burst == 50 class TestSettingsCache: """Tests for SettingsCache class.""" def test_set_and_get(self) -> None: """Test setting and getting cached settings.""" SettingsCache.clear() settings = LLMSettings( default_backend="litellm", enable_vllm=False, enable_llamacpp=False, external_url="http://localhost:8100", ) SettingsCache.set(settings) retrieved = SettingsCache.get() assert retrieved is settings def test_get_without_set_creates_instance(self) -> None: """Test that get() creates instance from environment if not set.""" SettingsCache.clear() env_vars = { "LLM_DEFAULT_BACKEND": "litellm", "LLM_ENABLE_VLLM": "false", "LLM_ENABLE_LLAMACPP": "false", "LLM_EXTERNAL_URL": "http://localhost:8100", } with patch.dict(os.environ, env_vars, clear=False): settings = SettingsCache.get() assert isinstance(settings, LLMSettings) assert settings.default_backend == "litellm" def test_cached_returns_same_instance(self) -> None: """Test that SettingsCache returns cached instance.""" SettingsCache.clear() settings = LLMSettings( default_backend="litellm", enable_vllm=False, enable_llamacpp=False, external_url="http://localhost:8100", ) SettingsCache.set(settings) settings1 = SettingsCache.get() settings2 = SettingsCache.get() assert settings1 is settings2 def test_clear_removes_cached_instance(self) -> None: """Test that clear() removes the cached instance.""" settings = LLMSettings( default_backend="litellm", enable_vllm=False, enable_llamacpp=False, external_url="http://localhost:8100", ) SettingsCache.set(settings) SettingsCache.clear() # After clear, _instance should be None assert SettingsCache._instance is None