fastmcp/tests/utilities/test_tests.py
Viktor Szépe 81fada4922
Fix typos (#4498)
* Fix typos

* Document Cachable* -> Cacheable* rename as v4 breaking change

Adds the response-cache model rename to the change register, per
maintainer decision to skip compatibility aliases in favor of clear
documentation.

* Skip invalid Before import in doc test; use inline codespell ignore

The Cachable* -> Cacheable* breaking-change entry showed the old,
now-invalid import for contrast, which the doc-example test picked up
as a real import and flagged as a regression. Comment out the
deliberately-broken "Before" line (matching the McpError entry just
above it) so only the working "After" import is exercised.

Also swap the blanket codespell ignore-words-list entry for a
narrower inline `codespell:ignore` directive on the one line that
needs it, so codespell keeps flagging "cachable" everywhere else.

---------

Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
2026-07-18 21:18:39 -04:00

90 lines
3.6 KiB
Python

from unittest.mock import AsyncMock, MagicMock, patch
import pytest
import fastmcp
from fastmcp import FastMCP
from fastmcp.utilities.tests import HeadlessOAuth, temporary_settings
class TestTemporarySettings:
def test_temporary_settings(self):
assert fastmcp.settings.log_level == "DEBUG"
with temporary_settings(log_level="ERROR"):
assert fastmcp.settings.log_level == "ERROR"
assert fastmcp.settings.log_level == "DEBUG"
class TestTransportSetting:
def test_transport_default_is_stdio(self):
assert fastmcp.settings.transport == "stdio"
def test_transport_setting_can_be_changed(self):
with temporary_settings(transport="http"):
assert fastmcp.settings.transport == "http"
assert fastmcp.settings.transport == "stdio"
async def test_run_async_uses_transport_setting(self):
mcp = FastMCP("test")
with temporary_settings(transport="http"):
with patch.object(
mcp, "run_http_async", new_callable=AsyncMock
) as mock_http:
await mcp.run_async()
mock_http.assert_called_once()
async def test_run_async_explicit_transport_overrides_setting(self):
mcp = FastMCP("test")
with temporary_settings(transport="http"):
with patch.object(
mcp, "run_stdio_async", new_callable=AsyncMock
) as mock_stdio:
await mcp.run_async(transport="stdio")
mock_stdio.assert_called_once()
class TestHeadlessOAuthCallbackHandler:
"""Regression tests for #4056: blank query values must survive parse_qs.
The OAuth callback handler in HeadlessOAuth parses the redirect Location
header. parse_qs without keep_blank_values=True silently drops keys whose
value is empty (e.g. `?state=`), which misrepresents real OAuth callbacks
where an empty `state` is distinct from a missing one.
"""
def _make_oauth_with_redirect(self, location: str) -> HeadlessOAuth:
"""Build a HeadlessOAuth with a fake stored 302 response."""
oauth = HeadlessOAuth.__new__(HeadlessOAuth)
response = MagicMock()
response.status_code = 302
response.headers = {"location": location}
oauth._stored_response = response
return oauth
async def test_callback_preserves_blank_state(self):
"""An explicitly-empty state must round-trip as "" rather than None."""
oauth = self._make_oauth_with_redirect(
"https://example.com/callback?code=abc&state="
)
result = await oauth.callback_handler()
assert result.code == "abc"
assert result.state == ""
async def test_callback_returns_none_when_state_missing(self):
"""A truly missing state still returns None (default)."""
oauth = self._make_oauth_with_redirect("https://example.com/callback?code=abc")
result = await oauth.callback_handler()
assert result.code == "abc"
assert result.state is None
async def test_callback_uses_blank_error_description_verbatim(self):
"""When the OAuth provider sends an empty error_description, surface
it as "" rather than falling back to "Unknown error". The fallback is
meant for the truly-absent case; with keep_blank_values=True the
explicit empty value is preserved and used directly.
"""
oauth = self._make_oauth_with_redirect(
"https://example.com/callback?error=invalid_request&error_description="
)
with pytest.raises(RuntimeError, match=r"invalid_request - $"):
await oauth.callback_handler()