added tests
This commit is contained in:
206
tests/test_mcp_servers.py
Normal file
206
tests/test_mcp_servers.py
Normal file
@@ -0,0 +1,206 @@
|
||||
"""Tests for MCP servers — tools, resources, prompts via the MCP protocol."""
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from fastmcp import Client
|
||||
|
||||
|
||||
def _client(module: str) -> Client:
|
||||
return Client({"mcpServers": {"default": {
|
||||
"command": "uv",
|
||||
"args": ["run", "python", "-m", module],
|
||||
}}})
|
||||
|
||||
|
||||
def _parse_result(result) -> any:
|
||||
"""Parse a fastmcp CallToolResult into Python data."""
|
||||
if hasattr(result, "content"):
|
||||
texts = [c.text for c in result.content if hasattr(c, "text")]
|
||||
elif isinstance(result, list):
|
||||
texts = [c.text for c in result if hasattr(c, "text")]
|
||||
else:
|
||||
return result
|
||||
|
||||
if not texts:
|
||||
return None
|
||||
combined = texts[0] if len(texts) == 1 else "[" + ",".join(texts) + "]"
|
||||
try:
|
||||
return json.loads(combined)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return combined
|
||||
|
||||
|
||||
class TestSharedServer:
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_tools(self):
|
||||
async with _client("mcp_servers.shared") as c:
|
||||
tools = await c.list_tools()
|
||||
names = [t.name for t in tools]
|
||||
assert "get_flight_status" in names
|
||||
assert "get_flight_details" in names
|
||||
assert "get_irregular_ops" in names
|
||||
assert "get_route_weather" in names
|
||||
assert "get_hub_forecasts" in names
|
||||
assert "get_airport_status" in names
|
||||
assert "get_airport_congestion" in names
|
||||
assert "get_maintenance_flags" in names
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_flight_status_found(self):
|
||||
async with _client("mcp_servers.shared") as c:
|
||||
result = await c.call_tool("get_flight_status", {"flight_id": "UA432"})
|
||||
data = _parse_result(result)
|
||||
assert data["flight_id"] == "UA432"
|
||||
assert data["status"] == "DELAYED"
|
||||
assert data["delay_minutes"] == 55
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_flight_status_not_found(self):
|
||||
async with _client("mcp_servers.shared") as c:
|
||||
result = await c.call_tool("get_flight_status", {"flight_id": "FAKE999"})
|
||||
data = _parse_result(result)
|
||||
assert "error" in data
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_irregular_ops(self):
|
||||
async with _client("mcp_servers.shared") as c:
|
||||
result = await c.call_tool("get_irregular_ops", {"hub": "ORD"})
|
||||
data = _parse_result(result)
|
||||
assert isinstance(data, list)
|
||||
assert len(data) >= 4
|
||||
for f in data:
|
||||
assert f["irrop_type"] in ("DELAYED", "CANCELLED", "DIVERTED")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_maintenance_flags(self):
|
||||
async with _client("mcp_servers.shared") as c:
|
||||
result = await c.call_tool("get_maintenance_flags", {"aircraft_tail": "N78501"})
|
||||
data = _parse_result(result)
|
||||
assert isinstance(data, list)
|
||||
assert len(data) >= 1
|
||||
assert "system" in data[0]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_maintenance_flags_empty(self):
|
||||
async with _client("mcp_servers.shared") as c:
|
||||
result = await c.call_tool("get_maintenance_flags", {"aircraft_tail": "FAKE"})
|
||||
data = _parse_result(result)
|
||||
# Empty list may come back as [] or as "[]" string
|
||||
if isinstance(data, list):
|
||||
assert len(data) == 0
|
||||
else:
|
||||
assert data == "[]" or data is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_prompts(self):
|
||||
async with _client("mcp_servers.shared") as c:
|
||||
prompts = await c.list_prompts()
|
||||
names = [p.name for p in prompts]
|
||||
assert "delay_explainer" in names
|
||||
|
||||
|
||||
class TestOpsServer:
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_tools(self):
|
||||
async with _client("mcp_servers.ops") as c:
|
||||
tools = await c.list_tools()
|
||||
names = [t.name for t in tools]
|
||||
assert "get_crew_notes" in names
|
||||
assert "get_crew_duty_status" in names
|
||||
assert "get_pending_rebookings" in names
|
||||
assert "generate_narrative" in names
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_crew_notes(self):
|
||||
async with _client("mcp_servers.ops") as c:
|
||||
result = await c.call_tool("get_crew_notes", {"flight_id": "UA432"})
|
||||
data = _parse_result(result)
|
||||
assert isinstance(data, list)
|
||||
assert len(data) > 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_crew_duty_status(self):
|
||||
async with _client("mcp_servers.ops") as c:
|
||||
result = await c.call_tool(
|
||||
"get_crew_duty_status",
|
||||
{"crew_ids": ["CR-1001", "CR-1003"]}
|
||||
)
|
||||
data = _parse_result(result)
|
||||
assert isinstance(data, list)
|
||||
assert len(data) == 2
|
||||
for c_data in data:
|
||||
assert "hours_until_limit" in c_data
|
||||
assert "at_risk" in c_data
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_crew_at_risk_detection(self):
|
||||
async with _client("mcp_servers.ops") as c:
|
||||
result = await c.call_tool(
|
||||
"get_crew_duty_status",
|
||||
{"crew_ids": ["CR-1003"]}
|
||||
)
|
||||
data = _parse_result(result)
|
||||
crew = data[0] if isinstance(data, list) else data
|
||||
assert crew["at_risk"] is True
|
||||
assert crew["hours_until_limit"] <= 2.0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pending_rebookings(self):
|
||||
async with _client("mcp_servers.ops") as c:
|
||||
result = await c.call_tool("get_pending_rebookings", {"hub": "ORD"})
|
||||
data = _parse_result(result)
|
||||
assert isinstance(data, list)
|
||||
assert len(data) > 0
|
||||
urgencies = [r["urgency"] for r in data]
|
||||
assert urgencies[0] == "HIGH"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_resources(self):
|
||||
async with _client("mcp_servers.ops") as c:
|
||||
resources = await c.list_resources()
|
||||
uris = [str(r.uri) for r in resources]
|
||||
assert "ops://crew/roster" in uris
|
||||
assert "ops://handover/latest" in uris
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_prompts(self):
|
||||
async with _client("mcp_servers.ops") as c:
|
||||
prompts = await c.list_prompts()
|
||||
names = [p.name for p in prompts]
|
||||
assert "handover_brief" in names
|
||||
|
||||
|
||||
class TestPassengerServer:
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_tools(self):
|
||||
async with _client("mcp_servers.passenger") as c:
|
||||
tools = await c.list_tools()
|
||||
names = [t.name for t in tools]
|
||||
assert "generate_notification" in names
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_notification(self):
|
||||
async with _client("mcp_servers.passenger") as c:
|
||||
result = await c.call_tool("generate_notification", {
|
||||
"context": {
|
||||
"flight_id": "UA432",
|
||||
"origin": "ORD",
|
||||
"destination": "SFO",
|
||||
"status": "DELAYED",
|
||||
"delay_minutes": 55,
|
||||
"delay_cause": "WEATHER",
|
||||
"gate": "H14",
|
||||
}
|
||||
})
|
||||
text = _parse_result(result)
|
||||
assert "UA432" in text
|
||||
assert "DELAYED" in text or "delayed" in text
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_prompts(self):
|
||||
async with _client("mcp_servers.passenger") as c:
|
||||
prompts = await c.list_prompts()
|
||||
names = [p.name for p in prompts]
|
||||
assert "passenger_notification" in names
|
||||
Reference in New Issue
Block a user