Skip to content

Commit 7c36c1d

Browse files
committed
fix(ci, test): Resolve CI failures and hanging SSE transport test
This commit addresses two issues: 1. Fixes GitHub Actions CI workflow failures (exit code 127) by switching dependency installation from `uv` to standard `pip`, ensuring `pytest` is found. 2. Resolves a hanging test (`test_sse_route_connection` in `tests/test_sse_transport.py`) by refactoring it to call the `handle_sse` coroutine directly with a mocked Request object, bypassing the problematic `TestClient.stream()` or `TestClient.get()` interaction. With these changes, the CI pipeline and the test suite now complete successfully. The CHANGELOG.md has been updated.
1 parent c5ffffe commit 7c36c1d

File tree

3 files changed

+65
-53
lines changed

3 files changed

+65
-53
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,12 @@ jobs:
2121
with:
2222
python-version: ${{ matrix.python-version }}
2323

24-
- name: Install uv
25-
run: curl -LsSf https://astral.sh/uv/install.sh | sh
26-
27-
- name: Install dependencies
28-
run: ~/.cargo/bin/uv pip install -e ".[dev]"
29-
# Use pip if you prefer:
30-
# run: |
31-
# python -m pip install --upgrade pip
32-
# pip install -e ".[dev]"
24+
- name: Install dependencies using pip
25+
# run: ~/.cargo/bin/uv pip install -e ".[dev]" # Use pip instead
26+
# Use pip (standard method)
27+
run: |
28+
python -m pip install --upgrade pip
29+
pip install -e ".[dev]"
3330
3431
- name: Test with pytest
3532
run: python -m pytest

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [0.2.3] - 2025-04-19
8+
## [0.2.3] - 2025-04-20
99

1010
### Added
1111
- CI workflow using GitHub Actions (`.github/workflows/ci.yml`) to run tests on Python 3.9 and 3.12.
@@ -31,6 +31,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3131
- Multiple test failures related to incorrect mocking, error expectations, path handling, test setup, and imports (`StringIO`, `FastMCP`).
3232
- Invalid escape sequence in `pyproject.toml` coverage exclusion pattern.
3333
- Several issues in SSE transport (`sse_transport.py`) related to refactoring, including incorrect `SseServerTransport` initialization, missing `/messages` route handling, and incorrect parameters passed to the underlying `mcp.server.Server.run` method, causing connection failures.
34+
- GitHub Actions CI workflow failure (exit code 127) by switching dependency installation from `uv` to standard `pip` to ensure `pytest` is found.
35+
- Hanging test (`test_sse_route_connection` in `tests/test_sse_transport.py`) by refactoring to call the handler directly with a mock request instead of using `TestClient`.
3436

3537
## [0.2.2] - 2025-04-19
3638

tests/test_sse_transport.py

Lines changed: 56 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -49,56 +49,69 @@ def test_root_route(dummy_server_config):
4949

5050
@pytest.mark.asyncio
5151
async def test_sse_route_connection(dummy_server_config):
52-
"""Test that connecting to /sse triggers MCP connection logic."""
53-
# Mock the FastMCP instance and its underlying server's run method
54-
mock_mcp_internal_server = mock.AsyncMock()
55-
mock_mcp = mock.Mock(spec=FastMCP)
56-
mock_mcp._mcp_server = mock.Mock()
57-
mock_mcp._mcp_server.run = mock_mcp_internal_server # Mock the run method
52+
"""Test that handle_sse calls dependencies correctly."""
53+
# --- Mocks Setup ---
54+
# Mock the underlying Server.run method
55+
mock_internal_run = mock.AsyncMock()
56+
# Mock the underlying Server.create_initialization_options method
57+
mock_init_options = mock.Mock(name="InitializationOptionsMock")
58+
mock_create_options = mock.Mock(return_value=mock_init_options)
59+
# Mock the underlying Server instance
60+
mock_underlying_server = mock.Mock()
61+
mock_underlying_server.run = mock_internal_run
62+
mock_underlying_server.create_initialization_options = mock_create_options
63+
# Mock the FastMCP wrapper instance
64+
mock_mcp_wrapper = mock.Mock(spec=FastMCP)
65+
mock_mcp_wrapper._mcp_server = mock_underlying_server # Link wrapper to underlying mock
5866

59-
# Mock the SseServerTransport.connect_sse context manager
60-
# It needs to yield mock stream objects
67+
# Mock the streams that connect_sse yields
6168
mock_read_stream = mock.AsyncMock()
6269
mock_write_stream = mock.AsyncMock()
70+
71+
# Mock the connect_sse async context manager
6372
mock_connect_sse_cm = mock.AsyncMock()
6473
mock_connect_sse_cm.__aenter__.return_value = (mock_read_stream, mock_write_stream)
74+
75+
# Mock the SseServerTransport instance
76+
mock_transport_instance = mock.Mock(spec=SseServerTransport)
77+
mock_transport_instance.connect_sse.return_value = mock_connect_sse_cm
78+
79+
# --- Create Mock Request and Call Handler Directly ---
80+
# Mock the Starlette Request object
81+
mock_request = mock.AsyncMock()
82+
# Mock app state attached to the request
83+
mock_request.app = mock.Mock()
84+
mock_request.app.state = mock.Mock()
85+
mock_request.app.state.mcp_server = mock_mcp_wrapper
86+
mock_request.app.state.sse_transport = mock_transport_instance
87+
mock_request.app.state.config = dummy_server_config # Include config if needed by handler
88+
# Mock client info if needed for logging within handler
89+
mock_request.client = mock.Mock()
90+
mock_request.client.host = "127.0.0.1"
91+
mock_request.client.port = 12345
92+
# Mock ASGI scope/receive/send if connect_sse needs them (it does)
93+
mock_request.scope = {}
94+
mock_request.receive = mock.AsyncMock()
95+
mock_request._send = mock.AsyncMock() # Note: _send is often used internally
6596

66-
app = sse_transport.create_starlette_app(mock_mcp, dummy_server_config)
67-
client = TestClient(app)
68-
69-
# Patch the SseServerTransport directly where it's used in handle_sse
70-
with mock.patch('cursor_notebook_mcp.sse_transport.SseServerTransport') as MockTransportClass:
71-
# Configure the instance's connect_sse method
72-
mock_transport_instance = MockTransportClass.return_value
73-
mock_transport_instance.connect_sse.return_value = mock_connect_sse_cm
74-
75-
# Simulate connecting to the SSE endpoint
76-
# TestClient doesn't fully support SSE streaming tests easily,
77-
# but we can verify the setup calls happen.
78-
# We expect the connection setup to proceed, calling connect_sse and then run.
79-
# Making a simple GET might not be enough, POST is often used for MCP init.
80-
# Let's try GET and see if connect_sse is called.
81-
try:
82-
# Use stream=True to mimic connection attempt, though full SSE isn't tested
83-
# We expect Starlette to handle the request and call our handler
84-
with client.stream("GET", "/sse") as response:
85-
# We don't need to consume the stream, just check mocks
86-
pass
87-
except Exception as e:
88-
# Ignore potential exceptions during stream handling in TestClient
89-
# as we are only checking mock calls
90-
print(f"Ignoring TestClient stream exception: {e}")
91-
pass
97+
# Call the handler directly
98+
await sse_transport.handle_sse(mock_request)
9299

93-
# Assertions
94-
MockTransportClass.assert_called_once() # Was SseServerTransport instantiated?
95-
mock_transport_instance.connect_sse.assert_called_once() # Was connect_sse called?
96-
# Assert that the MCP server's run method was called via the transport
97-
mock_mcp_internal_server.assert_awaited_once_with(
98-
transport='stream',
99-
read_stream=mock_read_stream,
100-
write_stream=mock_write_stream
101-
)
100+
# --- Assertions ---
101+
# Check connect_sse was called on the transport instance with ASGI args
102+
mock_transport_instance.connect_sse.assert_called_once_with(
103+
mock_request.scope, mock_request.receive, mock_request._send
104+
)
105+
106+
# Check create_initialization_options was called on the underlying server
107+
mock_create_options.assert_called_once()
108+
109+
# Check run was called on the underlying server with correct arguments
110+
mock_internal_run.assert_awaited_once_with(
111+
read_stream=mock_read_stream,
112+
write_stream=mock_write_stream,
113+
initialization_options=mock_init_options
114+
)
102115

103116
# TODO: Add tests for error handling in run_sse_server
104117

0 commit comments

Comments
 (0)