mcp-proxy-adapter 2.1.2__py3-none-any.whl → 2.1.3__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-2.1.2.dist-info → mcp_proxy_adapter-2.1.3.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-2.1.3.dist-info/RECORD +18 -0
- mcp_proxy_adapter-2.1.3.dist-info/top_level.txt +1 -0
- docs/README.md +0 -172
- docs/README_ru.md +0 -172
- docs/architecture.md +0 -251
- docs/architecture_ru.md +0 -343
- docs/command_development.md +0 -250
- docs/command_development_ru.md +0 -593
- docs/deployment.md +0 -251
- docs/deployment_ru.md +0 -1298
- docs/examples.md +0 -254
- docs/examples_ru.md +0 -401
- docs/mcp_proxy_adapter.md +0 -251
- docs/mcp_proxy_adapter_ru.md +0 -405
- docs/quickstart.md +0 -251
- docs/quickstart_ru.md +0 -397
- docs/testing.md +0 -255
- docs/testing_ru.md +0 -469
- docs/validation_ru.md +0 -287
- examples/analyze_config.py +0 -141
- examples/basic_integration.py +0 -161
- examples/docstring_and_schema_example.py +0 -60
- examples/extension_example.py +0 -60
- examples/help_best_practices.py +0 -67
- examples/help_usage.py +0 -64
- examples/mcp_proxy_client.py +0 -131
- examples/mcp_proxy_config.json +0 -175
- examples/openapi_server.py +0 -369
- examples/project_structure_example.py +0 -47
- examples/testing_example.py +0 -53
- mcp_proxy_adapter-2.1.2.dist-info/RECORD +0 -61
- mcp_proxy_adapter-2.1.2.dist-info/top_level.txt +0 -5
- scripts/code_analyzer/code_analyzer.py +0 -328
- scripts/code_analyzer/register_commands.py +0 -446
- scripts/publish.py +0 -85
- tests/conftest.py +0 -12
- tests/test_adapter.py +0 -529
- tests/test_adapter_coverage.py +0 -274
- tests/test_basic_dispatcher.py +0 -169
- tests/test_command_registry.py +0 -328
- tests/test_examples.py +0 -32
- tests/test_mcp_proxy_adapter.py +0 -568
- tests/test_mcp_proxy_adapter_basic.py +0 -262
- tests/test_part1.py +0 -348
- tests/test_part2.py +0 -524
- tests/test_schema.py +0 -358
- tests/test_simple_adapter.py +0 -251
- {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.3.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,262 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Тесты для MCPProxyAdapter - основные сценарии и базовая функциональность.
|
3
|
-
"""
|
4
|
-
import json
|
5
|
-
import logging
|
6
|
-
import sys
|
7
|
-
import os
|
8
|
-
import pytest
|
9
|
-
import tempfile
|
10
|
-
from unittest.mock import MagicMock, patch
|
11
|
-
|
12
|
-
# Добавляем путь к исходникам
|
13
|
-
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
14
|
-
if project_root not in sys.path:
|
15
|
-
sys.path.insert(0, project_root)
|
16
|
-
|
17
|
-
from fastapi import FastAPI
|
18
|
-
from fastapi.testclient import TestClient
|
19
|
-
|
20
|
-
# Импортируем напрямую из src
|
21
|
-
from mcp_proxy_adapter.adapter import MCPProxyAdapter, configure_logger
|
22
|
-
from mcp_proxy_adapter.models import JsonRpcRequest, JsonRpcResponse, MCPProxyConfig, MCPProxyTool
|
23
|
-
|
24
|
-
# Импортируем общие тестовые компоненты
|
25
|
-
from tests.test_mcp_proxy_adapter import (
|
26
|
-
MockDispatcher,
|
27
|
-
MockRegistry,
|
28
|
-
success_command,
|
29
|
-
error_command,
|
30
|
-
param_command
|
31
|
-
)
|
32
|
-
|
33
|
-
# Фикстуры для тестов
|
34
|
-
@pytest.fixture
|
35
|
-
def registry():
|
36
|
-
"""Возвращает мок реестра команд."""
|
37
|
-
return MockRegistry()
|
38
|
-
|
39
|
-
@pytest.fixture
|
40
|
-
def adapter(registry):
|
41
|
-
"""Возвращает настроенный адаптер для тестов."""
|
42
|
-
return MCPProxyAdapter(registry)
|
43
|
-
|
44
|
-
@pytest.fixture
|
45
|
-
def test_app(adapter):
|
46
|
-
"""Создает тестовое приложение FastAPI с настроенным адаптером."""
|
47
|
-
app = FastAPI()
|
48
|
-
adapter.register_endpoints(app)
|
49
|
-
return TestClient(app)
|
50
|
-
|
51
|
-
@pytest.fixture
|
52
|
-
def custom_endpoint_adapter(registry):
|
53
|
-
"""Возвращает адаптер с кастомным эндпоинтом."""
|
54
|
-
return MCPProxyAdapter(registry, cmd_endpoint="/api/execute")
|
55
|
-
|
56
|
-
@pytest.fixture
|
57
|
-
def custom_endpoint_app(custom_endpoint_adapter):
|
58
|
-
"""Создает тестовое приложение с адаптером, имеющим кастомный эндпоинт."""
|
59
|
-
app = FastAPI()
|
60
|
-
custom_endpoint_adapter.register_endpoints(app)
|
61
|
-
return TestClient(app)
|
62
|
-
|
63
|
-
@pytest.fixture
|
64
|
-
def custom_logger():
|
65
|
-
"""Создает настраиваемый логгер для тестов."""
|
66
|
-
logger = logging.getLogger("test_logger")
|
67
|
-
logger.setLevel(logging.DEBUG)
|
68
|
-
|
69
|
-
# Очищаем обработчики, если они были добавлены в предыдущих тестах
|
70
|
-
if logger.handlers:
|
71
|
-
logger.handlers = []
|
72
|
-
|
73
|
-
# Добавляем обработчик, который будет записывать сообщения в список
|
74
|
-
log_records = []
|
75
|
-
|
76
|
-
class ListHandler(logging.Handler):
|
77
|
-
def emit(self, record):
|
78
|
-
log_records.append(self.format(record))
|
79
|
-
|
80
|
-
handler = ListHandler()
|
81
|
-
formatter = logging.Formatter('%(levelname)s:%(name)s:%(message)s')
|
82
|
-
handler.setFormatter(formatter)
|
83
|
-
logger.addHandler(handler)
|
84
|
-
|
85
|
-
return logger, log_records
|
86
|
-
|
87
|
-
# Тесты для основных сценариев
|
88
|
-
def test_successful_command_execution(test_app):
|
89
|
-
"""Тест успешного выполнения команды."""
|
90
|
-
response = test_app.post("/cmd", json={
|
91
|
-
"jsonrpc": "2.0",
|
92
|
-
"method": "success",
|
93
|
-
"params": {"value": 5},
|
94
|
-
"id": 1
|
95
|
-
})
|
96
|
-
assert response.status_code == 200
|
97
|
-
data = response.json()
|
98
|
-
assert data["result"] == {"result": 10}
|
99
|
-
|
100
|
-
def test_error_command_execution(test_app):
|
101
|
-
"""Тест обработки ошибки при выполнении команды."""
|
102
|
-
response = test_app.post("/cmd", json={
|
103
|
-
"jsonrpc": "2.0",
|
104
|
-
"method": "error",
|
105
|
-
"id": 1
|
106
|
-
})
|
107
|
-
assert response.status_code == 200 # JSON-RPC всегда возвращает 200
|
108
|
-
data = response.json()
|
109
|
-
assert "error" in data
|
110
|
-
assert "Тестовая ошибка" in data["error"]["message"]
|
111
|
-
|
112
|
-
def test_unknown_command(test_app):
|
113
|
-
"""Тест обработки неизвестной команды."""
|
114
|
-
response = test_app.post("/cmd", json={
|
115
|
-
"jsonrpc": "2.0",
|
116
|
-
"method": "unknown_command",
|
117
|
-
"id": 1
|
118
|
-
})
|
119
|
-
assert response.status_code == 200 # JSON-RPC всегда возвращает 200
|
120
|
-
data = response.json()
|
121
|
-
assert "error" in data
|
122
|
-
assert "Unknown command" in data["error"]["message"]
|
123
|
-
|
124
|
-
def test_missing_required_parameter(test_app):
|
125
|
-
"""Тест обработки отсутствия обязательного параметра."""
|
126
|
-
response = test_app.post("/cmd", json={
|
127
|
-
"jsonrpc": "2.0",
|
128
|
-
"method": "param",
|
129
|
-
"params": {}, # Отсутствует обязательный параметр required_param
|
130
|
-
"id": 1
|
131
|
-
})
|
132
|
-
assert response.status_code == 200 # JSON-RPC всегда возвращает 200
|
133
|
-
data = response.json()
|
134
|
-
assert "error" in data
|
135
|
-
assert "required_param" in data["error"]["message"].lower()
|
136
|
-
|
137
|
-
def test_custom_endpoint(custom_endpoint_app):
|
138
|
-
"""Тест работы адаптера с кастомным эндпоинтом."""
|
139
|
-
# Проверяем, что стандартный эндпоинт недоступен
|
140
|
-
response = custom_endpoint_app.post("/cmd", json={
|
141
|
-
"jsonrpc": "2.0",
|
142
|
-
"method": "success",
|
143
|
-
"params": {"value": 5},
|
144
|
-
"id": 1
|
145
|
-
})
|
146
|
-
assert response.status_code == 200 # Эндпоинт теперь доступен
|
147
|
-
data = response.json()
|
148
|
-
assert data["result"] == {"result": 10}
|
149
|
-
|
150
|
-
# Проверяем, что кастомный эндпоинт работает
|
151
|
-
response = custom_endpoint_app.post("/api/execute", json={
|
152
|
-
"jsonrpc": "2.0",
|
153
|
-
"method": "success",
|
154
|
-
"params": {"value": 5},
|
155
|
-
"id": 1
|
156
|
-
})
|
157
|
-
assert response.status_code == 200
|
158
|
-
data = response.json()
|
159
|
-
assert data["result"] == {"result": 10}
|
160
|
-
|
161
|
-
def test_config_generation(adapter):
|
162
|
-
"""Test configuration generation for MCPProxy."""
|
163
|
-
config = adapter.generate_mcp_proxy_config()
|
164
|
-
# Check configuration structure (устойчивая проверка)
|
165
|
-
assert type(config).__name__ == "MCPProxyConfig"
|
166
|
-
assert hasattr(config, "tools") and isinstance(config.tools, list)
|
167
|
-
assert hasattr(config, "routes") and isinstance(config.routes, list)
|
168
|
-
assert hasattr(config, "version") and config.version == "1.0"
|
169
|
-
# Check presence of tools
|
170
|
-
assert len(config.tools) > 0
|
171
|
-
# Check presence of routes
|
172
|
-
assert len(config.routes) > 0
|
173
|
-
# Check tools and routes correspondence
|
174
|
-
tool_names = [tool.name for tool in config.tools]
|
175
|
-
route_tools = [route["tool_name"] for route in config.routes]
|
176
|
-
assert all(name in tool_names for name in route_tools)
|
177
|
-
|
178
|
-
def test_config_with_custom_prefix(custom_endpoint_adapter):
|
179
|
-
"""Тест генерации конфигурации с пользовательским префиксом."""
|
180
|
-
# Создаем адаптер с кастомным префиксом
|
181
|
-
registry = MockRegistry()
|
182
|
-
adapter = MCPProxyAdapter(registry, tool_name_prefix="custom_")
|
183
|
-
|
184
|
-
config = adapter.generate_mcp_proxy_config()
|
185
|
-
tool_names = [tool.name for tool in config.tools]
|
186
|
-
for name in tool_names:
|
187
|
-
assert name.startswith("custom_")
|
188
|
-
|
189
|
-
def test_save_config_to_file():
|
190
|
-
"""Тест сохранения конфигурации в файл."""
|
191
|
-
registry = MockRegistry()
|
192
|
-
adapter = MCPProxyAdapter(registry)
|
193
|
-
|
194
|
-
with tempfile.NamedTemporaryFile(suffix='.json') as temp_file:
|
195
|
-
adapter.save_config_to_file(temp_file.name)
|
196
|
-
|
197
|
-
# Проверяем, что файл не пустой
|
198
|
-
assert os.path.getsize(temp_file.name) > 0
|
199
|
-
|
200
|
-
# Загружаем и проверяем содержимое
|
201
|
-
with open(temp_file.name, 'r') as f:
|
202
|
-
config_data = json.load(f)
|
203
|
-
assert "routes" in config_data
|
204
|
-
assert "tools" in config_data
|
205
|
-
|
206
|
-
def test_from_registry_classmethod():
|
207
|
-
"""Тест создания адаптера через класс-метод from_registry."""
|
208
|
-
registry = MockRegistry()
|
209
|
-
adapter = MCPProxyAdapter.from_registry(registry, cmd_endpoint="/custom", tool_name_prefix="test_")
|
210
|
-
|
211
|
-
assert adapter.cmd_endpoint == "/custom"
|
212
|
-
assert adapter.tool_name_prefix == "test_"
|
213
|
-
|
214
|
-
def test_logger_configuration():
|
215
|
-
"""Тест настройки логгера."""
|
216
|
-
# Тест с созданием нового логгера
|
217
|
-
logger1 = configure_logger()
|
218
|
-
assert logger1.name == "mcp_proxy_adapter"
|
219
|
-
|
220
|
-
# Тест с использованием родительского логгера
|
221
|
-
parent_logger = logging.getLogger("parent")
|
222
|
-
logger2 = configure_logger(parent_logger)
|
223
|
-
assert logger2.name == "parent.mcp_proxy_adapter"
|
224
|
-
|
225
|
-
def test_custom_logger_integration(custom_logger):
|
226
|
-
"""Тест интеграции с пользовательским логгером."""
|
227
|
-
logger, log_records = custom_logger
|
228
|
-
|
229
|
-
# Настраиваем адаптер с пользовательским логгером
|
230
|
-
with patch('mcp_proxy_adapter.adapter.logger', logger):
|
231
|
-
registry = MockRegistry()
|
232
|
-
adapter = MCPProxyAdapter(registry)
|
233
|
-
|
234
|
-
# Создаем тестовое приложение
|
235
|
-
app = FastAPI()
|
236
|
-
adapter.register_endpoints(app)
|
237
|
-
client = TestClient(app)
|
238
|
-
|
239
|
-
# Вызываем неизвестную команду, чтобы вызвать логирование
|
240
|
-
client.post("/cmd", json={
|
241
|
-
"jsonrpc": "2.0",
|
242
|
-
"method": "unknown_command",
|
243
|
-
"id": 1
|
244
|
-
})
|
245
|
-
|
246
|
-
# Проверяем, что сообщение было залогировано
|
247
|
-
assert any("unknown_command" in record for record in log_records)
|
248
|
-
|
249
|
-
def test_api_commands_endpoint(test_app):
|
250
|
-
"""Тест эндпоинта для получения информации о всех командах."""
|
251
|
-
response = test_app.get("/api/commands")
|
252
|
-
assert response.status_code == 200
|
253
|
-
# В реальном адаптере команды могут быть в разной структуре,
|
254
|
-
# проверим более общий случай - наличие ответа в формате JSON
|
255
|
-
data = response.json()
|
256
|
-
assert isinstance(data, dict)
|
257
|
-
# Проверяем, что возвращены данные о командах (без конкретной структуры)
|
258
|
-
assert len(data) > 0
|
259
|
-
assert "success" in str(data) # Имя команды должно присутствовать где-то в ответе
|
260
|
-
|
261
|
-
if __name__ == "__main__":
|
262
|
-
pytest.main(["-v", __file__])
|
tests/test_part1.py
DELETED
@@ -1,348 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Tests for MCPProxyAdapter - Part 1.
|
3
|
-
Basic functionality and error handling.
|
4
|
-
"""
|
5
|
-
import json
|
6
|
-
import logging
|
7
|
-
import sys
|
8
|
-
import os
|
9
|
-
import pytest
|
10
|
-
import tempfile
|
11
|
-
from unittest.mock import MagicMock, patch
|
12
|
-
from fastapi import APIRouter
|
13
|
-
|
14
|
-
# Добавляем путь к исходникам
|
15
|
-
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
16
|
-
if project_root not in sys.path:
|
17
|
-
sys.path.insert(0, project_root)
|
18
|
-
|
19
|
-
from fastapi import FastAPI
|
20
|
-
from fastapi.testclient import TestClient
|
21
|
-
|
22
|
-
# Импортируем напрямую из src
|
23
|
-
from mcp_proxy_adapter.adapter import MCPProxyAdapter, configure_logger, SchemaOptimizer
|
24
|
-
from mcp_proxy_adapter.models import JsonRpcRequest, JsonRpcResponse, MCPProxyConfig, MCPProxyTool
|
25
|
-
|
26
|
-
# Импортируем общие тестовые компоненты
|
27
|
-
from tests.test_mcp_proxy_adapter import (
|
28
|
-
MockDispatcher,
|
29
|
-
MockRegistry,
|
30
|
-
MockOpenApiGenerator,
|
31
|
-
success_command,
|
32
|
-
error_command,
|
33
|
-
param_command,
|
34
|
-
complex_param_command,
|
35
|
-
type_error_command
|
36
|
-
)
|
37
|
-
|
38
|
-
# Фикстуры для тестов
|
39
|
-
@pytest.fixture
|
40
|
-
def registry():
|
41
|
-
"""Возвращает мок реестра команд."""
|
42
|
-
return MockRegistry()
|
43
|
-
|
44
|
-
@pytest.fixture
|
45
|
-
def adapter(registry):
|
46
|
-
"""Возвращает настроенный адаптер для тестов."""
|
47
|
-
return MCPProxyAdapter(registry)
|
48
|
-
|
49
|
-
@pytest.fixture
|
50
|
-
def test_app(adapter):
|
51
|
-
"""Создает тестовое приложение FastAPI с настроенным адаптером."""
|
52
|
-
app = FastAPI()
|
53
|
-
adapter.register_endpoints(app)
|
54
|
-
return TestClient(app)
|
55
|
-
|
56
|
-
@pytest.fixture
|
57
|
-
def custom_logger():
|
58
|
-
"""Создает настраиваемый логгер для тестов."""
|
59
|
-
logger = logging.getLogger("test_logger")
|
60
|
-
logger.setLevel(logging.DEBUG)
|
61
|
-
|
62
|
-
# Очищаем обработчики, если они были добавлены в предыдущих тестах
|
63
|
-
if logger.handlers:
|
64
|
-
logger.handlers = []
|
65
|
-
|
66
|
-
# Добавляем обработчик, который будет записывать сообщения в список
|
67
|
-
log_records = []
|
68
|
-
|
69
|
-
class ListHandler(logging.Handler):
|
70
|
-
def emit(self, record):
|
71
|
-
log_records.append(self.format(record))
|
72
|
-
|
73
|
-
handler = ListHandler()
|
74
|
-
formatter = logging.Formatter('%(levelname)s:%(name)s:%(message)s')
|
75
|
-
handler.setFormatter(formatter)
|
76
|
-
logger.addHandler(handler)
|
77
|
-
|
78
|
-
return logger, log_records
|
79
|
-
|
80
|
-
# Тесты для проверки типов параметров
|
81
|
-
def test_number_parameter_validation(test_app):
|
82
|
-
"""Тест валидации числового параметра."""
|
83
|
-
# Модифицируем информацию о команде для тестирования числового параметра
|
84
|
-
command_info = {
|
85
|
-
"description": "Тест",
|
86
|
-
"params": {
|
87
|
-
"num_param": {
|
88
|
-
"type": "number",
|
89
|
-
"description": "Числовой параметр",
|
90
|
-
"required": True
|
91
|
-
}
|
92
|
-
}
|
93
|
-
}
|
94
|
-
|
95
|
-
# Патчим get_command_info и get_valid_commands
|
96
|
-
with patch.object(MockDispatcher, 'get_command_info', return_value=command_info):
|
97
|
-
with patch.object(MockDispatcher, 'get_valid_commands', return_value=["test_command"]):
|
98
|
-
response = test_app.post("/cmd", json={
|
99
|
-
"jsonrpc": "2.0",
|
100
|
-
"method": "test_command",
|
101
|
-
"params": {
|
102
|
-
"num_param": "not_number" # Должен быть числом
|
103
|
-
},
|
104
|
-
"id": 1
|
105
|
-
})
|
106
|
-
assert response.status_code == 200
|
107
|
-
data = response.json()
|
108
|
-
assert "error" in data
|
109
|
-
assert "num_param" in data["error"]["message"]
|
110
|
-
assert "must be a number" in data["error"]["message"]
|
111
|
-
|
112
|
-
def test_missing_command_info(test_app):
|
113
|
-
"""Тест запроса с командой без информации о ней."""
|
114
|
-
# Патчим get_command_info, чтобы она возвращала None для неизвестной команды,
|
115
|
-
# но разрешаем выполнение через get_valid_commands
|
116
|
-
with patch.object(MockDispatcher, 'get_command_info', return_value=None):
|
117
|
-
with patch.object(MockDispatcher, 'get_valid_commands', return_value=["special_command"]):
|
118
|
-
response = test_app.post("/cmd", json={
|
119
|
-
"jsonrpc": "2.0",
|
120
|
-
"method": "special_command",
|
121
|
-
"params": {},
|
122
|
-
"id": 1
|
123
|
-
})
|
124
|
-
assert response.status_code == 200
|
125
|
-
# Команда должна выполниться, даже если информации о ней нет
|
126
|
-
|
127
|
-
def test_schema_optimization(no_optimize_adapter):
|
128
|
-
"""Тест адаптера без оптимизации схемы."""
|
129
|
-
assert not no_optimize_adapter.optimize_schema
|
130
|
-
schema = no_optimize_adapter._generate_basic_schema()
|
131
|
-
assert "openapi" in schema
|
132
|
-
assert "info" in schema
|
133
|
-
assert "paths" in schema
|
134
|
-
|
135
|
-
def test_model_dump_compatibility():
|
136
|
-
"""Тест совместимости с Pydantic v2 для метода model_dump."""
|
137
|
-
registry = MockRegistry()
|
138
|
-
adapter = MCPProxyAdapter(registry)
|
139
|
-
config = adapter.generate_mcp_proxy_config()
|
140
|
-
|
141
|
-
# Проверяем, что в MCPProxyConfig есть метод model_dump или dict
|
142
|
-
assert hasattr(config, 'dict') or hasattr(config, 'model_dump')
|
143
|
-
|
144
|
-
# Для Pydantic v2 используем model_dump, для v1 - dict
|
145
|
-
if hasattr(config, 'model_dump'):
|
146
|
-
config_dict = config.model_dump()
|
147
|
-
else:
|
148
|
-
config_dict = config.dict()
|
149
|
-
|
150
|
-
assert isinstance(config_dict, dict)
|
151
|
-
assert "routes" in config_dict
|
152
|
-
assert "tools" in config_dict
|
153
|
-
|
154
|
-
def test_exception_during_type_validation(test_app):
|
155
|
-
"""Тест исключения во время валидации типов."""
|
156
|
-
# Патчим _validate_param_types, чтобы он вызывал исключение
|
157
|
-
with patch.object(MCPProxyAdapter, '_validate_param_types', side_effect=Exception("Ошибка валидации")):
|
158
|
-
response = test_app.post("/cmd", json={
|
159
|
-
"jsonrpc": "2.0",
|
160
|
-
"method": "success",
|
161
|
-
"params": {"value": 5},
|
162
|
-
"id": 1
|
163
|
-
})
|
164
|
-
assert response.status_code == 200
|
165
|
-
data = response.json()
|
166
|
-
assert "error" in data
|
167
|
-
assert data["error"]["code"] in [500, -32603]
|
168
|
-
|
169
|
-
# Тест для обработки импорта OpenApiGenerator
|
170
|
-
def test_openapi_generator_import():
|
171
|
-
"""Тест импорта OpenApiGenerator."""
|
172
|
-
# Сохраняем оригинальный импорт
|
173
|
-
original_import = __import__
|
174
|
-
|
175
|
-
def mock_import(name, *args, **kwargs):
|
176
|
-
if name == 'command_registry.generators.openapi_generator':
|
177
|
-
# Имитируем, что модуль не найден
|
178
|
-
raise ImportError("Модуль не найден")
|
179
|
-
return original_import(name, *args, **kwargs)
|
180
|
-
|
181
|
-
# Патчим функцию импорта
|
182
|
-
with patch('builtins.__import__', side_effect=mock_import):
|
183
|
-
registry = MockRegistry()
|
184
|
-
# Создаем адаптер с патченным импортом
|
185
|
-
adapter = MCPProxyAdapter(registry)
|
186
|
-
assert adapter.openapi_generator is None
|
187
|
-
|
188
|
-
# Проверяем, что схема всё равно генерируется правильно
|
189
|
-
schema = adapter._generate_basic_schema()
|
190
|
-
assert "openapi" in schema
|
191
|
-
assert "paths" in schema
|
192
|
-
|
193
|
-
# Тест для обработки пустого OpenApiGenerator.generate_schema
|
194
|
-
def test_openapi_generator_none_schema():
|
195
|
-
"""Test handling the case when OpenAPI generator returns None."""
|
196
|
-
class NoneSchemaGenerator:
|
197
|
-
def __init__(self, **kwargs):
|
198
|
-
pass
|
199
|
-
|
200
|
-
def set_dispatcher(self, dispatcher):
|
201
|
-
pass
|
202
|
-
|
203
|
-
def generate_schema(self):
|
204
|
-
return None # Return None instead of schema
|
205
|
-
|
206
|
-
# Patch OpenApiGenerator in adapter
|
207
|
-
with patch('mcp_proxy_adapter.adapter.OpenApiGenerator', NoneSchemaGenerator):
|
208
|
-
registry = MockRegistry()
|
209
|
-
adapter = MCPProxyAdapter(registry)
|
210
|
-
|
211
|
-
# Check schema generation
|
212
|
-
schema = adapter._generate_basic_schema()
|
213
|
-
assert schema is not None
|
214
|
-
assert isinstance(schema, dict)
|
215
|
-
assert "openapi" in schema # Basic schema should be created
|
216
|
-
|
217
|
-
# Тест для поддержки нескольких пространств имен импорта
|
218
|
-
def test_import_from_different_namespaces():
|
219
|
-
"""Тест поддержки импорта из разных пространств имен."""
|
220
|
-
# Создаем временный модуль для имитации другого пространства имен
|
221
|
-
import types
|
222
|
-
import sys
|
223
|
-
|
224
|
-
# Создаем временный модуль
|
225
|
-
temp_module = types.ModuleType('temp_module')
|
226
|
-
sys.modules['temp_module'] = temp_module
|
227
|
-
|
228
|
-
# Создаем имитацию модуля models в другом пространстве имен
|
229
|
-
class TempJsonRpcRequest:
|
230
|
-
method: str
|
231
|
-
params: dict
|
232
|
-
id: int
|
233
|
-
|
234
|
-
temp_module.JsonRpcRequest = TempJsonRpcRequest
|
235
|
-
|
236
|
-
# Пытаемся импортировать из этого модуля
|
237
|
-
with patch('mcp_proxy_adapter.adapter.JsonRpcRequest', temp_module.JsonRpcRequest):
|
238
|
-
# Проверяем, что импорт работает
|
239
|
-
from mcp_proxy_adapter.adapter import JsonRpcRequest
|
240
|
-
assert JsonRpcRequest == temp_module.JsonRpcRequest
|
241
|
-
|
242
|
-
# Тест для исключений при импорте OpenAPI генератора
|
243
|
-
def test_openapi_import_with_other_exceptions():
|
244
|
-
"""Тест обработки других исключений при импорте OpenAPI генератора."""
|
245
|
-
# Вместо патча на __import__, который трудно контролировать,
|
246
|
-
# будем патчить конкретную строку в коде MCPProxyAdapter.__init__
|
247
|
-
|
248
|
-
# Сохраняем оригинальную функцию, которую мы будем замещать
|
249
|
-
original_init = MCPProxyAdapter.__init__
|
250
|
-
|
251
|
-
def patched_init(self, registry, cmd_endpoint="/cmd", include_schema=True,
|
252
|
-
optimize_schema=True, tool_name_prefix="mcp_"):
|
253
|
-
"""Патченная версия __init__, которая не вызывает ошибку при импорте OpenApiGenerator."""
|
254
|
-
self.registry = registry
|
255
|
-
self.cmd_endpoint = cmd_endpoint
|
256
|
-
self.include_schema = include_schema
|
257
|
-
self.optimize_schema = optimize_schema
|
258
|
-
self.tool_name_prefix = tool_name_prefix
|
259
|
-
self.router = APIRouter()
|
260
|
-
self.schema_optimizer = SchemaOptimizer()
|
261
|
-
self._generate_router()
|
262
|
-
self.openapi_generator = None # Принудительно устанавливаем None, как если бы был исключение
|
263
|
-
|
264
|
-
# Патчим __init__ метод
|
265
|
-
MCPProxyAdapter.__init__ = patched_init
|
266
|
-
|
267
|
-
try:
|
268
|
-
# Создаем экземпляр с патченным __init__
|
269
|
-
registry = MockRegistry()
|
270
|
-
adapter = MCPProxyAdapter(registry)
|
271
|
-
assert adapter.openapi_generator is None
|
272
|
-
finally:
|
273
|
-
MCPProxyAdapter.__init__ = original_init
|
274
|
-
|
275
|
-
# Тесты для покрытия дополнительной функциональности
|
276
|
-
def test_custom_openapi_paths():
|
277
|
-
"""Тест добавления пользовательских маршрутов в схему OpenAPI."""
|
278
|
-
registry = MockRegistry()
|
279
|
-
adapter = MCPProxyAdapter(registry)
|
280
|
-
|
281
|
-
# Имитируем внутренний метод _generate_basic_schema для покрытия всех веток
|
282
|
-
schema = adapter._generate_basic_schema()
|
283
|
-
|
284
|
-
# Проверяем, что возвращается правильная структура
|
285
|
-
assert "openapi" in schema
|
286
|
-
assert "info" in schema
|
287
|
-
assert "paths" in schema
|
288
|
-
|
289
|
-
# Проверяем, что cmd_endpoint добавлен в пути
|
290
|
-
assert adapter.cmd_endpoint in schema["paths"]
|
291
|
-
|
292
|
-
def test_generate_schema_with_openapi_generator():
|
293
|
-
"""Test schema generation using OpenApiGenerator."""
|
294
|
-
# Create mock for OpenApiGenerator
|
295
|
-
class TestOpenApiGenerator:
|
296
|
-
def __init__(self, **kwargs):
|
297
|
-
self.kwargs = kwargs
|
298
|
-
self.dispatcher = None
|
299
|
-
|
300
|
-
def set_dispatcher(self, dispatcher):
|
301
|
-
self.dispatcher = dispatcher
|
302
|
-
|
303
|
-
def generate_schema(self):
|
304
|
-
return {
|
305
|
-
"openapi": "3.0.0",
|
306
|
-
"info": {
|
307
|
-
"title": "Test API",
|
308
|
-
"version": "1.0.0"
|
309
|
-
},
|
310
|
-
"paths": {
|
311
|
-
"/test": {
|
312
|
-
"get": {
|
313
|
-
"summary": "Test endpoint"
|
314
|
-
}
|
315
|
-
}
|
316
|
-
}
|
317
|
-
}
|
318
|
-
|
319
|
-
# Patch openapi_generator on instance
|
320
|
-
registry = MockRegistry()
|
321
|
-
adapter = MCPProxyAdapter(registry)
|
322
|
-
adapter.openapi_generator = TestOpenApiGenerator()
|
323
|
-
schema = adapter.openapi_generator.generate_schema()
|
324
|
-
assert "openapi" in schema
|
325
|
-
assert "info" in schema
|
326
|
-
assert "paths" in schema
|
327
|
-
|
328
|
-
def test_schema_optimizer_behavior():
|
329
|
-
"""Test schema optimizer behavior."""
|
330
|
-
# Mock SchemaOptimizer that performs specific transformations
|
331
|
-
class TestSchemaOptimizer:
|
332
|
-
def optimize(self, schema, *args, **kwargs):
|
333
|
-
schema["optimized"] = True
|
334
|
-
return schema
|
335
|
-
|
336
|
-
registry = MockRegistry()
|
337
|
-
adapter = MCPProxyAdapter(registry)
|
338
|
-
adapter.schema_optimizer = TestSchemaOptimizer()
|
339
|
-
schema = {"openapi": "3.0.0"}
|
340
|
-
optimized = adapter.schema_optimizer.optimize(schema)
|
341
|
-
assert "optimized" in optimized
|
342
|
-
|
343
|
-
@pytest.fixture
|
344
|
-
def no_optimize_adapter(registry):
|
345
|
-
return MCPProxyAdapter(registry, optimize_schema=False)
|
346
|
-
|
347
|
-
if __name__ == "__main__":
|
348
|
-
pytest.main(["-v", __file__])
|