Add production Docker Compose setup with Nginx and SSL; enhance test fixtures and environment variables for monitoring, logging, and schema caching; and clean up unused imports.

This commit is contained in:
Yûki VACHOT 2025-11-29 02:41:39 +01:00
parent bf5d1b890a
commit 283d7deee4
6 changed files with 109 additions and 58 deletions

View file

@ -0,0 +1,71 @@
version: '3.8'
# Production deployment with Nginx reverse proxy and SSL support
services:
geoguessr-mcp:
build:
context: .
dockerfile: Dockerfile
container_name: geoguessr-mcp-server
restart: unless-stopped
expose:
- "8000"
environment:
- GEOGUESSR_NCFA_COOKIE=${GEOGUESSR_NCFA_COOKIE:-}
- MCP_TRANSPORT=streamable-http
- MCP_HOST=0.0.0.0
- MCP_PORT=8000
- MONITORING_ENABLED=true
- MONITORING_INTERVAL_HOURS=24
- SCHEMA_CACHE_DIR=/app/data/schemas
- LOG_LEVEL=INFO
volumes:
- geoguessr-schemas:/app/data/schemas
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:8000/health" ]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
networks:
- internal
nginx:
image: nginx:alpine
container_name: geoguessr-mcp-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- ./nginx/logs:/var/log/nginx
depends_on:
geoguessr-mcp:
condition: service_healthy
networks:
- internal
- external
logging:
driver: "json-file"
options:
max-size: "5m"
max-file: "3"
volumes:
geoguessr-schemas:
name: geoguessr-mcp-schemas-prod
networks:
internal:
name: geoguessr-internal
internal: true
external:
name: geoguessr-external

View file

@ -10,38 +10,39 @@ services:
ports: ports:
- "${MCP_PORT:-8000}:8000" - "${MCP_PORT:-8000}:8000"
environment: environment:
# Required: Your GeoGuessr _ncfa cookie for authentication # GeoGuessr Authentication (optional - can use login tool instead)
- GEOGUESSR_NCFA_COOKIE=${GEOGUESSR_NCFA_COOKIE} - GEOGUESSR_NCFA_COOKIE=${GEOGUESSR_NCFA_COOKIE:-}
# MCP Server configuration # MCP Server configuration
- MCP_TRANSPORT=${MCP_TRANSPORT:-streamable-http} - MCP_TRANSPORT=${MCP_TRANSPORT:-streamable-http}
- MCP_HOST=0.0.0.0 - MCP_HOST=0.0.0.0
- MCP_PORT=8000 - MCP_PORT=8000
# Monitoring configuration
- MONITORING_ENABLED=${MONITORING_ENABLED:-true}
- MONITORING_INTERVAL_HOURS=${MONITORING_INTERVAL_HOURS:-24}
- SCHEMA_CACHE_DIR=/app/data/schemas
# Logging
- LOG_LEVEL=${LOG_LEVEL:-INFO}
volumes:
# Persist schema cache between restarts
- geoguessr-schemas:/app/data/schemas
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"] test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
start_period: 10s start_period: 15s
logging: logging:
driver: "json-file" driver: "json-file"
options: options:
max-size: "10m" max-size: "10m"
max-file: "3" max-file: "3"
# Optional: Nginx reverse proxy with SSL (recommended for production) volumes:
# Uncomment and configure if you want SSL termination geoguessr-schemas:
# nginx: name: geoguessr-mcp-schemas
# image: nginx:alpine
# container_name: geoguessr-mcp-nginx
# restart: unless-stopped
# ports:
# - "80:80"
# - "443:443"
# volumes:
# - ./nginx.conf:/etc/nginx/nginx.conf:ro
# - ./ssl:/etc/nginx/ssl:ro
# depends_on:
# - geoguessr-mcp
networks: networks:
default: default:

View file

@ -10,4 +10,4 @@ __author__ = "Yûki VACHOT"
from .main import main, mcp from .main import main, mcp
__all__ = ["mcp", "main", "__version__"] __all__ = ["mcp", "main", "__version__", "__author__"]

View file

@ -5,7 +5,6 @@ This server provides tools for analyzing GeoGuessr game statistics,
with automatic API monitoring and dynamic schema adaptation. with automatic API monitoring and dynamic schema adaptation.
""" """
import asyncio
import logging import logging
import sys import sys

View file

@ -1,10 +1,11 @@
"""Shared test fixtures.""" """Shared test fixtures."""
from unittest.mock import AsyncMock, MagicMock from unittest.mock import AsyncMock, MagicMock, patch
import pytest import pytest
from geoguessr_mcp.api.dynamic_response import DynamicResponse from geoguessr_mcp.api.dynamic_response import DynamicResponse
from geoguessr_mcp.auth import SessionManager
from geoguessr_mcp.models import RoundGuess, Game from geoguessr_mcp.models import RoundGuess, Game
from geoguessr_mcp.services import AnalysisService, GameService, ProfileService from geoguessr_mcp.services import AnalysisService, GameService, ProfileService
@ -32,6 +33,21 @@ def mock_session():
return mock_client return mock_client
@pytest.fixture
def session_manager():
return SessionManager()
@pytest.fixture
def mock_httpx_client():
with patch("httpx.AsyncClient") as mock_client_class:
mock_client = AsyncMock()
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
mock_client_class.return_value = mock_client
yield mock_client
@pytest.fixture @pytest.fixture
def mock_game_service(): def mock_game_service():
"""Create a mock GameService.""" """Create a mock GameService."""

View file

@ -17,21 +17,6 @@ from geoguessr_mcp.auth.session import SessionManager, UserSession
class TestAuthenticationFlow: class TestAuthenticationFlow:
"""Integration tests for authentication flow with mocked HTTP.""" """Integration tests for authentication flow with mocked HTTP."""
@pytest.fixture
def session_manager(self):
"""Create a fresh session manager for each test."""
return SessionManager()
@pytest.fixture
def mock_httpx_client(self):
"""Create a mock httpx client."""
with patch("httpx.AsyncClient") as mock_client_class:
mock_client = AsyncMock()
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
mock_client_class.return_value = mock_client
yield mock_client
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_complete_login_flow(self, session_manager, mock_httpx_client, mock_profile_data): async def test_complete_login_flow(self, session_manager, mock_httpx_client, mock_profile_data):
"""Test complete login flow from credentials to session.""" """Test complete login flow from credentials to session."""
@ -59,7 +44,7 @@ class TestAuthenticationFlow:
# Verify session was created # Verify session was created
assert session_token is not None assert session_token is not None
assert len(session_token) > 20 # Token should be substantial assert len(session_token) > 20 # Token should be significant
assert session.ncfa_cookie == "test_cookie_value" assert session.ncfa_cookie == "test_cookie_value"
assert session.username == "TestPlayer" assert session.username == "TestPlayer"
assert session.user_id == "test-user-id" assert session.user_id == "test-user-id"
@ -233,19 +218,6 @@ class TestAuthenticationFlow:
class TestLoginErrorHandling: class TestLoginErrorHandling:
"""Tests for login error scenarios.""" """Tests for login error scenarios."""
@pytest.fixture
def session_manager(self):
return SessionManager()
@pytest.fixture
def mock_httpx_client(self):
with patch("httpx.AsyncClient") as mock_client_class:
mock_client = AsyncMock()
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
mock_client_class.return_value = mock_client
yield mock_client
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_login_invalid_credentials(self, session_manager, mock_httpx_client): async def test_login_invalid_credentials(self, session_manager, mock_httpx_client):
"""Test login with invalid credentials.""" """Test login with invalid credentials."""
@ -326,10 +298,6 @@ class TestLoginErrorHandling:
class TestCookieValidation: class TestCookieValidation:
"""Tests for cookie validation functionality.""" """Tests for cookie validation functionality."""
@pytest.fixture
def session_manager(self):
return SessionManager()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_validate_valid_cookie(self, session_manager, mock_profile_data): async def test_validate_valid_cookie(self, session_manager, mock_profile_data):
"""Test validating a valid cookie.""" """Test validating a valid cookie."""
@ -395,10 +363,6 @@ class TestRealAuthFlow:
running with -m integration flag. running with -m integration flag.
""" """
@pytest.fixture
def session_manager(self):
return SessionManager()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_real_cookie_validation(self, session_manager): async def test_real_cookie_validation(self, session_manager):
"""Test validating a real cookie against the API.""" """Test validating a real cookie against the API."""