Skip to content

Commit 97ca48d

Browse files
committed
speed up tests
1 parent 0456b1b commit 97ca48d

File tree

1 file changed

+75
-64
lines changed

1 file changed

+75
-64
lines changed

tests/server/test_streamableHttp.py

Lines changed: 75 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -85,25 +85,10 @@ async def handle_call_tool(name: str, args: dict) -> list[TextContent]:
8585
return [TextContent(type="text", text=f"Called {name}")]
8686

8787

88-
@pytest.fixture
89-
def server_port() -> int:
90-
"""Find an available port for the test server."""
91-
with socket.socket() as s:
92-
s.bind(("127.0.0.1", 0))
93-
return s.getsockname()[1]
94-
95-
96-
@pytest.fixture
97-
def server_url(server_port: int) -> str:
98-
"""Get the URL for the test server."""
99-
return f"http://127.0.0.1:{server_port}"
100-
101-
102-
def create_app(session_id=None, is_json_response_enabled=False) -> Starlette:
88+
def create_app(is_json_response_enabled=False) -> Starlette:
10389
"""Create a Starlette application for testing that matches the example server.
10490
10591
Args:
106-
session_id: Optional session ID to use for the server.
10792
is_json_response_enabled: If True, use JSON responses instead of SSE streams.
10893
"""
10994
# Create server instance
@@ -197,20 +182,19 @@ async def run_server():
197182
return app
198183

199184

200-
def run_server(port: int, session_id=None, is_json_response_enabled=False) -> None:
185+
def run_server(port: int, is_json_response_enabled=False) -> None:
201186
"""Run the test server.
202187
203188
Args:
204189
port: Port to listen on.
205-
session_id: Optional session ID to use for the server.
206190
is_json_response_enabled: If True, use JSON responses instead of SSE streams.
207191
"""
208192
print(
209193
f"Starting test server on port {port} with "
210-
f"session_id={session_id}, json_enabled={is_json_response_enabled}"
194+
f"json_enabled={is_json_response_enabled}"
211195
)
212196

213-
app = create_app(session_id, is_json_response_enabled)
197+
app = create_app(is_json_response_enabled)
214198
# Configure server
215199
config = uvicorn.Config(
216200
app=app,
@@ -238,22 +222,38 @@ def run_server(port: int, session_id=None, is_json_response_enabled=False) -> No
238222
print("Server shutdown")
239223

240224

225+
# Test fixtures - using same approach as SSE tests
241226
@pytest.fixture
242-
def basic_server(server_port: int) -> Generator[None, None, None]:
243-
"""Start a basic server without session ID."""
244-
# Start server process
245-
process = multiprocessing.Process(
246-
target=run_server, kwargs={"port": server_port}, daemon=True
227+
def basic_server_port() -> int:
228+
"""Find an available port for the basic server."""
229+
with socket.socket() as s:
230+
s.bind(("127.0.0.1", 0))
231+
return s.getsockname()[1]
232+
233+
234+
@pytest.fixture
235+
def json_server_port() -> int:
236+
"""Find an available port for the JSON response server."""
237+
with socket.socket() as s:
238+
s.bind(("127.0.0.1", 0))
239+
return s.getsockname()[1]
240+
241+
242+
@pytest.fixture
243+
def basic_server(basic_server_port: int) -> Generator[None, None, None]:
244+
"""Start a basic server."""
245+
proc = multiprocessing.Process(
246+
target=run_server, kwargs={"port": basic_server_port}, daemon=True
247247
)
248-
process.start()
248+
proc.start()
249249

250-
# Wait for server to start
250+
# Wait for server to be running
251251
max_attempts = 20
252252
attempt = 0
253253
while attempt < max_attempts:
254254
try:
255255
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
256-
s.connect(("127.0.0.1", server_port))
256+
s.connect(("127.0.0.1", basic_server_port))
257257
break
258258
except ConnectionRefusedError:
259259
time.sleep(0.1)
@@ -264,30 +264,29 @@ def basic_server(server_port: int) -> Generator[None, None, None]:
264264
yield
265265

266266
# Clean up
267-
process.terminate()
268-
process.join(timeout=1)
269-
if process.is_alive():
270-
process.kill()
267+
proc.kill()
268+
proc.join(timeout=2)
269+
if proc.is_alive():
270+
print("server process failed to terminate")
271271

272272

273273
@pytest.fixture
274-
def json_response_server(server_port: int) -> Generator[None, None, None]:
274+
def json_response_server(json_server_port: int) -> Generator[None, None, None]:
275275
"""Start a server with JSON response enabled."""
276-
# Start server process with is_json_response_enabled=True
277-
process = multiprocessing.Process(
276+
proc = multiprocessing.Process(
278277
target=run_server,
279-
kwargs={"port": server_port, "is_json_response_enabled": True},
278+
kwargs={"port": json_server_port, "is_json_response_enabled": True},
280279
daemon=True,
281280
)
282-
process.start()
281+
proc.start()
283282

284-
# Wait for server to start
283+
# Wait for server to be running
285284
max_attempts = 20
286285
attempt = 0
287286
while attempt < max_attempts:
288287
try:
289288
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
290-
s.connect(("127.0.0.1", server_port))
289+
s.connect(("127.0.0.1", json_server_port))
291290
break
292291
except ConnectionRefusedError:
293292
time.sleep(0.1)
@@ -298,30 +297,42 @@ def json_response_server(server_port: int) -> Generator[None, None, None]:
298297
yield
299298

300299
# Clean up
301-
process.terminate()
302-
process.join(timeout=1)
303-
if process.is_alive():
304-
process.kill()
300+
proc.kill()
301+
proc.join(timeout=2)
302+
if proc.is_alive():
303+
print("server process failed to terminate")
304+
305+
306+
@pytest.fixture
307+
def basic_server_url(basic_server_port: int) -> str:
308+
"""Get the URL for the basic test server."""
309+
return f"http://127.0.0.1:{basic_server_port}"
310+
311+
312+
@pytest.fixture
313+
def json_server_url(json_server_port: int) -> str:
314+
"""Get the URL for the JSON response test server."""
315+
return f"http://127.0.0.1:{json_server_port}"
305316

306317

307318
# Basic request validation tests
308-
def test_accept_header_validation(basic_server, server_url):
319+
def test_accept_header_validation(basic_server, basic_server_url):
309320
"""Test that Accept header is properly validated."""
310321
# Test without Accept header
311322
response = requests.post(
312-
f"{server_url}/mcp",
323+
f"{basic_server_url}/mcp",
313324
headers={"Content-Type": "application/json"},
314325
json={"jsonrpc": "2.0", "method": "initialize", "id": 1},
315326
)
316327
assert response.status_code == 406
317328
assert "Not Acceptable" in response.text
318329

319330

320-
def test_content_type_validation(basic_server, server_url):
331+
def test_content_type_validation(basic_server, basic_server_url):
321332
"""Test that Content-Type header is properly validated."""
322333
# Test with incorrect Content-Type
323334
response = requests.post(
324-
f"{server_url}/mcp",
335+
f"{basic_server_url}/mcp",
325336
headers={
326337
"Accept": "application/json, text/event-stream",
327338
"Content-Type": "text/plain",
@@ -332,11 +343,11 @@ def test_content_type_validation(basic_server, server_url):
332343
assert "Unsupported Media Type" in response.text
333344

334345

335-
def test_json_validation(basic_server, server_url):
346+
def test_json_validation(basic_server, basic_server_url):
336347
"""Test that JSON content is properly validated."""
337348
# Test with invalid JSON
338349
response = requests.post(
339-
f"{server_url}/mcp",
350+
f"{basic_server_url}/mcp",
340351
headers={
341352
"Accept": "application/json, text/event-stream",
342353
"Content-Type": "application/json",
@@ -347,11 +358,11 @@ def test_json_validation(basic_server, server_url):
347358
assert "Parse error" in response.text
348359

349360

350-
def test_json_parsing(basic_server, server_url):
361+
def test_json_parsing(basic_server, basic_server_url):
351362
"""Test that JSON content is properly parse."""
352363
# Test with valid JSON but invalid JSON-RPC
353364
response = requests.post(
354-
f"{server_url}/mcp",
365+
f"{basic_server_url}/mcp",
355366
headers={
356367
"Accept": "application/json, text/event-stream",
357368
"Content-Type": "application/json",
@@ -362,11 +373,11 @@ def test_json_parsing(basic_server, server_url):
362373
assert "Validation error" in response.text
363374

364375

365-
def test_method_not_allowed(basic_server, server_url):
376+
def test_method_not_allowed(basic_server, basic_server_url):
366377
"""Test that unsupported HTTP methods are rejected."""
367378
# Test with unsupported method (PUT)
368379
response = requests.put(
369-
f"{server_url}/mcp",
380+
f"{basic_server_url}/mcp",
370381
headers={
371382
"Accept": "application/json, text/event-stream",
372383
"Content-Type": "application/json",
@@ -377,13 +388,13 @@ def test_method_not_allowed(basic_server, server_url):
377388
assert "Method Not Allowed" in response.text
378389

379390

380-
def test_session_validation(basic_server, server_url):
391+
def test_session_validation(basic_server, basic_server_url):
381392
"""Test session ID validation."""
382393
# session_id not used directly in this test
383394

384395
# Test without session ID
385396
response = requests.post(
386-
f"{server_url}/mcp",
397+
f"{basic_server_url}/mcp",
387398
headers={
388399
"Accept": "application/json, text/event-stream",
389400
"Content-Type": "application/json",
@@ -452,10 +463,10 @@ def test_streamable_http_transport_init_validation():
452463
StreamableHTTPServerTransport(mcp_session_id="test\n")
453464

454465

455-
def test_session_termination(basic_server, server_url):
466+
def test_session_termination(basic_server, basic_server_url):
456467
"""Test session termination via DELETE and subsequent request handling."""
457468
response = requests.post(
458-
f"{server_url}/mcp",
469+
f"{basic_server_url}/mcp",
459470
headers={
460471
"Accept": "application/json, text/event-stream",
461472
"Content-Type": "application/json",
@@ -467,15 +478,15 @@ def test_session_termination(basic_server, server_url):
467478
# Now terminate the session
468479
session_id = response.headers.get(MCP_SESSION_ID_HEADER)
469480
response = requests.delete(
470-
f"{server_url}/mcp",
481+
f"{basic_server_url}/mcp",
471482
headers={MCP_SESSION_ID_HEADER: session_id},
472483
)
473484
assert response.status_code == 200
474485
assert "Session terminated" in response.text
475486

476487
# Try to use the terminated session
477488
response = requests.post(
478-
f"{server_url}/mcp",
489+
f"{basic_server_url}/mcp",
479490
headers={
480491
"Accept": "application/json, text/event-stream",
481492
"Content-Type": "application/json",
@@ -487,9 +498,9 @@ def test_session_termination(basic_server, server_url):
487498
assert "Session has been terminated" in response.text
488499

489500

490-
def test_response(basic_server, server_url):
501+
def test_response(basic_server, basic_server_url):
491502
"""Test response handling for a valid request."""
492-
mcp_url = f"{server_url}/mcp"
503+
mcp_url = f"{basic_server_url}/mcp"
493504
response = requests.post(
494505
mcp_url,
495506
headers={
@@ -518,9 +529,9 @@ def test_response(basic_server, server_url):
518529
assert tools_response.headers.get("Content-Type") == "text/event-stream"
519530

520531

521-
def test_json_response(json_response_server, server_url):
532+
def test_json_response(json_response_server, json_server_url):
522533
"""Test response handling when is_json_response_enabled is True."""
523-
mcp_url = f"{server_url}/mcp"
534+
mcp_url = f"{json_server_url}/mcp"
524535
response = requests.post(
525536
mcp_url,
526537
headers={
@@ -530,4 +541,4 @@ def test_json_response(json_response_server, server_url):
530541
json=INIT_REQUEST,
531542
)
532543
assert response.status_code == 200
533-
assert response.headers.get("Content-Type") == "application/json"
544+
assert response.headers.get("Content-Type") == "application/json"

0 commit comments

Comments
 (0)