Skip to main content
Glama

FastAPI OpenAPI MCP Server

by jason-chang
test_base.py6.1 kB
""" 测试 BaseMcpTool 基类 """ from typing import Any from unittest.mock import MagicMock import pytest from mcp.types import CallToolResult, TextContent from openapi_mcp.tools.base import BaseMcpTool class ConcreteTool(BaseMcpTool): """测试用的具体 Tool 实现""" name = 'test_tool' description = '测试工具' input_schema = { 'type': 'object', 'properties': {'param': {'type': 'string', 'description': '测试参数'}}, 'required': ['param'], } async def execute(self, **kwargs: Any) -> CallToolResult: """实现 execute 方法""" param = kwargs.get('param', 'default') return CallToolResult( content=[TextContent(type='text', text=f'执行结果: {param}')] ) class AbstractTool(BaseMcpTool): """未实现 execute 方法的 Tool(用于测试抽象方法强制实现)""" name = 'abstract_tool' description = '抽象工具' @pytest.fixture def mock_server() -> MagicMock: """创建 mock 的 OpenApiMcpServer""" server = MagicMock() server._get_openapi_spec.return_value = { 'openapi': '3.0.0', 'info': {'title': 'Test API', 'version': '1.0.0'}, 'paths': { '/users': { 'get': {'summary': 'List users', 'tags': ['users']}, } }, } return server class TestBaseMcpTool: """测试 BaseMcpTool 基类""" def test_tool_initialization(self, mock_server: MagicMock) -> None: """测试 Tool 初始化""" tool = ConcreteTool(mock_server) assert tool.server is mock_server assert tool.name == 'test_tool' assert tool.description == '测试工具' assert tool.input_schema is not None assert tool.input_schema['type'] == 'object' def test_get_openapi_spec(self, mock_server: MagicMock) -> None: """测试获取 OpenAPI spec""" tool = ConcreteTool(mock_server) spec = tool.get_openapi_spec() assert spec is not None assert spec['openapi'] == '3.0.0' assert 'paths' in spec assert '/users' in spec['paths'] mock_server._get_openapi_spec.assert_called_once() async def test_execute_implementation(self, mock_server: MagicMock) -> None: """测试 execute 方法的实现""" tool = ConcreteTool(mock_server) result = await tool.execute(param='test_value') assert isinstance(result, CallToolResult) assert len(result.content) == 1 text_content = result.content[0] assert isinstance(text_content, TextContent) assert text_content.type == 'text' assert '执行结果: test_value' in text_content.text async def test_execute_with_default_value(self, mock_server: MagicMock) -> None: """测试 execute 方法使用默认值""" tool = ConcreteTool(mock_server) result = await tool.execute() assert isinstance(result, CallToolResult) text_content = result.content[0] assert isinstance(text_content, TextContent) assert '执行结果: default' in text_content.text def test_abstract_method_enforcement(self, mock_server: MagicMock) -> None: """测试抽象方法必须实现""" # Python 的 ABC 在实例化时就会检查抽象方法 # 尝试创建未实现 execute 方法的实例应该抛出 TypeError with pytest.raises( TypeError, match="Can't instantiate abstract class AbstractTool without an implementation for abstract method 'execute'", ): AbstractTool(mock_server) # type: ignore[abstract] def test_tool_without_input_schema(self, mock_server: MagicMock) -> None: """测试没有输入参数的 Tool""" class NoInputTool(BaseMcpTool): name = 'no_input_tool' description = '无输入参数的工具' input_schema = None async def execute(self, **kwargs: Any) -> CallToolResult: return CallToolResult( content=[TextContent(type='text', text='无参数执行')] ) tool = NoInputTool(mock_server) assert tool.input_schema is None assert tool.name == 'no_input_tool' async def test_tool_execution_without_schema(self, mock_server: MagicMock) -> None: """测试无 schema 的 Tool 可以正常执行""" class NoInputTool(BaseMcpTool): name = 'no_input_tool' description = '无输入参数的工具' input_schema = None async def execute(self, **kwargs: Any) -> CallToolResult: return CallToolResult( content=[TextContent(type='text', text='无参数执行')] ) tool = NoInputTool(mock_server) result = await tool.execute() assert isinstance(result, CallToolResult) text_content = result.content[0] assert isinstance(text_content, TextContent) assert '无参数执行' in text_content.text def test_get_openapi_spec_multiple_calls(self, mock_server: MagicMock) -> None: """测试多次调用 get_openapi_spec""" tool = ConcreteTool(mock_server) spec1 = tool.get_openapi_spec() spec2 = tool.get_openapi_spec() assert spec1 == spec2 # 验证调用了两次(没有在 Tool 层面缓存) assert mock_server._get_openapi_spec.call_count == 2 def test_tool_attributes_are_class_level(self, mock_server: MagicMock) -> None: """测试 Tool 属性是类级别的""" tool1 = ConcreteTool(mock_server) tool2 = ConcreteTool(mock_server) # name 和 description 应该是相同的(类属性) assert tool1.name == tool2.name assert tool1.description == tool2.description assert tool1.input_schema == tool2.input_schema async def test_execute_with_multiple_params(self, mock_server: MagicMock) -> None: """测试带多个参数的 execute 方法""" class MultiParamTool(BaseMcpTool): name = 'multi_param_tool' description = '多参数工具' input_schema = { 'type': 'object', 'properties': { 'param1': {'type': 'string'}, 'param2': {'type': 'integer'}, }, } async def execute(self, **kwargs: Any) -> CallToolResult: param1 = kwargs.get('param1', 'default1') param2 = kwargs.get('param2', 0) return CallToolResult( content=[ TextContent( type='text', text=f'参数1: {param1}, 参数2: {param2}' ) ] ) tool = MultiParamTool(mock_server) result = await tool.execute(param1='test', param2=42) text_content = result.content[0] assert isinstance(text_content, TextContent) assert '参数1: test, 参数2: 42' in text_content.text

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jason-chang/fastapi-openapi-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server