""" MCP server tests — tool surface, lazy gateway resolution, call safety. Uses the FastMCP in-memory client (no network, no mounted app). """ import pytest from fastmcp import Client from config import Settings from core.dial_plan import is_emergency_number from core.gateway import AIPSTNGateway from mcp_server.server import create_mcp_server EXPECTED_TOOLS = { "make_call", "get_call_status", "transfer_call", "hangup", "list_active_calls", "get_call_flow", "create_call_flow", "send_dtmf", "get_call_transcript", "get_call_recording", "get_call_summary", "search_call_history", "list_devices", "gateway_status", } def _make_gateway(max_calls: int = 4) -> AIPSTNGateway: """Unstarted gateway on the in-memory MockSIPEngine — no network, no DB.""" return AIPSTNGateway(settings=Settings(max_concurrent_calls=max_calls)) class TestToolSurface: async def test_tool_listing_matches_expected(self): mcp = create_mcp_server(lambda: None) async with Client(mcp) as client: tools = {t.name for t in await client.list_tools()} assert tools == EXPECTED_TOOLS async def test_auth_configured_when_token_given(self): assert create_mcp_server(lambda: None, api_token="sekrit").auth is not None assert create_mcp_server(lambda: None).auth is None class TestGatewayResolution: async def test_tool_errors_cleanly_before_gateway_ready(self): mcp = create_mcp_server(lambda: None) async with Client(mcp) as client: with pytest.raises(Exception, match="starting up"): await client.call_tool("list_active_calls", {}) async def test_make_call_happy_path(self): gateway = _make_gateway() mcp = create_mcp_server(lambda: gateway) async with Client(mcp) as client: result = await client.call_tool( "make_call", {"number": "+15551234567", "mode": "direct"} ) text = result.content[0].text assert "initiated" in text assert "+15551234567" in text assert len(gateway.call_manager.active_calls) == 1 class TestCallSafety: def test_emergency_number_detection(self): for number in ("911", "9911", "112", "+1911", "+112", " 911 ", "9-1-1"): assert is_emergency_number(number), number for number in ("+19115551234", "+18005551234", "211", "999"): assert not is_emergency_number(number), number async def test_gateway_refuses_emergency_numbers(self): gateway = _make_gateway() with pytest.raises(ValueError, match="emergency"): await gateway.make_call("911") assert gateway.call_manager.active_calls == {} async def test_mcp_make_call_refuses_emergency(self): gateway = _make_gateway() mcp = create_mcp_server(lambda: gateway) async with Client(mcp) as client: with pytest.raises(Exception, match="[Ee]mergency"): await client.call_tool("make_call", {"number": "911"}) async def test_concurrent_call_cap(self): gateway = _make_gateway(max_calls=1) await gateway.make_call("+15551234567") with pytest.raises(ValueError, match="limit"): await gateway.make_call("+15557654321")