Add regression tests for caching with mounted server prefixes (#2762)

This commit is contained in:
Jeremiah Lowin 2025-12-26 16:33:03 -05:00 committed by GitHub
commit 2e8adafdf5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -521,3 +521,96 @@ class TestCachableToolResult:
assert cached_tool_result.content == tool_result.content
assert cached_tool_result.structured_content == tool_result.structured_content
assert cached_tool_result.meta == tool_result.meta
class TestCachingWithImportedServerPrefixes:
"""Test that caching preserves prefixes from imported servers.
Regression tests for issue #2300: ResponseCachingMiddleware was losing
prefix information when caching components from imported servers.
"""
@pytest.fixture
async def parent_with_imported_child(self, tracking_calculator: TrackingCalculator):
"""Create a parent server with an imported child server (prefixed)."""
child = FastMCP("child")
tracking_calculator.add_tools(fastmcp=child)
tracking_calculator.add_resources(fastmcp=child)
tracking_calculator.add_prompts(fastmcp=child)
parent = FastMCP("parent")
parent.add_middleware(ResponseCachingMiddleware())
parent.mount(child, namespace="child")
return parent
async def test_tool_prefixes_preserved_after_cache_hit(
self, parent_with_imported_child: FastMCP
):
"""Tool names should retain prefix after being served from cache."""
async with Client(parent_with_imported_child) as client:
# First call populates cache
tools_first = await client.list_tools()
tool_names_first = [t.name for t in tools_first]
# Second call should come from cache
tools_cached = await client.list_tools()
tool_names_cached = [t.name for t in tools_cached]
# All tools should have prefix in both calls
assert all(name.startswith("child_") for name in tool_names_first)
assert all(name.startswith("child_") for name in tool_names_cached)
assert tool_names_first == tool_names_cached
async def test_resource_prefixes_preserved_after_cache_hit(
self, parent_with_imported_child: FastMCP
):
"""Resource URIs should retain prefix after being served from cache."""
async with Client(parent_with_imported_child) as client:
# First call populates cache
resources_first = await client.list_resources()
resource_uris_first = [str(r.uri) for r in resources_first]
# Second call should come from cache
resources_cached = await client.list_resources()
resource_uris_cached = [str(r.uri) for r in resources_cached]
# All resources should have prefix in URI path in both calls
# Resources get path-style prefix: resource://child/path
assert all("://child/" in uri for uri in resource_uris_first)
assert all("://child/" in uri for uri in resource_uris_cached)
assert resource_uris_first == resource_uris_cached
async def test_prompt_prefixes_preserved_after_cache_hit(
self, parent_with_imported_child: FastMCP
):
"""Prompt names should retain prefix after being served from cache."""
async with Client(parent_with_imported_child) as client:
# First call populates cache
prompts_first = await client.list_prompts()
prompt_names_first = [p.name for p in prompts_first]
# Second call should come from cache
prompts_cached = await client.list_prompts()
prompt_names_cached = [p.name for p in prompts_cached]
# All prompts should have prefix in both calls
assert all(name.startswith("child_") for name in prompt_names_first)
assert all(name.startswith("child_") for name in prompt_names_cached)
assert prompt_names_first == prompt_names_cached
async def test_prefixed_tool_callable_after_cache_hit(
self,
parent_with_imported_child: FastMCP,
tracking_calculator: TrackingCalculator,
):
"""Prefixed tools should be callable after cache populates."""
async with Client(parent_with_imported_child) as client:
# Trigger cache population
await client.list_tools()
await client.list_tools() # From cache
# Tool should be callable with prefixed name
result = await client.call_tool("child_add", {"a": 5, "b": 3})
assert not result.is_error
assert tracking_calculator.add_calls == 1