mcp-proxy-adapter 3.1.6__py3-none-any.whl → 4.0.0__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/api/app.py +65 -27
- mcp_proxy_adapter/api/handlers.py +1 -1
- mcp_proxy_adapter/api/middleware/error_handling.py +11 -10
- mcp_proxy_adapter/api/tool_integration.py +5 -2
- mcp_proxy_adapter/api/tools.py +3 -3
- mcp_proxy_adapter/commands/base.py +19 -1
- mcp_proxy_adapter/commands/command_registry.py +243 -6
- mcp_proxy_adapter/commands/hooks.py +260 -0
- mcp_proxy_adapter/commands/reload_command.py +211 -0
- mcp_proxy_adapter/commands/reload_settings_command.py +125 -0
- mcp_proxy_adapter/commands/settings_command.py +189 -0
- mcp_proxy_adapter/config.py +16 -1
- mcp_proxy_adapter/core/__init__.py +44 -0
- mcp_proxy_adapter/core/logging.py +87 -34
- mcp_proxy_adapter/core/settings.py +376 -0
- mcp_proxy_adapter/core/utils.py +2 -2
- mcp_proxy_adapter/custom_openapi.py +81 -2
- mcp_proxy_adapter/examples/README.md +124 -0
- mcp_proxy_adapter/examples/__init__.py +7 -0
- mcp_proxy_adapter/examples/basic_server/README.md +60 -0
- mcp_proxy_adapter/examples/basic_server/__init__.py +7 -0
- mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +39 -0
- mcp_proxy_adapter/examples/basic_server/config.json +35 -0
- mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +238 -0
- mcp_proxy_adapter/examples/basic_server/server.py +98 -0
- mcp_proxy_adapter/examples/custom_commands/README.md +127 -0
- mcp_proxy_adapter/examples/custom_commands/__init__.py +27 -0
- mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +250 -0
- mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +6 -0
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +103 -0
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +111 -0
- mcp_proxy_adapter/examples/custom_commands/config.json +62 -0
- mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +169 -0
- mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +215 -0
- mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +76 -0
- mcp_proxy_adapter/examples/custom_commands/custom_settings.json +96 -0
- mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +241 -0
- mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +135 -0
- mcp_proxy_adapter/examples/custom_commands/echo_command.py +122 -0
- mcp_proxy_adapter/examples/custom_commands/hooks.py +230 -0
- mcp_proxy_adapter/examples/custom_commands/intercept_command.py +123 -0
- mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +103 -0
- mcp_proxy_adapter/examples/custom_commands/server.py +223 -0
- mcp_proxy_adapter/examples/custom_commands/test_hooks.py +176 -0
- mcp_proxy_adapter/examples/deployment/README.md +49 -0
- mcp_proxy_adapter/examples/deployment/__init__.py +7 -0
- mcp_proxy_adapter/examples/deployment/config.development.json +8 -0
- {examples/basic_example → mcp_proxy_adapter/examples/deployment}/config.json +11 -7
- mcp_proxy_adapter/examples/deployment/config.production.json +12 -0
- mcp_proxy_adapter/examples/deployment/config.staging.json +11 -0
- mcp_proxy_adapter/examples/deployment/docker-compose.yml +31 -0
- mcp_proxy_adapter/examples/deployment/run.sh +43 -0
- mcp_proxy_adapter/examples/deployment/run_docker.sh +84 -0
- mcp_proxy_adapter/openapi.py +3 -2
- mcp_proxy_adapter/tests/api/test_custom_openapi.py +617 -0
- mcp_proxy_adapter/tests/api/test_handlers.py +522 -0
- mcp_proxy_adapter/tests/api/test_schemas.py +546 -0
- mcp_proxy_adapter/tests/api/test_tool_integration.py +531 -0
- mcp_proxy_adapter/tests/unit/test_base_command.py +391 -85
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.0.0.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-4.0.0.dist-info/RECORD +110 -0
- {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.0.0.dist-info}/WHEEL +1 -1
- {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.0.0.dist-info}/top_level.txt +0 -1
- examples/__init__.py +0 -19
- examples/anti_patterns/README.md +0 -51
- examples/anti_patterns/__init__.py +0 -9
- examples/anti_patterns/bad_design/README.md +0 -72
- examples/anti_patterns/bad_design/global_state.py +0 -170
- examples/anti_patterns/bad_design/monolithic_command.py +0 -272
- examples/basic_example/README.md +0 -245
- examples/basic_example/__init__.py +0 -8
- examples/basic_example/commands/__init__.py +0 -5
- examples/basic_example/commands/echo_command.py +0 -95
- examples/basic_example/commands/math_command.py +0 -151
- examples/basic_example/commands/time_command.py +0 -152
- examples/basic_example/docs/EN/README.md +0 -177
- examples/basic_example/docs/RU/README.md +0 -177
- examples/basic_example/server.py +0 -151
- examples/basic_example/tests/conftest.py +0 -243
- examples/check_vstl_schema.py +0 -106
- examples/commands/echo_command.py +0 -52
- examples/commands/echo_command_di.py +0 -152
- examples/commands/echo_result.py +0 -65
- examples/commands/get_date_command.py +0 -98
- examples/commands/new_uuid4_command.py +0 -91
- examples/complete_example/Dockerfile +0 -24
- examples/complete_example/README.md +0 -92
- examples/complete_example/__init__.py +0 -8
- examples/complete_example/commands/__init__.py +0 -5
- examples/complete_example/commands/system_command.py +0 -328
- examples/complete_example/config.json +0 -41
- examples/complete_example/configs/config.dev.yaml +0 -40
- examples/complete_example/configs/config.docker.yaml +0 -40
- examples/complete_example/docker-compose.yml +0 -35
- examples/complete_example/requirements.txt +0 -20
- examples/complete_example/server.py +0 -113
- examples/di_example/.pytest_cache/README.md +0 -8
- examples/di_example/server.py +0 -249
- examples/fix_vstl_help.py +0 -123
- examples/minimal_example/README.md +0 -65
- examples/minimal_example/__init__.py +0 -8
- examples/minimal_example/config.json +0 -14
- examples/minimal_example/main.py +0 -136
- examples/minimal_example/simple_server.py +0 -163
- examples/minimal_example/tests/conftest.py +0 -171
- examples/minimal_example/tests/test_hello_command.py +0 -111
- examples/minimal_example/tests/test_integration.py +0 -181
- examples/patch_vstl_service.py +0 -105
- examples/patch_vstl_service_mcp.py +0 -108
- examples/server.py +0 -69
- examples/simple_server.py +0 -128
- examples/test_package_3.1.4.py +0 -177
- examples/test_server.py +0 -134
- examples/tool_description_example.py +0 -82
- mcp_proxy_adapter/py.typed +0 -0
- mcp_proxy_adapter-3.1.6.dist-info/RECORD +0 -118
- {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,546 @@
|
|
1
|
+
"""
|
2
|
+
Tests for schemas module.
|
3
|
+
|
4
|
+
This module contains comprehensive tests for API schema definitions
|
5
|
+
to ensure 90%+ code coverage.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import pytest
|
9
|
+
import json
|
10
|
+
from typing import Dict, Any
|
11
|
+
|
12
|
+
from mcp_proxy_adapter.api.schemas import (
|
13
|
+
ErrorResponse, ErrorWrapper, JsonRpcRequest, JsonRpcError,
|
14
|
+
JsonRpcSuccessResponse, JsonRpcErrorResponse, CommandResponse,
|
15
|
+
HealthResponse, CommandListResponse, CommandRequest,
|
16
|
+
CommandSuccessResponse, CommandErrorResponse, APIToolDescription
|
17
|
+
)
|
18
|
+
|
19
|
+
|
20
|
+
class TestErrorResponse:
|
21
|
+
"""Test cases for ErrorResponse model."""
|
22
|
+
|
23
|
+
def test_error_response_creation(self):
|
24
|
+
"""Test ErrorResponse creation with all fields."""
|
25
|
+
error = ErrorResponse(
|
26
|
+
code=404,
|
27
|
+
message="Not found",
|
28
|
+
details={"resource": "user", "id": 123}
|
29
|
+
)
|
30
|
+
|
31
|
+
assert error.code == 404
|
32
|
+
assert error.message == "Not found"
|
33
|
+
assert error.details == {"resource": "user", "id": 123}
|
34
|
+
|
35
|
+
def test_error_response_creation_without_details(self):
|
36
|
+
"""Test ErrorResponse creation without optional details."""
|
37
|
+
error = ErrorResponse(code=500, message="Internal server error")
|
38
|
+
|
39
|
+
assert error.code == 500
|
40
|
+
assert error.message == "Internal server error"
|
41
|
+
assert error.details is None
|
42
|
+
|
43
|
+
def test_error_response_to_dict(self):
|
44
|
+
"""Test ErrorResponse serialization to dict."""
|
45
|
+
error = ErrorResponse(
|
46
|
+
code=400,
|
47
|
+
message="Bad request",
|
48
|
+
details={"field": "email"}
|
49
|
+
)
|
50
|
+
|
51
|
+
error_dict = error.model_dump()
|
52
|
+
assert error_dict["code"] == 400
|
53
|
+
assert error_dict["message"] == "Bad request"
|
54
|
+
assert error_dict["details"] == {"field": "email"}
|
55
|
+
|
56
|
+
|
57
|
+
class TestErrorWrapper:
|
58
|
+
"""Test cases for ErrorWrapper model."""
|
59
|
+
|
60
|
+
def test_error_wrapper_creation(self):
|
61
|
+
"""Test ErrorWrapper creation."""
|
62
|
+
error_response = ErrorResponse(code=404, message="Not found")
|
63
|
+
wrapper = ErrorWrapper(error=error_response)
|
64
|
+
|
65
|
+
assert wrapper.error == error_response
|
66
|
+
assert wrapper.error.code == 404
|
67
|
+
|
68
|
+
def test_error_wrapper_to_dict(self):
|
69
|
+
"""Test ErrorWrapper serialization to dict."""
|
70
|
+
error_response = ErrorResponse(code=500, message="Server error")
|
71
|
+
wrapper = ErrorWrapper(error=error_response)
|
72
|
+
|
73
|
+
wrapper_dict = wrapper.model_dump()
|
74
|
+
assert "error" in wrapper_dict
|
75
|
+
assert wrapper_dict["error"]["code"] == 500
|
76
|
+
|
77
|
+
|
78
|
+
class TestJsonRpcRequest:
|
79
|
+
"""Test cases for JsonRpcRequest model."""
|
80
|
+
|
81
|
+
def test_json_rpc_request_creation(self):
|
82
|
+
"""Test JsonRpcRequest creation with all fields."""
|
83
|
+
request = JsonRpcRequest(
|
84
|
+
method="test_method",
|
85
|
+
params={"param1": "value1", "param2": 42},
|
86
|
+
id="request_123"
|
87
|
+
)
|
88
|
+
|
89
|
+
assert request.jsonrpc == "2.0"
|
90
|
+
assert request.method == "test_method"
|
91
|
+
assert request.params == {"param1": "value1", "param2": 42}
|
92
|
+
assert request.id == "request_123"
|
93
|
+
|
94
|
+
def test_json_rpc_request_creation_without_params(self):
|
95
|
+
"""Test JsonRpcRequest creation without params."""
|
96
|
+
request = JsonRpcRequest(method="simple_method", id=1)
|
97
|
+
|
98
|
+
assert request.method == "simple_method"
|
99
|
+
assert request.params is None
|
100
|
+
assert request.id == 1
|
101
|
+
|
102
|
+
def test_json_rpc_request_creation_with_list_params(self):
|
103
|
+
"""Test JsonRpcRequest creation with list params."""
|
104
|
+
request = JsonRpcRequest(
|
105
|
+
method="list_method",
|
106
|
+
params=["item1", "item2", "item3"],
|
107
|
+
id="list_request"
|
108
|
+
)
|
109
|
+
|
110
|
+
assert request.params == ["item1", "item2", "item3"]
|
111
|
+
|
112
|
+
def test_json_rpc_request_to_dict(self):
|
113
|
+
"""Test JsonRpcRequest serialization to dict."""
|
114
|
+
request = JsonRpcRequest(
|
115
|
+
method="test_method",
|
116
|
+
params={"test": "value"},
|
117
|
+
id="test_id"
|
118
|
+
)
|
119
|
+
|
120
|
+
request_dict = request.model_dump()
|
121
|
+
assert request_dict["jsonrpc"] == "2.0"
|
122
|
+
assert request_dict["method"] == "test_method"
|
123
|
+
assert request_dict["params"] == {"test": "value"}
|
124
|
+
assert request_dict["id"] == "test_id"
|
125
|
+
|
126
|
+
|
127
|
+
class TestJsonRpcError:
|
128
|
+
"""Test cases for JsonRpcError model."""
|
129
|
+
|
130
|
+
def test_json_rpc_error_creation(self):
|
131
|
+
"""Test JsonRpcError creation with all fields."""
|
132
|
+
error = JsonRpcError(
|
133
|
+
code=-32601,
|
134
|
+
message="Method not found",
|
135
|
+
data={"available_methods": ["help", "config"]}
|
136
|
+
)
|
137
|
+
|
138
|
+
assert error.code == -32601
|
139
|
+
assert error.message == "Method not found"
|
140
|
+
assert error.data == {"available_methods": ["help", "config"]}
|
141
|
+
|
142
|
+
def test_json_rpc_error_creation_without_data(self):
|
143
|
+
"""Test JsonRpcError creation without optional data."""
|
144
|
+
error = JsonRpcError(code=-32700, message="Parse error")
|
145
|
+
|
146
|
+
assert error.code == -32700
|
147
|
+
assert error.message == "Parse error"
|
148
|
+
assert error.data is None
|
149
|
+
|
150
|
+
def test_json_rpc_error_to_dict(self):
|
151
|
+
"""Test JsonRpcError serialization to dict."""
|
152
|
+
error = JsonRpcError(
|
153
|
+
code=-32602,
|
154
|
+
message="Invalid params",
|
155
|
+
data={"expected": "string", "received": "number"}
|
156
|
+
)
|
157
|
+
|
158
|
+
error_dict = error.model_dump()
|
159
|
+
assert error_dict["code"] == -32602
|
160
|
+
assert error_dict["message"] == "Invalid params"
|
161
|
+
assert error_dict["data"] == {"expected": "string", "received": "number"}
|
162
|
+
|
163
|
+
|
164
|
+
class TestJsonRpcSuccessResponse:
|
165
|
+
"""Test cases for JsonRpcSuccessResponse model."""
|
166
|
+
|
167
|
+
def test_json_rpc_success_response_creation(self):
|
168
|
+
"""Test JsonRpcSuccessResponse creation."""
|
169
|
+
response = JsonRpcSuccessResponse(
|
170
|
+
result={"status": "success", "data": "test_data"},
|
171
|
+
id="response_123"
|
172
|
+
)
|
173
|
+
|
174
|
+
assert response.jsonrpc == "2.0"
|
175
|
+
assert response.result == {"status": "success", "data": "test_data"}
|
176
|
+
assert response.id == "response_123"
|
177
|
+
|
178
|
+
def test_json_rpc_success_response_to_dict(self):
|
179
|
+
"""Test JsonRpcSuccessResponse serialization to dict."""
|
180
|
+
response = JsonRpcSuccessResponse(
|
181
|
+
result={"message": "OK"},
|
182
|
+
id=1
|
183
|
+
)
|
184
|
+
|
185
|
+
response_dict = response.model_dump()
|
186
|
+
assert response_dict["jsonrpc"] == "2.0"
|
187
|
+
assert response_dict["result"] == {"message": "OK"}
|
188
|
+
assert response_dict["id"] == 1
|
189
|
+
|
190
|
+
|
191
|
+
class TestJsonRpcErrorResponse:
|
192
|
+
"""Test cases for JsonRpcErrorResponse model."""
|
193
|
+
|
194
|
+
def test_json_rpc_error_response_creation(self):
|
195
|
+
"""Test JsonRpcErrorResponse creation."""
|
196
|
+
error = JsonRpcError(code=-32601, message="Method not found")
|
197
|
+
response = JsonRpcErrorResponse(error=error, id="error_response")
|
198
|
+
|
199
|
+
assert response.jsonrpc == "2.0"
|
200
|
+
assert response.error == error
|
201
|
+
assert response.id == "error_response"
|
202
|
+
|
203
|
+
def test_json_rpc_error_response_to_dict(self):
|
204
|
+
"""Test JsonRpcErrorResponse serialization to dict."""
|
205
|
+
error = JsonRpcError(code=-32700, message="Parse error")
|
206
|
+
response = JsonRpcErrorResponse(error=error, id=1)
|
207
|
+
|
208
|
+
response_dict = response.model_dump()
|
209
|
+
assert response_dict["jsonrpc"] == "2.0"
|
210
|
+
assert response_dict["error"]["code"] == -32700
|
211
|
+
assert response_dict["error"]["message"] == "Parse error"
|
212
|
+
assert response_dict["id"] == 1
|
213
|
+
|
214
|
+
|
215
|
+
class TestCommandResponse:
|
216
|
+
"""Test cases for CommandResponse model."""
|
217
|
+
|
218
|
+
def test_command_response_success(self):
|
219
|
+
"""Test CommandResponse creation for success case."""
|
220
|
+
response = CommandResponse(
|
221
|
+
success=True,
|
222
|
+
data={"result": "command executed"},
|
223
|
+
message="Command completed successfully"
|
224
|
+
)
|
225
|
+
|
226
|
+
assert response.success is True
|
227
|
+
assert response.data == {"result": "command executed"}
|
228
|
+
assert response.message == "Command completed successfully"
|
229
|
+
assert response.error is None
|
230
|
+
|
231
|
+
def test_command_response_error(self):
|
232
|
+
"""Test CommandResponse creation for error case."""
|
233
|
+
error = ErrorResponse(code=400, message="Bad request")
|
234
|
+
response = CommandResponse(
|
235
|
+
success=False,
|
236
|
+
error=error,
|
237
|
+
message="Command failed"
|
238
|
+
)
|
239
|
+
|
240
|
+
assert response.success is False
|
241
|
+
assert response.data is None
|
242
|
+
assert response.message == "Command failed"
|
243
|
+
assert response.error == error
|
244
|
+
|
245
|
+
def test_command_response_to_dict(self):
|
246
|
+
"""Test CommandResponse serialization to dict."""
|
247
|
+
response = CommandResponse(
|
248
|
+
success=True,
|
249
|
+
data={"status": "ok"},
|
250
|
+
message="Success"
|
251
|
+
)
|
252
|
+
|
253
|
+
response_dict = response.model_dump()
|
254
|
+
assert response_dict["success"] is True
|
255
|
+
assert response_dict["data"] == {"status": "ok"}
|
256
|
+
assert response_dict["message"] == "Success"
|
257
|
+
assert response_dict["error"] is None
|
258
|
+
|
259
|
+
|
260
|
+
class TestHealthResponse:
|
261
|
+
"""Test cases for HealthResponse model."""
|
262
|
+
|
263
|
+
def test_health_response_creation(self):
|
264
|
+
"""Test HealthResponse creation."""
|
265
|
+
response = HealthResponse(
|
266
|
+
status="healthy",
|
267
|
+
version="1.0.0",
|
268
|
+
uptime=3600.5,
|
269
|
+
components={"database": "ok", "cache": "ok"}
|
270
|
+
)
|
271
|
+
|
272
|
+
assert response.status == "healthy"
|
273
|
+
assert response.version == "1.0.0"
|
274
|
+
assert response.uptime == 3600.5
|
275
|
+
assert response.components == {"database": "ok", "cache": "ok"}
|
276
|
+
|
277
|
+
def test_health_response_to_dict(self):
|
278
|
+
"""Test HealthResponse serialization to dict."""
|
279
|
+
response = HealthResponse(
|
280
|
+
status="degraded",
|
281
|
+
version="2.1.0",
|
282
|
+
uptime=7200.0,
|
283
|
+
components={"database": "ok", "cache": "error"}
|
284
|
+
)
|
285
|
+
|
286
|
+
response_dict = response.model_dump()
|
287
|
+
assert response_dict["status"] == "degraded"
|
288
|
+
assert response_dict["version"] == "2.1.0"
|
289
|
+
assert response_dict["uptime"] == 7200.0
|
290
|
+
assert response_dict["components"] == {"database": "ok", "cache": "error"}
|
291
|
+
|
292
|
+
|
293
|
+
class TestCommandListResponse:
|
294
|
+
"""Test cases for CommandListResponse model."""
|
295
|
+
|
296
|
+
def test_command_list_response_creation(self):
|
297
|
+
"""Test CommandListResponse creation."""
|
298
|
+
commands = {
|
299
|
+
"help": {"summary": "Show help", "params": {}},
|
300
|
+
"config": {"summary": "Get config", "params": {"section": "string"}}
|
301
|
+
}
|
302
|
+
response = CommandListResponse(commands=commands)
|
303
|
+
|
304
|
+
assert response.commands == commands
|
305
|
+
assert len(response.commands) == 2
|
306
|
+
|
307
|
+
def test_command_list_response_to_dict(self):
|
308
|
+
"""Test CommandListResponse serialization to dict."""
|
309
|
+
commands = {"test": {"summary": "Test command"}}
|
310
|
+
response = CommandListResponse(commands=commands)
|
311
|
+
|
312
|
+
response_dict = response.model_dump()
|
313
|
+
assert response_dict["commands"] == commands
|
314
|
+
|
315
|
+
|
316
|
+
class TestCommandRequest:
|
317
|
+
"""Test cases for CommandRequest model."""
|
318
|
+
|
319
|
+
def test_command_request_creation(self):
|
320
|
+
"""Test CommandRequest creation with params."""
|
321
|
+
request = CommandRequest(
|
322
|
+
command="test_command",
|
323
|
+
params={"param1": "value1", "param2": 42}
|
324
|
+
)
|
325
|
+
|
326
|
+
assert request.command == "test_command"
|
327
|
+
assert request.params == {"param1": "value1", "param2": 42}
|
328
|
+
|
329
|
+
def test_command_request_creation_without_params(self):
|
330
|
+
"""Test CommandRequest creation without params."""
|
331
|
+
request = CommandRequest(command="simple_command")
|
332
|
+
|
333
|
+
assert request.command == "simple_command"
|
334
|
+
assert request.params == {}
|
335
|
+
|
336
|
+
def test_command_request_to_dict(self):
|
337
|
+
"""Test CommandRequest serialization to dict."""
|
338
|
+
request = CommandRequest(
|
339
|
+
command="test_command",
|
340
|
+
params={"test": "value"}
|
341
|
+
)
|
342
|
+
|
343
|
+
request_dict = request.model_dump()
|
344
|
+
assert request_dict["command"] == "test_command"
|
345
|
+
assert request_dict["params"] == {"test": "value"}
|
346
|
+
|
347
|
+
|
348
|
+
class TestCommandSuccessResponse:
|
349
|
+
"""Test cases for CommandSuccessResponse model."""
|
350
|
+
|
351
|
+
def test_command_success_response_creation(self):
|
352
|
+
"""Test CommandSuccessResponse creation."""
|
353
|
+
response = CommandSuccessResponse(
|
354
|
+
result={"status": "success", "data": "test_data"}
|
355
|
+
)
|
356
|
+
|
357
|
+
assert response.result == {"status": "success", "data": "test_data"}
|
358
|
+
|
359
|
+
def test_command_success_response_with_id(self):
|
360
|
+
"""Test CommandSuccessResponse creation with id."""
|
361
|
+
response = CommandSuccessResponse(
|
362
|
+
result={"message": "OK"}
|
363
|
+
)
|
364
|
+
|
365
|
+
assert response.result == {"message": "OK"}
|
366
|
+
|
367
|
+
def test_command_success_response_to_dict(self):
|
368
|
+
"""Test CommandSuccessResponse serialization to dict."""
|
369
|
+
response = CommandSuccessResponse(
|
370
|
+
result={"data": "test"}
|
371
|
+
)
|
372
|
+
|
373
|
+
response_dict = response.model_dump()
|
374
|
+
assert response_dict["result"] == {"data": "test"}
|
375
|
+
|
376
|
+
|
377
|
+
class TestCommandErrorResponse:
|
378
|
+
"""Test cases for CommandErrorResponse model."""
|
379
|
+
|
380
|
+
def test_command_error_response_creation(self):
|
381
|
+
"""Test CommandErrorResponse creation."""
|
382
|
+
error = JsonRpcError(code=-32601, message="Method not found")
|
383
|
+
response = CommandErrorResponse(error=error)
|
384
|
+
|
385
|
+
assert response.error == error
|
386
|
+
|
387
|
+
def test_command_error_response_with_id(self):
|
388
|
+
"""Test CommandErrorResponse creation with id."""
|
389
|
+
error = JsonRpcError(code=-32700, message="Parse error")
|
390
|
+
response = CommandErrorResponse(error=error)
|
391
|
+
|
392
|
+
assert response.error == error
|
393
|
+
|
394
|
+
def test_command_error_response_to_dict(self):
|
395
|
+
"""Test CommandErrorResponse serialization to dict."""
|
396
|
+
error = JsonRpcError(code=-32602, message="Invalid params")
|
397
|
+
response = CommandErrorResponse(error=error)
|
398
|
+
|
399
|
+
response_dict = response.model_dump()
|
400
|
+
assert response_dict["error"]["code"] == -32602
|
401
|
+
assert response_dict["error"]["message"] == "Invalid params"
|
402
|
+
|
403
|
+
|
404
|
+
class TestAPIToolDescription:
|
405
|
+
"""Test cases for APIToolDescription class."""
|
406
|
+
|
407
|
+
@pytest.fixture
|
408
|
+
def mock_registry(self):
|
409
|
+
"""Create a mock command registry for testing."""
|
410
|
+
from unittest.mock import Mock
|
411
|
+
|
412
|
+
registry = Mock()
|
413
|
+
registry.get_all_metadata.return_value = {
|
414
|
+
"help": {
|
415
|
+
"summary": "Show help information",
|
416
|
+
"description": "Display help for commands",
|
417
|
+
"params": {
|
418
|
+
"command": {
|
419
|
+
"type": "строка",
|
420
|
+
"description": "Command name",
|
421
|
+
"required": False
|
422
|
+
}
|
423
|
+
},
|
424
|
+
"examples": [
|
425
|
+
{
|
426
|
+
"command": "help",
|
427
|
+
"params": {"command": "config"}
|
428
|
+
}
|
429
|
+
]
|
430
|
+
},
|
431
|
+
"config": {
|
432
|
+
"summary": "Get configuration",
|
433
|
+
"description": "Retrieve configuration settings",
|
434
|
+
"params": {
|
435
|
+
"section": {
|
436
|
+
"type": "строка",
|
437
|
+
"description": "Configuration section",
|
438
|
+
"required": True
|
439
|
+
}
|
440
|
+
},
|
441
|
+
"examples": [
|
442
|
+
{
|
443
|
+
"command": "config",
|
444
|
+
"params": {"section": "database"}
|
445
|
+
}
|
446
|
+
]
|
447
|
+
}
|
448
|
+
}
|
449
|
+
|
450
|
+
return registry
|
451
|
+
|
452
|
+
def test_generate_tool_description(self, mock_registry):
|
453
|
+
"""Test tool description generation."""
|
454
|
+
tool_name = "test_tool"
|
455
|
+
|
456
|
+
description = APIToolDescription.generate_tool_description(tool_name, mock_registry)
|
457
|
+
|
458
|
+
assert description["name"] == tool_name
|
459
|
+
assert "description" in description
|
460
|
+
assert "supported_commands" in description
|
461
|
+
assert "examples" in description
|
462
|
+
assert "help" in description["supported_commands"]
|
463
|
+
assert "config" in description["supported_commands"]
|
464
|
+
|
465
|
+
def test_generate_tool_description_text(self, mock_registry):
|
466
|
+
"""Test tool description text generation."""
|
467
|
+
tool_name = "test_tool"
|
468
|
+
|
469
|
+
text = APIToolDescription.generate_tool_description_text(tool_name, mock_registry)
|
470
|
+
|
471
|
+
assert isinstance(text, str)
|
472
|
+
assert tool_name in text
|
473
|
+
assert "help" in text
|
474
|
+
assert "config" in text
|
475
|
+
|
476
|
+
def test_simplify_type_conversion(self):
|
477
|
+
"""Test type simplification for various input types."""
|
478
|
+
# Test string types
|
479
|
+
assert APIToolDescription._simplify_type("str") == "строка"
|
480
|
+
assert APIToolDescription._simplify_type("int") == "целое число"
|
481
|
+
assert APIToolDescription._simplify_type("float") == "число"
|
482
|
+
assert APIToolDescription._simplify_type("bool") == "логическое значение"
|
483
|
+
assert APIToolDescription._simplify_type("List") == "список"
|
484
|
+
assert APIToolDescription._simplify_type("Dict") == "объект"
|
485
|
+
|
486
|
+
# Test unknown type
|
487
|
+
assert APIToolDescription._simplify_type("неизвестный") == "значение"
|
488
|
+
|
489
|
+
def test_extract_param_description(self):
|
490
|
+
"""Test parameter description extraction."""
|
491
|
+
doc_string = """
|
492
|
+
Test command.
|
493
|
+
|
494
|
+
Args:
|
495
|
+
param1: First parameter description
|
496
|
+
param2: Second parameter description
|
497
|
+
"""
|
498
|
+
|
499
|
+
description = APIToolDescription._extract_param_description(doc_string, "param1")
|
500
|
+
assert "First parameter description" in description
|
501
|
+
|
502
|
+
description = APIToolDescription._extract_param_description(doc_string, "param2")
|
503
|
+
assert "Second parameter description" in description
|
504
|
+
|
505
|
+
# Test non-existent parameter
|
506
|
+
description = APIToolDescription._extract_param_description(doc_string, "nonexistent")
|
507
|
+
assert description == ""
|
508
|
+
|
509
|
+
def test_extract_param_description_no_args_section(self):
|
510
|
+
"""Test parameter description extraction without Args section."""
|
511
|
+
doc_string = "Simple command without Args section."
|
512
|
+
|
513
|
+
description = APIToolDescription._extract_param_description(doc_string, "param")
|
514
|
+
assert description == ""
|
515
|
+
|
516
|
+
def test_extract_param_description_empty_docstring(self):
|
517
|
+
"""Test parameter description extraction with empty docstring."""
|
518
|
+
description = APIToolDescription._extract_param_description("", "param")
|
519
|
+
assert description == ""
|
520
|
+
|
521
|
+
def test_generate_tool_description_empty_registry(self, mock_registry):
|
522
|
+
"""Test tool description generation with empty registry."""
|
523
|
+
mock_registry.get_all_metadata.return_value = {}
|
524
|
+
|
525
|
+
description = APIToolDescription.generate_tool_description("empty_tool", mock_registry)
|
526
|
+
|
527
|
+
assert description["name"] == "empty_tool"
|
528
|
+
assert description["supported_commands"] == {}
|
529
|
+
assert description["examples"] == []
|
530
|
+
|
531
|
+
def test_generate_tool_description_with_missing_fields(self, mock_registry):
|
532
|
+
"""Test tool description generation with missing metadata fields."""
|
533
|
+
mock_registry.get_all_metadata.return_value = {
|
534
|
+
"incomplete": {
|
535
|
+
"summary": "Incomplete command",
|
536
|
+
"description": "Incomplete command description",
|
537
|
+
"params": {}
|
538
|
+
# Missing examples
|
539
|
+
}
|
540
|
+
}
|
541
|
+
|
542
|
+
description = APIToolDescription.generate_tool_description("incomplete_tool", mock_registry)
|
543
|
+
|
544
|
+
assert "incomplete" in description["supported_commands"]
|
545
|
+
# Should handle missing fields gracefully
|
546
|
+
assert description["supported_commands"]["incomplete"]["summary"] == "Incomplete command"
|