diff --git a/tests/test_configuration.py b/tests/test_configuration.py index da9c805..3b172cc 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -437,3 +437,181 @@ def test_filtering_functionality(): with pytest.raises(ValueError): FastApiMCP(app, include_tags=["items"], exclude_tags=["write"]) + + +def test_filtering_edge_cases(): + """Test edge cases for the filtering functionality.""" + app = FastAPI() + + # Define endpoints with different operation IDs and tags + @app.get("/items/", operation_id="list_items", tags=["items"]) + async def list_items(): + return [{"id": 1}] + + @app.get("/items/{item_id}", operation_id="get_item", tags=["items", "read"]) + async def get_item(item_id: int): + return {"id": item_id} + + # Test with no filtering (default behavior) + default_mcp = FastApiMCP(app) + assert len(default_mcp.tools) == 2 + assert {tool.name for tool in default_mcp.tools} == {"get_item", "list_items"} + + # Test with empty include_operations + empty_include_ops_mcp = FastApiMCP(app, include_operations=[]) + assert len(empty_include_ops_mcp.tools) == 0 + assert empty_include_ops_mcp.tools == [] + + # Test with empty exclude_operations (should include all) + empty_exclude_ops_mcp = FastApiMCP(app, exclude_operations=[]) + assert len(empty_exclude_ops_mcp.tools) == 2 + assert {tool.name for tool in empty_exclude_ops_mcp.tools} == {"get_item", "list_items"} + + # Test with empty include_tags + empty_include_tags_mcp = FastApiMCP(app, include_tags=[]) + assert len(empty_include_tags_mcp.tools) == 0 + assert empty_include_tags_mcp.tools == [] + + # Test with empty exclude_tags (should include all) + empty_exclude_tags_mcp = FastApiMCP(app, exclude_tags=[]) + assert len(empty_exclude_tags_mcp.tools) == 2 + assert {tool.name for tool in empty_exclude_tags_mcp.tools} == {"get_item", "list_items"} + + # Test with non-existent operation IDs + nonexistent_ops_mcp = FastApiMCP(app, include_operations=["non_existent_op"]) + assert len(nonexistent_ops_mcp.tools) == 0 + assert nonexistent_ops_mcp.tools == [] + + # Test with non-existent tags + nonexistent_tags_mcp = FastApiMCP(app, include_tags=["non_existent_tag"]) + assert len(nonexistent_tags_mcp.tools) == 0 + assert nonexistent_tags_mcp.tools == [] + + # Test excluding non-existent operation IDs + exclude_nonexistent_ops_mcp = FastApiMCP(app, exclude_operations=["non_existent_op"]) + assert len(exclude_nonexistent_ops_mcp.tools) == 2 + assert {tool.name for tool in exclude_nonexistent_ops_mcp.tools} == {"get_item", "list_items"} + + # Test excluding non-existent tags + exclude_nonexistent_tags_mcp = FastApiMCP(app, exclude_tags=["non_existent_tag"]) + assert len(exclude_nonexistent_tags_mcp.tools) == 2 + assert {tool.name for tool in exclude_nonexistent_tags_mcp.tools} == {"get_item", "list_items"} + + # Test with an endpoint that has no tags + @app.get("/no-tags", operation_id="no_tags") + async def no_tags(): + return {"result": "no tags"} + + # Test include_tags with an endpoint that has no tags + no_tags_app_mcp = FastApiMCP(app, include_tags=["items"]) + assert len(no_tags_app_mcp.tools) == 2 + assert "no_tags" not in {tool.name for tool in no_tags_app_mcp.tools} + + # Test exclude_tags with an endpoint that has no tags + no_tags_exclude_mcp = FastApiMCP(app, exclude_tags=["items"]) + assert len(no_tags_exclude_mcp.tools) == 1 + assert {tool.name for tool in no_tags_exclude_mcp.tools} == {"no_tags"} + + +def test_filtering_with_missing_operation_ids(): + """Test filtering behavior with endpoints that don't have operation IDs.""" + app = FastAPI() + + # Define an endpoint with an operation ID + @app.get("/items/", operation_id="list_items", tags=["items"]) + async def list_items(): + return [{"id": 1}] + + # Define an endpoint without an operation ID + @app.get("/no-op-id/") + async def no_op_id(): + return {"result": "no operation ID"} + + # Test that both endpoints are discovered + default_mcp = FastApiMCP(app) + + # FastAPI-MCP will generate an operation ID for endpoints without one + # The auto-generated ID will typically be 'no_op_id_no_op_id__get' + assert len(default_mcp.tools) == 2 + + # Get the auto-generated operation ID + auto_generated_op_id = None + for tool in default_mcp.tools: + if tool.name != "list_items": + auto_generated_op_id = tool.name + break + + assert auto_generated_op_id is not None + assert "list_items" in {tool.name for tool in default_mcp.tools} + + # Test include_operations with the known operation ID + include_ops_mcp = FastApiMCP(app, include_operations=["list_items"]) + assert len(include_ops_mcp.tools) == 1 + assert {tool.name for tool in include_ops_mcp.tools} == {"list_items"} + + # Test include_operations with the auto-generated operation ID + include_auto_ops_mcp = FastApiMCP(app, include_operations=[auto_generated_op_id]) + assert len(include_auto_ops_mcp.tools) == 1 + assert {tool.name for tool in include_auto_ops_mcp.tools} == {auto_generated_op_id} + + # Test include_tags with a tag that matches the endpoint + include_tags_mcp = FastApiMCP(app, include_tags=["items"]) + assert len(include_tags_mcp.tools) == 1 + assert {tool.name for tool in include_tags_mcp.tools} == {"list_items"} + + +def test_filter_with_empty_tools(): + """Test filtering with an empty tools list to ensure it handles this edge case correctly.""" + # Create a FastAPI app without any routes + app = FastAPI() + + # Create MCP server (should have no tools) + empty_mcp = FastApiMCP(app) + assert len(empty_mcp.tools) == 0 + + # Test filtering with various options on an empty app + include_ops_mcp = FastApiMCP(app, include_operations=["some_op"]) + assert len(include_ops_mcp.tools) == 0 + + exclude_ops_mcp = FastApiMCP(app, exclude_operations=["some_op"]) + assert len(exclude_ops_mcp.tools) == 0 + + include_tags_mcp = FastApiMCP(app, include_tags=["some_tag"]) + assert len(include_tags_mcp.tools) == 0 + + exclude_tags_mcp = FastApiMCP(app, exclude_tags=["some_tag"]) + assert len(exclude_tags_mcp.tools) == 0 + + # Test combined filtering + combined_mcp = FastApiMCP(app, include_operations=["op"], include_tags=["tag"]) + assert len(combined_mcp.tools) == 0 + + +def test_filtering_with_empty_tags_array(): + """Test filtering behavior with endpoints that have empty tags array.""" + app = FastAPI() + + # Define an endpoint with tags + @app.get("/items/", operation_id="list_items", tags=["items"]) + async def list_items(): + return [{"id": 1}] + + # Define an endpoint with an empty tags array + @app.get("/empty-tags/", operation_id="empty_tags", tags=[]) + async def empty_tags(): + return {"result": "empty tags"} + + # Test default behavior + default_mcp = FastApiMCP(app) + assert len(default_mcp.tools) == 2 + assert {tool.name for tool in default_mcp.tools} == {"list_items", "empty_tags"} + + # Test include_tags + include_tags_mcp = FastApiMCP(app, include_tags=["items"]) + assert len(include_tags_mcp.tools) == 1 + assert {tool.name for tool in include_tags_mcp.tools} == {"list_items"} + + # Test exclude_tags + exclude_tags_mcp = FastApiMCP(app, exclude_tags=["items"]) + assert len(exclude_tags_mcp.tools) == 1 + assert {tool.name for tool in exclude_tags_mcp.tools} == {"empty_tags"}