Get baseline 100% clean coverage (#1553)

This commit is contained in:
Max Isbey 2025-11-11 13:09:32 +00:00 • committed by GitHub
commit 89e9c43acf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 1032 additions and 1084 deletions

View file

@ -13,7 +13,7 @@ def mcp_server() -> Server:
server = Server(name="test_server")
@server.list_resources()
async def handle_list_resources():
async def handle_list_resources(): # pragma: no cover
return [
Resource(
uri=AnyUrl("memory://test"),

View file

@ -41,7 +41,7 @@ async def test_bidirectional_progress_notifications():
async for message in server_session.incoming_messages:
try:
await server._handle_message(message, server_session, {})
except Exception as e:
except Exception as e: # pragma: no cover
raise e
# Track progress updates
@ -91,10 +91,10 @@ async def test_bidirectional_progress_notifications():
if arguments and "_meta" in arguments:
progressToken = arguments["_meta"]["progressToken"]
if not progressToken:
if not progressToken: # pragma: no cover
raise ValueError("Empty progress token received")
if progressToken != client_progress_token:
if progressToken != client_progress_token: # pragma: no cover
raise ValueError("Server sending back incorrect progressToken")
# Send progress notifications
@ -119,22 +119,22 @@ async def test_bidirectional_progress_notifications():
message="Server progress 100%",
)
else:
else: # pragma: no cover
raise ValueError("Progress token not sent.")
return [types.TextContent(type="text", text="Tool executed successfully")]
raise ValueError(f"Unknown tool: {name}")
raise ValueError(f"Unknown tool: {name}") # pragma: no cover
# Client message handler to store progress notifications
async def handle_client_message(
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
) -> None:
if isinstance(message, Exception):
if isinstance(message, Exception): # pragma: no cover
raise message
if isinstance(message, types.ServerNotification):
if isinstance(message.root, types.ProgressNotification):
if isinstance(message, types.ServerNotification): # pragma: no branch
if isinstance(message.root, types.ProgressNotification): # pragma: no branch
params = message.root.params
client_progress_updates.append(
{
@ -248,14 +248,14 @@ async def test_progress_context_manager():
async for message in server_session.incoming_messages:
try:
await server._handle_message(message, server_session, {})
except Exception as e:
except Exception as e: # pragma: no cover
raise e
# Client message handler
async def handle_client_message(
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
) -> None:
if isinstance(message, Exception):
if isinstance(message, Exception): # pragma: no cover
raise message
# run client session
@ -335,7 +335,9 @@ async def test_progress_callback_exception_logging():
logged_errors.append(msg % args if args else msg)
# Create a progress callback that raises an exception
async def failing_progress_callback(progress: float, total: float | None, message: str | None) -> None:
async def failing_progress_callback(
progress: float, total: float | None, message: str | None
) -> None: # pragma: no cover
raise ValueError("Progress callback failed!")
# Create a server with a tool that sends progress notifications
@ -352,7 +354,7 @@ async def test_progress_callback_exception_logging():
message="Halfway done",
)
return [types.TextContent(type="text", text="progress_result")]
raise ValueError(f"Unknown tool: {name}")
raise ValueError(f"Unknown tool: {name}") # pragma: no cover
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:

View file

@ -66,8 +66,8 @@ async def test_request_cancellation():
request_id = server.request_context.request_id
ev_tool_called.set()
await anyio.sleep(10) # Long enough to ensure we can cancel
return []
raise ValueError(f"Unknown tool: {name}")
return [] # pragma: no cover
raise ValueError(f"Unknown tool: {name}") # pragma: no cover
# Register the tool so it shows up in list_tools
@server.list_tools()
@ -93,7 +93,7 @@ async def test_request_cancellation():
),
types.CallToolResult,
)
pytest.fail("Request should have been cancelled")
pytest.fail("Request should have been cancelled") # pragma: no cover
except McpError as e:
# Expected - request was cancelled
assert "Request cancelled" in str(e)
@ -141,7 +141,7 @@ async def test_connection_closed():
try:
# any request will do
await client_session.initialize()
pytest.fail("Request should have errored")
pytest.fail("Request should have errored") # pragma: no cover
except McpError as e:
# Expected - request errored
assert "Connection closed" in str(e)

View file

@ -50,7 +50,7 @@ def server_url(server_port: int) -> str:
# Test server implementation
class ServerTest(Server):
class ServerTest(Server): # pragma: no cover
def __init__(self):
super().__init__(SERVER_NAME)
@ -81,7 +81,7 @@ class ServerTest(Server):
# Test fixtures
def make_server_app() -> Starlette:
def make_server_app() -> Starlette: # pragma: no cover
"""Create test Starlette app with SSE transport"""
# Configure security with allowed hosts/origins for testing
security_settings = TransportSecuritySettings(
@ -105,7 +105,7 @@ def make_server_app() -> Starlette:
return app
def run_server(server_port: int) -> None:
def run_server(server_port: int) -> None: # pragma: no cover
app = make_server_app()
server = uvicorn.Server(config=uvicorn.Config(app=app, host="127.0.0.1", port=server_port, log_level="error"))
print(f"starting server on {server_port}")
@ -133,7 +133,7 @@ def server(server_port: int) -> Generator[None, None, None]:
# Signal the server to stop
proc.kill()
proc.join(timeout=2)
if proc.is_alive():
if proc.is_alive(): # pragma: no cover
print("server process failed to terminate")
@ -156,7 +156,7 @@ async def test_raw_sse_connection(http_client: httpx.AsyncClient) -> None:
assert response.headers["content-type"] == "text/event-stream; charset=utf-8"
line_number = 0
async for line in response.aiter_lines():
async for line in response.aiter_lines(): # pragma: no branch
if line_number == 0:
assert line == "event: endpoint"
elif line_number == 1:
@ -214,7 +214,7 @@ async def test_sse_client_exception_handling(
@pytest.mark.anyio
@pytest.mark.skip("this test highlights a possible bug in SSE read timeout exception handling")
async def test_sse_client_timeout(
async def test_sse_client_timeout( # pragma: no cover
initialized_sse_client_session: ClientSession,
) -> None:
session = initialized_sse_client_session
@ -232,7 +232,7 @@ async def test_sse_client_timeout(
pytest.fail("the client should have timed out and returned an error already")
def run_mounted_server(server_port: int) -> None:
def run_mounted_server(server_port: int) -> None: # pragma: no cover
app = make_server_app()
main_app = Starlette(routes=[Mount("/mounted_app", app=app)])
server = uvicorn.Server(config=uvicorn.Config(app=main_app, host="127.0.0.1", port=server_port, log_level="error"))
@ -261,7 +261,7 @@ def mounted_server(server_port: int) -> Generator[None, None, None]:
# Signal the server to stop
proc.kill()
proc.join(timeout=2)
if proc.is_alive():
if proc.is_alive(): # pragma: no cover
print("server process failed to terminate")
@ -280,7 +280,7 @@ async def test_sse_client_basic_connection_mounted_app(mounted_server: None, ser
# Test server with request context that returns headers in the response
class RequestContextServer(Server[object, Request]):
class RequestContextServer(Server[object, Request]): # pragma: no cover
def __init__(self):
super().__init__("request_context_server")
@ -322,7 +322,7 @@ class RequestContextServer(Server[object, Request]):
]
def run_context_server(server_port: int) -> None:
def run_context_server(server_port: int) -> None: # pragma: no cover
"""Run a server that captures request context"""
# Configure security with allowed hosts/origins for testing
security_settings = TransportSecuritySettings(
@ -364,7 +364,7 @@ def context_server(server_port: int) -> Generator[None, None, None]:
print("killing context server")
proc.kill()
proc.join(timeout=2)
if proc.is_alive():
if proc.is_alive(): # pragma: no cover
print("context server process failed to terminate")

View file

@ -60,7 +60,7 @@ INIT_REQUEST = {
# Helper functions
def extract_protocol_version_from_sse(response: requests.Response) -> str:
def extract_protocol_version_from_sse(response: requests.Response) -> str: # pragma: no cover
"""Extract the negotiated protocol version from an SSE initialization response."""
assert response.headers.get("Content-Type") == "text/event-stream"
for line in response.text.splitlines():
@ -78,14 +78,14 @@ class SimpleEventStore(EventStore):
self._events: list[tuple[StreamId, EventId, types.JSONRPCMessage]] = []
self._event_id_counter = 0
async def store_event(self, stream_id: StreamId, message: types.JSONRPCMessage) -> EventId:
async def store_event(self, stream_id: StreamId, message: types.JSONRPCMessage) -> EventId: # pragma: no cover
"""Store an event and return its ID."""
self._event_id_counter += 1
event_id = str(self._event_id_counter)
self._events.append((stream_id, event_id, message))
return event_id
async def replay_events_after(
async def replay_events_after( # pragma: no cover
self,
last_event_id: EventId,
send_callback: EventCallback,
@ -114,7 +114,7 @@ class SimpleEventStore(EventStore):
# Test server implementation that follows MCP protocol
class ServerTest(Server):
class ServerTest(Server): # pragma: no cover
def __init__(self):
super().__init__(SERVER_NAME)
self._lock = None # Will be initialized in async context
@ -254,7 +254,9 @@ class ServerTest(Server):
return [TextContent(type="text", text=f"Called {name}")]
def create_app(is_json_response_enabled: bool = False, event_store: EventStore | None = None) -> Starlette:
def create_app(
is_json_response_enabled: bool = False, event_store: EventStore | None = None
) -> Starlette: # pragma: no cover
"""Create a Starlette application for testing using the session manager.
Args:
@ -287,7 +289,9 @@ def create_app(is_json_response_enabled: bool = False, event_store: EventStore |
return app
def run_server(port: int, is_json_response_enabled: bool = False, event_store: EventStore | None = None) -> None:
def run_server(
port: int, is_json_response_enabled: bool = False, event_store: EventStore | None = None
) -> None: # pragma: no cover
"""Run the test server.
Args:
@ -726,8 +730,8 @@ def test_get_sse_stream(basic_server: None, basic_server_url: str):
# Extract negotiated protocol version from SSE response
init_data = None
assert init_response.headers.get("Content-Type") == "text/event-stream"
for line in init_response.text.splitlines():
if line.startswith("data: "):
for line in init_response.text.splitlines(): # pragma: no branch
if line.startswith("data: "): # pragma: no cover
init_data = json.loads(line[6:])
break
assert init_data is not None
@ -786,8 +790,8 @@ def test_get_validation(basic_server: None, basic_server_url: str):
# Extract negotiated protocol version from SSE response
init_data = None
assert init_response.headers.get("Content-Type") == "text/event-stream"
for line in init_response.text.splitlines():
if line.startswith("data: "):
for line in init_response.text.splitlines(): # pragma: no branch
if line.startswith("data: "): # pragma: no cover
init_data = json.loads(line[6:])
break
assert init_data is not None
@ -820,7 +824,7 @@ def test_get_validation(basic_server: None, basic_server_url: str):
# Client-specific fixtures
@pytest.fixture
async def http_client(basic_server: None, basic_server_url: str):
async def http_client(basic_server: None, basic_server_url: str): # pragma: no cover
"""Create test client matching the SSE test pattern."""
async with httpx.AsyncClient(base_url=basic_server_url) as client:
yield client
@ -959,10 +963,10 @@ async def test_streamablehttp_client_get_stream(basic_server: None, basic_server
notifications_received: list[types.ServerNotification] = []
# Define message handler to capture notifications
async def message_handler(
async def message_handler( # pragma: no branch
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
) -> None:
if isinstance(message, types.ServerNotification):
if isinstance(message, types.ServerNotification): # pragma: no branch
notifications_received.append(message)
async with streamablehttp_client(f"{basic_server_url}/mcp") as (
@ -984,7 +988,7 @@ async def test_streamablehttp_client_get_stream(basic_server: None, basic_server
# Verify the notification is a ResourceUpdatedNotification
resource_update_found = False
for notif in notifications_received:
if isinstance(notif.root, types.ResourceUpdatedNotification):
if isinstance(notif.root, types.ResourceUpdatedNotification): # pragma: no branch
assert str(notif.root.params.uri) == "http://test_resource/"
resource_update_found = True
@ -1014,8 +1018,8 @@ async def test_streamablehttp_client_session_termination(basic_server: None, bas
tools = await session.list_tools()
assert len(tools.tools) == 6
headers: dict[str, str] = {}
if captured_session_id:
headers: dict[str, str] = {} # pragma: no cover
if captured_session_id: # pragma: no cover
headers[MCP_SESSION_ID_HEADER] = captured_session_id
async with streamablehttp_client(f"{basic_server_url}/mcp", headers=headers) as (
@ -1025,7 +1029,7 @@ async def test_streamablehttp_client_session_termination(basic_server: None, bas
):
async with ClientSession(read_stream, write_stream) as session:
# Attempt to make a request after termination
with pytest.raises(
with pytest.raises( # pragma: no branch
McpError,
match="Session terminated",
):
@ -1080,8 +1084,8 @@ async def test_streamablehttp_client_session_termination_204(
tools = await session.list_tools()
assert len(tools.tools) == 6
headers: dict[str, str] = {}
if captured_session_id:
headers: dict[str, str] = {} # pragma: no cover
if captured_session_id: # pragma: no cover
headers[MCP_SESSION_ID_HEADER] = captured_session_id
async with streamablehttp_client(f"{basic_server_url}/mcp", headers=headers) as (
@ -1091,7 +1095,7 @@ async def test_streamablehttp_client_session_termination_204(
):
async with ClientSession(read_stream, write_stream) as session:
# Attempt to make a request after termination
with pytest.raises(
with pytest.raises( # pragma: no branch
McpError,
match="Session terminated",
):
@ -1110,13 +1114,13 @@ async def test_streamablehttp_client_resumption(event_server: tuple[SimpleEventS
captured_protocol_version = None
first_notification_received = False
async def message_handler(
async def message_handler( # pragma: no branch
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
) -> None:
if isinstance(message, types.ServerNotification):
if isinstance(message, types.ServerNotification): # pragma: no branch
captured_notifications.append(message)
# Look for our first notification
if isinstance(message.root, types.LoggingMessageNotification):
if isinstance(message.root, types.LoggingMessageNotification): # pragma: no branch
if message.root.params.data == "First notification before lock":
nonlocal first_notification_received
first_notification_received = True
@ -1169,18 +1173,18 @@ async def test_streamablehttp_client_resumption(event_server: tuple[SimpleEventS
tg.cancel_scope.cancel()
# Verify we received exactly one notification
assert len(captured_notifications) == 1
assert isinstance(captured_notifications[0].root, types.LoggingMessageNotification)
assert captured_notifications[0].root.params.data == "First notification before lock"
assert len(captured_notifications) == 1 # pragma: no cover
assert isinstance(captured_notifications[0].root, types.LoggingMessageNotification) # pragma: no cover
assert captured_notifications[0].root.params.data == "First notification before lock" # pragma: no cover
# Clear notifications for the second phase
captured_notifications = []
captured_notifications = [] # pragma: no cover
# Now resume the session with the same mcp-session-id and protocol version
headers: dict[str, Any] = {}
if captured_session_id:
headers: dict[str, Any] = {} # pragma: no cover
if captured_session_id: # pragma: no cover
headers[MCP_SESSION_ID_HEADER] = captured_session_id
if captured_protocol_version:
if captured_protocol_version: # pragma: no cover
headers[MCP_PROTOCOL_VERSION_HEADER] = captured_protocol_version
async with streamablehttp_client(f"{server_url}/mcp", headers=headers) as (
read_stream,
@ -1278,7 +1282,7 @@ async def test_streamablehttp_server_sampling(basic_server: None, basic_server_u
# Context-aware server implementation for testing request context propagation
class ContextAwareServerTest(Server):
class ContextAwareServerTest(Server): # pragma: no cover
def __init__(self):
super().__init__("ContextAwareServer")
@ -1338,7 +1342,7 @@ class ContextAwareServerTest(Server):
# Server runner for context-aware testing
def run_context_aware_server(port: int):
def run_context_aware_server(port: int): # pragma: no cover
"""Run the context-aware test server."""
server = ContextAwareServerTest()
@ -1380,7 +1384,7 @@ def context_aware_server(basic_server_port: int) -> Generator[None, None, None]:
proc.kill()
proc.join(timeout=2)
if proc.is_alive():
if proc.is_alive(): # pragma: no cover
print("Context-aware server process failed to terminate")
@ -1443,8 +1447,8 @@ async def test_streamablehttp_request_context_isolation(context_aware_server: No
contexts.append(context_data)
# Verify each request had its own context
assert len(contexts) == 3
for i, ctx in enumerate(contexts):
assert len(contexts) == 3 # pragma: no cover
for i, ctx in enumerate(contexts): # pragma: no cover
assert ctx["request_id"] == f"request-{i}"
assert ctx["headers"].get("x-request-id") == f"request-{i}"
assert ctx["headers"].get("x-custom-value") == f"value-{i}"

View file

@ -44,7 +44,7 @@ def server_url(server_port: int) -> str:
# Test server implementation
class ServerTest(Server):
class ServerTest(Server): # pragma: no cover
def __init__(self):
super().__init__(SERVER_NAME)
@ -75,7 +75,7 @@ class ServerTest(Server):
# Test fixtures
def make_server_app() -> Starlette:
def make_server_app() -> Starlette: # pragma: no cover
"""Create test Starlette app with WebSocket transport"""
server = ServerTest()
@ -92,7 +92,7 @@ def make_server_app() -> Starlette:
return app
def run_server(server_port: int) -> None:
def run_server(server_port: int) -> None: # pragma: no cover
app = make_server_app()
server = uvicorn.Server(config=uvicorn.Config(app=app, host="127.0.0.1", port=server_port, log_level="error"))
print(f"starting server on {server_port}")
@ -104,7 +104,7 @@ def run_server(server_port: int) -> None:
time.sleep(0.5)
@pytest.fixture()
@pytest.fixture() # pragma: no cover
def server(server_port: int) -> Generator[None, None, None]:
proc = multiprocessing.Process(target=run_server, kwargs={"server_port": server_port}, daemon=True)
print("starting process")
@ -120,7 +120,7 @@ def server(server_port: int) -> Generator[None, None, None]:
# Signal the server to stop
proc.kill()
proc.join(timeout=2)
if proc.is_alive():
if proc.is_alive(): # pragma: no cover
print("server process failed to terminate")