mcp-proxy-adapter 2.1.2__py3-none-any.whl → 2.1.4__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.
- {examples → mcp_proxy_adapter/examples}/openapi_server.py +35 -7
- {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.4.dist-info}/METADATA +98 -2
- mcp_proxy_adapter-2.1.4.dist-info/RECORD +28 -0
- mcp_proxy_adapter-2.1.4.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/mcp_proxy_config.json +0 -175
- 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
- {examples → mcp_proxy_adapter/examples}/analyze_config.py +0 -0
- {examples → mcp_proxy_adapter/examples}/basic_integration.py +0 -0
- {examples → mcp_proxy_adapter/examples}/docstring_and_schema_example.py +0 -0
- {examples → mcp_proxy_adapter/examples}/extension_example.py +0 -0
- {examples → mcp_proxy_adapter/examples}/help_best_practices.py +0 -0
- {examples → mcp_proxy_adapter/examples}/help_usage.py +0 -0
- {examples → mcp_proxy_adapter/examples}/mcp_proxy_client.py +0 -0
- {examples → mcp_proxy_adapter/examples}/project_structure_example.py +0 -0
- {examples → mcp_proxy_adapter/examples}/testing_example.py +0 -0
- {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.4.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.4.dist-info}/licenses/LICENSE +0 -0
tests/test_mcp_proxy_adapter.py
DELETED
@@ -1,568 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Тесты для MCPProxyAdapter с акцентом на 100% покрытие кода,
|
3
|
-
включая обработку ошибок, пограничные случаи и неполное заполнение сигнатур.
|
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
|
-
import types
|
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
|
24
|
-
from mcp_proxy_adapter.models import JsonRpcRequest, JsonRpcResponse, MCPProxyConfig, MCPProxyTool
|
25
|
-
|
26
|
-
# Тестовые функции-команды
|
27
|
-
def success_command(value: int = 1) -> dict:
|
28
|
-
"""Тестовая команда, завершающаяся успешно."""
|
29
|
-
return {"result": value * 2}
|
30
|
-
|
31
|
-
def error_command() -> None:
|
32
|
-
"""Тестовая команда, генерирующая ошибку."""
|
33
|
-
raise ValueError("Тестовая ошибка")
|
34
|
-
|
35
|
-
def param_command(required_param: str, optional_param: int = 0) -> dict:
|
36
|
-
"""Тестовая команда с обязательным и необязательным параметрами."""
|
37
|
-
return {"required": required_param, "optional": optional_param}
|
38
|
-
|
39
|
-
def complex_param_command(array_param: list, object_param: dict, bool_param: bool = True) -> dict:
|
40
|
-
"""Тестовая команда со сложными типами параметров."""
|
41
|
-
return {
|
42
|
-
"array_length": len(array_param),
|
43
|
-
"object_keys": list(object_param.keys()),
|
44
|
-
"bool_value": bool_param
|
45
|
-
}
|
46
|
-
|
47
|
-
def type_error_command(param: int) -> dict:
|
48
|
-
"""Команда, которая вызовет TypeError при неправильном типе параметра."""
|
49
|
-
return {"param": param + 1} # Требуется int
|
50
|
-
|
51
|
-
# Мок для диспетчера команд
|
52
|
-
class MockDispatcher:
|
53
|
-
"""Mock for command dispatcher in tests."""
|
54
|
-
|
55
|
-
def __init__(self):
|
56
|
-
self.commands = {
|
57
|
-
"success": success_command,
|
58
|
-
"error": error_command,
|
59
|
-
"param": param_command,
|
60
|
-
"execute": self.execute_from_params
|
61
|
-
}
|
62
|
-
self.commands_info = {
|
63
|
-
"success": {
|
64
|
-
"description": "Successful command",
|
65
|
-
"params": {
|
66
|
-
"value": {
|
67
|
-
"type": "integer",
|
68
|
-
"description": "Input value",
|
69
|
-
"required": False,
|
70
|
-
"default": 1
|
71
|
-
}
|
72
|
-
}
|
73
|
-
},
|
74
|
-
"error": {
|
75
|
-
"description": "Command with error",
|
76
|
-
"params": {}
|
77
|
-
},
|
78
|
-
"param": {
|
79
|
-
"description": "Command with parameters",
|
80
|
-
"params": {
|
81
|
-
"required_param": {
|
82
|
-
"type": "string",
|
83
|
-
"description": "Required parameter",
|
84
|
-
"required": True
|
85
|
-
},
|
86
|
-
"optional_param": {
|
87
|
-
"type": "integer",
|
88
|
-
"description": "Optional parameter",
|
89
|
-
"required": False,
|
90
|
-
"default": 0
|
91
|
-
}
|
92
|
-
}
|
93
|
-
},
|
94
|
-
"execute": {
|
95
|
-
"description": "Universal command for executing other commands",
|
96
|
-
"params": {
|
97
|
-
"query": {
|
98
|
-
"type": "string",
|
99
|
-
"description": "Command or query to execute",
|
100
|
-
"required": False
|
101
|
-
}
|
102
|
-
}
|
103
|
-
},
|
104
|
-
"complex_param": {
|
105
|
-
"description": "Command with complex parameters",
|
106
|
-
"params": {
|
107
|
-
"array_param": {
|
108
|
-
"type": "array",
|
109
|
-
"description": "Array of values",
|
110
|
-
"required": True
|
111
|
-
},
|
112
|
-
"object_param": {
|
113
|
-
"type": "object",
|
114
|
-
"description": "Object",
|
115
|
-
"required": True
|
116
|
-
},
|
117
|
-
"bool_param": {
|
118
|
-
"type": "boolean",
|
119
|
-
"description": "Boolean value",
|
120
|
-
"required": False,
|
121
|
-
"default": True
|
122
|
-
}
|
123
|
-
}
|
124
|
-
},
|
125
|
-
"type_error": {
|
126
|
-
"description": "Command that will raise TypeError",
|
127
|
-
"params": {
|
128
|
-
"param": {
|
129
|
-
"type": "integer",
|
130
|
-
"description": "Integer parameter",
|
131
|
-
"required": True
|
132
|
-
}
|
133
|
-
}
|
134
|
-
}
|
135
|
-
}
|
136
|
-
|
137
|
-
def execute_from_params(self, **params):
|
138
|
-
"""Executes command based on parameters."""
|
139
|
-
if "query" in params and params["query"] in self.commands:
|
140
|
-
command = params.pop("query")
|
141
|
-
return self.execute(command, **params)
|
142
|
-
return {
|
143
|
-
"available_commands": self.get_valid_commands(),
|
144
|
-
"received_params": params
|
145
|
-
}
|
146
|
-
|
147
|
-
def execute(self, command, **params):
|
148
|
-
"""Executes command with specified parameters."""
|
149
|
-
if command not in self.commands:
|
150
|
-
raise KeyError(f"Unknown command: {command}")
|
151
|
-
return self.commands[command](**params)
|
152
|
-
|
153
|
-
def get_valid_commands(self):
|
154
|
-
"""Returns list of available commands."""
|
155
|
-
return list(self.commands.keys())
|
156
|
-
|
157
|
-
def get_command_info(self, command):
|
158
|
-
"""Returns information about command."""
|
159
|
-
return self.commands_info.get(command)
|
160
|
-
|
161
|
-
def get_commands_info(self):
|
162
|
-
"""Returns information about all commands."""
|
163
|
-
return self.commands_info
|
164
|
-
|
165
|
-
# Мок для CommandRegistry
|
166
|
-
class MockRegistry:
|
167
|
-
"""Мок для CommandRegistry в тестах."""
|
168
|
-
|
169
|
-
def __init__(self, use_openapi_generator=False):
|
170
|
-
self.dispatcher = MockDispatcher()
|
171
|
-
self.generators = []
|
172
|
-
self.use_openapi_generator = use_openapi_generator
|
173
|
-
|
174
|
-
def get_commands_info(self):
|
175
|
-
"""Возвращает информацию о командах из диспетчера."""
|
176
|
-
return self.dispatcher.get_commands_info()
|
177
|
-
|
178
|
-
def add_generator(self, generator):
|
179
|
-
"""Добавляет генератор API."""
|
180
|
-
self.generators.append(generator)
|
181
|
-
if hasattr(generator, 'set_dispatcher'):
|
182
|
-
generator.set_dispatcher(self.dispatcher)
|
183
|
-
|
184
|
-
# Мок для OpenApiGenerator
|
185
|
-
class MockOpenApiGenerator:
|
186
|
-
"""Мок для OpenApiGenerator в тестах."""
|
187
|
-
|
188
|
-
def __init__(self, **kwargs):
|
189
|
-
self.dispatcher = None
|
190
|
-
self.kwargs = kwargs
|
191
|
-
|
192
|
-
def set_dispatcher(self, dispatcher):
|
193
|
-
"""Устанавливает диспетчер команд."""
|
194
|
-
self.dispatcher = dispatcher
|
195
|
-
|
196
|
-
def generate_schema(self):
|
197
|
-
"""Генерирует схему OpenAPI."""
|
198
|
-
return {
|
199
|
-
"openapi": "3.0.0",
|
200
|
-
"info": {
|
201
|
-
"title": self.kwargs.get("title", "API"),
|
202
|
-
"version": self.kwargs.get("version", "1.0.0"),
|
203
|
-
"description": self.kwargs.get("description", "API description")
|
204
|
-
},
|
205
|
-
"paths": {
|
206
|
-
"/test": {
|
207
|
-
"get": {
|
208
|
-
"summary": "Test endpoint",
|
209
|
-
"responses": {
|
210
|
-
"200": {
|
211
|
-
"description": "Successful response"
|
212
|
-
}
|
213
|
-
}
|
214
|
-
}
|
215
|
-
}
|
216
|
-
}
|
217
|
-
}
|
218
|
-
|
219
|
-
# Фикстуры для тестов
|
220
|
-
@pytest.fixture
|
221
|
-
def registry():
|
222
|
-
"""Возвращает мок реестра команд."""
|
223
|
-
return MockRegistry()
|
224
|
-
|
225
|
-
@pytest.fixture
|
226
|
-
def registry_with_openapi():
|
227
|
-
"""Возвращает мок реестра команд с поддержкой OpenAPI."""
|
228
|
-
registry = MockRegistry(use_openapi_generator=True)
|
229
|
-
return registry
|
230
|
-
|
231
|
-
@pytest.fixture
|
232
|
-
def adapter(registry):
|
233
|
-
"""Возвращает настроенный адаптер для тестов."""
|
234
|
-
return MCPProxyAdapter(registry)
|
235
|
-
|
236
|
-
@pytest.fixture
|
237
|
-
def adapter_with_openapi(registry):
|
238
|
-
"""Возвращает адаптер с поддержкой OpenAPI для тестов."""
|
239
|
-
with patch('mcp_proxy_adapter.adapter.OpenApiGenerator', MockOpenApiGenerator):
|
240
|
-
return MCPProxyAdapter(registry)
|
241
|
-
|
242
|
-
@pytest.fixture
|
243
|
-
def test_app(adapter):
|
244
|
-
"""Создает тестовое приложение FastAPI с настроенным адаптером."""
|
245
|
-
app = FastAPI()
|
246
|
-
adapter.register_endpoints(app)
|
247
|
-
return TestClient(app)
|
248
|
-
|
249
|
-
@pytest.fixture
|
250
|
-
def custom_endpoint_adapter(registry):
|
251
|
-
"""Возвращает адаптер с кастомным эндпоинтом."""
|
252
|
-
return MCPProxyAdapter(registry, cmd_endpoint="/api/execute")
|
253
|
-
|
254
|
-
@pytest.fixture
|
255
|
-
def custom_endpoint_app(custom_endpoint_adapter):
|
256
|
-
"""Создает тестовое приложение с адаптером, имеющим кастомный эндпоинт."""
|
257
|
-
app = FastAPI()
|
258
|
-
custom_endpoint_adapter.register_endpoints(app)
|
259
|
-
return TestClient(app)
|
260
|
-
|
261
|
-
@pytest.fixture
|
262
|
-
def no_schema_adapter(registry):
|
263
|
-
"""Возвращает адаптер без включения схемы OpenAPI."""
|
264
|
-
return MCPProxyAdapter(registry, include_schema=False)
|
265
|
-
|
266
|
-
@pytest.fixture
|
267
|
-
def no_schema_app(no_schema_adapter):
|
268
|
-
"""Создает тестовое приложение без эндпоинта схемы OpenAPI."""
|
269
|
-
app = FastAPI()
|
270
|
-
no_schema_adapter.register_endpoints(app)
|
271
|
-
return TestClient(app)
|
272
|
-
|
273
|
-
@pytest.fixture
|
274
|
-
def no_optimize_adapter(registry):
|
275
|
-
"""Возвращает адаптер без оптимизации схемы."""
|
276
|
-
return MCPProxyAdapter(registry, optimize_schema=False)
|
277
|
-
|
278
|
-
@pytest.fixture
|
279
|
-
def custom_prefix_adapter(registry):
|
280
|
-
"""Возвращает адаптер с пользовательским префиксом инструментов."""
|
281
|
-
return MCPProxyAdapter(registry, tool_name_prefix="custom_")
|
282
|
-
|
283
|
-
@pytest.fixture
|
284
|
-
def custom_logger():
|
285
|
-
"""Создает настраиваемый логгер для тестов."""
|
286
|
-
logger = logging.getLogger("test_logger")
|
287
|
-
logger.setLevel(logging.DEBUG)
|
288
|
-
|
289
|
-
# Очищаем обработчики, если они были добавлены в предыдущих тестах
|
290
|
-
if logger.handlers:
|
291
|
-
logger.handlers = []
|
292
|
-
|
293
|
-
# Добавляем обработчик, который будет записывать сообщения в список
|
294
|
-
log_records = []
|
295
|
-
|
296
|
-
class ListHandler(logging.Handler):
|
297
|
-
def emit(self, record):
|
298
|
-
log_records.append(self.format(record))
|
299
|
-
|
300
|
-
handler = ListHandler()
|
301
|
-
formatter = logging.Formatter('%(levelname)s:%(name)s:%(message)s')
|
302
|
-
handler.setFormatter(formatter)
|
303
|
-
logger.addHandler(handler)
|
304
|
-
|
305
|
-
return logger, log_records
|
306
|
-
|
307
|
-
@pytest.fixture
|
308
|
-
def help_project_command():
|
309
|
-
"""Фикстура: help реализован в проекте (как команда)."""
|
310
|
-
def help_handler(**params):
|
311
|
-
# Симулируем поведение help-команды проекта
|
312
|
-
if not params or not params.get('command'):
|
313
|
-
return {"source": "project", "commands": ["success", "error"]}
|
314
|
-
if params["command"] == "success":
|
315
|
-
return {"source": "project", "command": "success", "info": {"description": "Success command"}}
|
316
|
-
return {"error": f"Command '{params['command']}' not found", "available_commands": ["success", "error"]}
|
317
|
-
return help_handler
|
318
|
-
|
319
|
-
@pytest.fixture
|
320
|
-
def help_adapter_command():
|
321
|
-
"""Фикстура: help-адаптер (стандартный)."""
|
322
|
-
def help_handler(**params):
|
323
|
-
# Симулируем поведение help-адаптера
|
324
|
-
if not params or not params.get('command'):
|
325
|
-
return {"source": "adapter", "commands": ["success", "error"]}
|
326
|
-
if params["command"] == "success":
|
327
|
-
return {"source": "adapter", "command": "success", "info": {"description": "Success command (adapter)"}}
|
328
|
-
return {"source": "adapter", "error": f"Command '{params['command']}' not found (adapter)", "available_commands": ["success", "error"]}
|
329
|
-
return help_handler
|
330
|
-
|
331
|
-
class HelpDispatcher(MockDispatcher):
|
332
|
-
def __init__(self, project_help=None, adapter_help=None):
|
333
|
-
super().__init__()
|
334
|
-
self.project_help = project_help
|
335
|
-
self.adapter_help = adapter_help
|
336
|
-
# Добавляем help в список команд, если задан project_help
|
337
|
-
if project_help:
|
338
|
-
self.commands["help"] = self.help_command
|
339
|
-
self.commands_info["help"] = {"description": "Project help command", "params": {"command": {"type": "string", "required": False}}}
|
340
|
-
def help_command(self, **params):
|
341
|
-
return self.project_help(**params)
|
342
|
-
def adapter_help_command(self, **params):
|
343
|
-
return self.adapter_help(**params)
|
344
|
-
|
345
|
-
class HelpRegistry(MockRegistry):
|
346
|
-
def __init__(self, project_help=None, adapter_help=None):
|
347
|
-
self.dispatcher = HelpDispatcher(project_help, adapter_help)
|
348
|
-
self.generators = []
|
349
|
-
self.use_openapi_generator = False
|
350
|
-
def get_commands_info(self):
|
351
|
-
return self.dispatcher.get_commands_info()
|
352
|
-
def add_generator(self, generator):
|
353
|
-
self.generators.append(generator)
|
354
|
-
if hasattr(generator, 'set_dispatcher'):
|
355
|
-
generator.set_dispatcher(self.dispatcher)
|
356
|
-
|
357
|
-
# Тесты для основных сценариев
|
358
|
-
def test_successful_command_execution(test_app):
|
359
|
-
"""Тест успешного выполнения команды."""
|
360
|
-
response = test_app.post("/cmd", json={
|
361
|
-
"jsonrpc": "2.0",
|
362
|
-
"method": "success",
|
363
|
-
"params": {"value": 5},
|
364
|
-
"id": 1
|
365
|
-
})
|
366
|
-
assert response.status_code == 200
|
367
|
-
data = response.json()
|
368
|
-
assert data["result"] == {"result": 10}
|
369
|
-
|
370
|
-
# === HELP WRAPPER TESTS ===
|
371
|
-
def test_help_project_no_param(monkeypatch, help_project_command, help_adapter_command):
|
372
|
-
"""help реализован в проекте, вызов без параметров: должен вызываться help проекта."""
|
373
|
-
registry = HelpRegistry(project_help=help_project_command, adapter_help=help_adapter_command)
|
374
|
-
adapter = MCPProxyAdapter(registry)
|
375
|
-
# monkeypatch: simulate help-wrapper logic
|
376
|
-
result = registry.dispatcher.help_command()
|
377
|
-
assert result["source"] == "project"
|
378
|
-
assert "commands" in result
|
379
|
-
|
380
|
-
def test_help_project_with_param(monkeypatch, help_project_command, help_adapter_command):
|
381
|
-
"""help реализован в проекте, вызов с существующим параметром: должен вызываться help проекта."""
|
382
|
-
registry = HelpRegistry(project_help=help_project_command, adapter_help=help_adapter_command)
|
383
|
-
adapter = MCPProxyAdapter(registry)
|
384
|
-
result = registry.dispatcher.help_command(command="success")
|
385
|
-
assert result["source"] == "project"
|
386
|
-
assert result["command"] == "success"
|
387
|
-
assert "info" in result
|
388
|
-
|
389
|
-
def test_help_project_with_wrong_param(monkeypatch, help_project_command, help_adapter_command):
|
390
|
-
"""help реализован в проекте, вызов с несуществующим параметром: help проекта возвращает ошибку, вызывается help-адаптер."""
|
391
|
-
registry = HelpRegistry(project_help=help_project_command, adapter_help=help_adapter_command)
|
392
|
-
adapter = MCPProxyAdapter(registry)
|
393
|
-
# Симулируем: если help проекта вернул ошибку, вызываем help-адаптер
|
394
|
-
result = registry.dispatcher.help_command(command="unknown")
|
395
|
-
if "error" in result:
|
396
|
-
adapter_result = registry.dispatcher.adapter_help_command(command="unknown")
|
397
|
-
assert adapter_result["source"] == "adapter"
|
398
|
-
assert "error" in adapter_result
|
399
|
-
else:
|
400
|
-
assert False, "Project help should return error for unknown command"
|
401
|
-
|
402
|
-
def test_help_adapter_no_project(monkeypatch, help_adapter_command):
|
403
|
-
"""help не реализован в проекте, вызов без параметров: должен вызываться help-адаптер."""
|
404
|
-
registry = HelpRegistry(project_help=None, adapter_help=help_adapter_command)
|
405
|
-
adapter = MCPProxyAdapter(registry)
|
406
|
-
# Симулируем: help-адаптер вызывается напрямую
|
407
|
-
result = registry.dispatcher.adapter_help_command()
|
408
|
-
assert result["source"] == "adapter"
|
409
|
-
assert "commands" in result
|
410
|
-
|
411
|
-
def test_help_adapter_with_param_no_project(monkeypatch, help_adapter_command):
|
412
|
-
"""help не реализован в проекте, вызов с параметром: должен вызываться help-адаптер."""
|
413
|
-
registry = HelpRegistry(project_help=None, adapter_help=help_adapter_command)
|
414
|
-
adapter = MCPProxyAdapter(registry)
|
415
|
-
result = registry.dispatcher.adapter_help_command(command="success")
|
416
|
-
assert result["source"] == "adapter"
|
417
|
-
assert result["command"] == "success"
|
418
|
-
assert "info" in result
|
419
|
-
|
420
|
-
# === COVERAGE BOOST TESTS ===
|
421
|
-
def test_dispatcher_keyerror():
|
422
|
-
"""Test KeyError for unknown command in MockDispatcher."""
|
423
|
-
dispatcher = MockDispatcher()
|
424
|
-
with pytest.raises(KeyError):
|
425
|
-
dispatcher.execute("unknown")
|
426
|
-
|
427
|
-
def test_dispatcher_type_error():
|
428
|
-
"""Test TypeError for wrong param type in type_error_command."""
|
429
|
-
dispatcher = MockDispatcher()
|
430
|
-
# Явно добавляем type_error команду, если вдруг отсутствует
|
431
|
-
dispatcher.commands["type_error"] = type_error_command
|
432
|
-
with pytest.raises(TypeError):
|
433
|
-
dispatcher.execute("type_error", param="not_an_int")
|
434
|
-
|
435
|
-
def test_execute_from_params_edge():
|
436
|
-
"""Test execute_from_params with unknown query and empty params."""
|
437
|
-
dispatcher = MockDispatcher()
|
438
|
-
result = dispatcher.execute_from_params(query="unknown")
|
439
|
-
assert "available_commands" in result
|
440
|
-
assert "received_params" in result
|
441
|
-
result2 = dispatcher.execute_from_params()
|
442
|
-
assert isinstance(result2, dict)
|
443
|
-
|
444
|
-
def test_registry_fixtures(registry, registry_with_openapi):
|
445
|
-
"""Test registry and registry_with_openapi fixtures."""
|
446
|
-
assert isinstance(registry, MockRegistry)
|
447
|
-
assert isinstance(registry_with_openapi, MockRegistry)
|
448
|
-
assert registry_with_openapi.use_openapi_generator
|
449
|
-
|
450
|
-
def test_adapter_fixtures(adapter, adapter_with_openapi, no_schema_adapter, no_optimize_adapter, custom_prefix_adapter):
|
451
|
-
"""Test all adapter fixtures."""
|
452
|
-
assert isinstance(adapter, MCPProxyAdapter)
|
453
|
-
assert isinstance(adapter_with_openapi, MCPProxyAdapter)
|
454
|
-
assert isinstance(no_schema_adapter, MCPProxyAdapter)
|
455
|
-
assert isinstance(no_optimize_adapter, MCPProxyAdapter)
|
456
|
-
assert isinstance(custom_prefix_adapter, MCPProxyAdapter)
|
457
|
-
|
458
|
-
def test_custom_logger_fixture(custom_logger):
|
459
|
-
"""Test custom_logger fixture."""
|
460
|
-
logger, log_records = custom_logger
|
461
|
-
logger.info("test message")
|
462
|
-
assert any("test message" in rec for rec in log_records)
|
463
|
-
|
464
|
-
def test_custom_endpoint_app(custom_endpoint_app):
|
465
|
-
"""Test custom endpoint app fixture."""
|
466
|
-
response = custom_endpoint_app.post("/api/execute", json={"jsonrpc": "2.0", "method": "success", "params": {"value": 2}, "id": 1})
|
467
|
-
assert response.status_code == 200
|
468
|
-
assert response.json()["result"] == {"result": 4}
|
469
|
-
|
470
|
-
def test_no_schema_app(no_schema_app):
|
471
|
-
"""Test no_schema_app fixture."""
|
472
|
-
response = no_schema_app.post("/cmd", json={"jsonrpc": "2.0", "method": "success", "params": {"value": 3}, "id": 1})
|
473
|
-
assert response.status_code == 200
|
474
|
-
assert response.json()["result"] == {"result": 6}
|
475
|
-
|
476
|
-
def test_help_project_empty_param(help_project_command):
|
477
|
-
"""Test help_project_command with empty param dict."""
|
478
|
-
result = help_project_command()
|
479
|
-
assert result["source"] == "project"
|
480
|
-
|
481
|
-
def test_help_adapter_empty_param(help_adapter_command):
|
482
|
-
"""Test help_adapter_command with empty param dict."""
|
483
|
-
result = help_adapter_command()
|
484
|
-
assert result["source"] == "adapter"
|
485
|
-
|
486
|
-
def test_help_dispatcher_and_registry():
|
487
|
-
"""Test HelpDispatcher and HelpRegistry edge cases."""
|
488
|
-
dispatcher = HelpDispatcher()
|
489
|
-
registry = HelpRegistry()
|
490
|
-
# help not registered
|
491
|
-
assert "help" not in dispatcher.commands
|
492
|
-
# add generator
|
493
|
-
class DummyGen: pass
|
494
|
-
registry.add_generator(DummyGen())
|
495
|
-
assert registry.generators
|
496
|
-
|
497
|
-
# === DETAILED HELP-COMMAND TESTS ===
|
498
|
-
def test_project_help_priority(monkeypatch, help_project_command, help_adapter_command):
|
499
|
-
"""If project help exists, it must always be called first (no param)."""
|
500
|
-
registry = HelpRegistry(project_help=help_project_command, adapter_help=help_adapter_command)
|
501
|
-
adapter = MCPProxyAdapter(registry)
|
502
|
-
# Симулируем вызов help без параметров
|
503
|
-
result = registry.dispatcher.help_command()
|
504
|
-
assert result["source"] == "project"
|
505
|
-
assert "commands" in result
|
506
|
-
# Адаптер не должен вызываться
|
507
|
-
assert not ("adapter" in result.get("source", ""))
|
508
|
-
|
509
|
-
def test_project_help_with_param_success(monkeypatch, help_project_command, help_adapter_command):
|
510
|
-
"""Project help with valid param: must return project info, not adapter."""
|
511
|
-
registry = HelpRegistry(project_help=help_project_command, adapter_help=help_adapter_command)
|
512
|
-
adapter = MCPProxyAdapter(registry)
|
513
|
-
result = registry.dispatcher.help_command(command="success")
|
514
|
-
assert result["source"] == "project"
|
515
|
-
assert result["command"] == "success"
|
516
|
-
assert "info" in result
|
517
|
-
# Адаптер не должен вызываться
|
518
|
-
assert not ("adapter" in result.get("source", ""))
|
519
|
-
|
520
|
-
def test_project_help_with_param_not_found(monkeypatch, help_project_command, help_adapter_command):
|
521
|
-
"""Project help with unknown param: must call adapter help after project help error."""
|
522
|
-
registry = HelpRegistry(project_help=help_project_command, adapter_help=help_adapter_command)
|
523
|
-
adapter = MCPProxyAdapter(registry)
|
524
|
-
result = registry.dispatcher.help_command(command="unknown")
|
525
|
-
# Проектный help возвращает ошибку
|
526
|
-
assert "error" in result
|
527
|
-
# После ошибки вызывается help-адаптер
|
528
|
-
adapter_result = registry.dispatcher.adapter_help_command(command="unknown")
|
529
|
-
assert adapter_result["source"] == "adapter"
|
530
|
-
assert "error" in adapter_result
|
531
|
-
|
532
|
-
def test_project_help_with_param_exception(monkeypatch, help_adapter_command):
|
533
|
-
"""Project help raises exception: adapter help must be called as fallback."""
|
534
|
-
def broken_help(**params):
|
535
|
-
raise RuntimeError("project help failed")
|
536
|
-
registry = HelpRegistry(project_help=broken_help, adapter_help=help_adapter_command)
|
537
|
-
adapter = MCPProxyAdapter(registry)
|
538
|
-
# Симулируем: если проектный help падает, вызываем help-адаптер
|
539
|
-
try:
|
540
|
-
registry.dispatcher.help_command(command="any")
|
541
|
-
except Exception as e:
|
542
|
-
adapter_result = registry.dispatcher.adapter_help_command(command="any")
|
543
|
-
assert adapter_result["source"] == "adapter"
|
544
|
-
assert "commands" in adapter_result or "error" in adapter_result
|
545
|
-
|
546
|
-
def test_project_help_with_param_none(monkeypatch, help_project_command, help_adapter_command):
|
547
|
-
"""Project help with param=None: must return project help info."""
|
548
|
-
registry = HelpRegistry(project_help=help_project_command, adapter_help=help_adapter_command)
|
549
|
-
adapter = MCPProxyAdapter(registry)
|
550
|
-
result = registry.dispatcher.help_command(command=None)
|
551
|
-
assert result["source"] == "project"
|
552
|
-
assert "commands" in result
|
553
|
-
|
554
|
-
def test_project_help_returns_unexpected_type(monkeypatch, help_adapter_command):
|
555
|
-
"""Project help returns unexpected type: adapter help must be called as fallback."""
|
556
|
-
def weird_help(**params):
|
557
|
-
return "not a dict"
|
558
|
-
registry = HelpRegistry(project_help=weird_help, adapter_help=help_adapter_command)
|
559
|
-
adapter = MCPProxyAdapter(registry)
|
560
|
-
result = registry.dispatcher.help_command(command="any")
|
561
|
-
# Если результат не dict, вызываем help-адаптер
|
562
|
-
if not isinstance(result, dict):
|
563
|
-
adapter_result = registry.dispatcher.adapter_help_command(command="any")
|
564
|
-
assert adapter_result["source"] == "adapter"
|
565
|
-
assert "commands" in adapter_result or "error" in adapter_result
|
566
|
-
|
567
|
-
if __name__ == "__main__":
|
568
|
-
pytest.main(["-v", __file__])
|