forked from cardosofelipe/fast-next-template
Improved code readability and uniformity by standardizing line breaks, indentation, and inline conditions across safety-related services, models, and tests, including content filters, validation rules, and emergency controls.
312 lines
10 KiB
Python
312 lines
10 KiB
Python
"""
|
|
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"
|