mcp-proxy-adapter 4.1.1__py3-none-any.whl → 6.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_proxy_adapter/__main__.py +32 -0
- mcp_proxy_adapter/api/app.py +290 -33
- mcp_proxy_adapter/api/handlers.py +32 -6
- mcp_proxy_adapter/api/middleware/__init__.py +38 -32
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
- mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
- mcp_proxy_adapter/api/middleware/factory.py +243 -0
- mcp_proxy_adapter/api/middleware/logging.py +32 -6
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +201 -0
- mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
- mcp_proxy_adapter/api/middleware/unified_security.py +197 -0
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +158 -0
- mcp_proxy_adapter/commands/__init__.py +19 -4
- mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
- mcp_proxy_adapter/commands/base.py +66 -32
- mcp_proxy_adapter/commands/builtin_commands.py +95 -0
- mcp_proxy_adapter/commands/catalog_manager.py +838 -0
- mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
- mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
- mcp_proxy_adapter/commands/command_registry.py +711 -354
- mcp_proxy_adapter/commands/dependency_manager.py +245 -0
- mcp_proxy_adapter/commands/echo_command.py +81 -0
- mcp_proxy_adapter/commands/health_command.py +8 -1
- mcp_proxy_adapter/commands/help_command.py +21 -14
- mcp_proxy_adapter/commands/hooks.py +200 -167
- mcp_proxy_adapter/commands/key_management_command.py +506 -0
- mcp_proxy_adapter/commands/load_command.py +176 -0
- mcp_proxy_adapter/commands/plugins_command.py +235 -0
- mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
- mcp_proxy_adapter/commands/proxy_registration_command.py +409 -0
- mcp_proxy_adapter/commands/reload_command.py +48 -50
- mcp_proxy_adapter/commands/result.py +1 -0
- mcp_proxy_adapter/commands/role_test_command.py +141 -0
- mcp_proxy_adapter/commands/roles_management_command.py +697 -0
- mcp_proxy_adapter/commands/security_command.py +488 -0
- mcp_proxy_adapter/commands/ssl_setup_command.py +366 -0
- mcp_proxy_adapter/commands/token_management_command.py +529 -0
- mcp_proxy_adapter/commands/transport_management_command.py +144 -0
- mcp_proxy_adapter/commands/unload_command.py +158 -0
- mcp_proxy_adapter/config.py +394 -14
- mcp_proxy_adapter/core/app_factory.py +410 -0
- mcp_proxy_adapter/core/app_runner.py +272 -0
- mcp_proxy_adapter/core/auth_validator.py +606 -0
- mcp_proxy_adapter/core/certificate_utils.py +1045 -0
- mcp_proxy_adapter/core/client.py +574 -0
- mcp_proxy_adapter/core/client_manager.py +284 -0
- mcp_proxy_adapter/core/client_security.py +384 -0
- mcp_proxy_adapter/core/config_converter.py +405 -0
- mcp_proxy_adapter/core/config_validator.py +218 -0
- mcp_proxy_adapter/core/logging.py +19 -3
- mcp_proxy_adapter/core/mtls_asgi.py +156 -0
- mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
- mcp_proxy_adapter/core/protocol_manager.py +385 -0
- mcp_proxy_adapter/core/proxy_client.py +602 -0
- mcp_proxy_adapter/core/proxy_registration.py +522 -0
- mcp_proxy_adapter/core/role_utils.py +426 -0
- mcp_proxy_adapter/core/security_adapter.py +370 -0
- mcp_proxy_adapter/core/security_factory.py +239 -0
- mcp_proxy_adapter/core/security_integration.py +286 -0
- mcp_proxy_adapter/core/server_adapter.py +282 -0
- mcp_proxy_adapter/core/server_engine.py +270 -0
- mcp_proxy_adapter/core/settings.py +1 -0
- mcp_proxy_adapter/core/ssl_utils.py +234 -0
- mcp_proxy_adapter/core/transport_manager.py +292 -0
- mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
- mcp_proxy_adapter/custom_openapi.py +22 -11
- mcp_proxy_adapter/examples/__init__.py +13 -4
- mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
- mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/main.py +44 -0
- mcp_proxy_adapter/examples/commands/__init__.py +5 -0
- mcp_proxy_adapter/examples/create_certificates_simple.py +550 -0
- mcp_proxy_adapter/examples/debug_request_state.py +112 -0
- mcp_proxy_adapter/examples/debug_role_chain.py +158 -0
- mcp_proxy_adapter/examples/demo_client.py +275 -0
- mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
- mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
- mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
- mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
- mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
- mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
- mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
- mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
- mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +80 -0
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +90 -0
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +75 -0
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +71 -0
- mcp_proxy_adapter/examples/full_application/main.py +173 -0
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
- mcp_proxy_adapter/examples/generate_all_certificates.py +362 -0
- mcp_proxy_adapter/examples/generate_certificates.py +177 -0
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
- mcp_proxy_adapter/examples/generate_test_configs.py +331 -0
- mcp_proxy_adapter/examples/proxy_registration_example.py +334 -0
- mcp_proxy_adapter/examples/run_example.py +59 -0
- mcp_proxy_adapter/examples/run_full_test_suite.py +318 -0
- mcp_proxy_adapter/examples/run_proxy_server.py +146 -0
- mcp_proxy_adapter/examples/run_security_tests.py +544 -0
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +247 -0
- mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
- mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
- mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
- mcp_proxy_adapter/examples/security_test_client.py +782 -0
- mcp_proxy_adapter/examples/setup_test_environment.py +328 -0
- mcp_proxy_adapter/examples/test_config.py +148 -0
- mcp_proxy_adapter/examples/test_config_generator.py +86 -0
- mcp_proxy_adapter/examples/test_examples.py +281 -0
- mcp_proxy_adapter/examples/universal_client.py +620 -0
- mcp_proxy_adapter/main.py +93 -0
- mcp_proxy_adapter/utils/config_generator.py +1008 -0
- mcp_proxy_adapter/version.py +5 -2
- mcp_proxy_adapter-6.0.1.dist-info/METADATA +679 -0
- mcp_proxy_adapter-6.0.1.dist-info/RECORD +140 -0
- mcp_proxy_adapter-6.0.1.dist-info/entry_points.txt +2 -0
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/licenses/LICENSE +2 -2
- mcp_proxy_adapter/api/middleware/auth.py +0 -146
- mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
- mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
- mcp_proxy_adapter/examples/README.md +0 -124
- mcp_proxy_adapter/examples/basic_server/README.md +0 -60
- mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
- mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
- mcp_proxy_adapter/examples/basic_server/config.json +0 -35
- mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
- mcp_proxy_adapter/examples/basic_server/server.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
- mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
- mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -250
- mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
- mcp_proxy_adapter/examples/custom_commands/config.json +0 -35
- mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
- mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
- mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
- mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
- mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
- mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
- mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
- mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
- mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
- mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/server.py +0 -228
- mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
- mcp_proxy_adapter/examples/deployment/README.md +0 -49
- mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
- mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
- mcp_proxy_adapter/examples/deployment/config.json +0 -29
- mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
- mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
- mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
- mcp_proxy_adapter/examples/deployment/run.sh +0 -43
- mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
- mcp_proxy_adapter/schemas/base_schema.json +0 -114
- mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
- mcp_proxy_adapter/tests/__init__.py +0 -0
- mcp_proxy_adapter/tests/api/__init__.py +0 -3
- mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
- mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
- mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
- mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
- mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
- mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
- mcp_proxy_adapter/tests/commands/__init__.py +0 -3
- mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
- mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
- mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
- mcp_proxy_adapter/tests/conftest.py +0 -131
- mcp_proxy_adapter/tests/functional/__init__.py +0 -3
- mcp_proxy_adapter/tests/functional/test_api.py +0 -253
- mcp_proxy_adapter/tests/integration/__init__.py +0 -3
- mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
- mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
- mcp_proxy_adapter/tests/performance/__init__.py +0 -3
- mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
- mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
- mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
- mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
- mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
- mcp_proxy_adapter/tests/test_base_command.py +0 -123
- mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
- mcp_proxy_adapter/tests/test_command_registry.py +0 -281
- mcp_proxy_adapter/tests/test_config.py +0 -127
- mcp_proxy_adapter/tests/test_utils.py +0 -65
- mcp_proxy_adapter/tests/unit/__init__.py +0 -3
- mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
- mcp_proxy_adapter/tests/unit/test_config.py +0 -217
- mcp_proxy_adapter-4.1.1.dist-info/METADATA +0 -200
- mcp_proxy_adapter-4.1.1.dist-info/RECORD +0 -110
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/top_level.txt +0 -0
@@ -1,617 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Tests for custom_openapi module.
|
3
|
-
|
4
|
-
This module contains comprehensive tests for the CustomOpenAPIGenerator class
|
5
|
-
and related functions to ensure 90%+ code coverage.
|
6
|
-
"""
|
7
|
-
|
8
|
-
import pytest
|
9
|
-
import json
|
10
|
-
from unittest.mock import Mock, patch, MagicMock
|
11
|
-
from pathlib import Path
|
12
|
-
from typing import Dict, Any
|
13
|
-
|
14
|
-
from fastapi import FastAPI
|
15
|
-
|
16
|
-
from mcp_proxy_adapter.custom_openapi import CustomOpenAPIGenerator, custom_openapi
|
17
|
-
from mcp_proxy_adapter.commands.base import Command
|
18
|
-
|
19
|
-
|
20
|
-
class TestCustomOpenAPIGenerator:
|
21
|
-
"""Test cases for CustomOpenAPIGenerator class."""
|
22
|
-
|
23
|
-
@pytest.fixture
|
24
|
-
def mock_base_schema(self):
|
25
|
-
"""Create a mock base schema for testing."""
|
26
|
-
return {
|
27
|
-
"info": {
|
28
|
-
"title": "Test API",
|
29
|
-
"description": "Test API description",
|
30
|
-
"version": "1.0.0"
|
31
|
-
},
|
32
|
-
"components": {
|
33
|
-
"schemas": {
|
34
|
-
"CommandRequest": {
|
35
|
-
"properties": {
|
36
|
-
"command": {
|
37
|
-
"type": "string",
|
38
|
-
"enum": []
|
39
|
-
},
|
40
|
-
"params": {
|
41
|
-
"type": "object",
|
42
|
-
"oneOf": []
|
43
|
-
}
|
44
|
-
}
|
45
|
-
}
|
46
|
-
}
|
47
|
-
}
|
48
|
-
}
|
49
|
-
|
50
|
-
@pytest.fixture
|
51
|
-
def mock_command_class(self):
|
52
|
-
"""Create a mock command class for testing."""
|
53
|
-
class MockCommand(Command):
|
54
|
-
name = "test_command"
|
55
|
-
|
56
|
-
@classmethod
|
57
|
-
def get_schema(cls):
|
58
|
-
return {
|
59
|
-
"type": "object",
|
60
|
-
"properties": {
|
61
|
-
"param1": {
|
62
|
-
"type": "string",
|
63
|
-
"description": "Test parameter"
|
64
|
-
}
|
65
|
-
}
|
66
|
-
}
|
67
|
-
|
68
|
-
async def execute(self, **kwargs):
|
69
|
-
return {"result": "test"}
|
70
|
-
|
71
|
-
return MockCommand
|
72
|
-
|
73
|
-
@pytest.fixture
|
74
|
-
def generator(self, mock_base_schema):
|
75
|
-
"""Create a CustomOpenAPIGenerator instance with mocked base schema."""
|
76
|
-
with patch('mcp_proxy_adapter.custom_openapi.CustomOpenAPIGenerator._load_base_schema') as mock_load:
|
77
|
-
mock_load.return_value = mock_base_schema
|
78
|
-
generator = CustomOpenAPIGenerator()
|
79
|
-
return generator
|
80
|
-
|
81
|
-
def test_generator_initialization(self, mock_base_schema):
|
82
|
-
"""Test CustomOpenAPIGenerator initialization."""
|
83
|
-
with patch('mcp_proxy_adapter.custom_openapi.CustomOpenAPIGenerator._load_base_schema') as mock_load:
|
84
|
-
mock_load.return_value = mock_base_schema
|
85
|
-
generator = CustomOpenAPIGenerator()
|
86
|
-
|
87
|
-
assert generator.base_schema == mock_base_schema
|
88
|
-
assert "schemas" in generator.base_schema_path.parts
|
89
|
-
|
90
|
-
def test_load_base_schema(self):
|
91
|
-
"""Test loading base schema from file."""
|
92
|
-
with patch('builtins.open', create=True) as mock_open:
|
93
|
-
mock_open.return_value.__enter__.return_value.read.return_value = '{"test": "schema"}'
|
94
|
-
|
95
|
-
generator = CustomOpenAPIGenerator()
|
96
|
-
schema = generator._load_base_schema()
|
97
|
-
|
98
|
-
assert schema == {"test": "schema"}
|
99
|
-
|
100
|
-
def test_add_commands_to_schema(self, generator, mock_command_class):
|
101
|
-
"""Test adding commands to schema."""
|
102
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
103
|
-
mock_registry.get_all_commands.return_value = {
|
104
|
-
"test_command": mock_command_class
|
105
|
-
}
|
106
|
-
|
107
|
-
schema = {
|
108
|
-
"components": {
|
109
|
-
"schemas": {
|
110
|
-
"CommandRequest": {
|
111
|
-
"properties": {
|
112
|
-
"command": {"type": "string", "enum": []},
|
113
|
-
"params": {"type": "object", "oneOf": []}
|
114
|
-
}
|
115
|
-
}
|
116
|
-
}
|
117
|
-
}
|
118
|
-
}
|
119
|
-
generator._add_commands_to_schema(schema)
|
120
|
-
|
121
|
-
# Check that command was added to enum
|
122
|
-
command_enum = schema["components"]["schemas"]["CommandRequest"]["properties"]["command"]["enum"]
|
123
|
-
assert "test_command" in command_enum
|
124
|
-
|
125
|
-
# Check that params schema was created
|
126
|
-
assert "Test_commandParams" in schema["components"]["schemas"]
|
127
|
-
|
128
|
-
def test_create_params_schema(self, generator, mock_command_class):
|
129
|
-
"""Test creating parameters schema for a command."""
|
130
|
-
schema = generator._create_params_schema(mock_command_class)
|
131
|
-
|
132
|
-
assert schema["title"] == "Parameters for test_command"
|
133
|
-
assert schema["description"] == "Parameters for the test_command command"
|
134
|
-
assert "properties" in schema
|
135
|
-
assert "param1" in schema["properties"]
|
136
|
-
|
137
|
-
def test_generate_with_defaults(self, generator):
|
138
|
-
"""Test schema generation with default parameters."""
|
139
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
140
|
-
mock_registry.get_all_commands.return_value = {}
|
141
|
-
|
142
|
-
schema = generator.generate()
|
143
|
-
|
144
|
-
assert "info" in schema
|
145
|
-
assert "components" in schema
|
146
|
-
assert schema["info"]["title"] == "Test API"
|
147
|
-
|
148
|
-
def test_generate_with_custom_title(self, generator):
|
149
|
-
"""Test schema generation with custom title."""
|
150
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
151
|
-
mock_registry.get_all_commands.return_value = {}
|
152
|
-
|
153
|
-
schema = generator.generate(title="Custom Title")
|
154
|
-
|
155
|
-
assert schema["info"]["title"] == "Custom Title"
|
156
|
-
|
157
|
-
def test_generate_with_custom_description(self, generator):
|
158
|
-
"""Test schema generation with custom description."""
|
159
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
160
|
-
mock_registry.get_all_commands.return_value = {}
|
161
|
-
|
162
|
-
schema = generator.generate(description="Custom Description")
|
163
|
-
|
164
|
-
assert "Custom Description" in schema["info"]["description"]
|
165
|
-
|
166
|
-
def test_generate_with_custom_version(self, generator):
|
167
|
-
"""Test schema generation with custom version."""
|
168
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
169
|
-
mock_registry.get_all_commands.return_value = {}
|
170
|
-
|
171
|
-
schema = generator.generate(version="2.0.0")
|
172
|
-
|
173
|
-
assert schema["info"]["version"] == "2.0.0"
|
174
|
-
|
175
|
-
def test_generate_with_commands(self, generator, mock_command_class):
|
176
|
-
"""Test schema generation with registered commands."""
|
177
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
178
|
-
mock_registry.get_all_commands.return_value = {
|
179
|
-
"test_command": mock_command_class
|
180
|
-
}
|
181
|
-
|
182
|
-
schema = generator.generate()
|
183
|
-
|
184
|
-
# Check that commands were added
|
185
|
-
command_enum = schema["components"]["schemas"]["CommandRequest"]["properties"]["command"]["enum"]
|
186
|
-
assert "test_command" in command_enum
|
187
|
-
|
188
|
-
def test_generate_enhances_description_with_commands(self, generator):
|
189
|
-
"""Test that description is enhanced with command information."""
|
190
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
191
|
-
# Create proper mock commands with get_schema method
|
192
|
-
mock_help = Mock()
|
193
|
-
mock_help.get_schema.return_value = {"type": "object", "properties": {}}
|
194
|
-
mock_help.name = "help"
|
195
|
-
|
196
|
-
mock_config = Mock()
|
197
|
-
mock_config.get_schema.return_value = {"type": "object", "properties": {}}
|
198
|
-
mock_config.name = "config"
|
199
|
-
|
200
|
-
mock_registry.get_all_commands.return_value = {
|
201
|
-
"help": mock_help,
|
202
|
-
"config": mock_config
|
203
|
-
}
|
204
|
-
|
205
|
-
schema = generator.generate()
|
206
|
-
|
207
|
-
description = schema["info"]["description"]
|
208
|
-
assert "Available commands:" in description
|
209
|
-
assert "help" in description
|
210
|
-
assert "config" in description
|
211
|
-
|
212
|
-
def test_generate_creates_tool_description(self, generator):
|
213
|
-
"""Test that ToolDescription schema is created."""
|
214
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
215
|
-
mock_registry.get_all_commands.return_value = {}
|
216
|
-
|
217
|
-
schema = generator.generate()
|
218
|
-
|
219
|
-
assert "ToolDescription" in schema["components"]["schemas"]
|
220
|
-
tool_desc = schema["components"]["schemas"]["ToolDescription"]
|
221
|
-
assert "properties" in tool_desc
|
222
|
-
assert "name" in tool_desc["properties"]
|
223
|
-
assert "description" in tool_desc["properties"]
|
224
|
-
|
225
|
-
def test_generate_adds_help_examples(self, generator):
|
226
|
-
"""Test that help examples are added to tool description."""
|
227
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
228
|
-
# Create proper mock command with get_schema method
|
229
|
-
mock_help = Mock()
|
230
|
-
mock_help.get_schema.return_value = {"type": "object", "properties": {}}
|
231
|
-
mock_help.name = "help"
|
232
|
-
|
233
|
-
mock_registry.get_all_commands.return_value = {"help": mock_help}
|
234
|
-
|
235
|
-
schema = generator.generate()
|
236
|
-
|
237
|
-
tool_desc = schema["components"]["schemas"]["ToolDescription"]
|
238
|
-
assert "help_examples" in tool_desc["properties"]
|
239
|
-
help_examples = tool_desc["properties"]["help_examples"]
|
240
|
-
assert "without_params" in help_examples["properties"]
|
241
|
-
assert "with_params" in help_examples["properties"]
|
242
|
-
|
243
|
-
def test_generate_adds_available_commands(self, generator):
|
244
|
-
"""Test that available commands are added to tool description."""
|
245
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
246
|
-
# Create proper mock commands with get_schema method
|
247
|
-
mock_help = Mock()
|
248
|
-
mock_help.get_schema.return_value = {"type": "object", "properties": {}}
|
249
|
-
mock_help.name = "help"
|
250
|
-
|
251
|
-
mock_config = Mock()
|
252
|
-
mock_config.get_schema.return_value = {"type": "object", "properties": {}}
|
253
|
-
mock_config.name = "config"
|
254
|
-
|
255
|
-
mock_registry.get_all_commands.return_value = {"help": mock_help, "config": mock_config}
|
256
|
-
|
257
|
-
schema = generator.generate()
|
258
|
-
|
259
|
-
tool_desc = schema["components"]["schemas"]["ToolDescription"]
|
260
|
-
assert "available_commands" in tool_desc["properties"]
|
261
|
-
available_commands = tool_desc["properties"]["available_commands"]
|
262
|
-
assert available_commands["type"] == "array"
|
263
|
-
|
264
|
-
def test_generate_with_empty_commands(self, generator):
|
265
|
-
"""Test schema generation with no commands."""
|
266
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
267
|
-
mock_registry.get_all_commands.return_value = {}
|
268
|
-
|
269
|
-
schema = generator.generate()
|
270
|
-
|
271
|
-
# Should handle empty commands gracefully
|
272
|
-
assert "components" in schema
|
273
|
-
assert "schemas" in schema["components"]
|
274
|
-
|
275
|
-
def test_generate_logs_command_count(self, generator):
|
276
|
-
"""Test that command count is logged during generation."""
|
277
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
278
|
-
# Create proper mock commands with get_schema method
|
279
|
-
mock_cmd1 = Mock()
|
280
|
-
mock_cmd1.get_schema.return_value = {"type": "object", "properties": {}}
|
281
|
-
mock_cmd1.name = "cmd1"
|
282
|
-
|
283
|
-
mock_cmd2 = Mock()
|
284
|
-
mock_cmd2.get_schema.return_value = {"type": "object", "properties": {}}
|
285
|
-
mock_cmd2.name = "cmd2"
|
286
|
-
|
287
|
-
mock_registry.get_all_commands.return_value = {"cmd1": mock_cmd1, "cmd2": mock_cmd2}
|
288
|
-
|
289
|
-
with patch('mcp_proxy_adapter.custom_openapi.logger') as mock_logger:
|
290
|
-
generator.generate()
|
291
|
-
|
292
|
-
mock_logger.info.assert_called_with("Generated OpenAPI schema with 2 commands")
|
293
|
-
|
294
|
-
def test_generate_with_custom_title_preserves_description(self, generator):
|
295
|
-
"""Test that custom title preserves original description."""
|
296
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
297
|
-
mock_registry.get_all_commands.return_value = {}
|
298
|
-
|
299
|
-
# Set custom title to trigger special handling
|
300
|
-
generator.base_schema["info"]["title"] = "Custom Title"
|
301
|
-
schema = generator.generate(title="Custom Title")
|
302
|
-
|
303
|
-
# Description should remain unchanged for test case
|
304
|
-
assert "Test API description" in schema["info"]["description"]
|
305
|
-
|
306
|
-
|
307
|
-
class TestCustomOpenAPIFunction:
|
308
|
-
"""Test cases for custom_openapi function."""
|
309
|
-
|
310
|
-
@pytest.fixture
|
311
|
-
def mock_app(self):
|
312
|
-
"""Create a mock FastAPI application."""
|
313
|
-
app = Mock(spec=FastAPI)
|
314
|
-
app.title = "Test App"
|
315
|
-
app.description = "Test App Description"
|
316
|
-
app.version = "1.0.0"
|
317
|
-
return app
|
318
|
-
|
319
|
-
def test_custom_openapi_function(self, mock_app):
|
320
|
-
"""Test custom_openapi function."""
|
321
|
-
with patch('mcp_proxy_adapter.custom_openapi.CustomOpenAPIGenerator') as mock_generator_class:
|
322
|
-
mock_generator = Mock()
|
323
|
-
mock_generator.generate.return_value = {"test": "schema"}
|
324
|
-
mock_generator_class.return_value = mock_generator
|
325
|
-
|
326
|
-
result = custom_openapi(mock_app)
|
327
|
-
|
328
|
-
# Check that generator was called with app attributes
|
329
|
-
mock_generator.generate.assert_called_with(
|
330
|
-
title="Test App",
|
331
|
-
description="Test App Description",
|
332
|
-
version="1.0.0"
|
333
|
-
)
|
334
|
-
|
335
|
-
# Check that schema was cached
|
336
|
-
assert mock_app.openapi_schema == {"test": "schema"}
|
337
|
-
assert result == {"test": "schema"}
|
338
|
-
|
339
|
-
def test_custom_openapi_with_missing_attributes(self):
|
340
|
-
"""Test custom_openapi function with app missing attributes."""
|
341
|
-
app = Mock(spec=FastAPI)
|
342
|
-
# Don't set title, description, version
|
343
|
-
|
344
|
-
with patch('mcp_proxy_adapter.custom_openapi.CustomOpenAPIGenerator') as mock_generator_class:
|
345
|
-
mock_generator = Mock()
|
346
|
-
mock_generator.generate.return_value = {"test": "schema"}
|
347
|
-
mock_generator_class.return_value = mock_generator
|
348
|
-
|
349
|
-
result = custom_openapi(app)
|
350
|
-
|
351
|
-
# Check that generator was called with None values
|
352
|
-
mock_generator.generate.assert_called_with(
|
353
|
-
title=None,
|
354
|
-
description=None,
|
355
|
-
version=None
|
356
|
-
)
|
357
|
-
|
358
|
-
def test_custom_openapi_with_partial_attributes(self):
|
359
|
-
"""Test custom_openapi function with app having only some attributes."""
|
360
|
-
app = Mock(spec=FastAPI)
|
361
|
-
app.title = "Partial App"
|
362
|
-
# Don't set description and version
|
363
|
-
|
364
|
-
with patch('mcp_proxy_adapter.custom_openapi.CustomOpenAPIGenerator') as mock_generator_class:
|
365
|
-
mock_generator = Mock()
|
366
|
-
mock_generator.generate.return_value = {"test": "schema"}
|
367
|
-
mock_generator_class.return_value = mock_generator
|
368
|
-
|
369
|
-
result = custom_openapi(app)
|
370
|
-
|
371
|
-
# Check that generator was called with partial values
|
372
|
-
mock_generator.generate.assert_called_with(
|
373
|
-
title="Partial App",
|
374
|
-
description=None,
|
375
|
-
version=None
|
376
|
-
)
|
377
|
-
|
378
|
-
|
379
|
-
class TestCustomOpenAPIGeneratorEdgeCases:
|
380
|
-
"""Test edge cases and error conditions."""
|
381
|
-
|
382
|
-
def test_generator_with_missing_base_schema_file(self):
|
383
|
-
"""Test generator initialization with missing base schema file."""
|
384
|
-
with patch('builtins.open', side_effect=FileNotFoundError("File not found")):
|
385
|
-
with pytest.raises(FileNotFoundError):
|
386
|
-
CustomOpenAPIGenerator()
|
387
|
-
|
388
|
-
def test_generator_with_invalid_json_in_base_schema(self):
|
389
|
-
"""Test generator initialization with invalid JSON in base schema."""
|
390
|
-
with patch('builtins.open', create=True) as mock_open:
|
391
|
-
mock_open.return_value.__enter__.return_value.read.return_value = "invalid json"
|
392
|
-
|
393
|
-
with pytest.raises(json.JSONDecodeError):
|
394
|
-
CustomOpenAPIGenerator()
|
395
|
-
|
396
|
-
def test_add_commands_to_schema_with_empty_registry(self):
|
397
|
-
"""Test adding commands to schema with empty registry."""
|
398
|
-
with patch('mcp_proxy_adapter.custom_openapi.CustomOpenAPIGenerator._load_base_schema') as mock_load:
|
399
|
-
mock_load.return_value = {
|
400
|
-
"components": {
|
401
|
-
"schemas": {
|
402
|
-
"CommandRequest": {
|
403
|
-
"properties": {
|
404
|
-
"command": {"type": "string", "enum": []},
|
405
|
-
"params": {"type": "object", "oneOf": []}
|
406
|
-
}
|
407
|
-
}
|
408
|
-
}
|
409
|
-
}
|
410
|
-
}
|
411
|
-
|
412
|
-
generator = CustomOpenAPIGenerator()
|
413
|
-
|
414
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
415
|
-
mock_registry.get_all_commands.return_value = {}
|
416
|
-
|
417
|
-
schema = {
|
418
|
-
"components": {
|
419
|
-
"schemas": {
|
420
|
-
"CommandRequest": {
|
421
|
-
"properties": {
|
422
|
-
"command": {"type": "string", "enum": []},
|
423
|
-
"params": {"type": "object", "oneOf": []}
|
424
|
-
}
|
425
|
-
}
|
426
|
-
}
|
427
|
-
}
|
428
|
-
}
|
429
|
-
generator._add_commands_to_schema(schema)
|
430
|
-
|
431
|
-
# Should handle empty registry gracefully
|
432
|
-
command_enum = schema["components"]["schemas"]["CommandRequest"]["properties"]["command"]["enum"]
|
433
|
-
assert command_enum == []
|
434
|
-
|
435
|
-
def test_create_params_schema_with_command_without_schema(self):
|
436
|
-
"""Test creating params schema for command without get_schema method."""
|
437
|
-
with patch('mcp_proxy_adapter.custom_openapi.CustomOpenAPIGenerator._load_base_schema') as mock_load:
|
438
|
-
mock_load.return_value = {}
|
439
|
-
|
440
|
-
generator = CustomOpenAPIGenerator()
|
441
|
-
|
442
|
-
# Create a command class without get_schema method
|
443
|
-
class CommandWithoutSchema(Command):
|
444
|
-
name = "test_command"
|
445
|
-
|
446
|
-
async def execute(self, **kwargs):
|
447
|
-
return {"result": "test"}
|
448
|
-
|
449
|
-
# Should handle command without get_schema gracefully
|
450
|
-
schema = generator._create_params_schema(CommandWithoutSchema)
|
451
|
-
assert "title" in schema
|
452
|
-
assert "description" in schema
|
453
|
-
|
454
|
-
def test_generate_with_missing_components_in_base_schema(self):
|
455
|
-
"""Test generation with base schema missing components."""
|
456
|
-
with patch('mcp_proxy_adapter.custom_openapi.CustomOpenAPIGenerator._load_base_schema') as mock_load:
|
457
|
-
mock_load.return_value = {
|
458
|
-
"info": {
|
459
|
-
"title": "Test",
|
460
|
-
"description": "Test description"
|
461
|
-
},
|
462
|
-
"components": {
|
463
|
-
"schemas": {
|
464
|
-
"CommandRequest": {
|
465
|
-
"properties": {
|
466
|
-
"command": {"type": "string", "enum": []},
|
467
|
-
"params": {"type": "object", "oneOf": []}
|
468
|
-
}
|
469
|
-
}
|
470
|
-
}
|
471
|
-
}
|
472
|
-
}
|
473
|
-
|
474
|
-
generator = CustomOpenAPIGenerator()
|
475
|
-
|
476
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
477
|
-
mock_registry.get_all_commands.return_value = {}
|
478
|
-
|
479
|
-
# Should handle missing components gracefully
|
480
|
-
schema = generator.generate()
|
481
|
-
assert "components" in schema
|
482
|
-
assert "schemas" in schema["components"]
|
483
|
-
|
484
|
-
def test_generate_with_completely_empty_base_schema(self):
|
485
|
-
"""Test generation with completely empty base schema (missing components and schemas)."""
|
486
|
-
with patch('mcp_proxy_adapter.custom_openapi.CustomOpenAPIGenerator._load_base_schema') as mock_load:
|
487
|
-
mock_load.return_value = {
|
488
|
-
"info": {
|
489
|
-
"title": "Test",
|
490
|
-
"description": "Test description"
|
491
|
-
}
|
492
|
-
# Missing components entirely
|
493
|
-
}
|
494
|
-
|
495
|
-
generator = CustomOpenAPIGenerator()
|
496
|
-
|
497
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
498
|
-
mock_registry.get_all_commands.return_value = {}
|
499
|
-
|
500
|
-
# Should handle completely missing components gracefully
|
501
|
-
schema = generator.generate()
|
502
|
-
assert "components" in schema
|
503
|
-
assert "schemas" in schema["components"]
|
504
|
-
assert "ToolDescription" in schema["components"]["schemas"]
|
505
|
-
|
506
|
-
def test_generate_with_missing_schemas_in_components(self):
|
507
|
-
"""Test generation with components but missing schemas."""
|
508
|
-
with patch('mcp_proxy_adapter.custom_openapi.CustomOpenAPIGenerator._load_base_schema') as mock_load:
|
509
|
-
mock_load.return_value = {
|
510
|
-
"info": {
|
511
|
-
"title": "Test",
|
512
|
-
"description": "Test description"
|
513
|
-
},
|
514
|
-
"components": {
|
515
|
-
# Missing schemas
|
516
|
-
}
|
517
|
-
}
|
518
|
-
|
519
|
-
generator = CustomOpenAPIGenerator()
|
520
|
-
|
521
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
522
|
-
mock_registry.get_all_commands.return_value = {}
|
523
|
-
|
524
|
-
# Should handle missing schemas gracefully
|
525
|
-
schema = generator.generate()
|
526
|
-
assert "components" in schema
|
527
|
-
assert "schemas" in schema["components"]
|
528
|
-
assert "ToolDescription" in schema["components"]["schemas"]
|
529
|
-
|
530
|
-
def test_generate_with_missing_command_request_in_schemas(self):
|
531
|
-
"""Test generation with schemas but missing CommandRequest."""
|
532
|
-
with patch('mcp_proxy_adapter.custom_openapi.CustomOpenAPIGenerator._load_base_schema') as mock_load:
|
533
|
-
mock_load.return_value = {
|
534
|
-
"info": {
|
535
|
-
"title": "Test",
|
536
|
-
"description": "Test description"
|
537
|
-
},
|
538
|
-
"components": {
|
539
|
-
"schemas": {
|
540
|
-
# Missing CommandRequest
|
541
|
-
"SomeOtherSchema": {"type": "object"}
|
542
|
-
}
|
543
|
-
}
|
544
|
-
}
|
545
|
-
|
546
|
-
generator = CustomOpenAPIGenerator()
|
547
|
-
|
548
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
549
|
-
mock_registry.get_all_commands.return_value = {}
|
550
|
-
|
551
|
-
# Should handle missing CommandRequest gracefully
|
552
|
-
schema = generator.generate()
|
553
|
-
assert "components" in schema
|
554
|
-
assert "schemas" in schema["components"]
|
555
|
-
assert "ToolDescription" in schema["components"]["schemas"]
|
556
|
-
|
557
|
-
|
558
|
-
class TestCustomOpenAPIGeneratorIntegration:
|
559
|
-
"""Integration tests for CustomOpenAPIGenerator."""
|
560
|
-
|
561
|
-
def test_full_generation_workflow(self):
|
562
|
-
"""Test the complete schema generation workflow."""
|
563
|
-
with patch('mcp_proxy_adapter.custom_openapi.CustomOpenAPIGenerator._load_base_schema') as mock_load:
|
564
|
-
mock_load.return_value = {
|
565
|
-
"info": {
|
566
|
-
"title": "Test API",
|
567
|
-
"description": "Test description",
|
568
|
-
"version": "1.0.0"
|
569
|
-
},
|
570
|
-
"components": {
|
571
|
-
"schemas": {
|
572
|
-
"CommandRequest": {
|
573
|
-
"properties": {
|
574
|
-
"command": {"type": "string", "enum": []},
|
575
|
-
"params": {"type": "object", "oneOf": []}
|
576
|
-
}
|
577
|
-
}
|
578
|
-
}
|
579
|
-
}
|
580
|
-
}
|
581
|
-
|
582
|
-
generator = CustomOpenAPIGenerator()
|
583
|
-
|
584
|
-
with patch('mcp_proxy_adapter.custom_openapi.registry') as mock_registry:
|
585
|
-
# Create a mock command
|
586
|
-
class TestCommand(Command):
|
587
|
-
name = "test_command"
|
588
|
-
|
589
|
-
@classmethod
|
590
|
-
def get_schema(cls):
|
591
|
-
return {
|
592
|
-
"type": "object",
|
593
|
-
"properties": {
|
594
|
-
"param1": {"type": "string"}
|
595
|
-
}
|
596
|
-
}
|
597
|
-
|
598
|
-
async def execute(self, **kwargs):
|
599
|
-
return {"result": "test"}
|
600
|
-
|
601
|
-
mock_registry.get_all_commands.return_value = {
|
602
|
-
"test_command": TestCommand
|
603
|
-
}
|
604
|
-
|
605
|
-
schema = generator.generate(
|
606
|
-
title="Custom Title",
|
607
|
-
description="Custom Description",
|
608
|
-
version="2.0.0"
|
609
|
-
)
|
610
|
-
|
611
|
-
# Verify the complete schema structure
|
612
|
-
assert schema["info"]["title"] == "Custom Title"
|
613
|
-
assert schema["info"]["version"] == "2.0.0"
|
614
|
-
assert "components" in schema
|
615
|
-
assert "schemas" in schema["components"]
|
616
|
-
assert "ToolDescription" in schema["components"]["schemas"]
|
617
|
-
assert "Test_commandParams" in schema["components"]["schemas"]
|