Spaces:
Running
Running
| """Tests for allOf handling at requestBody top level.""" | |
| from fastmcp.experimental.utilities.openapi.models import ( | |
| HTTPRoute, | |
| RequestBodyInfo, | |
| ) | |
| from fastmcp.experimental.utilities.openapi.schemas import _combine_schemas | |
| def test_allof_at_requestbody_top_level(): | |
| """Test that allOf schemas at requestBody top level are properly merged.""" | |
| # Create a route with allOf at the requestBody top level | |
| route = HTTPRoute( | |
| path="/test", | |
| method="POST", | |
| operation_id="testOperation", | |
| parameters=[], | |
| request_body=RequestBodyInfo( | |
| required=True, | |
| content_schema={ | |
| "application/json": { | |
| "allOf": [ | |
| { | |
| "type": "object", | |
| "properties": { | |
| "name": {"type": "string"}, | |
| "age": {"type": "integer"}, | |
| }, | |
| "required": ["name"], | |
| }, | |
| { | |
| "type": "object", | |
| "properties": { | |
| "email": {"type": "string"}, | |
| "phone": {"type": "string"}, | |
| }, | |
| "required": ["email"], | |
| }, | |
| ] | |
| } | |
| }, | |
| ), | |
| responses={}, | |
| ) | |
| # Combine schemas - this should merge allOf schemas | |
| combined = _combine_schemas(route) | |
| # Check that all properties from both allOf schemas are present | |
| properties = combined.get("properties", {}) | |
| assert "name" in properties | |
| assert "age" in properties | |
| assert "email" in properties | |
| assert "phone" in properties | |
| # Check property types | |
| assert properties["name"]["type"] == "string" | |
| assert properties["age"]["type"] == "integer" | |
| assert properties["email"]["type"] == "string" | |
| assert properties["phone"]["type"] == "string" | |
| # Check that required fields are merged correctly | |
| required = set(combined.get("required", [])) | |
| assert "name" in required | |
| assert "email" in required | |
| # allOf should be removed after merging | |
| assert "allOf" not in combined | |
| def test_allof_with_nested_properties(): | |
| """Test allOf with nested object properties.""" | |
| route = HTTPRoute( | |
| path="/test", | |
| method="POST", | |
| operation_id="testNested", | |
| parameters=[], | |
| request_body=RequestBodyInfo( | |
| required=True, | |
| content_schema={ | |
| "application/json": { | |
| "allOf": [ | |
| { | |
| "type": "object", | |
| "properties": { | |
| "user": { | |
| "type": "object", | |
| "properties": { | |
| "id": {"type": "integer"}, | |
| "name": {"type": "string"}, | |
| }, | |
| } | |
| }, | |
| "required": ["user"], | |
| }, | |
| { | |
| "type": "object", | |
| "properties": { | |
| "metadata": { | |
| "type": "object", | |
| "properties": { | |
| "created": {"type": "string"}, | |
| "updated": {"type": "string"}, | |
| }, | |
| } | |
| }, | |
| }, | |
| ] | |
| } | |
| }, | |
| ), | |
| responses={}, | |
| ) | |
| combined = _combine_schemas(route) | |
| # Check nested properties are preserved | |
| properties = combined.get("properties", {}) | |
| assert "user" in properties | |
| assert "metadata" in properties | |
| # Check nested structure | |
| assert properties["user"]["type"] == "object" | |
| assert "id" in properties["user"]["properties"] | |
| assert "name" in properties["user"]["properties"] | |
| assert properties["metadata"]["type"] == "object" | |
| assert "created" in properties["metadata"]["properties"] | |
| assert "updated" in properties["metadata"]["properties"] | |
| # Check required | |
| required = set(combined.get("required", [])) | |
| assert "user" in required | |
| assert "metadata" not in required # Not in any required array | |
| def test_allof_with_overlapping_properties(): | |
| """Test allOf with overlapping property names (later schemas override).""" | |
| route = HTTPRoute( | |
| path="/test", | |
| method="POST", | |
| operation_id="testOverlap", | |
| parameters=[], | |
| request_body=RequestBodyInfo( | |
| required=True, | |
| content_schema={ | |
| "application/json": { | |
| "allOf": [ | |
| { | |
| "type": "object", | |
| "properties": { | |
| "name": {"type": "string", "minLength": 1}, | |
| "age": {"type": "integer"}, | |
| }, | |
| "required": ["name"], | |
| }, | |
| { | |
| "type": "object", | |
| "properties": { | |
| "name": {"type": "string", "maxLength": 50}, # Override | |
| "email": {"type": "string"}, | |
| }, | |
| "required": ["email"], | |
| }, | |
| ] | |
| } | |
| }, | |
| ), | |
| responses={}, | |
| ) | |
| combined = _combine_schemas(route) | |
| properties = combined.get("properties", {}) | |
| # Later schema should win for overlapping properties | |
| assert "name" in properties | |
| assert properties["name"]["type"] == "string" | |
| assert "maxLength" in properties["name"] # From second schema | |
| assert properties["name"]["maxLength"] == 50 | |
| # Check other properties | |
| assert "age" in properties | |
| assert "email" in properties | |
| # Both name and email should be required | |
| required = set(combined.get("required", [])) | |
| assert "name" in required | |
| assert "email" in required | |
| def test_no_allof_passthrough(): | |
| """Test that schemas without allOf pass through unchanged.""" | |
| route = HTTPRoute( | |
| path="/test", | |
| method="POST", | |
| operation_id="testNoAllOf", | |
| parameters=[], | |
| request_body=RequestBodyInfo( | |
| required=True, | |
| content_schema={ | |
| "application/json": { | |
| "type": "object", | |
| "properties": {"simple": {"type": "string"}}, | |
| "required": ["simple"], | |
| } | |
| }, | |
| ), | |
| responses={}, | |
| ) | |
| combined = _combine_schemas(route) | |
| # Should pass through unchanged | |
| properties = combined.get("properties", {}) | |
| assert "simple" in properties | |
| assert properties["simple"]["type"] == "string" | |
| required = set(combined.get("required", [])) | |
| assert "simple" in required | |
| # No allOf in original or result | |
| assert "allOf" not in combined | |