"""Unit tests for OpenAPIProvider.""" import httpx import pytest from fastmcp import FastMCP from fastmcp.client import Client from fastmcp.server.providers.openapi import OpenAPIProvider from fastmcp.server.providers.openapi.provider import DEFAULT_TIMEOUT class TestOpenAPIProviderServerVariables: """Test that OpenAPIProvider resolves OpenAPI 3.x server variables.""" def test_server_variables_substituted_with_defaults(self): spec = { "openapi": "3.0.0", "info": {"title": "Test API", "version": "1.0.0"}, "servers": [ { "url": "https://{region}.api.example.com/v1", "variables": { "region": { "default": "us", "enum": ["us", "eu", "apac"], } }, } ], "paths": {}, } client = OpenAPIProvider._create_default_client(spec) assert str(client.base_url) == "https://us.api.example.com/v1/" def test_multiple_server_variables_substituted(self): spec = { "openapi": "3.0.0", "info": {"title": "Test API", "version": "1.0.0"}, "servers": [ { "url": "{scheme}://{host}/v1", "variables": { "scheme": {"default": "https"}, "host": {"default": "api.example.com"}, }, } ], "paths": {}, } client = OpenAPIProvider._create_default_client(spec) assert str(client.base_url) == "https://api.example.com/v1/" def test_static_server_url_unaffected(self): spec = { "openapi": "3.0.0", "info": {"title": "Test API", "version": "1.0.0"}, "servers": [{"url": "https://api.example.com"}], "paths": {}, } client = OpenAPIProvider._create_default_client(spec) assert str(client.base_url) == "https://api.example.com" class TestOpenAPIProviderBasicFunctionality: """Test basic OpenAPIProvider functionality.""" @pytest.fixture def simple_openapi_spec(self): """Simple OpenAPI spec for testing.""" return { "openapi": "3.0.0", "info": {"title": "Test API", "version": "1.0.0"}, "servers": [{"url": "https://api.example.com"}], "paths": { "/users/{id}": { "get": { "operationId": "get_user", "summary": "Get user by ID", "parameters": [ { "name": "id", "in": "path", "required": True, "schema": {"type": "integer"}, } ], "responses": { "200": { "description": "User retrieved successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": {"type": "integer"}, "name": {"type": "string"}, "email": {"type": "string"}, }, } } }, } }, } }, "/users": { "post": { "operationId": "create_user", "summary": "Create a new user", "requestBody": { "required": True, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": {"type": "string"}, "email": {"type": "string"}, }, "required": ["name", "email"], } } }, }, "responses": { "201": { "description": "User created successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": {"type": "integer"}, "name": {"type": "string"}, "email": {"type": "string"}, }, } } }, } }, } }, }, } def test_provider_initialization(self, simple_openapi_spec): """Test provider initialization with OpenAPI spec.""" client = httpx.AsyncClient(base_url="https://api.example.com") provider = OpenAPIProvider(openapi_spec=simple_openapi_spec, client=client) # Should have initialized RequestDirector successfully assert hasattr(provider, "_director") assert hasattr(provider, "_spec") def test_server_with_provider(self, simple_openapi_spec): """Test server initialization with OpenAPIProvider.""" client = httpx.AsyncClient(base_url="https://api.example.com") provider = OpenAPIProvider(openapi_spec=simple_openapi_spec, client=client) mcp = FastMCP("Test Server") mcp.add_provider(provider) assert mcp.name == "Test Server" async def test_provider_creates_tools_from_spec(self, simple_openapi_spec): """Test that provider creates tools from OpenAPI spec.""" async with httpx.AsyncClient(base_url="https://api.example.com") as client: provider = OpenAPIProvider(openapi_spec=simple_openapi_spec, client=client) mcp = FastMCP("Test Server") mcp.add_provider(provider) async with Client(mcp) as mcp_client: tools = await mcp_client.list_tools() # Should have created tools for both operations assert len(tools) == 2 tool_names = {tool.name for tool in tools} assert "get_user" in tool_names assert "create_user" in tool_names async def test_provider_tool_execution(self, simple_openapi_spec): """Test tool execution uses RequestDirector.""" mock_client = httpx.AsyncClient() provider = OpenAPIProvider(openapi_spec=simple_openapi_spec, client=mock_client) mcp = FastMCP("Test Server") mcp.add_provider(provider) async with Client(mcp) as mcp_client: tools = await mcp_client.list_tools() # Should have tools using RequestDirector assert len(tools) == 2 get_user_tool = next(tool for tool in tools if tool.name == "get_user") assert get_user_tool is not None assert get_user_tool.description is not None def test_provider_creates_default_client_from_spec(self, simple_openapi_spec): """Test that omitting client creates one from the spec's servers URL.""" provider = OpenAPIProvider(openapi_spec=simple_openapi_spec) assert str(provider._client.base_url).rstrip("/") == "https://api.example.com" assert provider._client.timeout == httpx.Timeout(DEFAULT_TIMEOUT) def test_provider_default_client_requires_servers(self): """Test that omitting client without servers in spec raises.""" spec = { "openapi": "3.0.0", "info": {"title": "No Servers", "version": "1.0.0"}, "paths": {}, } with pytest.raises(ValueError, match="No server URL"): OpenAPIProvider(openapi_spec=spec) def test_provider_with_empty_spec(self): """Test provider with minimal OpenAPI spec.""" minimal_spec = { "openapi": "3.0.0", "info": {"title": "Empty API", "version": "1.0.0"}, "paths": {}, } client = httpx.AsyncClient(base_url="https://api.example.com") provider = OpenAPIProvider(openapi_spec=minimal_spec, client=client) # Should handle empty paths gracefully assert hasattr(provider, "_director") assert hasattr(provider, "_spec") async def test_clean_schema_output_no_unused_defs(self): """Test that unused schema definitions are removed from tool schemas.""" spec_with_unused_defs = { "openapi": "3.0.0", "info": {"title": "Test API", "version": "1.0.0"}, "servers": [{"url": "https://api.example.com"}], "paths": { "/users": { "post": { "operationId": "create_user", "summary": "Create a new user", "requestBody": { "required": True, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": {"type": "string", "title": "Name"}, "active": { "type": "boolean", "title": "Active", }, }, "required": ["name", "active"], } } }, }, "responses": { "200": { "description": "User created successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "integer", "title": "Id", }, "name": { "type": "string", "title": "Name", }, "active": { "type": "boolean", "title": "Active", }, }, "required": ["id", "name", "active"], "title": "User", } } }, } }, } } }, "components": { "schemas": { "HTTPValidationError": { "properties": { "detail": { "items": { "$ref": "#/components/schemas/ValidationError" }, "title": "Detail", "type": "array", } }, "title": "HTTPValidationError", "type": "object", }, "ValidationError": { "properties": { "loc": { "items": { "anyOf": [{"type": "string"}, {"type": "integer"}] }, "title": "Location", "type": "array", }, "msg": {"title": "Message", "type": "string"}, "type": {"title": "Error Type", "type": "string"}, }, "required": ["loc", "msg", "type"], "title": "ValidationError", "type": "object", }, } }, } async with httpx.AsyncClient(base_url="https://api.example.com") as client: provider = OpenAPIProvider( openapi_spec=spec_with_unused_defs, client=client ) mcp = FastMCP("Test Server") mcp.add_provider(provider) async with Client(mcp) as mcp_client: tools = await mcp_client.list_tools() assert len(tools) == 1 tool = tools[0] assert tool.name == "create_user" expected_input_schema = { "type": "object", "properties": { "name": {"type": "string", "title": "Name"}, "active": {"type": "boolean", "title": "Active"}, }, "required": ["name", "active"], } assert tool.input_schema == expected_input_schema expected_output_schema = { "type": "object", "properties": { "id": {"type": "integer", "title": "Id"}, "name": {"type": "string", "title": "Name"}, "active": {"type": "boolean", "title": "Active"}, }, "required": ["id", "name", "active"], "title": "User", } assert tool.output_schema == expected_output_schema