""" Tests for MCP Configuration System """ import os import tempfile import pytest from app.services.mcp.config import ( MCPConfig, MCPServerConfig, TransportType, create_default_config, load_mcp_config, ) class TestTransportType: """Tests for TransportType enum.""" def test_transport_types(self): """Test that all transport types are defined.""" assert TransportType.HTTP == "http" assert TransportType.STDIO == "stdio" assert TransportType.SSE == "sse" def test_transport_type_from_string(self): """Test creating transport type from string.""" assert TransportType("http") == TransportType.HTTP assert TransportType("stdio") == TransportType.STDIO assert TransportType("sse") == TransportType.SSE class TestMCPServerConfig: """Tests for MCPServerConfig model.""" def test_minimal_config(self): """Test creating config with only required fields.""" config = MCPServerConfig(url="http://localhost:8000") assert config.url == "http://localhost:8000" assert config.transport == TransportType.HTTP assert config.timeout == 30 assert config.retry_attempts == 3 assert config.enabled is True def test_full_config(self): """Test creating config with all fields.""" config = MCPServerConfig( url="http://localhost:8000", transport=TransportType.SSE, timeout=60, retry_attempts=5, retry_delay=2.0, retry_max_delay=60.0, circuit_breaker_threshold=10, circuit_breaker_timeout=60.0, enabled=False, description="Test server", ) assert config.timeout == 60 assert config.transport == TransportType.SSE assert config.retry_attempts == 5 assert config.retry_delay == 2.0 assert config.retry_max_delay == 60.0 assert config.circuit_breaker_threshold == 10 assert config.circuit_breaker_timeout == 60.0 assert config.enabled is False assert config.description == "Test server" def test_env_var_expansion_simple(self): """Test simple environment variable expansion.""" os.environ["TEST_SERVER_URL"] = "http://test-server:9000" try: config = MCPServerConfig(url="${TEST_SERVER_URL}") assert config.url == "http://test-server:9000" finally: del os.environ["TEST_SERVER_URL"] def test_env_var_expansion_with_default(self): """Test environment variable expansion with default.""" # Ensure env var is not set os.environ.pop("NONEXISTENT_URL", None) config = MCPServerConfig(url="${NONEXISTENT_URL:-http://default:8000}") assert config.url == "http://default:8000" def test_env_var_expansion_override_default(self): """Test environment variable override of default.""" os.environ["TEST_OVERRIDE_URL"] = "http://override:9000" try: config = MCPServerConfig(url="${TEST_OVERRIDE_URL:-http://default:8000}") assert config.url == "http://override:9000" finally: del os.environ["TEST_OVERRIDE_URL"] def test_timeout_validation(self): """Test timeout validation bounds.""" # Valid bounds config = MCPServerConfig(url="http://localhost", timeout=1) assert config.timeout == 1 config = MCPServerConfig(url="http://localhost", timeout=600) assert config.timeout == 600 # Invalid bounds with pytest.raises(ValueError): MCPServerConfig(url="http://localhost", timeout=0) with pytest.raises(ValueError): MCPServerConfig(url="http://localhost", timeout=601) def test_retry_attempts_validation(self): """Test retry attempts validation bounds.""" config = MCPServerConfig(url="http://localhost", retry_attempts=0) assert config.retry_attempts == 0 config = MCPServerConfig(url="http://localhost", retry_attempts=10) assert config.retry_attempts == 10 with pytest.raises(ValueError): MCPServerConfig(url="http://localhost", retry_attempts=-1) with pytest.raises(ValueError): MCPServerConfig(url="http://localhost", retry_attempts=11) class TestMCPConfig: """Tests for MCPConfig model.""" def test_empty_config(self): """Test creating empty config.""" config = MCPConfig() assert config.mcp_servers == {} assert config.default_timeout == 30 assert config.default_retry_attempts == 3 assert config.connection_pool_size == 10 assert config.health_check_interval == 30 def test_config_with_servers(self): """Test creating config with servers.""" config = MCPConfig( mcp_servers={ "server-1": MCPServerConfig(url="http://server1:8000"), "server-2": MCPServerConfig(url="http://server2:8000"), } ) assert len(config.mcp_servers) == 2 assert "server-1" in config.mcp_servers assert "server-2" in config.mcp_servers def test_get_server(self): """Test getting server by name.""" config = MCPConfig( mcp_servers={ "server-1": MCPServerConfig(url="http://server1:8000"), } ) server = config.get_server("server-1") assert server is not None assert server.url == "http://server1:8000" missing = config.get_server("nonexistent") assert missing is None def test_get_enabled_servers(self): """Test getting only enabled servers.""" config = MCPConfig( mcp_servers={ "enabled-1": MCPServerConfig(url="http://e1:8000", enabled=True), "disabled-1": MCPServerConfig(url="http://d1:8000", enabled=False), "enabled-2": MCPServerConfig(url="http://e2:8000", enabled=True), } ) enabled = config.get_enabled_servers() assert len(enabled) == 2 assert "enabled-1" in enabled assert "enabled-2" in enabled assert "disabled-1" not in enabled def test_list_server_names(self): """Test listing server names.""" config = MCPConfig( mcp_servers={ "server-a": MCPServerConfig(url="http://a:8000"), "server-b": MCPServerConfig(url="http://b:8000"), } ) names = config.list_server_names() assert sorted(names) == ["server-a", "server-b"] def test_from_dict(self): """Test creating config from dictionary.""" data = { "mcp_servers": { "test-server": { "url": "http://test:8000", "timeout": 45, } }, "default_timeout": 60, } config = MCPConfig.from_dict(data) assert config.default_timeout == 60 assert config.mcp_servers["test-server"].timeout == 45 def test_from_yaml(self): """Test loading config from YAML file.""" yaml_content = """ mcp_servers: test-server: url: http://test:8000 timeout: 45 transport: http enabled: true default_timeout: 60 connection_pool_size: 20 """ with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: f.write(yaml_content) f.flush() try: config = MCPConfig.from_yaml(f.name) assert config.default_timeout == 60 assert config.connection_pool_size == 20 assert "test-server" in config.mcp_servers assert config.mcp_servers["test-server"].timeout == 45 finally: os.unlink(f.name) def test_from_yaml_file_not_found(self): """Test error when YAML file not found.""" with pytest.raises(FileNotFoundError): MCPConfig.from_yaml("/nonexistent/path/config.yaml") class TestLoadMCPConfig: """Tests for load_mcp_config function.""" def test_load_with_explicit_path(self): """Test loading config with explicit path.""" yaml_content = """ mcp_servers: explicit-server: url: http://explicit:8000 """ with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: f.write(yaml_content) f.flush() try: config = load_mcp_config(f.name) assert "explicit-server" in config.mcp_servers finally: os.unlink(f.name) def test_load_with_env_var(self): """Test loading config from environment variable path.""" yaml_content = """ mcp_servers: env-server: url: http://env:8000 """ with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: f.write(yaml_content) f.flush() os.environ["MCP_CONFIG_PATH"] = f.name try: config = load_mcp_config() assert "env-server" in config.mcp_servers finally: del os.environ["MCP_CONFIG_PATH"] os.unlink(f.name) def test_load_returns_empty_config_if_missing(self): """Test that missing file returns empty config.""" os.environ.pop("MCP_CONFIG_PATH", None) config = load_mcp_config("/nonexistent/path/config.yaml") assert config.mcp_servers == {} class TestCreateDefaultConfig: """Tests for create_default_config function.""" def test_creates_standard_servers(self): """Test that default config has standard servers.""" config = create_default_config() assert "llm-gateway" in config.mcp_servers assert "knowledge-base" in config.mcp_servers assert "git-ops" in config.mcp_servers assert "issues" in config.mcp_servers def test_servers_have_correct_defaults(self): """Test that servers have correct default values.""" config = create_default_config() llm = config.mcp_servers["llm-gateway"] assert llm.timeout == 60 # LLM has longer timeout assert llm.transport == TransportType.HTTP git = config.mcp_servers["git-ops"] assert git.timeout == 120 # Git ops has longest timeout def test_servers_are_enabled(self): """Test that all default servers are enabled.""" config = create_default_config() for name, server in config.mcp_servers.items(): assert server.enabled is True, f"Server {name} should be enabled"