164 lines
4.6 KiB
Python
164 lines
4.6 KiB
Python
from unittest.mock import AsyncMock, MagicMock
|
|
import pytest
|
|
from mcp.server.fastmcp import Context
|
|
from strava_mcp_server.tools.activities import register
|
|
|
|
|
|
class MockMCP:
|
|
def __init__(self):
|
|
self.tools = {}
|
|
|
|
def tool(self):
|
|
def decorator(func):
|
|
self.tools[func.__name__] = func
|
|
return func
|
|
|
|
return decorator
|
|
|
|
def resource(self, path, **kwargs):
|
|
if not hasattr(self, "resources"):
|
|
self.resources = {}
|
|
|
|
def decorator(func):
|
|
self.resources[path] = func
|
|
return func
|
|
|
|
return decorator
|
|
|
|
def custom_route(self, path, methods, **kwargs):
|
|
if not hasattr(self, "routes"):
|
|
self.routes = {}
|
|
|
|
def decorator(func):
|
|
self.routes[path] = func
|
|
return func
|
|
|
|
return decorator
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_activity_map_success():
|
|
mcp = MockMCP()
|
|
strava = MagicMock()
|
|
|
|
# Mock detailed activity with a valid polyline
|
|
strava.get_activity = AsyncMock(
|
|
return_value={
|
|
"name": "Evening Ride",
|
|
"map": {"polyline": "a~l~FqzbwOq}@it@..."},
|
|
"sport_type": "Ride",
|
|
}
|
|
)
|
|
strava.get_activity_streams = AsyncMock(
|
|
return_value={
|
|
"altitude": {"data": [100.0, 101.0, 102.0]},
|
|
"distance": {"data": [0.0, 100.0, 200.0]},
|
|
"latlng": {"data": [[47.1, 9.1], [47.2, 9.2], [47.3, 9.3]]},
|
|
}
|
|
)
|
|
|
|
ctx = AsyncMock(spec=Context)
|
|
|
|
# Register tools
|
|
register(mcp, strava)
|
|
|
|
# Retrieve and call get_activity_map tool
|
|
get_activity_map = mcp.tools["get_activity_map"]
|
|
result = await get_activity_map(ctx, 12345)
|
|
|
|
assert result.isError is not True
|
|
assert result.meta == {"ui": {"resourceUri": "strava://activity/12345/map"}}
|
|
|
|
content = result.content
|
|
assert len(content) == 2
|
|
assert content[0].type == "text"
|
|
assert 'iframe src="data:text/html;base64,' in content[0].text
|
|
assert content[1].type == "text"
|
|
assert "CRITICAL INSTRUCTION FOR THE LLM" in content[1].text
|
|
|
|
# Now fetch and test the actual HTML from the registered resource handler
|
|
assert "strava://activity/{activity_id}/map" in mcp.resources
|
|
resource_handler = mcp.resources["strava://activity/{activity_id}/map"]
|
|
html_text = await resource_handler(12345)
|
|
|
|
assert isinstance(html_text, str)
|
|
assert "<!DOCTYPE html>" in html_text
|
|
assert "unpkg.com/leaflet" in html_text
|
|
assert "latlngData" in html_text
|
|
assert "[47.1, 9.1]" in html_text
|
|
assert 'sportEmoji = "🚴"' in html_text
|
|
assert "L.control.layers" in html_text
|
|
assert "satellite" in html_text
|
|
assert "🟢" in html_text
|
|
assert "🏁" in html_text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_activity_map_no_polyline():
|
|
mcp = MockMCP()
|
|
strava = MagicMock()
|
|
|
|
# Mock activity without GPS map info
|
|
strava.get_activity = AsyncMock(
|
|
return_value={
|
|
"name": "Indoor Trainer",
|
|
"map": {"polyline": None, "summary_polyline": None},
|
|
}
|
|
)
|
|
|
|
ctx = AsyncMock(spec=Context)
|
|
|
|
# Register tools
|
|
register(mcp, strava)
|
|
|
|
# Retrieve and call get_activity_map tool
|
|
get_activity_map = mcp.tools["get_activity_map"]
|
|
result = await get_activity_map(ctx, 67890)
|
|
|
|
assert result.isError is True
|
|
assert len(result.content) == 1
|
|
assert result.content[0].type == "text"
|
|
assert "⚠️ Keine GPS-Routen-Daten" in result.content[0].text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_web_map_route_success():
|
|
mcp = MockMCP()
|
|
strava = MagicMock()
|
|
|
|
# Mock detailed activity with a valid polyline
|
|
strava.get_activity = AsyncMock(
|
|
return_value={
|
|
"name": "Evening Ride",
|
|
"map": {"polyline": "a~l~FqzbwOq}@it@..."},
|
|
"sport_type": "Ride",
|
|
}
|
|
)
|
|
strava.get_activity_streams = AsyncMock(
|
|
return_value={
|
|
"altitude": {"data": [100.0, 101.0, 102.0]},
|
|
"distance": {"data": [0.0, 100.0, 200.0]},
|
|
"latlng": {"data": [[47.1, 9.1], [47.2, 9.2], [47.3, 9.3]]},
|
|
}
|
|
)
|
|
|
|
register(mcp, strava)
|
|
|
|
assert "/get-map/{activity_id}" in mcp.routes
|
|
route_handler = mcp.routes["/get-map/{activity_id}"]
|
|
|
|
# Mock Starlette Request
|
|
request = MagicMock()
|
|
request.path_params = {"activity_id": "12345"}
|
|
|
|
response = await route_handler(request)
|
|
|
|
from starlette.responses import HTMLResponse
|
|
|
|
assert isinstance(response, HTMLResponse)
|
|
assert response.status_code == 200
|
|
|
|
html_body = response.body.decode("utf-8")
|
|
assert "<!DOCTYPE html>" in html_body
|
|
assert "Evening Ride" in html_body
|