311 lines
10 KiB
Python
311 lines
10 KiB
Python
"""
|
|
Tests for Docker integration with Claude Desktop MCP
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
class TestDockerClaudeDesktopIntegration:
|
|
"""Test Docker integration with Claude Desktop"""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self):
|
|
"""Setup for each test"""
|
|
self.project_root = Path(__file__).parent.parent
|
|
|
|
def test_mcp_config_docker_run_format(self):
|
|
"""Test MCP configuration for direct docker run"""
|
|
config = {
|
|
"mcpServers": {
|
|
"pal-mcp": {
|
|
"command": "docker",
|
|
"args": [
|
|
"run",
|
|
"--rm",
|
|
"-i",
|
|
"--env-file",
|
|
"/path/to/.env",
|
|
"-v",
|
|
"/path/to/logs:/app/logs",
|
|
"pal-mcp-server:latest",
|
|
],
|
|
}
|
|
}
|
|
}
|
|
|
|
# Validate configuration structure
|
|
assert "mcpServers" in config
|
|
assert "pal-mcp" in config["mcpServers"]
|
|
assert config["mcpServers"]["pal-mcp"]["command"] == "docker"
|
|
|
|
args = config["mcpServers"]["pal-mcp"]["args"]
|
|
assert "run" in args
|
|
assert "--rm" in args
|
|
assert "-i" in args
|
|
assert "--env-file" in args
|
|
|
|
def test_mcp_config_docker_compose_format(self):
|
|
"""Test MCP configuration for docker-compose run"""
|
|
config = {
|
|
"mcpServers": {
|
|
"pal-mcp": {
|
|
"command": "docker-compose",
|
|
"args": ["-f", "/path/to/docker-compose.yml", "run", "--rm", "pal-mcp"],
|
|
}
|
|
}
|
|
}
|
|
|
|
# Validate configuration structure
|
|
assert config["mcpServers"]["pal-mcp"]["command"] == "docker-compose"
|
|
|
|
args = config["mcpServers"]["pal-mcp"]["args"]
|
|
assert "-f" in args
|
|
assert "run" in args
|
|
assert "--rm" in args
|
|
assert "pal-mcp" in args
|
|
|
|
def test_mcp_config_environment_variables(self):
|
|
"""Test MCP configuration with inline environment variables"""
|
|
config = {
|
|
"mcpServers": {
|
|
"pal-mcp": {
|
|
"command": "docker",
|
|
"args": [
|
|
"run",
|
|
"--rm",
|
|
"-i",
|
|
"-e",
|
|
"GEMINI_API_KEY=test_key",
|
|
"-e",
|
|
"LOG_LEVEL=INFO",
|
|
"pal-mcp-server:latest",
|
|
],
|
|
}
|
|
}
|
|
}
|
|
|
|
args = config["mcpServers"]["pal-mcp"]["args"]
|
|
|
|
# Check that environment variables are properly formatted
|
|
env_args = [arg for arg in args if arg.startswith("-e")]
|
|
assert len(env_args) > 0, "Environment variables should be present"
|
|
|
|
# Check for API key environment variable
|
|
api_key_present = any("GEMINI_API_KEY=" in args[i + 1] for i, arg in enumerate(args[:-1]) if arg == "-e")
|
|
assert api_key_present, "API key environment variable should be set"
|
|
|
|
def test_windows_path_format(self):
|
|
"""Test Windows-specific path formatting"""
|
|
windows_config = {
|
|
"mcpServers": {
|
|
"pal-mcp": {
|
|
"command": "docker",
|
|
"args": [
|
|
"run",
|
|
"--rm",
|
|
"-i",
|
|
"--env-file",
|
|
"C:/Users/User/pal-mcp-server/.env",
|
|
"-v",
|
|
"C:/Users/User/pal-mcp-server/logs:/app/logs",
|
|
"pal-mcp-server:latest",
|
|
],
|
|
}
|
|
}
|
|
}
|
|
|
|
args = windows_config["mcpServers"]["pal-mcp"]["args"]
|
|
|
|
# Check Windows path format
|
|
windows_paths = [arg for arg in args if arg.startswith("C:/")]
|
|
assert len(windows_paths) > 0, "Windows paths should use forward slashes"
|
|
|
|
for path in windows_paths:
|
|
assert "\\" not in path, "Windows paths should use forward slashes"
|
|
|
|
def test_mcp_config_validation(self):
|
|
"""Test validation of MCP configuration"""
|
|
# Valid configuration
|
|
valid_config = {
|
|
"mcpServers": {"pal-mcp": {"command": "docker", "args": ["run", "--rm", "-i", "pal-mcp-server:latest"]}}
|
|
}
|
|
|
|
# Validate JSON serialization
|
|
config_json = json.dumps(valid_config)
|
|
loaded_config = json.loads(config_json)
|
|
assert loaded_config == valid_config
|
|
|
|
def test_mcp_stdio_communication(self):
|
|
"""Test that MCP configuration supports stdio communication"""
|
|
config = {
|
|
"mcpServers": {
|
|
"pal-mcp": {
|
|
"command": "docker",
|
|
"args": [
|
|
"run",
|
|
"--rm",
|
|
"-i", # Interactive mode for stdio
|
|
"pal-mcp-server:latest",
|
|
],
|
|
}
|
|
}
|
|
}
|
|
|
|
args = config["mcpServers"]["pal-mcp"]["args"]
|
|
|
|
# Check for interactive mode
|
|
assert "-i" in args, "Interactive mode required for stdio communication"
|
|
|
|
# Should not expose network ports for stdio communication
|
|
port_args = [arg for arg in args if arg.startswith("-p")]
|
|
assert len(port_args) == 0, "No ports should be exposed for stdio mode"
|
|
|
|
def test_docker_image_reference(self):
|
|
"""Test that Docker image is properly referenced"""
|
|
configs = [
|
|
{"image": "pal-mcp-server:latest"},
|
|
{"image": "pal-mcp-server:v1.0.0"},
|
|
{"image": "registry/pal-mcp-server:latest"},
|
|
]
|
|
|
|
for config in configs:
|
|
image = config["image"]
|
|
|
|
# Basic image format validation
|
|
assert ":" in image, "Image should have a tag"
|
|
assert len(image.split(":")) == 2, "Image should have exactly one tag"
|
|
|
|
@pytest.fixture
|
|
def temp_mcp_config(self):
|
|
"""Create temporary MCP configuration file"""
|
|
config = {
|
|
"mcpServers": {
|
|
"pal-mcp": {
|
|
"command": "docker",
|
|
"args": ["run", "--rm", "-i", "--env-file", "/tmp/.env", "pal-mcp-server:latest"],
|
|
}
|
|
}
|
|
}
|
|
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False, encoding="utf-8") as f:
|
|
json.dump(config, f, indent=2)
|
|
temp_file_path = f.name
|
|
|
|
yield temp_file_path
|
|
os.unlink(temp_file_path)
|
|
|
|
def test_mcp_config_file_parsing(self, temp_mcp_config):
|
|
"""Test parsing of MCP configuration file"""
|
|
# Read and parse the temporary config file
|
|
with open(temp_mcp_config, encoding="utf-8") as f:
|
|
config = json.load(f)
|
|
|
|
assert "mcpServers" in config
|
|
assert "pal-mcp" in config["mcpServers"]
|
|
|
|
def test_environment_file_integration(self):
|
|
"""Test integration with .env file"""
|
|
# Test .env file format expected by Docker
|
|
env_content = """GEMINI_API_KEY=test_key
|
|
OPENAI_API_KEY=test_key_2
|
|
LOG_LEVEL=INFO
|
|
DEFAULT_MODEL=auto
|
|
"""
|
|
|
|
# Parse environment content
|
|
env_vars = {}
|
|
for line in env_content.strip().split("\n"):
|
|
if "=" in line and not line.startswith("#"):
|
|
key, value = line.split("=", 1)
|
|
env_vars[key] = value
|
|
|
|
# Validate required environment variables
|
|
assert "GEMINI_API_KEY" in env_vars
|
|
assert len(env_vars["GEMINI_API_KEY"]) > 0
|
|
|
|
def test_docker_volume_mount_paths(self):
|
|
"""Test Docker volume mount path configurations"""
|
|
mount_configs = [
|
|
{"host": "./logs", "container": "/app/logs"},
|
|
{"host": "/absolute/path/logs", "container": "/app/logs"},
|
|
{"host": "C:/Windows/path/logs", "container": "/app/logs"},
|
|
]
|
|
|
|
for config in mount_configs:
|
|
mount_arg = f"{config['host']}:{config['container']}"
|
|
|
|
# Validate mount format
|
|
assert ":" in mount_arg
|
|
parts = mount_arg.split(":")
|
|
assert len(parts) >= 2
|
|
assert parts[-1].startswith("/"), "Container path should be absolute"
|
|
|
|
|
|
class TestDockerMCPErrorHandling:
|
|
"""Test error handling for Docker MCP integration"""
|
|
|
|
def test_missing_docker_image_handling(self):
|
|
"""Test handling of missing Docker image"""
|
|
# This would test what happens when the image doesn't exist
|
|
# In practice, Claude Desktop would show an error
|
|
nonexistent_config = {
|
|
"mcpServers": {"pal-mcp": {"command": "docker", "args": ["run", "--rm", "-i", "nonexistent:latest"]}}
|
|
}
|
|
|
|
# Configuration should be valid even if image doesn't exist
|
|
assert "pal-mcp" in nonexistent_config["mcpServers"]
|
|
|
|
def test_invalid_env_file_path(self):
|
|
"""Test handling of invalid .env file path"""
|
|
config_with_invalid_env = {
|
|
"mcpServers": {
|
|
"pal-mcp": {
|
|
"command": "docker",
|
|
"args": ["run", "--rm", "-i", "--env-file", "/nonexistent/.env", "pal-mcp-server:latest"],
|
|
}
|
|
}
|
|
}
|
|
|
|
# Configuration structure should still be valid
|
|
args = config_with_invalid_env["mcpServers"]["pal-mcp"]["args"]
|
|
assert "--env-file" in args
|
|
|
|
def test_docker_permission_issues(self):
|
|
"""Test configuration for potential Docker permission issues"""
|
|
# On some systems, Docker requires specific permissions
|
|
# The configuration should work with both cases
|
|
|
|
configs = [
|
|
# Regular Docker command
|
|
{"command": "docker"},
|
|
# Sudo Docker command (if needed)
|
|
{"command": "sudo", "extra_args": ["docker"]},
|
|
]
|
|
|
|
for config in configs:
|
|
assert len(config["command"]) > 0
|
|
|
|
def test_resource_limit_configurations(self):
|
|
"""Test Docker resource limit configurations"""
|
|
config_with_limits = {
|
|
"mcpServers": {
|
|
"pal-mcp": {
|
|
"command": "docker",
|
|
"args": ["run", "--rm", "-i", "--memory=512m", "--cpus=1.0", "pal-mcp-server:latest"],
|
|
}
|
|
}
|
|
}
|
|
|
|
args = config_with_limits["mcpServers"]["pal-mcp"]["args"]
|
|
|
|
# Check for resource limits
|
|
memory_limit = any("--memory" in arg for arg in args)
|
|
cpu_limit = any("--cpus" in arg for arg in args)
|
|
|
|
assert memory_limit or cpu_limit, "Resource limits should be configurable"
|