|
1 | 1 | """Tests for StreamableHTTPSessionManager."""
|
2 | 2 |
|
| 3 | +from http import HTTPStatus |
3 | 4 | from typing import Any
|
4 | 5 | from unittest.mock import AsyncMock, patch
|
5 | 6 |
|
@@ -202,6 +203,59 @@ async def mock_receive():
|
202 | 203 | assert not manager._server_instances, "No sessions should be tracked after the only session crashes"
|
203 | 204 |
|
204 | 205 |
|
| 206 | +@pytest.mark.anyio |
| 207 | +async def test_stateful_session_returning_http_not_found_when_not_found( |
| 208 | + running_manager: tuple[StreamableHTTPSessionManager, Server], |
| 209 | +): |
| 210 | + """Test that a request with a non-existent session ID returns HTTP 404 Not Found. |
| 211 | +
|
| 212 | + This is in accordance to the specification point 2.5.3 of: |
| 213 | + https://modelcontextprotocol.io/specification/2025-06-18/basic/transports |
| 214 | + """ |
| 215 | + manager, app = running_manager |
| 216 | + |
| 217 | + mock_mcp_run = AsyncMock(return_value=None) |
| 218 | + app.run = mock_mcp_run |
| 219 | + |
| 220 | + sent_messages: list[Message] = [] |
| 221 | + |
| 222 | + async def mock_send(message: Message): |
| 223 | + sent_messages.append(message) |
| 224 | + |
| 225 | + scope = { |
| 226 | + "type": "http", |
| 227 | + "method": "POST", |
| 228 | + "path": "/mcp", |
| 229 | + "headers": [ |
| 230 | + (b"content-type", b"application/json"), |
| 231 | + # The point of this test -- non-existent session ID: |
| 232 | + (b"mcp-session-id", b"non-existent-session-id"), |
| 233 | + ], |
| 234 | + } |
| 235 | + |
| 236 | + async def mock_receive(): |
| 237 | + return {"type": "http.request", "body": b"", "more_body": False} |
| 238 | + |
| 239 | + # Send the request with mcp-session-id header set to a non-existent value. |
| 240 | + await manager.handle_request(scope, mock_receive, mock_send) |
| 241 | + |
| 242 | + # Extract HTTP status and body from the messages. |
| 243 | + http_status = None |
| 244 | + body = None |
| 245 | + for msg in sent_messages: |
| 246 | + if msg["type"] == "http.response.start": |
| 247 | + http_status = msg["status"] |
| 248 | + break |
| 249 | + for msg in sent_messages: |
| 250 | + if msg["type"] == "http.response.body": |
| 251 | + body = msg["body"] |
| 252 | + break |
| 253 | + |
| 254 | + assert http_status == HTTPStatus.NOT_FOUND, "Response status should be 404 Not Found" |
| 255 | + assert body is not None, "Response body should not be None" |
| 256 | + assert b"Not Found" in body, "Response body should indicate Not Found" |
| 257 | + |
| 258 | + |
205 | 259 | @pytest.mark.anyio
|
206 | 260 | async def test_stateless_requests_memory_cleanup():
|
207 | 261 | """Test that stateless requests actually clean up resources using real transports."""
|
|
0 commit comments