Skip to content

Commit cdb0875

Browse files
committed
test: reduce test_ws.py to just the WS smoke test
The three tests converted to in-memory transport in the previous commit weren't testing WebSocket behavior anymore — they were sitting in test_ws.py with misleading names. - test_ws_client_happy_request_and_response: deleted. Duplicates tests/client/test_client.py::test_read_resource. - test_ws_client_timeout: deleted. tests/issues/test_88_random_error.py covers the same session-recovery-after-timeout scenario more thoroughly (uses read_timeout_seconds and an anyio.Event to release the slow handler cleanly). - test_ws_client_exception_handling: moved to test_client.py as test_read_resource_error_propagates. This was the only unique behavior — nothing else asserts that a handler-raised MCPError reaches the client with its error code intact. test_ws.py now contains only what it says on the tin.
1 parent 9e277d1 commit cdb0875

File tree

2 files changed

+24
-79
lines changed

2 files changed

+24
-79
lines changed

tests/client/test_client.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import pytest
99
from inline_snapshot import snapshot
1010

11-
from mcp import types
11+
from mcp import MCPError, types
1212
from mcp.client._memory import InMemoryTransport
1313
from mcp.client.client import Client
1414
from mcp.server import Server, ServerRequestContext
@@ -175,6 +175,21 @@ async def test_read_resource(app: MCPServer):
175175
)
176176

177177

178+
async def test_read_resource_error_propagates():
179+
"""MCPError raised by a server handler propagates to the client with its code intact."""
180+
181+
async def handle_read_resource(
182+
ctx: ServerRequestContext, params: types.ReadResourceRequestParams
183+
) -> ReadResourceResult:
184+
raise MCPError(code=404, message="no resource with that URI was found")
185+
186+
server = Server("test", on_read_resource=handle_read_resource)
187+
async with Client(server) as client:
188+
with pytest.raises(MCPError) as exc_info:
189+
await client.read_resource("unknown://example")
190+
assert exc_info.value.error.code == 404
191+
192+
178193
async def test_get_prompt(app: MCPServer):
179194
"""Test getting a prompt."""
180195
async with Client(app) as client:

tests/shared/test_ws.py

Lines changed: 8 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,27 @@
1-
"""Tests for the WebSocket transport.
1+
"""Smoke test for the WebSocket transport.
22
3-
The smoke test (``test_ws_client_basic_connection``) runs the full WS stack
4-
end-to-end over a real TCP connection and is what provides coverage of
5-
``src/mcp/client/websocket.py``.
6-
7-
The remaining tests verify transport-agnostic MCP semantics (error
8-
propagation, client-side timeouts) and use the in-memory ``Client`` transport
9-
to avoid the cost and flakiness of real network servers.
3+
Runs the full WS stack end-to-end over a real TCP connection to provide
4+
coverage of ``src/mcp/client/websocket.py``. MCP semantics (error
5+
propagation, timeouts, etc.) are transport-agnostic and are covered in
6+
``tests/client/test_client.py`` and ``tests/issues/test_88_random_error.py``.
107
"""
118

129
from collections.abc import Generator
13-
from urllib.parse import urlparse
1410

15-
import anyio
1611
import pytest
1712
from starlette.applications import Starlette
1813
from starlette.routing import WebSocketRoute
1914
from starlette.websockets import WebSocket
2015

21-
from mcp import Client, MCPError
2216
from mcp.client.session import ClientSession
2317
from mcp.client.websocket import websocket_client
24-
from mcp.server import Server, ServerRequestContext
18+
from mcp.server import Server
2519
from mcp.server.websocket import websocket_server
26-
from mcp.types import (
27-
EmptyResult,
28-
InitializeResult,
29-
ReadResourceRequestParams,
30-
ReadResourceResult,
31-
TextResourceContents,
32-
)
20+
from mcp.types import EmptyResult, InitializeResult
3321
from tests.test_helpers import run_uvicorn_in_thread
3422

3523
SERVER_NAME = "test_server_for_WS"
3624

37-
pytestmark = pytest.mark.anyio
38-
39-
40-
# --- WebSocket transport smoke test (real TCP) -------------------------------
41-
4225

4326
def make_server_app() -> Starlette:
4427
srv = Server(SERVER_NAME)
@@ -56,6 +39,7 @@ def ws_server_url() -> Generator[str, None, None]:
5639
yield base_url.replace("http://", "ws://") + "/ws"
5740

5841

42+
@pytest.mark.anyio
5943
async def test_ws_client_basic_connection(ws_server_url: str) -> None:
6044
async with websocket_client(ws_server_url) as streams:
6145
async with ClientSession(*streams) as session:
@@ -65,57 +49,3 @@ async def test_ws_client_basic_connection(ws_server_url: str) -> None:
6549

6650
ping_result = await session.send_ping()
6751
assert isinstance(ping_result, EmptyResult)
68-
69-
70-
# --- In-memory tests (transport-agnostic MCP semantics) ----------------------
71-
72-
73-
async def handle_read_resource(ctx: ServerRequestContext, params: ReadResourceRequestParams) -> ReadResourceResult:
74-
parsed = urlparse(str(params.uri))
75-
if parsed.scheme == "foobar":
76-
return ReadResourceResult(
77-
contents=[TextResourceContents(uri=str(params.uri), text=f"Read {parsed.netloc}", mime_type="text/plain")]
78-
)
79-
elif parsed.scheme == "slow":
80-
# Block indefinitely so the client-side fail_after() fires; the pending
81-
# server task is cancelled when the Client context manager exits.
82-
await anyio.sleep_forever()
83-
raise MCPError(code=404, message="OOPS! no resource with that URI was found")
84-
85-
86-
@pytest.fixture
87-
def server() -> Server:
88-
return Server(SERVER_NAME, on_read_resource=handle_read_resource)
89-
90-
91-
async def test_ws_client_happy_request_and_response(server: Server) -> None:
92-
async with Client(server) as client:
93-
result = await client.read_resource("foobar://example")
94-
assert isinstance(result, ReadResourceResult)
95-
assert isinstance(result.contents, list)
96-
assert len(result.contents) > 0
97-
assert isinstance(result.contents[0], TextResourceContents)
98-
assert result.contents[0].text == "Read example"
99-
100-
101-
async def test_ws_client_exception_handling(server: Server) -> None:
102-
async with Client(server) as client:
103-
with pytest.raises(MCPError) as exc_info:
104-
await client.read_resource("unknown://example")
105-
assert exc_info.value.error.code == 404
106-
107-
108-
async def test_ws_client_timeout(server: Server) -> None:
109-
async with Client(server) as client:
110-
with pytest.raises(TimeoutError):
111-
with anyio.fail_after(0.1):
112-
await client.read_resource("slow://example")
113-
114-
# Session remains usable after a client-side timeout abandons a request.
115-
with anyio.fail_after(5):
116-
result = await client.read_resource("foobar://example")
117-
assert isinstance(result, ReadResourceResult)
118-
assert isinstance(result.contents, list)
119-
assert len(result.contents) > 0
120-
assert isinstance(result.contents[0], TextResourceContents)
121-
assert result.contents[0].text == "Read example"

0 commit comments

Comments
 (0)