diff --git a/pyproject.toml b/pyproject.toml index f643b353b..c105a51e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,6 +64,7 @@ dev = [ "pytest-flakefinder", "pytest-httpx>=0.35.0", "pytest-report>=0.2.1", + "pytest-retry>=1.7.0", "pytest-timeout>=2.4.0", "pytest-xdist>=3.6.1", "ruff", diff --git a/tests/integration_tests/test_github_mcp_remote.py b/tests/integration_tests/test_github_mcp_remote.py index fb122a2e6..d8f3c47b8 100644 --- a/tests/integration_tests/test_github_mcp_remote.py +++ b/tests/integration_tests/test_github_mcp_remote.py @@ -33,85 +33,89 @@ def fixture_streamable_http_client() -> Client[StreamableHttpTransport]: ) ) +@pytest.mark.flaky(retries=2, delay=1) +class TestGithubMCPRemote: -async def test_connect_disconnect( - streamable_http_client: Client[StreamableHttpTransport], -): - async with streamable_http_client: - assert streamable_http_client.is_connected() is True - await streamable_http_client._disconnect() # pylint: disable=W0212 (protected-access) - assert streamable_http_client.is_connected() is False + async def test_connect_disconnect( + self, + streamable_http_client: Client[StreamableHttpTransport], + ): + async with streamable_http_client: + assert streamable_http_client.is_connected() is True + await streamable_http_client._disconnect() # pylint: disable=W0212 (protected-access) + assert streamable_http_client.is_connected() is False -async def test_ping(streamable_http_client: Client[StreamableHttpTransport]): - """Test pinging the server.""" - async with streamable_http_client: - assert streamable_http_client.is_connected() is True - result = await streamable_http_client.ping() - assert result is True + async def test_ping(self, streamable_http_client: Client[StreamableHttpTransport]): + """Test pinging the server.""" + async with streamable_http_client: + assert streamable_http_client.is_connected() is True + result = await streamable_http_client.ping() + assert result is True -async def test_list_tools(streamable_http_client: Client[StreamableHttpTransport]): - """Test listing the MCP tools""" - async with streamable_http_client: - assert streamable_http_client.is_connected() - tools = await streamable_http_client.list_tools() - assert isinstance(tools, list) - assert len(tools) > 0 # Ensure the tools list is non-empty - for tool in tools: - assert isinstance(tool, Tool) - assert len(tool.name) > 0 - assert tool.description is not None and len(tool.description) > 0 - assert isinstance(tool.inputSchema, dict) - assert len(tool.inputSchema) > 0 + async def test_list_tools(self, streamable_http_client: Client[StreamableHttpTransport]): + """Test listing the MCP tools""" + async with streamable_http_client: + assert streamable_http_client.is_connected() + tools = await streamable_http_client.list_tools() + assert isinstance(tools, list) + assert len(tools) > 0 # Ensure the tools list is non-empty + for tool in tools: + assert isinstance(tool, Tool) + assert len(tool.name) > 0 + assert tool.description is not None and len(tool.description) > 0 + assert isinstance(tool.inputSchema, dict) + assert len(tool.inputSchema) > 0 -async def test_list_resources(streamable_http_client: Client[StreamableHttpTransport]): - """Test listing the MCP resources""" - async with streamable_http_client: - assert streamable_http_client.is_connected() - resources = await streamable_http_client.list_resources() - assert isinstance(resources, list) - assert len(resources) == 0 + async def test_list_resources(self, streamable_http_client: Client[StreamableHttpTransport]): + """Test listing the MCP resources""" + async with streamable_http_client: + assert streamable_http_client.is_connected() + resources = await streamable_http_client.list_resources() + assert isinstance(resources, list) + assert len(resources) == 0 -async def test_list_prompts(streamable_http_client: Client[StreamableHttpTransport]): - """Test listing the MCP prompts""" - async with streamable_http_client: - assert streamable_http_client.is_connected() - prompts = await streamable_http_client.list_prompts() - # there is at least one prompt (as of July 2025) - assert len(prompts) >= 1 + async def test_list_prompts(self, streamable_http_client: Client[StreamableHttpTransport]): + """Test listing the MCP prompts""" + async with streamable_http_client: + assert streamable_http_client.is_connected() + prompts = await streamable_http_client.list_prompts() + # there is at least one prompt (as of July 2025) + assert len(prompts) >= 1 -async def test_call_tool_ko(streamable_http_client: Client[StreamableHttpTransport]): - """Test calling a non-existing tool""" - async with streamable_http_client: - assert streamable_http_client.is_connected() - with pytest.raises(McpError, match="tool not found"): - await streamable_http_client.call_tool("foo") + async def test_call_tool_ko(self, streamable_http_client: Client[StreamableHttpTransport]): + """Test calling a non-existing tool""" + async with streamable_http_client: + assert streamable_http_client.is_connected() + with pytest.raises(McpError, match="tool not found"): + await streamable_http_client.call_tool("foo") -async def test_call_tool_list_commits( - streamable_http_client: Client[StreamableHttpTransport], -): - """Test calling a list_commit tool""" - async with streamable_http_client: - assert streamable_http_client.is_connected() - result = await streamable_http_client.call_tool( - "list_commits", {"owner": "jlowin", "repo": "fastmcp"} - ) + async def test_call_tool_list_commits( + self, + streamable_http_client: Client[StreamableHttpTransport], + ): + """Test calling a list_commit tool""" + async with streamable_http_client: + assert streamable_http_client.is_connected() + result = await streamable_http_client.call_tool( + "list_commits", {"owner": "jlowin", "repo": "fastmcp"} + ) - # at this time, the github server does not support structured content - assert result.structured_content is None - assert isinstance(result.content, list) - assert len(result.content) == 1 - commits = json.loads(result.content[0].text) # type: ignore[attr-defined] - for commit in commits: - assert isinstance(commit, dict) - assert "sha" in commit - assert "commit" in commit - assert "author" in commit["commit"] - assert len(commit["commit"]["author"]["date"]) > 0 - assert len(commit["commit"]["author"]["name"]) > 0 - assert len(commit["commit"]["author"]["email"]) > 0 + # at this time, the github server does not support structured content + assert result.structured_content is None + assert isinstance(result.content, list) + assert len(result.content) == 1 + commits = json.loads(result.content[0].text) # type: ignore[attr-defined] + for commit in commits: + assert isinstance(commit, dict) + assert "sha" in commit + assert "commit" in commit + assert "author" in commit["commit"] + assert len(commit["commit"]["author"]["date"]) > 0 + assert len(commit["commit"]["author"]["name"]) > 0 + assert len(commit["commit"]["author"]["email"]) > 0 diff --git a/uv.lock b/uv.lock index 243457b91..82b662287 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.11'", @@ -558,6 +558,7 @@ dev = [ { name = "pytest-flakefinder" }, { name = "pytest-httpx" }, { name = "pytest-report" }, + { name = "pytest-retry" }, { name = "pytest-timeout" }, { name = "pytest-xdist" }, { name = "ruff" }, @@ -601,6 +602,7 @@ dev = [ { name = "pytest-flakefinder" }, { name = "pytest-httpx", specifier = ">=0.35.0" }, { name = "pytest-report", specifier = ">=0.2.1" }, + { name = "pytest-retry", specifier = ">=1.7.0" }, { name = "pytest-timeout", specifier = ">=2.4.0" }, { name = "pytest-xdist", specifier = ">=3.6.1" }, { name = "ruff" }, @@ -1588,6 +1590,18 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/3b/82/e141da085de0b6dac3f047ae009e136bcedbcfca4ada082a55359d6f735e/pytest-report-0.2.1.tar.gz", hash = "sha256:d382e8db4c52a815d39dae5f21ee5edc0da3ae8ec19a22e55e9be5c60714a39d", size = 3517, upload-time = "2016-05-11T02:08:04.665Z" } +[[package]] +name = "pytest-retry" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/5b/607b017994cca28de3a1ad22a3eee8418e5d428dcd8ec25b26b18e995a73/pytest_retry-1.7.0.tar.gz", hash = "sha256:f8d52339f01e949df47c11ba9ee8d5b362f5824dff580d3870ec9ae0057df80f", size = 19977, upload-time = "2025-01-19T01:56:13.115Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/ff/3266c8a73b9b93c4b14160a7e2b31d1e1088e28ed29f4c2d93ae34093bfd/pytest_retry-1.7.0-py3-none-any.whl", hash = "sha256:a2dac85b79a4e2375943f1429479c65beb6c69553e7dae6b8332be47a60954f4", size = 13775, upload-time = "2025-01-19T01:56:11.199Z" }, +] + [[package]] name = "pytest-timeout" version = "2.4.0"