#!/bin/bash
# Docker Testing Script for Google Calendar MCP Server
# Tests Docker container functionality including stdio/HTTP modes and calendar integration
set -e # Exit on any error
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
TEST_TIMEOUT=120
HTTP_PORT=3001 # Use different port to avoid conflicts
CONTAINER_NAME="test-calendar-mcp"
CONTAINER_NAME_STDIO="test-calendar-mcp-stdio"
CONTAINER_NAME_HTTP="test-calendar-mcp-http"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${BLUE}ℹ️ $1${NC}"
}
log_success() {
echo -e "${GREEN}✅ $1${NC}"
}
log_warn() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
log_error() {
echo -e "${RED}❌ $1${NC}"
}
# Cleanup function
cleanup() {
log_info "Cleaning up test containers..."
# Stop and remove containers
docker stop "$CONTAINER_NAME_STDIO" 2>/dev/null || true
docker stop "$CONTAINER_NAME_HTTP" 2>/dev/null || true
docker rm "$CONTAINER_NAME_STDIO" 2>/dev/null || true
docker rm "$CONTAINER_NAME_HTTP" 2>/dev/null || true
# Remove test network if it exists
docker network rm mcp-test-network 2>/dev/null || true
log_info "Cleanup completed"
}
# Trap to ensure cleanup on exit
trap cleanup EXIT
# Check prerequisites
check_prerequisites() {
log_info "Checking prerequisites..."
# Check Docker
if ! command -v docker &> /dev/null; then
log_error "Docker is not installed"
exit 1
fi
# Check docker compose (modern command)
if ! docker compose version &> /dev/null; then
log_error "Docker Compose plugin is not installed"
log_info "Please install Docker Compose plugin: https://docs.docker.com/compose/install/"
exit 1
fi
# Check OAuth credentials
if [[ ! -f "$PROJECT_ROOT/gcp-oauth.keys.json" ]]; then
log_error "OAuth credentials file not found: gcp-oauth.keys.json"
log_info "Please download OAuth credentials from Google Cloud Console"
exit 1
fi
# Check environment variables for integration tests
if [[ -z "$TEST_CALENDAR_ID" ]]; then
log_warn "TEST_CALENDAR_ID not set - integration tests will be limited"
fi
log_success "Prerequisites check passed"
}
# Build Docker image
build_image() {
log_info "Building Docker image..."
cd "$PROJECT_ROOT"
docker build -t google-calendar-mcp:test .
log_success "Docker image built successfully"
}
# Test container startup and basic functionality
test_container_health() {
local mode=$1
local container_name=$2
log_info "Testing $mode mode container health..."
case $mode in
"stdio")
# Start stdio container with shell to keep it running
docker run -d \
--name "$container_name" \
-v "$PROJECT_ROOT/gcp-oauth.keys.json:/app/gcp-oauth.keys.json:ro" \
-v mcp-test-tokens:/home/nodejs/.config/google-calendar-mcp \
-e NODE_ENV=test \
-e TRANSPORT=stdio \
--entrypoint=/bin/sh \
google-calendar-mcp:test -c "while true; do sleep 30; done"
;;
"http")
# Start HTTP container
docker run -d \
--name "$container_name" \
-p "$HTTP_PORT:3000" \
-v "$PROJECT_ROOT/gcp-oauth.keys.json:/app/gcp-oauth.keys.json:ro" \
-v mcp-test-tokens:/home/nodejs/.config/google-calendar-mcp \
-e NODE_ENV=test \
-e TRANSPORT=http \
-e HOST=0.0.0.0 \
-e PORT=3000 \
google-calendar-mcp:test
;;
esac
# Wait for container to be ready
log_info "Waiting for container to be ready..."
sleep 5
# Check if container is running
if ! docker ps | grep -q "$container_name"; then
log_error "Container $container_name failed to start"
docker logs "$container_name"
return 1
fi
log_success "$mode mode container is healthy"
}
# Test HTTP endpoint accessibility
test_http_endpoints() {
log_info "Testing HTTP endpoints..."
# Wait for HTTP server to be ready
for i in {1..30}; do
if curl -s "http://localhost:$HTTP_PORT/health" > /dev/null 2>&1; then
break
fi
sleep 1
if [[ $i -eq 30 ]]; then
log_error "HTTP server failed to start within 30 seconds"
docker logs "$CONTAINER_NAME_HTTP"
return 1
fi
done
# Test health endpoint
if ! curl -s "http://localhost:$HTTP_PORT/health" | grep -q "healthy"; then
log_error "Health endpoint not responding correctly"
return 1
fi
# Test info endpoint
if ! curl -s "http://localhost:$HTTP_PORT/info" > /dev/null; then
log_error "Info endpoint not accessible"
return 1
fi
log_success "HTTP endpoints are accessible"
}
# Test MCP tool listing via Docker
test_mcp_tools() {
local container_name=$1
log_info "Testing MCP tool availability in container..."
# Create a simple Node.js script to test MCP connection
cat > "$PROJECT_ROOT/test-mcp-connection.js" << 'EOF'
const { Client } = require("@modelcontextprotocol/sdk/client/index.js");
const { StdioClientTransport } = require("@modelcontextprotocol/sdk/client/stdio.js");
const { spawn } = require('child_process');
async function testMCPConnection() {
const client = new Client({
name: "docker-test-client",
version: "1.0.0"
}, {
capabilities: { tools: {} }
});
try {
// For stdio mode, exec into the container
const transport = new StdioClientTransport({
command: 'docker',
args: ['exec', '-i', process.argv[2], 'npm', 'start'],
env: { ...process.env, NODE_ENV: 'test' }
});
await client.connect(transport);
const tools = await client.listTools();
console.log(`✅ Successfully connected to MCP server in container`);
console.log(`📋 Available tools: ${tools.tools.length}`);
// Test a simple tool call (list-calendars doesn't require auth setup)
try {
const result = await client.callTool({
name: 'list-calendars',
arguments: {}
});
console.log(`🔧 Tool execution test: SUCCESS`);
} catch (toolError) {
// Expected for auth issues in test environment
console.log(`🔧 Tool execution test: ${toolError.message.includes('auth') ? 'AUTH_REQUIRED (expected)' : 'FAILED'}`);
}
await client.close();
process.exit(0);
} catch (error) {
console.error(`❌ MCP connection failed:`, error.message);
process.exit(1);
}
}
if (process.argv.length < 3) {
console.error('Usage: node test-mcp-connection.js <container-name>');
process.exit(1);
}
testMCPConnection();
EOF
# Run the MCP connection test
if node "$PROJECT_ROOT/test-mcp-connection.js" "$container_name"; then
log_success "MCP tools accessible via Docker"
else
log_error "MCP tools test failed"
return 1
fi
# Cleanup test file
rm -f "$PROJECT_ROOT/test-mcp-connection.js"
}
# Test Docker Compose integration (simplified setup)
test_docker_compose() {
log_info "Testing Docker Compose integration..."
cd "$PROJECT_ROOT"
# Test stdio mode (default)
docker compose up -d
sleep 5
if ! docker compose ps | grep -q "calendar-mcp.*Up"; then
log_error "Docker Compose stdio mode failed"
docker compose logs calendar-mcp
return 1
fi
docker compose down
# Test HTTP mode by temporarily modifying compose file
log_info "Testing HTTP mode (requires manual setup)..."
log_warn "HTTP mode test skipped - requires manual docker-compose.yml edit"
log_info "To test HTTP mode manually:"
log_info "1. Uncomment ports and environment sections in docker-compose.yml"
log_info "2. Run: docker compose up -d"
log_info "3. Test: curl http://localhost:3000/health"
log_success "Docker Compose integration working"
}
# Test authentication setup (if credentials available)
test_auth_setup() {
log_info "Testing authentication setup in container..."
# This will test if the auth command works (may fail due to interactive nature)
if docker exec "$CONTAINER_NAME_STDIO" npm run auth --help > /dev/null 2>&1; then
log_success "Auth command accessible in container"
else
log_warn "Auth command test inconclusive (expected for non-interactive environment)"
fi
# Test token file paths are accessible
if docker exec "$CONTAINER_NAME_STDIO" ls -la /home/nodejs/.config/google-calendar-mcp/ > /dev/null 2>&1; then
log_success "Token storage directory accessible"
else
log_error "Token storage directory not accessible"
return 1
fi
}
# Run integration tests against Docker container (if environment supports it)
test_calendar_integration() {
if [[ -z "$TEST_CALENDAR_ID" || -z "$CLAUDE_API_KEY" ]]; then
log_warn "Skipping calendar integration tests (missing TEST_CALENDAR_ID or CLAUDE_API_KEY)"
return 0
fi
log_info "Running calendar integration tests against Docker container..."
# Use existing integration test but point it to Docker container
cd "$PROJECT_ROOT"
# Set environment to use Docker container
export DOCKER_CONTAINER_NAME="$CONTAINER_NAME_STDIO"
export USE_DOCKER_CONTAINER=true
# Run subset of integration tests
if timeout $TEST_TIMEOUT npm run test:integration -- --reporter=verbose --run docker 2>/dev/null; then
log_success "Calendar integration tests passed"
else
log_warn "Calendar integration tests incomplete (may require manual auth)"
fi
}
# Performance testing
test_performance() {
log_info "Running basic performance tests..."
# Test HTTP response times
local avg_response_time
avg_response_time=$(curl -o /dev/null -s -w '%{time_total}\n' \
"http://localhost:$HTTP_PORT/health" \
"http://localhost:$HTTP_PORT/health" \
"http://localhost:$HTTP_PORT/health" | \
awk '{sum+=$1} END {print sum/NR}')
echo "Average HTTP response time: ${avg_response_time}s"
# Test container resource usage
local memory_usage
memory_usage=$(docker stats --no-stream --format "{{.MemUsage}}" "$CONTAINER_NAME_HTTP" | cut -d'/' -f1)
echo "Container memory usage: $memory_usage"
log_success "Performance tests completed"
}
# Main test execution
main() {
log_info "🐳 Starting Docker integration tests for Google Calendar MCP Server"
# Cleanup any existing test containers
cleanup
# Run test suite
check_prerequisites
build_image
# Test stdio mode
test_container_health "stdio" "$CONTAINER_NAME_STDIO"
test_mcp_tools "$CONTAINER_NAME_STDIO"
test_auth_setup
# Test HTTP mode
test_container_health "http" "$CONTAINER_NAME_HTTP"
test_http_endpoints
test_performance
# Test Docker Compose integration
test_docker_compose
# Test calendar integration (if environment supports it)
test_calendar_integration
log_success "🎉 All Docker tests completed successfully!"
# Print summary
echo ""
echo "📋 Test Summary:"
echo " ✅ Container Health (stdio & HTTP)"
echo " ✅ MCP Tool Accessibility"
echo " ✅ HTTP Endpoint Testing"
echo " ✅ Docker Compose Integration"
echo " ✅ Authentication Setup"
echo " ✅ Performance Metrics"
if [[ -n "$TEST_CALENDAR_ID" && -n "$CLAUDE_API_KEY" ]]; then
echo " ✅ Calendar Integration"
else
echo " ⚠️ Calendar Integration (skipped - missing env vars)"
fi
}
# Handle command line arguments
case "${1:-}" in
--help|-h)
echo "Docker Testing Script for Google Calendar MCP Server"
echo ""
echo "Usage: $0 [options]"
echo ""
echo "Options:"
echo " --help, -h Show this help message"
echo " --quick Run only essential tests (faster)"
echo " --integration Run full integration tests (requires auth)"
echo ""
echo "Environment Variables:"
echo " TEST_CALENDAR_ID Calendar ID for integration tests"
echo " CLAUDE_API_KEY Anthropic API key for Claude integration"
echo " INVITEE_1, INVITEE_2 Email addresses for event testing"
echo ""
echo "Prerequisites:"
echo " - Docker and Docker Compose plugin installed"
echo " - gcp-oauth.keys.json file in project root"
echo " - For integration tests: authenticated test account"
exit 0
;;
--quick)
log_info "Running quick Docker tests only..."
check_prerequisites
build_image
test_container_health "stdio" "$CONTAINER_NAME_STDIO"
test_container_health "http" "$CONTAINER_NAME_HTTP"
test_http_endpoints
test_docker_compose
log_success "Quick tests completed!"
;;
--integration)
if [[ -z "$TEST_CALENDAR_ID" ]]; then
log_error "--integration requires TEST_CALENDAR_ID environment variable"
exit 1
fi
main
;;
"")
main
;;
*)
log_error "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac