mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-12 16:49:10 +02:00
* Switch to new OpenAPI parser as default Remove the legacy OpenAPI parser and make the experimental parser the default. The experimental parser (introduced in 2.11) offers better performance, improved compatibility, and a more maintainable architecture. - Delete legacy parser (server/openapi.py, utilities/openapi.py) - Move experimental parser to main locations - Remove enable_new_openapi_parser feature flag - Update documentation to remove experimental references * Add deprecation stubs for experimental OpenAPI imports * Add deprecated enable_new_openapi_parser setting and deprecation tests * SDK docs * REview comments * Fix docstrings * Update docstring * Review comments * Fix broken links
192 lines
6.4 KiB
Python
192 lines
6.4 KiB
Python
"""Tests to ensure OpenAPI schema generation works correctly."""
|
|
|
|
import pytest
|
|
|
|
from fastmcp.utilities.openapi.models import (
|
|
HTTPRoute,
|
|
ParameterInfo,
|
|
RequestBodyInfo,
|
|
)
|
|
from fastmcp.utilities.openapi.schemas import (
|
|
_combine_schemas_and_map_params,
|
|
)
|
|
|
|
|
|
class TestSchemaGeneration:
|
|
"""Test that OpenAPI schema generation produces correct schemas."""
|
|
|
|
def test_optional_parameter_nullable_behavior(self):
|
|
"""Test that optional parameters are not made nullable - they can simply be omitted."""
|
|
route = HTTPRoute(
|
|
method="GET",
|
|
path="/test",
|
|
operation_id="test_op",
|
|
parameters=[
|
|
ParameterInfo(
|
|
name="required_param",
|
|
location="query",
|
|
required=True,
|
|
schema={"type": "string"},
|
|
),
|
|
ParameterInfo(
|
|
name="optional_param",
|
|
location="query",
|
|
required=False,
|
|
schema={"type": "string"},
|
|
),
|
|
],
|
|
)
|
|
|
|
schema, _ = _combine_schemas_and_map_params(route)
|
|
|
|
# Required parameter should have simple type
|
|
assert schema["properties"]["required_param"]["type"] == "string"
|
|
assert "anyOf" not in schema["properties"]["required_param"]
|
|
|
|
# Optional parameters should preserve original schema without making it nullable
|
|
assert "anyOf" not in schema["properties"]["optional_param"]
|
|
assert schema["properties"]["optional_param"]["type"] == "string"
|
|
|
|
# Required list should only include required parameters
|
|
assert "required_param" in schema["required"]
|
|
assert "optional_param" not in schema["required"]
|
|
|
|
def test_parameter_collision_handling(self):
|
|
"""Test that parameter collisions are handled with suffixes."""
|
|
route = HTTPRoute(
|
|
method="PUT",
|
|
path="/users/{id}",
|
|
operation_id="update_user",
|
|
parameters=[
|
|
ParameterInfo(
|
|
name="id",
|
|
location="path",
|
|
required=True,
|
|
schema={"type": "integer"},
|
|
)
|
|
],
|
|
request_body=RequestBodyInfo(
|
|
required=True,
|
|
content_schema={
|
|
"application/json": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": {"type": "integer"},
|
|
"name": {"type": "string"},
|
|
},
|
|
"required": ["name"],
|
|
}
|
|
},
|
|
),
|
|
)
|
|
|
|
schema, param_map = _combine_schemas_and_map_params(route)
|
|
|
|
# Should have path parameter with suffix
|
|
assert "id__path" in schema["properties"]
|
|
|
|
# Should have body parameter without suffix
|
|
assert "id" in schema["properties"]
|
|
|
|
# Should have name parameter from body
|
|
assert "name" in schema["properties"]
|
|
|
|
# Required should include path param (suffixed) and required body params
|
|
required = set(schema["required"])
|
|
assert "id__path" in required
|
|
assert "name" in required
|
|
|
|
# Parameter map should correctly map suffixed parameter
|
|
assert param_map["id__path"]["location"] == "path"
|
|
assert param_map["id__path"]["openapi_name"] == "id"
|
|
assert param_map["id"]["location"] == "body"
|
|
assert param_map["name"]["location"] == "body"
|
|
|
|
@pytest.mark.parametrize(
|
|
"param_type",
|
|
[
|
|
{"type": "integer"},
|
|
{"type": "number"},
|
|
{"type": "boolean"},
|
|
{"type": "array", "items": {"type": "string"}},
|
|
{"type": "object", "properties": {"name": {"type": "string"}}},
|
|
],
|
|
)
|
|
def test_nullable_behavior_different_types(self, param_type):
|
|
"""Test nullable behavior works for all parameter types."""
|
|
route = HTTPRoute(
|
|
method="GET",
|
|
path="/test",
|
|
operation_id="test_op",
|
|
parameters=[
|
|
ParameterInfo(
|
|
name="optional_param",
|
|
location="query",
|
|
required=False,
|
|
schema=param_type,
|
|
)
|
|
],
|
|
)
|
|
|
|
schema, _ = _combine_schemas_and_map_params(route)
|
|
|
|
# Should preserve original schema without making it nullable
|
|
param = schema["properties"]["optional_param"]
|
|
assert "anyOf" not in param
|
|
|
|
# Should match the original parameter schema
|
|
for key, value in param_type.items():
|
|
assert param[key] == value
|
|
|
|
def test_no_parameters_no_body(self):
|
|
"""Test schema generation when there are no parameters or body."""
|
|
route = HTTPRoute(
|
|
method="GET",
|
|
path="/health",
|
|
operation_id="health_check",
|
|
)
|
|
|
|
schema, param_map = _combine_schemas_and_map_params(route)
|
|
|
|
# Should have empty object schema
|
|
assert schema["type"] == "object"
|
|
assert schema["properties"] == {}
|
|
assert schema["required"] == []
|
|
assert param_map == {}
|
|
|
|
def test_body_only_no_parameters(self):
|
|
"""Test schema generation with only request body, no parameters."""
|
|
body_schema = {
|
|
"application/json": {
|
|
"type": "object",
|
|
"properties": {
|
|
"title": {"type": "string"},
|
|
"description": {"type": "string"},
|
|
},
|
|
"required": ["title"],
|
|
}
|
|
}
|
|
|
|
route = HTTPRoute(
|
|
method="POST",
|
|
path="/items",
|
|
operation_id="create_item",
|
|
request_body=RequestBodyInfo(
|
|
required=True,
|
|
content_schema=body_schema,
|
|
),
|
|
)
|
|
|
|
schema, param_map = _combine_schemas_and_map_params(route)
|
|
|
|
# Should have body properties
|
|
assert "title" in schema["properties"]
|
|
assert "description" in schema["properties"]
|
|
|
|
# Required should match body requirements
|
|
assert "title" in schema["required"]
|
|
assert "description" not in schema["required"]
|
|
|
|
# Parameter map should map body properties
|
|
assert param_map["title"]["location"] == "body"
|
|
assert param_map["description"]["location"] == "body"
|